straustrup2 (852740), страница 26
Текст из файла (страница 26)
memberpointer -> memberpointer [ expr ]expr ( expr_list )type ( expr_list )sizeof exprsizeof ( type )lvalue ++++ lvaluelvalue --- lvalue~ expr! expr- expr+ expr& lvalue* exprnew typedelete pointerdelete[] pointer( type ) exprobject . pointer-to-memberpointer -> pointer-to-memberexpr * exprexpr / exprexpr % exprexpr + exprexpr - exprexpr << exprexpr >> exprexpr < exprexpr <= exprexpr > exprexpr >= exprexpr == exprexpr != exprexpr & exprexpr ^ exprexpr | exprexpr && exprexpr || exprexpr? expr : exprlvalue = exprlvalue *= exprlvalue /= exprlvalue %= exprlvalue += exprlvalue -= exprlvalue <<= exprБьерн Страуструп.>>=&=|=^=,Язык программирования С++Присваивание со сдвигом вправоПрисваивание с поразрядным ИПрисваивание с поразрядным включающим ИЛИПрисваивание с поразрядным исключающим ИЛИЗапятая (последовательность)lvalue >>= exprlvalue &= exprlvalue |= exprlvalue ^= exprexpr , exprВсе операции таблицы, находящиеся между двумя ближайшими друг к другу горизонтальными чертами,имеют одинаковый приоритет.
Приоритет операций уменьшается при движении "сверху вниз".Например, a+b*c означает a+(b*c), так как * имеет приоритет выше, чем +; а выражение a+b-c означает(a+b)-c, поскольку + и - имеют одинаковый приоритет, и операции + и - применяются "слева направо".82Бьерн Страуструп.Язык программирования С++3.2.1 СкобкиСинтаксис языка С++ перегружен скобками, и разнообразие их применений способно сбить с толку. Онивыделяют фактические параметры при вызове функций, имена типов, задающих функции, а такжеслужат для разрешения конфликтов между операциями с одинаковым приоритетом. К счастью,последнее встречается не слишком часто, поскольку приоритеты и порядок применения операцийопределены так, чтобы выражения вычислялись "естественным образом" (т.е.
наиболеераспространенным образом). Например, выражениеif (i<=0 || max<i)// ...означает следующее: "Если i меньше или равно нулю, или если max меньше i". То есть, оноэквивалентноif ( (i<=0) || (max<i) )// ...но не эквивалентно допустимому, хотя и бессмысленному выражениюif (i <= (0||max) < i)// ...Тем не менее, если программист не уверен в указанных правилах, следует использовать скобки, причемнекоторые предпочитают для надежности писать более длинные и менее элегантные выражения, как:if ( (i<=0) || (max<i) )// ...При усложнении подвыражений скобки используются чаще.
Не надо, однако, забывать, что сложныевыражения являются источником ошибок. Поэтому, если у вас появится ощущение, что в этомвыражении нужны скобки, лучше разбейте его на части и введите дополнительную переменную.Бывают случаи, когда приоритеты операций не приводят к "естественному" порядку вычислений.Например, в выражении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. Если нет ограничений на порядоквычисления подвыражений, то транслятор получает возможность создавать более оптимальный код.Транслятору следовало бы предупреждать о двусмысленных выражениях, но к сожалению83Бьерн Страуструп.Язык программирования С++большинство из них не делает этого.
Для операций&&||,гарантируется, что их левый операнд вычисляется раньше правого операнда. Например, в выражении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];Это неэффективное решение: строка оканчивается нулем; единственный способ найти ее длину - этопрочитать ее всю до нулевого символа; в результате строка читается и для установления ее длины, идля копирования, то есть дважды.
Поэтому попробуем такой вариант:84Бьерн Страуструп.Язык программирования С++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;// запись нулевого символаОтметим, что результат выраженияпример и так:while ((*p++ = *q++) != 0)*p++ = *q++ равен *q.
Следовательно, можно переписать наш{ }В этом варианте учитывается, что *q равно нулю только тогда, когда *q уже скопировано в *p, поэтомуможно исключить завершающее присваивание нулевого символа. Наконец, можно еще более сократитьзапись этого примера, если учесть, что пустой блок не нужен, а операция "!= 0" избыточна, т.к.результат условного выражения и так всегда сравнивается с нулем. В результате мы приходим кпервоначальному варианту, который вызывал недоумение:while (*p++ = *q++) ;Неужели этот вариант труднее понять, чем приведенные выше? Только неопытным программистам наС++ или С! Будет ли последний вариант наиболее эффективным по затратам времени и памяти? Еслине считать первого варианта с функцией strlen(), то это неочевидно. Какой из вариантов окажетсяэффективнее, определяется как спецификой системы команд, так и возможностями транслятора.Наиболее эффективный алгоритм копирования для вашей машины можно найти в стандартной функциикопирования строк из файла <string.h>:int strcpy(char*, const char*);3.2.4 Поразрядные логические операцииПоразрядные логические операции&|^~>><<применяются к целым, то есть к объектам типа char, short, int, long и к их беззнаковым аналогам.Результат операции также будет целым.Чаще всего поразрядные логические операции используются для работы с небольшим по величинемножеством данных (массивом разрядов).
В этом случае каждый разряд беззнакового целогопредставляет один элемент множества, и число элементов определяется количеством разрядов.Бинарная операция & интерпретируется как пересечение множеств, операция | как объединение, аоперация ^ как разность множеств. С помощью перечисления можно задать имена элементаммножества. Ниже приведен пример, заимствованный из <iostream.h>:class ios {public:enum io_state {goodbit=0, eofbit=1, failbit=2, badbit=4};// ...};85Бьерн Страуструп.Язык программирования С++Состояние потока можно установить следующим присваиванием:cout.state = ios::goodbit;Уточнение именем ios необходимо, потому что определение io_state находится в классе ios, а такжечтобы не возникло коллизий, если пользователь заведет свои имена наподобие goodbit.Проверку на корректность потока и успешное окончание операции можно задать так:if (cout.state&(ios::badbit|ios::failbit))// ошибка в потокеЕще одни скобки необходимы потому, что операция & имеет более высокий приоритет, чем операция"|".Функция, обнаружившая конец входного потока, может сообщать об этом так:cin.state |= ios::eofbit;Операция |= используется потому, что в потоке уже могла быть ошибка (т.е.