Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
85
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

99

}

Что эти программы делают?

Оказывается, вторая реализация выполняется в два раза быстрее первой. Ожидали ли вы такого результата? Как вы его объясните?

Упражнение 3.15

Могли бы вы что-нибудь улучшить или дополнить в наборе операций класса string, приведенных в последнем разделе? Поясните свои предложения.

3.5. Спецификатор const

for ( int index = 0; index < 512; ++index )

Возьмем следующий пример кода:

... ;

С использованием литерала 512 связаны две проблемы. Первая состоит в легкости восприятия текста программы. Почему верхняя граница переменной цикла должна быть равна именно 512? Что скрывается за этой величиной? Она кажется случайной...

Вторая проблема касается простоты модификации и сопровождения кода. Предположим, программа состоит из 10 000 строк, и литерал 512 встречается в 4% из них. Допустим, в 80% случаев число 512 должно быть изменено на 1024. Способны ли вы представить трудоемкость такой работы и количество ошибок, которые можно сделать, исправив не то значение?

Обе эти проблемы решаются одновременно: нужно создать объект со значением 512. Присвоив ему осмысленное имя, например bufSize, мы сделаем программу гораздо более понятной: ясно, с чем именно сравнивается переменная цикла.

index < bufSize

В этом случае изменение размера bufSize не требует просмотра 400 строк кода для модификации 320 из них. Насколько уменьшается вероятность ошибок ценой добавления

int bufSize = 512; // размер буфера ввода

// ...

всего одного объекта! Теперь значение 512 локализовано.

for ( int index = 0; index < bufSize; ++index )

// ...

Остается одна маленькая проблема: переменная bufSize здесь является l-значением, которое можно случайно изменить в программе, что приведет к трудно отлавливаемой ошибке. Вот одна из распространенных ошибок использование операции присваивания (=) вместо сравнения (==):

// случайное изменение значения bufSize

С++ для начинающих

100

if ( bufSize = 1 )

//...

Врезультате выполнения этого кода значение bufSize станет равным 1, что может привести к совершенно непредсказуемому поведению программы. Ошибки такого рода обычно очень тяжело обнаружить, поскольку они попросту не видны.

Использование спецификатора const решает данную проблему. Объявив объект как

const int bufSize = 512; // размер буфера ввода

мы превращаем переменную в константу со значением 512, значение которой не может быть изменено: такие попытки пресекаются компилятором: неверное использование оператора присваивания вместо сравнения, как в приведенном примере, вызовет ошибку

// ошибка: попытка присваивания значения константе

компиляции.

if ( bufSize = 0 ) ...

Раз константе нельзя присвоить значение, она должна быть инициализирована в месте своего определения. Определение константы без ее инициализации также вызывает ошибку компиляции:

const double pi; // ошибка: неинициализированная константа

Давайте рассуждать дальше. Явная трансформация значения константы пресекается компилятором. Но как быть с косвенной адресацией? Можно ли присвоить адрес

const double minWage = 9.60;

// правильно? ошибка?

константы некоторому указателю? double *ptr = &minWage;

Должен ли компилятор разрешить подобное присваивание? Поскольку minWage константа, ей нельзя присвоить значение. С другой стороны, ничто не запрещает нам написать:

*ptr += 1.40; // изменение объекта minWage!

Как правило, компилятор не в состоянии уберечь от использования указателей и не сможет сигнализировать об ошибке в случае подобного их употребления. Для этого требуется слишком глубокий анализ логики программы. Поэтому компилятор просто запрещает присваивание адресов констант обычным указателям.

Что же, мы лишены возможности использовать указатели на константы? Нет. Для этого существуют указатели, объявленные со спецификатором const:

const double *cptr;

С++ для начинающих

101

где cptr указатель на объект типа const double. Тонкость заключается в том, что сам

 

 

const double *pc = 0;

 

 

 

 

const double minWage = 9.60;

 

 

// правильно: не можем изменять minWage с помощью pc

 

 

pc = &minWage;

 

 

double dval = 3.14;

 

 

// правильно: не можем изменять minWage с помощью pc

 

 

// хотя dval и не константа

 

 

pc = &dval; // правильно

 

 

dval = 3.14159; //правильно

 

указатель не константа, а значит, мы можем изменять его значение. Например:

 

 

*pc = 3.14159; // ошибка

 

 

 

 

 

 

Адрес константного объекта присваивается только указателю на константу. Вместе с тем,

 

такому указателю может быть присвоен и адрес обычной переменной:

 

 

pc = &dval;

 

 

 

 

 

 

Константный указатель не позволяет изменять адресуемый им объект с помощью

 

косвенной адресации. Хотя dval в примере выше и не является константой, компилятор

 

не допустит изменения переменной dval через pc. (Опять-таки потому, что он не в

 

состоянии определить, адрес какого объекта может содержать указатель в произвольный

 

момент выполнения программы.)

 

В реальных программах указатели на константы чаще всего употребляются как

 

формальные параметры функций. Их использование дает гарантию, что объект,

 

переданный в функцию в качестве фактического аргумента, не будет изменен этой

 

 

// В реальных программах указатели на константы чаще всего

 

 

 

 

// употребляются как формальные параметры функций

 

функцией. Например:

 

 

int strcmp( const char *str1, const char *str2 );

 

 

 

 

 

 

(Мы еще поговорим об указателях на константы в главе 7, когда речь пойдет о

 

функциях.)

 

Существуют и константные указатели. (Обратите внимание на разницу между

 

константным указателем и указателем на константу!). Константный указатель может

 

адресовать как константу, так и переменную. Например:

 

 

int errNumb = 0;

 

 

 

 

int *const currErr = &errNumb;

 

 

 

 

Здесь curErr константный указатель на неконстантный объект. Это значит, что мы не

 

можем присвоить ему адрес другого объекта, хотя сам объект допускает модификацию.

 

Вот как мог бы быть использован указатель curErr:

 

С++ для начинающих

102

do_something();

errorHandler();

*curErr = 0; // правильно: обнулим значение errNumb

if ( *curErr ) {

}

Попытка присвоить значение константному указателю вызовет ошибку компиляции:

curErr = &myErNumb; // ошибка

Константный указатель на константу является объединением двух рассмотренных

const double pi = 3.14159;

случаев.

const double *const pi_ptr = π

Ни значение объекта, на который указывает pi_ptr, ни значение самого указателя не может быть изменено в программе.

Упражнение 3.16

(a)

int i;

(d)

int *const cpi;

(b)

const int ic;

(e)

const int *const cpic;

Объясните значение следующих пяти определений. Есть ли среди них ошибочные?

(c) const int *pic;

Упражнение 3.17

(a)int i = -1;

(b)const int ic = i;

(c)const int *pic = &ic;

(d)int *const cpi = &ic;

Какие из приведенных определений правильны? Почему?

(e) const int *const cpic = &ic;

Упражнение 3.18

Используя определения из предыдущего упражнения, укажите правильные операторы

(a)

i =

ic;

(d)

pic = cpic;

(b)

pic

= &ic;

(i)

cpic = &ic;

присваивания. Объясните.

(c) cpi = pic; (f) ic = *cpic;