Як працює оператор Comma


175

Як працює оператор кома в C ++?

Наприклад, якщо я це роблю:

a = b, c;  

Чи є в кінцевому рахунку рівним b або c?

(Так, я знаю, що це легко перевірити - просто документуючи тут, щоб хтось швидко знайшов відповідь.)

Оновлення: це питання виявило нюанс при використанні оператора кома. Просто для документування цього:

a = b, c;    // a is set to the value of b!

a = (b, c);  // a is set to the value of c!

Це питання насправді було натхнене друкарською помилкою. Що мав бути

a = b;
c = d;

Перетворився в

a = b,    //  <-  Note comma typo!
c = d;

Детальніше про це читайте тут. stackoverflow.com/questions/12824378/…
Кодування Mash

1
Можливий дублікат того, що робить оператор кома `,` в C? . Це побив тебе за один день. І відповідь lillq дає відповідь на запитання про a = (b, c);.
jww

5
Але в цьому випадку a = b, c = d;насправді виконується так само, як і намічено a = b; c = d;?
Бондолін

@NargothBond Не обов'язково. Якщо bі dє оцінками функцій, які використовують (і змінюють) загальний стан, порядок виконання не визначається доти C++17.
ніроній

Відповіді:



129

Не забудьте зауважити, що оператор з комами може бути перевантажений на C ++. Таким чином, реальна поведінка може сильно відрізнятися від очікуваної.

Як приклад, Boost.Spirit досить хитро використовує оператор комами для реалізації ініціалізаторів списку для таблиць символів. Таким чином, він робить можливим і значущим такий синтаксис:

keywords = "and", "or", "not", "xor";

Зауважте, що завдяки перевазі оператора код (навмисно!) Ідентичний

(((keywords = "and"), "or"), "not"), "xor";

Тобто перший викликаний оператор, keywords.operator =("and")який повертає проксі-об'єкт, на який operator,викликаються інші s:

keywords.operator =("and").operator ,("or").operator ,("not").operator ,("xor");

Гм, однак, ви не можете змінити пріоритет, тобто ви, мабуть, повинні поставити круглі дужки навколо свого списку.
Jeff Burdges

18
@Jeff Навпаки. З круглими дужками навколо списку це не буде працювати, оскільки тоді компілятор просто бачить оператор коми між двома char[], які не можна перевантажувати. Код навмисно спочатку викликає, operator=а потім згодом operator,для кожного елемента, що залишився.
Конрад Рудольф

125

Оператор з комами має найнижчий пріоритет з усіх операторів C / C ++. Тому це завжди останнє, яке пов'язується з виразом, це означає:

a = b, c;

еквівалентно:

(a = b), c;

Ще один цікавий факт полягає в тому, що оператор з комами вводить точку послідовності . Це означає, що вираз:

a+b, c(), d

гарантується, що його три суб-вирази ( a + b , c () і d ) оцінюються в порядку. Це важливо, якщо вони мають побічні ефекти. Зазвичай компіляторам дозволено оцінювати підекспресії в будь-якому порядку, який вони вважають за потрібне; наприклад, у виклику функції:

someFunc(arg1, arg2, arg3)

аргументи можна оцінювати у довільному порядку. Зауважте, що коми у виклику функції не є операторами; вони є роздільниками.


15
Варто зазначити, що ,має такий низький пріоритет, він навіть відстає від себе ;) ... Тобто: оператор кома як асистент має нижчий пріоритет, ніж роздільник комах . Отже, якщо ви хочете використовувати оператор -заплідник в одному аргументі функції, присвоєнні змінної чи іншому списку, розділеному комами, тоді вам потрібно скористатися дужками, наприклад:int a = 1, b = 2, weirdVariable = (++a, b), d = 4;
підкреслити

68

Оператор з комами:

  • має найнижчий пріоритет
  • ліво-асоціативний

Версія операторів комами за замовчуванням визначена для всіх типів (вбудована та спеціальна), і вона працює наступним чином exprA , exprB:

  • exprA оцінюється
  • результат exprA ігнорується
  • exprB оцінюється
  • результат exprBповертається як результат усього вираження

У більшості операторів компілятору дозволено вибирати порядок виконання, і навіть потрібно пропустити виконання, якщо це не впливає на кінцевий результат (наприклад false && foo(), пропуск виклику буде пропущений foo). Однак це не стосується оператора комами, і зазначені вище кроки завжди будуть відбуватися * .

На практиці оператор комами за замовчуванням працює майже так само, як і крапка з комою. Різниця полягає в тому, що два вирази, розділені крапкою з комою, утворюють два окремих твердження, тоді як розділення комами зберігає всі як єдиний вираз. Ось чому оператор комами іноді використовується в таких сценаріях:

  • Синтаксис C вимагає єдиного вираження , а не твердження. наприклад вif( HERE )
  • Синтаксис C вимагає одного твердження, не більше, наприклад, при ініціалізації for циклуfor ( HERE ; ; )
  • Коли ви хочете пропустити фігурні дужки і зберегти єдине твердження: if (foo) HERE ;(будь ласка, не робіть цього, це справді некрасиво!)

Коли вислів не є виразом, крапку з комою не можна замінити комою. Наприклад, це заборонено:

  • (foo, if (foo) bar) (if не є виразом)
  • int x, int y (змінна декларація не є виразом)

У вашому випадку ми маємо:

  • a=b, c;, рівнозначне a=b; c;, якщо припустити, щоa це тип, який не перевантажує оператор комами.
  • a = b, c = d;еквівалент a=b; c=d;, якщо припустити, що aце тип, який не перевантажує оператор комами.

Зауважте, що не кожна кома насправді є оператором комах. Деякі коми, які мають зовсім інше значення:

  • int a, b; --- список змінних оголошень розділений комами, але це не оператори з комами
  • int a=5, b=3; --- це також список декларацій змінної комою, розділених комами
  • foo(x,y)--- список аргументів, розділений комами. Насправді, xі yможна оцінити в будь-якому порядку!
  • FOO(x,y) --- список макроаргументів, відокремлений комами
  • foo<a,b> --- список аргументів шаблону, розділений комами
  • int foo(int a, int b) --- список параметрів, розділених комами
  • Foo::Foo() : a(5), b(3) {} --- список ініціалізаторів, розділених комами в конструкторі класів

* Це не зовсім вірно, якщо ви застосовуєте оптимізацію. Якщо компілятор визнає, що певний фрагмент коду абсолютно не впливає на решту, він видалить непотрібні заяви.

Подальше читання: http://en.wikipedia.org/wiki/Comma_operator


Чи варто зауважити, що при operator ,перевантаженні ви втрачаєте гарантії асоціативності (як і ви втрачаєте властивості короткого замикання operator&&та operator||якщо вони перевантажені)?
YoungJohn

Кома-оператор ліво-асоціативний незалежно від того, перевантажений він чи ні. Вираз a, b, cзавжди означає (a, b), cі ніколи a, (b, c). Остання інтерпретація може навіть призвести до помилки компіляції, якщо елементи різного типу. Що ви, можливо, будете після того, як порядок оцінки аргументів? Я не впевнений у цьому, але, можливо, ви маєте рацію: це може статися, що cоцінюється раніше, (a, b) навіть якщо кома ліво-асоціативна.
CygnusX1

1
Лише невеликий коментар до відокремленого комами списку ініціалізації у конструкторі класів, порядок не визначається позицією у списку. Порядок визначається позицією декларації класу. Напр. struct Foo { Foo() : a(5), b(3) {} int b; int a; }Випаровується b(3)раніше a(5). Це важливо , якщо ваш список наступним чином: Foo() : a(5), b(a) {}. b не буде встановлено на 5, а скоріше неініціалізоване значення a, про яке ваш компілятор може або не може попереджати.
Джонатан Гаврич

Нещодавно я натрапив на оператор кома з двома плавцями, який сенс оцінювати та відкидати число?
Аарон Франке

Я не думаю, що хтось може відповісти на це. Вам доведеться показати це в контексті. Можливо, окреме питання?
CygnusX1

38

Значення aбуде b, але значення виразу буде c. Тобто в

d = (a = b, c);

a було б дорівнює bі dбуло б дорівнює c.


19
Майже правильно. Виписки не мають значення, вирази. Значення цього виразу c.
Леон Тіммерманс

Чому це використовується замість a = b; d = c;?
Аарон Франке

Це дало мені зрозуміти, про які побічні ефекти говорили люди.
шнурок


2

Значення a буде дорівнює b, оскільки оператор з комами має нижчий пріоритет, ніж оператор присвоєння.


2

Так, оператор Comma має низький пріоритет, ніж оператор призначення

#include<stdio.h>
int main()
{
          int i;
          i = (1,2,3);
          printf("i:%d\n",i);
          return 0;
}

Вихід: i = 3
Оскільки оператор комами завжди повертає найправіше значення.
У випадку з оператором коми з Оператором призначення:

 int main()
{
      int i;
      i = 1,2,3;
      printf("i:%d\n",i);
      return 0;
}

Ouput: i = 1
Як ми знаємо, оператор комами має нижчий пріоритет, ніж призначення .....


Тож чим відрізняється другий приклад від того, що я маю i = 1;на цій лінії?
Аарон Франке

-3

Перш за все: Comma насправді не є оператором, для компілятора це лише маркер, який набуває значення в контексті з іншими лексемами.

Що це означає і чому турбує?

Приклад 1:

Щоб зрозуміти різницю між значенням одного і того ж лексеми в іншому контексті, ми розглянемо цей приклад:

class Example {
   Foo<int, char*> ContentA;
}

Зазвичай C ++ початківець б думати , що цей вислів може / буде порівнювати речі , але це не так Absolutly, сенс <, >і, маркери на залежимо контексті використання.

Правильне тлумачення наведеного вище прикладу, звичайно, є тим, що це вкладення шаблону.

Приклад 2:

Коли ми пишемо типово для циклу з більш ніж однією змінною ініціалізації та / або більше ніж одним виразом, який слід робити після кожної ітерації циклу, ми також використовуємо кому:

for(a=5,b=0;a<42;a++,b--)
   ...

Значення кома залежить від контексту вживання, тут це контекст for побудови.

Що насправді означає кома в контексті?

Щоб ще більше ускладнити це (як завжди в C ++), оператор комами може сам перевантажуватися (завдяки Конраду Рудольфу за те, що вказав на це).

Щоб повернутися до питання, Кодексу

a = b, c;

означає для компілятора щось подібне

(a = b), c;

тому що пріоритет від =маркерів / оператора вище , ніж пріоритет з ,маркерів.

і це трактується в контексті подібного

a = b;
c;

(зауважте, що інтерпретація залежить від контексту, тут це не виклик функції / методу чи встановлення шаблону.)


1
впевнений, що так, можливо, я використав неправильну термінологію (для лексеру це знак, звичайно)
Quonux

2
Оскільки працює з оператором (sic), кома справді є оператором.
DragonLord

2
Хоча визнання того, що даний маркер кома визнаний оператором комами (на відміну, наприклад, роздільником аргументів), може бути викликом самостійно, це питання стосується конкретно оператора комами .
CygnusX1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.