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



Pdf көрінісі
бет74/256
Дата11.07.2022
өлшемі2,87 Mb.
#37591
1   ...   70   71   72   73   74   75   76   77   ...   256
3.2.2 Порядок вычислений 
Порядок вычисления подвыражений, входящих в выражение, не всегда определен. Например: 
int i = 1; 
v[i] = i++; 
Здесь выражение может вычисляться или как v[1]=1, или как v[2]=1. Если нет ограничений на порядок 
вычисления подвыражений, то транслятор получает возможность создавать более оптимальный код. 
Транслятору следовало бы предупреждать о двусмысленных выражениях, но к сожалению 


Бьерн Страуструп.
Язык программирования С++ 
 
84 
большинство из них не делает этого. Для операций 
&& || , 
гарантируется, что их левый операнд вычисляется раньше правого операнда. Например, в выражении 
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]; 
Это неэффективное решение: строка оканчивается нулем; единственный способ найти ее длину - это 
прочитать ее всю до нулевого символа; в результате строка читается и для установления ее длины, и 
для копирования, то есть дважды. Поэтому попробуем такой вариант: 


Бьерн Страуструп.
Язык программирования С++ 
 
85 
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   ...   70   71   72   73   74   75   76   77   ...   256




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

    Басты бет