Бьерн Страуструп.
Язык программирования С++
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*);
Достарыңызбен бөлісу: