Операторы в C и C++

Язык программирования C++ поддерживает все операторы своего прародителя Си и дополнен новыми операторами и возможностями.

После вычисления первого операнда для неперегруженных операторов «&&», «||» и «,» (оператор «запятая», англ. comma) компилятор вставляет точку следования (англ. sequence point), гарантирующую, что все побочные эффекты (например, оператор «постфиксный ++») будут выполнены до начала вычисления второго операнда.

Языки с Си-подобным синтаксисом (например, Java, C#, PHP и другие) часто заимствуют операторы Cи/C++ с сохранением не только поведения, но также приоритета и ассоциативности.

Таблицы

править

В таблицах используются следующие обозначения:

  • «a», «b» и «c»: имена объектов или значения (литералы, значения переменных, возвращаемые значения, lvalue);
  • «Перегружаемый»: возможность перегрузки оператора в языке C++;
  • «Реализован в Си»: существование оператора в языке Си;
  • «R», «T», «S»: имена типов;
  • «Пример»: пример объявления перегруженного оператора;
  • «Член типа T»: определение оператора в виде метода структуры или класса (внутри структуры или класса); пример:
struct T { // или class
   operator float () const;
};
T::operator float () const { /* реализация */ };
  • «Определение вне класса»: определение оператора в виде функции; пример:
#include <iostream>
struct T { // или class
   /* ... */
};
std::ostream & operator << ( std::ostream & a, T const & b ) { /* реализация */ }
  • «Н/Д»: недоступно.

Арифметические операторы

править
Операция (выражение) Оператор Синтаксис выражения Перегружаемый Реализован в Си Пример
Член типа T Определение вне класса
Присваивание = a = b Да Да R& T::operator =(S b); н/д
Сложение + a + b Да Да R T::operator +(S b); R operator +(T a, S b);
Вычитание - a - b Да Да R T::operator -(S b); R operator -(T a, S b);
Унарный плюс + +a Да Да R T::operator +(); R operator +(T a);
Унарный минус - -a Да Да R T::operator -(); R operator -(T a);
Умножение * a * b Да Да R T::operator *(S b); R operator *(T a, S b);
Деление / a / b Да Да R T::operator /(S b); R operator /(T a, S b);
Операция модуль (остаток от деления целых чисел)[note 1] % a % b Да Да R T::operator %(S b); R operator %(T a, S b);
Инкремент префиксный ++ ++a Да Да R& T::operator ++(); R& operator ++(T a);
суффиксный (постфиксный) ++ a++ Да Да R T::operator ++(int); R operator ++(T a, int);
[note 2]
Декремент префиксный -- --a Да Да R& T::operator --(); R& operator --(T a);
суффиксный (постфиксный) -- a-- Да Да R T::operator --(int); R operator --(T a, int);
[note 2]

Операторы сравнения

править
Операция (выражение) Оператор Синтаксис выражения Перегружаемый Реализован в Си Пример
Член типа T Определение вне класса
Равенство == a == b Да Да R T::operator ==(S b); R operator ==(T a, S b);
Неравенство != a != b Да Да R T::operator !=(S b); R operator !=(T a, S b);
Больше > a > b Да Да R T::operator >(S b); R operator >(T a, S b);
Меньше < a < b Да Да R T::operator <(S b); R operator <(T a, S b);
Больше или равно >= a >= b Да Да R T::operator >=(S b); R operator >=(T a, S b);
Меньше или больше <= a <= b Да Да R T::operator <=(S b); R operator <=(T a, S b);

Логические операторы

править
Операция (выражение) Оператор Синтаксис выражения Перегружаемый Реализован в Си Пример
Член типа T Определение вне класса
Логическое отрицание, НЕ ! !a Да Да R T::operator !(); R operator !(T a);
Логическое умножение, И && a && b Да Да R T::operator &&(S b); R operator &&(T a, S b);
Логическое сложение, ИЛИ || a || b Да Да R T::operator ||(S b); R operator ||(T a, S b);

Побитовые операторы

править
Операция (выражение) Оператор Синтаксис выражения Перегружаемый Реализован в Си Пример
Член типа T Определение вне класса
Побитовая инверсия ~ ~a Да Да R T::operator ~(); R operator ~(T a);
Побитовое И & a & b Да Да R T::operator &(S b); R operator &(T a, S b);
Побитовое ИЛИ (or) | a | b Да Да R T::operator |(S b); R operator |(T a, S b);
Побитовое исключающее ИЛИ (xor) ^ a ^ b Да Да R T::operator ^(S b); R operator ^(T a, S b);
Побитовый сдвиг влево[note 3] << a << b Да Да R T::operator <<(S b); R operator <<(T a, S b);
Побитовый сдвиг вправо[note 3][note 4] >> a >> b Да Да R T::operator >>(S b); R operator >>(T a, S b);

Составное присваивание

править
Операция (выражение) Оператор Синтаксис выражения Значение Перегружаемый Реализован в Си Пример
Член типа T Определение вне класса
Сложение, совмещённое с присваиванием += a += b a = a + b Да Да R T::operator +=(S b); R operator +=(T a, S b);
Вычитание, совмещённое с присваиванием -= a -= b a = a - b Да Да R T::operator -=(S b); R operator -=(T a, S b);
Умножение, совмещённое с присваиванием *= a *= b a = a * b Да Да R T::operator *=(S b); R operator *=(T a, S b);
Деление, совмещённое с присваиванием /= a /= b a = a / b Да Да R T::operator /=(S b); R operator /=(T a, S b);
Вычисление остатка от деления, совмещённое с присваиванием[note 1] %= a %= b a = a % b Да Да R T::operator %=(S b); R operator %=(T a, S b);
Побитовое «И» (AND), совмещённое с присваиванием &= a &= b a = a & b Да Да R T::operator &=(S b); R operator &=(T a, S b);
Побитовое «ИЛИ» (or), совмещённое с присваиванием |= a |= b a = a | b Да Да R T::operator |=(S b); R operator |=(T a, S b);
Побитовое «исключающее ИЛИ» (xor), совмещённое с присваиванием ^= a ^= b a = a ^ b Да Да R T::operator ^=(S b); R operator ^=(T a, S b);
Побитовый сдвиг влево, совмещённый с присваиванием <<= a <<= b a = a << b Да Да R T::operator <<=(S b); R operator <<=(T a, S b);
Побитовый сдвиг вправо, совмещённый с присваиванием[note 4] >>= a >>= b a = a >> b Да Да R T::operator >>=(S b); R operator >>=(T a, S b);

Операторы работы с указателями и членами класса

править
Оператор Синтаксис Перегружаемый Реализован в Си Пример
Член типа T Определение вне класса
Обращение к элементу массива a[b] Да Да R T::operator [](S b);
н/д
Непрямое обращение («объект, на который указывает a») *a Да Да R T::operator *(); R operator *(T a);
Ссылка («адрес a») &a Да Да R T::operator &(); R operator &(T a);
Обращение к члену структуры («член b объекта, на который указывает a») a->b Да Да R* T::operator ->();[note 5]
н/д
Обращение к члену структуры («член b объекта a») a.b Нет Да н/д
Член, на который указывает b в объекте, на который указывает a[note 6] a->*b Да Нет R T::operator ->*(S b); R operator ->*(T a, S b);
Член, на который указывает b в объекте a a.*b Нет Нет н/д

Другие операторы

править
Оператор Синтаксис Перегружаемый Реализован в Си Пример
Член типа T Определение вне класса
Функтор a(a1, a2) Да Да R T::operator ()(S a1, U a2, ...); н/д
Оператор «запятая» a, b Да Да R T::operator ,(S b); R operator ,(T a, S b);
Тернарная условная операция a ? b : c Нет Да н/д
Оператор расширения области видимости a::b Нет Нет н/д
Пользовательские литералы (введены в C++11) "a"_b Да Нет н/д R operator "" _b(T a)
Sizeof (размер) sizeof(a)[note 7]
sizeof(type)
Нет Да н/д
Align-of (выравнивание) alignof(type) или _Alignof(type)[note 8] Нет Да н/д
Интроспекция typeid(a)
typeid(type)
Нет Нет н/д
Приведение типа (type) a Да Да T::operator R(); н/д
[note 9]
Выделение памяти new type Да Нет void* T::operator new(size_t x); void* operator new(size_t x);
Выделение памяти для массива new type[n] Да Нет void* T::operator new[](size_t x); void* operator new[](size_t x);
Освобождение памяти delete a Да Нет void T::operator delete(void* x); void operator delete(void* x);
Освобождение памяти, занятой массивом delete[] a Да Нет void T::operator delete[](void* x); void operator delete[](void* x);

Примечания:

  1. 1 2 Оператор «%» работает только с целыми числами. Для чисел с плавающей точкой используйте функцию fmod() из файла «math.h».
  2. 1 2 Чтобы отличить префиксный и суффиксный (постфиксный) операторы друг от друга, у постфиксных операторов добавлен неиспользуемый формальный параметр типа int. Часто этому параметру даже не дают имя.
  3. 1 2 В библиотеке «iostream» операторы «<<» и «>>» используются для работы с потоковым выводом и вводом.
  4. 1 2 По стандарту C99, сдвиг вправо отрицательного числа — implementation defined behavior (см. неуточняемое поведение). Многие компиляторы, в том числе gcc (см. документацию Архивная копия от 22 сентября 2019 на Wayback Machine (англ.)), реализуют арифметический сдвиг, но стандарт не запрещает реализовывать логический сдвиг.
  5. Тип возвращаемого значения оператора «operator->()» должен быть типом, к которому применим оператор «->», например, указателем. Если «x» имеет тип «C», и класс «C» перегружает оператор «operator->()», выражение «x->y» раскрывается как «x.operator->()->y».
  6. См. пример в статье Архивная копия от 17 мая 2013 на Wayback Machine (англ.) «Реализация оператора ->* для умных указателей» Скотта Майерса из журнала «Dr. Dobb’s journal», выпуск за октябрь 1999 года.
  7. Оператор sizeof, обычно, записывают со скобками. Если операнд — имя переменной, указание скобок необязательно. Если операнд — имя типа, скобки обязательны.
  8. Стандарт языка C++ определяет оператор alignof. Аналогичный оператор в стандарте языка Си называется _Alignof.
  9. Для оператора приведения типа тип возвращаемого значения явно не указывается, так как совпадает с именем оператора.

Приоритеты операторов

править

В данной таблице указаны приоритеты операторов и их ассоциативность. Операторы, указанные в таблице выше (раньше), имеют более высокий приоритет (приоритет вычисления). При рассмотрении выражения, операторы, имеющие более высокий приоритет, будут вычислены раньше операторов с низким приоритетом. Если несколько операторов указаны в одной ячейке, то они имеют одинаковый приоритет и вычисляются в последовательности, задаваемой ассоциативностью. Приоритеты операторов не изменяются при их перегрузке.

Этой таблицы приоритетов в большинстве случаев бывает достаточно, за исключением следующих случаев. Тернарный оператор «?:» может содержать в среднем выражении оператор «запятая» или присваивание, но код «a ? b, c : d» компилятор воспринимает как «a ? (b, c) : d», а не как бессмысленное выражение «(a ? b), (c : d)». Таким образом выражение между ? и : воспринимается, как если бы оно было в скобках.

Приоритет Оператор Описание Ассоциативность
1

Наивысший

:: Разрешение области видимости Нет
2 ++ Суффиксный инкремент Слева направо
-- Суффиксный декремент
() Вызов функции
[] Взятие элемента массива
. Выбор элемента по ссылке
-> Выбор элемента по указателю
typeid() RTTI (только C++; см typeid)
const_cast Приведение типа (C++) (см const cast)
dynamic_cast Приведение типа (C++) (см dynamic cast)
reinterpret_cast Каламбур типизации (C++) (см reinterpret_cast)
static_cast Приведение типа (C++) (см static cast)
3 ++ Префиксный инкремент Справа налево
-- Префиксный декремент
+ Унарный плюс
- Унарный минус
! Логическое НЕ
~ Побитовое НЕ
(type) Приведение типа
* Разыменование указателя
& Взятие адреса объекта
sizeof Sizeof (размер)
new, new[] Выделение динамической памяти (C++)
delete, delete[] Освобождение динамической памяти (C++)
4 .* Указатель на член (C++) Слева направо
->* Указатель на член (C++)
5 * Умножение
/ Деление
% Получение остатка от деления
6 + Сложение
- Вычитание
7 << Побитовый сдвиг влево
>> Побитовый сдвиг вправо
8 < Меньше
<= Меньше или равно
> Больше
>= Больше или равно
9 == Равенство
!= Неравенство
10 & Побитовое И (and)
11 ^ Побитовое исключающее ИЛИ (xor)
12 | Побитовое ИЛИ (or)
13 && Логическое И
14 || Логическое ИЛИ
15 ?: Тернарная условная операция Справа налево
= Присваивание
+= Сложение, совмещённое с присваиванием
-= Вычитание, совмещённое с присваиванием
*= Умножение, совмещённое с присваиванием
/= Деление, совмещённое с присваиванием
%= Вычисление остатка от деления, совмещённое с присваиванием
<<= Побитовый сдвиг влево, совмещённый с присваиванием
>>= Побитовый сдвиг вправо, совмещённый с присваиванием
&= Побитовое «И», совмещённое с присваиванием
|= Побитовое «ИЛИ», совмещённое с присваиванием
^= Побитовое «исключающее ИЛИ» (xor), совмещённое с присваиванием
throw Оператор создания исключения (C++)
16 , Оператор «запятая» Слева направо

Описание

править

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

  • Например, ++x*3 был бы двусмысленным без каких-либо правил приоритетов. По таблице можно сказать, что x сначала связывается с оператором ++, и только затем с оператором *, поэтому независимо от действия оператора ++, это действие только над x (а не над x*3). Таким образом, выражение эквивалентно (++x, x*3).
  • Аналогично с кодом 3*x++, где таблица утверждает, что инкремент применяется только к x а не к 3*x. Функционально это выражение эквивалентно (tmp=x, x++, tmp=3*tmp, tmp), если выразить временную переменную как tmp.
 
Приоритеты и связывание

Связывание операторов в стандартах Си и C++ определено через грамматику языка, а не через таблицу. Это может создать конфликт. Например, в языке Си синтаксис условного оператора таков:

logical-OR-expression ? expression : conditional-expression

А в языке C++:

logical-OR-expression ? expression : assignment-expression

Из-за этого выражение:

e = a < d ? a++ : a = d

будет воспринято по-разному в этих двух языках. В Си выражение синтаксически некорректно, так как результат условного оператора не может служить lvalue (то есть, левой частью оператора присваивания).

В C++, выражение будет разобрано как корректное:[1]

e = (a < d ? a++ : (a = d))

Приоритеты побитовых логических операторов несколько неинтуитивны[2]. Концептуально & и | являются такими же арифметическими операторами как * и + соответственно.

Выражение a & b == 7 синтаксически воспринимается как a & (b == 7), но выражение a + b == 7 эквивалентно (a + b) == 7. Из-за этого часто требуется пользоваться скобками для явного задания порядка вычислений.

Синонимы операторов в C++

править

В стандарте C++ определены[3] диграфы для некоторых операторов:

Диграф Эквивалентная строка
and &&
bitand &
and_eq &=
or ||
bitor |
or_eq |=
xor ^
xor_eq ^=
not !
not_eq !=
compl ~

Диграфы могут использоваться точно так же как и операторы, являются синонимами операторов. Например, диграф «bitand» может использоваться для замены операторов «побитовое И» и «получение адреса» или в определении ссылочных типов. Так, код «int bitand ref = n;» эквивалентен коду «int & ref = n;».

Стандарт ANSI/ISO C определяет перечисленные диграфы в виде констант #define (см. препроцессор). Константы определены в заголовочном файле «iso646.h». Для совместимости с Си стандарт C++ определяет фиктивный заголовочный файл «ciso646».

Примечания

править
  1. Does the C/C++ ternary operator actually have the same precedence as assignment operators? Stack Overflow. Дата обращения: 22 сентября 2019. Архивировано 6 августа 2020 года.
  2. Chistory. Дата обращения: 11 января 2013. Архивировано из оригинала 22 июня 2013 года.
  3. ISO/IEC JTC1/SC22/WG21 - Комитет по стандартизации C++. ISO/IEC 14882:1998(E) Язык программирования C++ (англ.). — Международная группа по стандартизации языка программирования C++, 1998. — С. 40—41.

Ссылки

править