Сводка операций
Полное и подробное описание операций языка С++ дано в $$R.7. Советуемпрочитать этот раздел. Здесь же приводится краткая сводка операций инесколько примеров. Каждая операция сопровождается одним илинесколькими характерными для нее именами и примером ее использования.В этих примерах class_name обозначает имя класса, member - имя члена,object - выражение, задающее объект класса, pointer - выражение, задающееуказатель, expr - просто выражение, а lvalue (адрес) - выражение,обозначающее не являющийся константой объект. Обозначение (type) задаетимя типа в общем виде (с возможным добавлением *, () и т.д.).Если оно указано без скобок, существуют ограничения. Порядок применения унарных операций и операций присваивания"справа налево", а всех остальных операций - "слева направо".То есть, a=b=c означает a=(b=c), a+b+c означает (a+b)+c, и *p++ означает*(p++), а не (*p)++. ____________________________________________________________ Операции С++============================================================:: Разрешение области видимости class_name:: member:: Глобальное:: name____________________________________________________________. Выбор члена object. member-> Выбор члена pointer -> member[] Индексирование pointer [ expr ]() Вызов функции expr (expr_list)() Структурное значение type (expr_list)sizeof Размер объекта sizeof exprsizeof Размер типа sizeof (type)____________________________________________________________++ Постфиксный инкремент lvalue ++++ Префиксный инкремент ++ lvalue-- Постфиксный декремент lvalue ---- Префиксный декремент -- lvalue~ Дополнение ~ expr! Логическое НЕ! expr- Унарный минус - expr+ Унарный плюс + expr& Взятие адреса & lvalue* Косвенность * exprnew Создание (размещение) new typedelete Уничтожение (освобождение) delete pointerdelete[] Уничтожение массива delete[] pointer() Приведение(преобразование)типа (type) expr____________________________________________________________. * Выбор члена косвенный object. pointer-to-member->* Выбор члена косвенный pointer -> pointer-to-member____________________________________________________________* Умножение expr * expr/ Деление expr / expr% Остаток от деления expr % expr____________________________________________________________+ Сложение (плюс) expr + expr- Вычитание (минус) expr - expr____________________________________________________________ Все операции таблицы, находящиеся между двумя ближайшими другк другу горизонтальными чертами,имеют одинаковый приоритет. Приоритет операций уменьшается придвижении "сверху вниз". Например, a+b*c означает a+(b*c), так как *имеет приоритет выше, чем +; а выражение a+b-c означает (a+b)-c,поскольку + и - имеют одинаковый приоритет, и операции + и -применяются "слева направо". Э____________________________________________________________ Операции С++ (продолжение)============================================================<< Сдвиг влево expr << expr>> Сдвиг вправо expr >> expr____________________________________________________________< Меньше expr < expr<= Меньше или равно expr <= expr> Больше expr > expr>= Больше или равно expr >= expr____________________________________________________________== Равно expr == expr!= Не равно expr!= expr____________________________________________________________& Поразрядное И expr & expr____________________________________________________________^ Поразрядное исключающее ИЛИ expr ^ expr____________________________________________________________| Поразрядное включающее ИЛИ expr | expr____________________________________________________________&& Логическое И expr && expr____________________________________________________________|| Логическое ИЛИ expr || expr____________________________________________________________?: Операция условия expr? expr: expr____________________________________________________________= Простое присваивание lvalue = expr*= Присваивание с умножением lvalue *= expr/= Присваивание с делением lvalue /= expr%= Присваивание с взятием lvalue %= expr остатка от деления+= Присваивание со сложением lvalue += expr-= Присваивание с вычитанием lvalue -= expr<<= Присваивание со сдвигом влево lvalue <<= expr>>= Присваивание со сдвигом вправо lvalue >>= expr&= Присваивание с поразрядным И lvalue &= expr|= Присваивание с поразрядным lvalue |= expr включающим ИЛИ^= Присваивание с поразрядным lvalue ^= expr исключающим ИЛИ____________________________________________________________ Запятая (последовательность) expr, expr____________________________________________________________Скобки
Синтаксис языка С++ перегружен скобками, и разнообразие их примененийспособно сбить с толку. Они выделяют фактические параметры привызове функций, имена типов, задающих функции, а также служат дляразрешения конфликтов между операциями с одинаковым приоритетом.К счастью, последнее встречается не слишком часто, поскольку приоритетыи порядок применения операций определены так, чтобы выражения вычислялись"естественным образом" (т.е. наиболее распространенным образом).Например, выражение 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) // ошибка: присваивание константы в условии //... Она вполне объяснима, поскольку в большинстве языков "=" означает "равно".Для транслятора не составит труда сообщать об ошибках подобного рода.Порядок вычислений
Порядок вычисления подвыражений, входящих в выражение, не всегдаопределен. Например: 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)/ могутразличаться весьма значительно.Инкремент и декремент
Операция ++ явно задает инкремент в отличие от неявного его заданияс помощью сложения и присваивания. По определению ++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(), то это неочевидно. Какой извариантов окажется эффективнее, определяется как спецификой системыкоманд, так и возможностями транслятора. Наиболее эффективный алгоритмкопирования для вашей машины можно найти в стандартной функции копированиястрок из файла <string.h>: int strcpy(char*, const char*);