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

Ссылки