- •Объектно-ориентированное программирование
- •Глава 1. Объектно-ориентированный подход.
- •Глава 4. Статические компоненты классов
- •Глава 5. Друзья класса
- •Глава 7. Перегрузка стандартных операторов
- •Глава 8. Классы ресурсоемких объектов.
- •Глава 11. Динамическая идентификация типа (rtti). Операторы приведения типа.
- •Глава 12. Обработка исключительных ситуаций
- •Глава 16. Библиотека ввода-вывода.
- •Глава 17. Контейнеры, итераторы, алгоритмы
Глава 4. Статические компоненты классов
В некоторых случаях важно, чтобы все объекты класса работали с одной и той же переменной.
В Си для этого использовались глобальные переменные. Но данных подход небезопасен, поскольку существует возможность изменения такой переменной в других частях программы; безопасная альтернатива – статические поля классов.
Статическое поле– поле класса, которое относится ко всему классу в целом и не тиражируется в его объектах.
Каждое статическое поле существует в единственном экземпляре, и в отличие от обычных полей размещается не в стеке, а в сегменте данных (статическое поле – глобальная переменная, доступ к которой ограничен).
Статические компоненты находятся в контексте класса (т.е. не тиражируются).
Статические поля обладают особенностями:
Допускают только внешнее определение;
К статическому полю можно обратиться даже не имея объекта класса;
На них не распространяется действие спецификатора доступа (public,protected,private).
п.4.1. Создание статических полей
Статическое поле должно быть объявлено в классе с ключевым словом static, и иметь внешнее определение без этого ключевого слова.
Пример:
class Point
{
static int counter;
double x, y;
public:
Point();
Point(const Point& p);
~Point();
}
//внешнее определение статического поля:
int Point::counter;
п.4.2. Обращение к статическому полю
К нему можно обратиться как к обычному полю класса:
имя_объекта.статическое_поле;
указатель_на_объект->статическое_поле;
Учитывая принадлежность статического поля к классу, мы можем обратиться к нему:
имя_класса::статическое_поле;
Внутри методов класса к этому полю можно обратиться:
Point::Point()
{
x = 0.0;
y = 0.0;
++counter;
}
Point::Point(const Point &p)
{
x = px;
y = py;
++counter;
}
Point::~Point()
{
--counter;
}
п.4.3 Статические методы
По аналогии со статическими полями существуют статические метода (то есть к ним применимы те же свойства)
Статическое методы класса не могут вызывать не статические (так как статический метод не предполагает существование объекта). Следовательно, статические методы не могут работать с не статическими полями.
Пример:
class Point
{
static int counter;
double x, y;
public:
Point();
Point(const Point &);
~Point();
static void view();
};
int Point::counter = 0;
...
void Point::view()
{
cout << “В программе существует ” << counter << “точек\n”;
}
int main()
{
Point p1, p2;
{
Point p3 = p2;
Point::view(); // 3 (точки)
}
p1.view(); // 2 (точки)
return 0;
}
Глава 5. Друзья класса
В общем случае ни одна внешняя по отношению к классу функция не имеет доступа к его закрытым и защищенным компонентам. Тем не менее возникают ситуации, когда необходимо предоставить такой доступ.
п.5.1. Дружественные функции
Дружественная функция– внешняя по отношению к классу функция (возможно метод другого класса), которая имеет доступ к его закрытым и защищенным компонентам.
Чтобы внешняя функция стала дружественной классу, ее нужно объявить в теле класса с ключевым слово friend.
Замечание: Поскольку дружественная функция не является методом класса, на нее не действуют спецификаторы доступа.
class Point
{
...
private:
friend double RadiusVector(Point &p);
};
...
double RadiusVector(Point &p)
{
return sqrt(p.x*p.x + p.y*p.y);
}
____________________________________________
class Point;
class Editor
{
public:
void edit(Point &p);
};
class Point
{
...
friend void Editor::edit(Point &p);
};
...
void Editor::edit(Point &p)
{
p.x = 8;
p.y = p.x – 6;
}
п.5.2. Дружественные классы
Аналогично можно добавить в друзья целый класс. В этом случае все методы дружественного класса становятся дружественными функциями.
friend class Editor;
п.5.3. Вложенные классы
В С++ есть понятия вложенный и локальный классы.
Вложенный класс (определен внутри определения другого класса)
class outer
{
...
class inner
{
...
};
...
};
Локальный класс (определен внутри блока, например, функции)
void func()
{
class local
{
...
};
...
}
На вложенный класс распространяются ограничения доступа внешнего класса.
Вложенный класс выступает как внутренний тип данных, используемый в пределах внешнего. Внешний класс не имеет доступа к закрытым и защищенным компонентам внутреннего. Чтобы обеспечить такой доступ, необходимо выполнить следующие шаги:
Объявить внутренний класс в теле внешнего;
Объявить внутренний класс в теле внешнего в качестве дружественного;
Определить внутренний класс в теле внешнего;
class outer
{
int x, y;
public:
class inner;
friend class inner;
outer();
outer(const outer &);
class inner
{
int a, b;
public:
inner();
inner(const inner &);
};
};
outer::outer()
{
...
}
outer::outer(const outer &o)
{
...
}
outer::inner::inner()
{
...
}
outer::inner::inner(const outer::inner &i)
{
...
}
п.5.4. Важное предупреждение!
Концепция дружественности классов – это добровольный отказ программиста от средств автоматического контроля доступа. Эта технология крайне небезопасна и пользоваться ей следует только в крайнем случае.
Point::Point(double x, double y)
{
this->x = x;
this->y = y;
}
Глава 6. Константность в языке С++
п.6.1. Переменная-константа
const тип имя; // Имя пременной не является леводопустимым выражением.
п.6.2. Константность в указателях
(1) тип (2) * (3) имя;
Необходимо читать данную запись слева-направо.
сonst тип * имя;
const int * x;
x– это указатель наconstint, то естьуказатель на константу.
x = y; - можно
*x = z; - нельзя
Можно изменять сам указатель, но нельзя изменять ячейку, на которую он настроен.
тип const * имя;
int const * x;
x– это указатель наconstтипаint.
тип * const имя;
int * const x;
x– это константный указатель на ячейку типаint
x = y; - нельзя
*x = z; - можно
Нельзя перенастраивать указатель.
const тип * const имя;
x – константный указатель на константу
Имя массива является константным указателем на первый элемент.
Пример:
int x;
int *px;
const int *pcx;
px = &x;
pcx = &x;
x = 5; // можно
*pcx = 5; // нельзя
const int x = 5;
int *px;
const int *pcx;
px = &x; // нельзя (открывает возможность изменять ячейки,
// которые запрещено изменять
pcx = & x; // можно
Адрес неконстантного объекта можно присвоить указателю на константу.
Адрес константного объекта нельзя присвоить обычному указателю.
п.6.3. Константы в ссылках
int x = 5;
const int & rx = x;
Константная ссылка и ссылка на константу – одно и то же.
x++; // можно
rx++; // нельзя
п.6.4. Передача константы как параметра функции
void func(const int x)
{
++x; // нельзя
}
вызов функции: (при вызове функции в нее передается копия объекта (в стеке)
int a = 5;
const int b = 6;
func(a);
func(b);
п.6.5. Возврат константы
Возврат константы для встроенных и
пользовательских типов данных отличается.
Пример1:
int func1()
{
return 5;
}
const int func2()
{
return 5;
}
int main()
{
int a1, a2;
const int b1, b2;
a1 = func1(); // Все 4 записи
b1 = func1(); // допустимы
a2 = func2(); // При работе со встроенными типами данных
b2 = func2(); // не требует точного соответсвия по константности
return 0;
}
class Point
{
...
};
Point func1() // Существенная особенность:
{ // При возврате на пользовательский тип данных
return Point(); // возвращаемое значение и переменная-приемник
} // должны юыть совместимы по константности
const Point func2()
{
return Point();
}
int main()
{
Point a1, a2;
const Point b1, b2;
a1 = func1(); // +
b1 = func1(); // -
a2 = func2(); // -
b2 = func2(); // +
return 0;
}
п.6.6. Временные объекты
Временный объект – объект, который создается компилятором, как промежуточное значение в процессе вычислений и не связан ни с какой конкретной переменной в программе.
(1) Функционально временный объект не
отличается от обычного, за исключением
того, что он является константой.
Пример:
class Point
{
public:
void print();
};
void f(Print &x)
{
}
Point g()
{
return Point();
}
int main()
{
g().print(); // можно
f(g()); // нельзя (*)
return 0;
}
(*) Сначала вызывается функция g(), после этого вызывается функцияf(), принимающая на вход результат функцииg(). Функцияg() возвращает объект классаPoint, который представляет собой временный объект, подаваемый на вход функцииf(). В силу утверждения (1), объект, подаваемый на вход функцииf(), является константой.
g()->Point->const Point->f(Point &)
п.6.7. Константные поля классов
Обычное поле класса, объявленное с ключевым словом const, играет роль локальной константы, принадлежащей конкретному объекту данного класса. С такой константой обязательно связана конкретная ячейка памяти. Инициализировать можно только 1 раз при создании объекта (в конструкторе).
п.6.8. Список инициализации конструктора
Указать начальное значение константного поля можно тольков списке инициализации конструктора. В этом списке перечислены через запятую вызовы конструкторов, формирующих компоненты создаваемого объекта. Действия, описанные в списке инициализации выполняются сразу после выделения памяти для нового объекта и до вызова его конструктора.
имя_конструктора(параметры):список_инициализации
{
тело_конструктора
}
имя_компонента(параметры_конструктора_компонента)
Это элемент списка инициализации.
class Int
{
const int minVal;
const int maxVal;
int Val;
public:
Int(int v = 0, int min = 0, max = 25);
Int(const Int &other);
...
};
Int::Int(int v, int min, int max): val(v), minVal(min), maxVal(max)
{
}
Int::Int(const Int &other):minVal(other.minVal), maxVal(other.maxVal)
{
val = other.val;
}
Если конструктор компонента отсутствует в списке инициализации конструктора объекта, то для данного компонента вызывается конструктор умолчаний. Конструкторы вызываются в порядке их упоминания.
п.6.9. Константы времени компиляции в классах
Статическое константное поле класса представляет собой общую для всего класса константу, которая не тиражируется в объектах. Такое поле можно рассматривать как константу времени компиляции.
Статическое константное поле должно быть инициализировано непосредственно в теле класса. Оно может быть только целым.
class Stack
{
static const int size = 100;
int data[size];
int count;
public:
Stack();
void push(int value);
int pop();
};
Stack::Stack():count(0)
{
}
void Stack::push(int value)
{
if(count < size)
data[count++] = value;
}
int Stack::pop()
{
if(count > 0)
return data[--count];
else
return 0;
}
п.6.10. Константные методы класса
При создании в программе константного объекта класса компилятор обязан обеспечить неизменность полей объекта.
class Point
{
double x, y;
public:
...
void print();
};
int main()
{
...
const Point cp(0.1, 0.7);
cp.print(); // выдаст ошибку, т.к. для константного объекта
... // вызывается метод, который потенциально может
} // содержать программный код, изменяющий
// состояние объекта.
void Point::print()
{
cout << “x= “ << x << endl;
cout << “y= “ << y << endl;
}
Компилятор запрещает вызов обычных методов для константных объектов.
Константный метод класса – метод, который гарантирует неизменность объекта, для которого он вызван. Для константных объектов разрешено вызывать только константные методы.
Правильно:
void print() const;
...
void Point::print() const;
Ни конструкторы, ни деструкторы не могу быть константными методами.
Замечание 1: Перегрузка методов класса может осуществляться не только по списку параметров, но и по константности.
Замечание 2: Техническая реализация:
При вызове константного метода указатель thisявляется указателем на константу.
constимя класса * const this;
...
p.print();
Point::print(&p);
Вывод: Все методы, которые по своей сути не предполагают изменение объекта класса, следует делать константными. Глобальные функции в программе и статические методы класса не могут быть константными, так как в них по определению отсутствует объект, которому необходимо обеспечивать константность.
п.6.11. Ключевое слово mutable
Иногда возникает потребность в изменении состояния объекта константным методом (например, подсчитать количество вызовов данного метода для объекта). Если определить поле класса с ключевым словом mutable, то это поле можно изменять внутри константного метода.
class Point
{
mutable unsigned int counter;
...
};
...
void Point::print() const
{
...
counter++;
}