Лекции / LECS10
.DOCСтатические члены данных
Члены данных могут быть объявлены с использованием модификатора класса памяти static. Член данных, который объявлен как static, разделяется всеми переменными своего класса и хранится в одном месте. Нестатические члены данных создаются для каждого экземпляра класса.
Так как статический член данных не зависит от конкретного экземпляра, к нему можно обратиться следующим образом:
имя-класса :: идентификатор
Здесь используется оператор разрешения области видимости. Статический член глобального класса должен быть явно объявлен и определен в области видимости файла.
Например:
class str {
public:
static int how many; //объявление
void print ( );
void assign (const char*);
…..
private: //реализовано в виде символьного
char s [100]; //массива фиксированной длины
};
int str :: how_many = 0 //определение и инициализация
В нашем примере how_many может отслеживать, сколько памяти используется для хранение переменной str. Итак,
str sl, s2, s3, *p;
str :: how_many = 3; //предпочтительнее использовать ::
…..
str t;
t.how_many++; //оператор доступа «точка»
…..
р = new str;
р -> how many++; //оператор доступа через указатель
…..
delete p;
str::how_many--;
Предпочтительным стилем программирования для доступа к статическим членам является использование разрешения области видимости. Новым является разрешение инициализации статической константы в пределах объявления класса:
class ch_stаck {
…..
privatе:
static const int max_len = 10000; //инициализатор
…...
};
const stack :: int max_len; //необходимо объявление
Указатель this
Ключевое слово this обозначает объявленный неявно указатель на себя. Он может использоваться только в нестатических функциях-членах. В статических функциях-членах неявные аргументы недопустимы. Простая иллюстрация использования указателя this приведена ниже.
//Указатель this
class с_pair {
public:
void init (char b) { c2 = 1 + (cl = b); }
с_pair increment ( ) { c1++; c2++; return (*this); }
с_pair* where_am_I( ) ( return this; )
void print ( ) { cout << c1 << c2 << ' \ t'; }
privatе:
char c1, c2;
};
int main ( )
{
с_pair а, b;
a.init ('А');
a.print ( );
cout << " is at " << a.where_am_I ( ) << end l;
b.init ('B');
b.print ( );
cout << “ is at ” << b.where_am_I ( ) << end l;
b.increment ( ). print ( ) ;
}
Функция-член increment использует неявный указатель this, чтобы возвратить приращенные значения с1 и с2. Функция-член where_am_I возвращает адрес заданного объекта. Ключевое слово this предоставляет встроенный не требующий объявления указатель. Это то же самое, как если бы в c_pair неявно объявлялся закрытый член c_pair* const this. Указатель this нельзя изменить.
Функции-члены типа static и const.
C++ позволяет использовать функции-члены типа static и const. Синтаксически статическая функция-член содержит модификатор static,предшествующий возвращаемому типу функции внутри объявления класса. Определение вне класса не должно включать этот модификатор:
class foo {
. . . . .
static int foo_fcn ( ); // сначала — ключевое слово static
. . . . .
};
int foo::foo_fcn ( ) // здесь не должно быть слова static
{ /* определение функции */ }
Синтаксически, функция-член типа const вводится модификатором const, следующим за списком аргументов внутри объявления класса. Определение вне класса также должно включать этот модификатор:
class foo {
. . . . .
int foo_fcn ( ) const;
. . . . .
int foo::foo_fcn ( ) const //необходимо ключевое слово const
{ /* определение функции */ }
Особенности применения функций-членов const и static можно усвоить, используя указатель this. Обычная функция-член, вызываемая как
х.mem ( i, j, k ) ;
имеет явный список аргументов и неявный список аргументов, а именно, список всех членов данных объекта х. Неявные аргументы могут пониматься как данные, доступные через указатель this. В противоположность этому, статическая функция-член не получает неявных аргументов. Постоянная функция-член не может модифицировать свои неявные аргументы. Объявление постоянных функций-членов и постоянных параметров называется контролем постоянства (const-correctness). Контроль постоянства — важное подспорье при написании кода. Благодаря такому контролю можно быть уверенным в том, что компилятор убедится в неизменности значений объектов. Кроме того, контроль постоянства позволяет компилятору производить специальную оптимизацию, например, расположить объект const в памяти только для чтения.
Следующий пример иллюстрирует вышесказанное.
//Вычисление оклада с использованием статических членов
class salary { // оклад
public:
void init (int b) {b_sal = b; your_bonus = 0;}
void calc_bonus (double perc) //вычисление личной премии
{ your_bonus = b_sal * perc; }
//премия для всех сотрудников
static void reset_all (int p) { all_bonus = p; }
int comp_tot ( ) const //суммарный оклад
{ return (b_sal + your_bonus + all_bonus); }
private:
int b_sal;
int you r_bonu s ;
static int all_bonus; //объявление
};
//объявление и определение
int salary :: all_bonus = 100;
int main ( )
{
salary wl, w2;
wl.init (l000) ;
w2.init (2000) ;
wl.calc_bonus (0.2);
w2 .calc_bonus (0.15) ;
salary :: reset_all (400) ;
cout << “ wl ” << wl. comp_tot ( ) << “ w2 ”
<< w2. comp_tot( ) << end l ;
Изменчивость (mutable)
Ключевое слово mutable позволяет членам класса, переменные которого были объявлены как константы, быть, тем не менее, изменяемыми. Таким образом отпадает необходимость отказываться от постоянства, используя конструкцию const_cast<>. Это относительно новая возможность, поддерживаемая не всеми компиляторами С ++. Вот так она применяется:
// Класс с членами mutable
class person { // человек
public:
person (const char*, int, unsigned long);
void bday ( ) { ++ age; } //в день рождения
//увеличиваем возраст
. . . . .
private:
const char* name;
mutable int age;
unsigned long soc_sec;
};
. . . . .
{
// возраст всегда изменяется
const person ira (“ ira pohl ”, 38, 1110111);
. . . . .
ira.bday ( ); //правильно, ira.age — mutable
}
Контейнеры и доступ к их содержимому
Контейнерные классы, такие как стеки и двумерные динамические массивы,— очень полезные типы данных. При этом можно добавить возможность доступа к отдельным членам объектов и их модификации.
//Двумерный динамический массив
class twod {
public:
bool allocate (int r, int s) ; //псевдоконструктор
void deallocate ( ); //псевдодеструктор
double& element_lval(int i, int j) const
{ return base[i][j]; }
double element_rval(int i, int j) const
{ return base[i]tjl; }
int r_size( ) const { return row_size; }
int c_size( ) const { return column_size; }
private:
double** base;
int row_size, column_size;
};
bool twod :: allocate (int r, int s)
{
base = new double*[s];
if (base = = 0) // выделение памяти не выполнено
return false;
for (int i = 0; i < s; ++i) {
base [i] = new double [r];
if (base [i] = = 0) // выделение памяти не выполнено
return false;
}
row_size = r;
column_size = s;
return true;
}
void twod :: deallocate ( )
{
for (int i = 0; i < column_size; ++i)
delete [ ] base [i] ;
delete [ ] base;
row_size = 0;
column_size = 0;
}
Выше определены две почти идентичные функции-члена - element_lval ( ) и element_rval ( ). Они служат для иллюстрации разницы между понятиями lvalue (левостороннее значение или именующее выражение) и rvalue (правостороннее значение). Отличие кроется в возвращаемом типе. Возвращаемый тип double& предполагает, что возвращается ссылка на объект. Так, element_lval ( ) определяет lvalue для манипулирования отдельными индексированными элементами. Функция double element_rval ( ) требует, чтобы было возвращено rvalue индексированных элементов. Изложенное можно проиллюстрировать так:
twod m;
m.allocate (5, 10);
m.element_lval (1, 1) = 5; //поместим 5 в m.base[1][1]
m.element_rval (2, 2) = 6.5; //недопустимое присваивание rvalue
cout << (m.element_rval (1, 1) = =
m.element_lval(1, 1)); //правильно, 5 = = 5
Мы можем написать функцию find_max ( ), которая находит наибольшее значение элемента двумерного объекта:
double find_max (const twod& m)
{
int i , j ;
double max = m.element_rval (0,0) ;
for (i =0; i < m.c_size( ); ++i)
for (j =0; j < m.r_size( ); ++j)
if (m.element_rval(i, j) > max)
max = m.element_rval(i, j);
return max;
}
Заметьте, что element_lval ( ) тоже можно было бы использовать. Теперь давайте напишем алгоритм, транспонирующий двумерную матрицу.
void transpose(twod& m)
{
int i, j;
double temp;
for (i = 0; i < m.c_size ( ) - 1 ; ++i)
for (j = i + 1; j < m.r_size ( ); ++j) {
temp = m.element_lval (i, j);
m.element_lval (i, j) = m.element_lval (j, i) ;
m.element_lval (j, i) = temp;
}
}
Поскольку этот алгоритм манипулирует значениями, которые хранятся в элементах контейнерного объекта m, он предполагает использование lvalue.