Материалы книги получены с http://www.itlibitum.ru/
Переменные и константы
Болтовню о том, что такое переменные и для чего они нужны, пропускаем. Нашего внимания заслуживают две темы: константность и сравнение динамических объектов со стековыми.
const
Ключевое слово const, которое в разных контекстах принимает разные значения, одно из самых информативных в C++. Да, между этими значениями есть кое-что общее, но вам все равно придется запомнить все конкретные случаи.
Константные переменные
Если переменная объявлена с ключевым словом const, значит, она не должна меняться. После определения константной переменной вы уже не сможете изменить ее значение или передать ее в качестве аргумента функции, которая не гарантирует ее неизменности. Рассмотрим простой пример с константной целой переменной.
const int j = 17; // Целая константа
j = 29; // Нельзя, значение не должно меняться
const int i; // Нельзя, отсутствует начальное значение
Третья строка неверна, поскольку в ней компилятору предлагается определить случайную переменную, которую никогда не удастся изменить, - этакий странный генератор случайных целых констант.
Вообще говоря, вы сообщаете компилятору, какой конструктор он должен использовать в конкретном случае. Если бы переменная i относилась к нетривиальному классу, то при объявлении константного экземпляра пришлось бы явно указать конструктор и его аргументы. int - вырожденный случай, поскольку на самом деле const int j=17; - то же, что и int j(17).
Но вот компилятор узнал, что нечто должно быть константным. Он просыпается и начинает искать ошибки - не только фактические, но и потенциальные. Компилятор не разрешит использовать ваше константное нечто в любом неконстантном контексте, даже если шестилетний ребенок разберется в программе и докажет, что в ней нет ни одной ошибки.
const i = 17;
int& j = 1; // Нельзя, потому что позднее j может измениться
Не важно, будете ли вы изменять величину, на которую ссылается j. Компилятор предполагает, что вам захочется это сделать, и на всякий случай устраняет искушение. Иначе говоря, константность -свойство переменной, а не данных, поэтому неконстантная переменная не может ссылаться на константную величину.
const и #define
Две следующие строки не эквивалентны:
const int i = 17;
#define i 17;
В первой строке определяется переменная, занимающая некоторую область памяти, а во второй - макрос. Обычно отличия несущественны, если не считать одного-двух лишних тактов, затраченных на каждое обращение к константной переменной. Однако если переменная является глобальной и принадлежит нетривиальному классу со своим конструктором, ситуация резко меняется.
Дополнительные сведения приведены в разделе «Инициализация глобальных объектов» этой главы.
Константы в перечислениях
Перечисления (епит) не очень широко использовались в языке С по одной простой причине: символические имена констант имеют глобальную область действия и быстро захламляют пространство имен. В C++ эта проблема исчезла, поскольку область действия символических имен ограничивается классом или структурой.
class Foo {
public:
enum Status { kOpen = 1, kClosed };
};
// Где-то в программе
Foo::Status s = Foo::kOpen;
Обратите внимание - область действия должна быть явно указана как в имени типа, так и в символическом имени. Следовательно, символические имена kOpen и kClosed можно использовать в программе и для других целей. Компилятор рассматривает символические имена перечислений как макросы, а не как константные переменные. Это обстоятельство может оказаться важным при инициализации глобальных переменных (см, далее в этой главе).
Указатель на константу
С указателями дело обстоит несколько сложнее, поскольку приходится учитывать два значения: адрес и содержимое памяти по этому адресу. В следующем примере р - это указатель на константу; находящийся в указателе адрес может измениться, но содержимое памяти по этому адресу - нет.
const int* p;
int i = 17;
p = &i; // Можно
*p = 29; // Нельзя
Сказанное также относится к структурам и объектам.
class foo {
public:
int x;
};
const foo* f = new foo;
f->x = 17; // Нельзя, присвоение членам класса не допускается
Константный указатель
С константными указателями все наоборот: адрес изменять нельзя, но зато можно изменять содержимое памяти по этому адресу.
int i = 17;
int j = 29;
int* const p; // Нельзя! Должно быть задано начальное значение
int* const p1 = &i; // Порядок
*p1 = 29; // Можно; величина, на которую ссылается указатель,
// может изменяться
p1 = &j; // Нельзя
Константный указатель на константу
Константный указатель на константу (попробуйте-ка трижды быстро произнести это вслух!) изменить вообще нельзя. Это неизменяемый адрес неизменяемой величины.
int i = 17;
int j = 29;
const int* const p; // Нельзя. Должен быть задан начальный адрес
const int* const p1 = &i; // Можно
*p1 = 29; // Нельзя
p1 = &j; // Нельзя
Константные аргументы функций
Константный аргумент функции должен подчиняться тем же правилам, что и любая другая константная переменная.
void f(const int* p)
{
*p = 17; // Нельзя
int i = 29;
p = &i; // Можно, но зачем?
}
// Где-то в программе
int i = 17;
f(&i); // Порядок, фактический аргумент не обязан быть константой
Обратите внимание - аргумент, указанный при вызове функции, не обязан быть константным. Этот вопрос целиком остается на усмотрение стороны-получателя. Передача по ссылке осуществляется по тем же правилам, что и передача по адресу.
void f(const int& p)
{
p = 17; // Нельзя
int i = 29;
p = i; // Можно (на грани фола)
}
// Где-то глубоко в программе
int i = 17;
f(i); // Порядок
Неконстантные аргументы функций
Если формальный аргумент функции объявлен неконстантным, то и фактический аргумент, используемый при вызове, тоже должен быть неконстантным.
void f(int*);
int i = 17;
const int* p = &i;
const int j = 29;
f(&i); // Можно, потому что i - не константа
f(p); // Нельзя
f(&j); // Тоже нельзя, потому что j - константа
Это еще одно средство, с помощью которого компилятор соблюдает принцип «единожды константный всегда остается константным». Даже если функция f на самом деле не изменяет значения своего формального параметра, это ни на что не влияет.
Константные функции классов
В константных функциях классов переменная this интерпретируется как указатель на константу. Компилятор даст вам по рукам, если вы попробуете воспользоваться переменной this для изменения переменной класса или найти для нее иное, неконстантное применение. Смысл ключевого слова const зависит от его места в объявлении функции; для константных функций оно, словно бородавка, торчит после сигнатуры функции.
class foo {
private:
int x;
public:
void f() const;
void g();
};
void h(int*);
void m(foo*);
void foo::f();
{
x = 17; // Нельзя: изменяется переменная класса
this->g(); // Нельзя: g - некоторая функция
h(&x); // Нельзя: h может изменить x
m(this); // Нельзя: неконстантный аргумент в m()
}
Первая ошибка - попытка изменить переменную класса через this. В константных функциях класса foo переменная this фактически объявляется как const foo* this;. Вторая ошибка сложнее. Из приведенного фрагмента неизвестно, изменяет ли функция g какие-либо переменные класса foo, но это и не важно; одной возможности достаточно, чтобы ваш компилятор разразился негодующими воплями.
Из константной функции класса нельзя вызывать неконстантные функции через this. Похожая ситуация возникает с третьей и четвертой ошибкой - компилятор попытается спасти вас от самого себя и не допустит потенциально опасные строки.
Один из верных признаков профессионала C++ - ключевые слова const, обильно разбросанные по функциям классов. Любая функция класса, которая гарантированно не изменяет this, должна без малейших размышлений объявляться константной. Впрочем, как видно из приведенного выше фрагмента, эта стратегия работает лишь в том случае, если все участники команды следуют вашему примеру и объявят константными свои функции, В противном случае возникают каскадные ошибки.
Часто выясняется, что недавно купленная библиотека классов не использует константные функции и нарушает ваш пуританский стиль кодирования. Мораль: константные функции классов нужно использовать либо с полным фанатизмом (желательно), либо не использовать вовсе.
Назад Содержание Далее
|