Бьерн Страуструп. Язык программирования С++



бет24/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   20   21   22   23   24   25   26   27   ...   124

3.2.1 Скобки


Синтаксис языка С++ перегружен скобками, и разнообразие их применений

способно сбить с толку. Они выделяют фактические параметры при

вызове функций, имена типов, задающих функции, а также служат для

разрешения конфликтов между операциями с одинаковым приоритетом.

К счастью, последнее встречается не слишком часто, поскольку приоритеты

и порядок применения операций определены так, чтобы выражения вычислялись

"естественным образом" (т.е. наиболее распространенным образом).

Например, выражение
if (i<=0 || maxозначает следующее: "Если i меньше или равно нулю, или если max меньше i".

То есть, оно эквивалентно


if ( (i<=0) || (maxно не эквивалентно допустимому, хотя и бессмысленному выражению
if (i <= (0||max) < i) // ...
Тем не менее, если программист не уверен в указанных правилах,

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

надежности писать более длинные и менее элегантные выражения, как:
if ( (i<=0) || (maxПри усложнении подвыражений скобки используются чаще. Не надо, однако,

забывать, что сложные выражения являются источником ошибок. Поэтому,

если у вас появится ощущение, что в этом выражении нужны скобки,

лучше разбейте его на части и введите дополнительную переменную.

Бывают случаи, когда приоритеты операций не приводят к "естественному"

порядку вычислений. Например, в выражении


if (i&mask == 0) // ловушка! & применяется после ==
не происходит маскирование i (i&mask), а затем проверка результата

на 0. Поскольку у == приоритет выше, чем у &, это выражение эквивалентно

i&(mask==0). В этом случае скобки играют важную роль:
if ((i&mask) == 0) // ...
Имеет смысл привести еще одно выражение, которое вычисляется

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


if (0 <= a <= 99) // ...
Оно допустимо, но интерпретируется как (0<=a)<=99, и результат первого

сравнения равен или 0, или 1, но не значению a (если, конечно,

a не есть 1). Проверить, попадает ли a в диапазон 0...99, можно так:
if (0<=a && a<=99) // ...
Среди новичков распространена ошибка, когда в условии вместо ==

(равно) используют = (присвоить):


if (a = 7) // ошибка: присваивание константы в условии

// ...
Она вполне объяснима, поскольку в большинстве языков "=" означает "равно".

Для транслятора не составит труда сообщать об ошибках подобного рода.

3.2.2 Порядок вычислений


Порядок вычисления подвыражений, входящих в выражение, не всегда

определен. Например:
int i = 1;

v[i] = i++;


Здесь выражение может вычисляться или как v[1]=1, или как v[2]=1.

Если нет ограничений на порядок вычисления подвыражений, то транслятор

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

следовало бы предупреждать о двусмысленных выражениях, но к сожалению

большинство из них не делает этого.

Для операций


&& || ,
гарантируется, что их левый операнд вычисляется раньше правого операнда.

Например, в выражении b=(a=2,a+1) b присвоится значение 3. Пример

операции || был дан в $$3.2.1, а пример операции && есть в $$3.3.1.

Отметим, что операция запятая отличается по смыслу от той запятой, которая

используется для разделения параметров при вызове функций. Пусть есть

выражения:


f1(v[i],i++); // два параметра

f2( (v[i],i++) ) // один параметр


Вызов функции f1 происходит с двумя параметрами: v[i] и i++, но

порядок вычисления выражений параметров неопределен. Зависимость

вычисления значений фактических параметров от порядка вычислений

- далеко не лучший стиль программирования. К тому же программа

становится непереносимой.

Вызов f2 происходит с одним параметром, являющимся выражением,

содержащим операцию запятая: (v[i], i++). Оно эквивалентно i++.

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

a*(b/c) может вычисляться как (a*b)/c (если только пользователь

видит в этом какое-то различие). Заметим, что для значений с плавающей

точкой результаты вычисления выражений a*(b/c) и (a*b)/ могут

различаться весьма значительно.



3.2.3 Инкремент и декремент


Операция ++ явно задает инкремент в отличие от неявного его задания

с помощью сложения и присваивания. По определению ++lvalue означает

lvalue+=1, что, в свою очередь означает lvalue=lvalue+1 при условии,

что содержимое lvalue не вызывает побочных эффектов. Выражение,

обозначающее операнд инкремента, вычисляется только один раз. Аналогично

обозначается операция декремента (--). Операции ++ и -- могут

использоваться как префиксные и постфиксные операции. Значением ++x

является новое (т. е. увеличенное на 1) значение x. Например, y=++x

эквивалентно y=(x+=1). Напротив, значение x++ равно прежнему значению x.

Например, y=x++ эквивалентно y=(t=x,x+=1,t), где t - переменная того

же типа, что и x.

Напомним, что операции инкремента и декремента указателя

эквивалентны сложению 1 с указателем или вычитанию 1 из указателя, причем

вычисление происходит в элементах массива, на который настроен

указатель. Так, результатом p++ будет указатель на следующий элемент.

Для указателя p типа T* следующее соотношение верно по определению:
long(p+1) == long(p) + sizeof(T);
Чаще всего операции инкремента и декремента используются для

изменения переменных в цикле. Например, копирование строки,

оканчивающейся нулевым символом, задается следующим образом:
inline void cpy(char* p, const char* q)

{

while (*p++ = *q++) ;



}
Язык С++ (подобно С) имеет как сторонников, так и противников именно

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

программирования. Оператор
while (*p++ = *q++) ;
вероятнее всего, покажется невразумительным для незнакомых с С.

Имеет смысл повнимательнее посмотреть на такие конструкции, поскольку

для C и C++ они не является редкостью.

Сначала рассмотрим более традиционный способ копирования массива

символов:
int length = strlen(q)

for (int i = 0; i<=length; i++) p[i] = q[i];


Это неэффективное решение: строка оканчивается нулем; единственный

способ найти ее длину - это прочитать ее всю до нулевого символа;

в результате строка читается и для установления ее длины, и для

копирования, то есть дважды. Поэтому попробуем такой вариант:


for (int i = 0; q[i] !=0 ; i++) p[i] = q[i];

p[i] = 0; // запись нулевого символа


Поскольку p и q - указатели, можно обойтись без переменной i,

используемой для индексации:


while (*q !=0) {

*p = *q;


p++; // указатель на следующий символ

q++; // указатель на следующий символ

}

*p = 0; // запись нулевого символа


Поскольку операция постфиксного инкремента позволяет сначала использовать

значение, а затем уже увеличить его, можно переписать цикл так:


while (*q != 0) {

*p++ = *q++;

}

*p = 0; // запись нулевого символа


Отметим, что результат выражения *p++ = *q++ равен *q. Следовательно,

можно переписать наш пример и так:


while ((*p++ = *q++) != 0) { }
В этом варианте учитывается, что *q равно нулю только тогда, когда

*q уже скопировано в *p, поэтому можно исключить завершающее

присваивание нулевого символа. Наконец, можно еще более сократить

запись этого примера, если учесть, что пустой блок не нужен, а

операция "!= 0" избыточна, т.к. результат условного выражения и так

всегда сравнивается с нулем. В результате мы приходим к

первоначальному варианту, который вызывал недоумение:
while (*p++ = *q++) ;
Неужели этот вариант труднее понять, чем приведенные выше? Только

неопытным программистам на С++ или С! Будет ли последний вариант

наиболее эффективным по затратам времени и памяти? Если не считать

первого варианта с функцией strlen(), то это неочевидно. Какой из

вариантов окажется эффективнее, определяется как спецификой системы

команд, так и возможностями транслятора. Наиболее эффективный алгоритм

копирования для вашей машины можно найти в стандартной функции копирования

строк из файла :


int strcpy(char*, const char*);



Достарыңызбен бөлісу:
1   ...   20   21   22   23   24   25   26   27   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет