- •1)Классы, объявление классов, спецификаторы доступа.
- •2)Классы, поля классов, инкапсуляция.
- •3)Методы и их реализация на примере класса «Комплексное число».
- •4)Создание объектов. Объекты и методы.
- •5)Конструкторы, виды конструкторов.
- •6) Конструктор по умолчанию, конструктор с параметрами.
- •7)Конструктор преобразования, конструктор копии.
- •8) Деструкторы
- •9)Перегрузка операций.
- •10) Стековые и динамические объекты, указатели и ссылки, достоинства и недостатки стековых объектов.
- •Размещение в стеке
- •Динамическое размещение
- •Указатели и ссылки
- •Недостатки стековых объектов
- •Достоинства стековых объектов
- •11) Указатели, арифметика указателей.
- •12)Указатель на константу, константный указатель, константный указатель на константу, константные аргументы функций, константные функции классов.
- •Константные переменные
- •Константы в перечислениях
- •Указатель на константу
- •Константный указатель
- •Константный указатель на константу
- •Константные аргументы функций
- •Неконстантные аргументы функций
- •Константные функции классов
- •13)Механизмы передачи параметров в функции, аргументы по умолчанию, указатель на функцию.
- •14)Дружественные функции (методы).
- •15)Статические поля и методы.
- •16) Инкапсуляция, ее реализация через понятие class.
Достоинства стековых объектов
С другой стороны, память в стеке выделяется с головокружительной быстротой — так же быстро, как компилятор выделяет память под другие автоматические переменные (скажем, целые). Оператор new (по крайней мере, его стандартная версия) тратит несколько тактов на то, чтобы решить, откуда взять блок памяти и где оставить данные для его последующего освобождения. Быстродействие — одна из веских причин в пользу выделения памяти из стека. Как вы вскоре убедитесь, существует немало способов ускорить работу оператора new, так что эта причина менее важна, чем может показаться с первого взгляда.
Автоматическое удаление — второе большое преимущество стековых объектов, поэтому программисты часто создают маленькие вспомогательные стековые классы, которые играют роль «обертки» для динамических объектов. В следующем забавном примере динамический класс Foo «упаковывается» в стековый класс PFoo. Конструктор выделяет память для Foo; деструктор освобождает ее. Если вы незнакомы с операторами преобразования, обратитесь к соответствующему разделу этой главы. В двух словах, функция operator Foo*() позволяет использовать класс PFoo везде, где должен использоваться Foo* — например, при вызове функции g().
class PFoo {
private:
Foo* f;
public:
PFoo() : f(new Foo) {}
~PFoo() { delete f; }
operator Foo*() { return f; }
}
void g(Foo*);
{
PFoo p;
g(p); // Вызывает функцию operator Foo*() для преобразования
// Уничтожается p, а за ним – Foo
}
Обратите внимание, что этот класс не совсем безопасен, поскольку адрес, возвращаемый функцией operator Foo*(), становится недействительным после удаления вмещающего PFoo. Мы разберемся с этим чуть позже.
Динамические объекты тоже могут удаляться автоматически, но это достаточно сложно.
У стековых объектов есть еще одно преимущество — если ваш компилятор поддерживает ANSI-совместимую обработку исключений (exception). Когда во время раскрутки стека происходит исключение, деструкторы стековых объектов вызываются автоматически. Для динамических объектов это не работает.
11) Указатели, арифметика указателей.
Каждая переменная (как и любой другой программный объект) имеет свой адрес - т.е. координату места ее размещения в памяти. Память представляется как простая последовательность байтов, пронумерованных целыми положительными числами. Под координатой объекта в памяти понимается номер первого байта этого объекта в указанной системе нумерации. Для определения адреса используется специальная унарная префиксная операция &. Выражение &a возвращает адрес переменной a. Для работы с адресами объектов используются указатели. Указатели - это специальный вид объектов, существующих в C/C++ программах. Значение указателя - это адрес некоторого объекта. Для того, чтобы обеспечить доступ к объекту, указатель должен, кроме адреса объекта, содержать информацию о его размере и структуре. Как известно, такая информация содержится в типах данных. Исходя из этого, указатели объявляются следующим образом:
<тип>* <указатель>
Например:
int *iptr; // Указатель на переменную типа int
char *cptr; // Указатель на переменную типа char
С указателями допустимы следующие операции:
-
Присваивание значения одного указателя другому:
int *ptr1, *ptr2;
ptr1=ptr2;
-
Присваивание указателю значения адреса объекта соответствующего типа:
int *ptr;
int x;
ptr = &a;
-
Операции сравнения на равенство и неравенство.
-
К указателям можно применять операции сложения с целым числом. Эти операции работают своеобразно и дают мощные средства управления памятью. Выясним это на конкретном примере.
Пусть объявлен указатель dptr типа double* (значениями этого указателя могут быть адреса объектов типа double). Напомним, что переменные типа double занимают в памяти 8 байт. В этой ситуации выражение dptr+k (где k - целое число) будет иметь тип double* (т.е. снова является указателем на объекты типа double) и значение, равное исходному значению переменной dptr плюс 8k. То есть указатель перемещается по памяти вперед на расстояние, равное размеру места, занимаемого в памяти k элементами типа, к которому отнесен указатель. Операция вычитания работает аналогично. С операциями сложения и вычитания связаны такие операции, как ++, --, += и -=. Эти операции работают так же, как и операции сложения и вычитания.
-
Разность двух указателей является целым числом. Это число имеет следующий смысл: на каком расстоянии друг от друга в памяти находятся объекты, адреса которых содержатся в указателях.
-
Кроме того, указателю можно присвоить любое целое значение, но это весьма рискованное дело - это число преобразуется в абсолютный адрес в памяти. А что там лежит???
-
Последняя операция, применимая к указателям, - это операция разыменования или получения значения объекта, адрес которого содержит указатель. Это операция "унарная * ". Рассмотрим пример:
Пусть dptr - это указатель на объекты типа double и пусть в некоторый момент времени этот указатель содержит адрес какого-то реально существующего в памяти объекта типа double (например, ранее описана переменная x типа double и указателю dptr присвоен адрес этой переменной). Синтаксически *dptr представляет собой выражение. Значение этого выражения - ссылка на объект, адрес которого содержится в переменной dptr. Это выражение может использоваться в других выражениях всюду, где допустимо использование переменной соответствующего типа (в том числе и в левой части выражения присваивания). Например, выражение *dptr=12.7 обрабатывается так: в объект типа double, расположенный по адресу, находящемуся в переменной dptr записывается значение 12.7, которое становится значением всего выражения присваивания.
Значение выражения *(dptr+6) определяется следующим образом: область памяти, начинающаяся с адреса, значение которого равно значению переменной (указателя) dptr плюс 48 (48=8х6) и занимающая в памяти 8 байт, трактуется как объект типа double, ссылка на который и становится значением всего выражения.
Пока неясно, для чего необходимо применять указатели.
Указатели полезны в том случае, когда надо хранить и обрабатывать объекты больших размеров. Если их просто описывать так, как указано выше, то эти объекты будут размещены в так называемом программном стеке. А он имеет очень маленький объем. Для использования же всей оперативной памяти и необходимы указатели, так как только с их помощью мы можем получить доступ к этой памяти.
На работе с указателями построена вся работа с массивами.
Указатели существенно используются при передаче параметров в функции и, наконец, создание динамических структур данных (также рассмотренных ниже) невозможно без использования указателей.