Как работает оператор запятой


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? , Это побило тебя на один день. И ответ Лилк дает ответ на вопрос о a = (b, c);.
— августа

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");

Хм, вы не можете изменить приоритет, то есть, вероятно, вам следует заключить скобки в список.
— Джефф Берджес

18
@Джефф Наоборот. С круглыми скобками в списке это не сработает, так как компилятор просто видит оператор запятой между двумя 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;
— underscore_d

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; }evaulates b(3)перед тем a(5). Это важно , если ваш список следующим образом: Foo() : a(5), b(a) {}. b не будет установлен на 5, а скорее на неинициализированное значение a, о котором ваш компилятор может или не может предупреждать.
— Джонатан Гаврич

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

Я не думаю, что кто-то может ответить на это. Вы должны показать это в контексте. Наверное, отдельный вопрос?
— CygnusX1

38

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

d = (a = b, c);

а будет равно bи dбудет равно c.


19
Почти правильно. Утверждения не имеют значений, выражения имеют. Значение этого выражения c.
— Леон Тиммерманс

Почему это используется вместо a = b; d = c;?
— Аарон Франке

Это заставило меня понять, о каких побочных эффектах говорили люди.
— шнурок


2

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


2

Да Оператор запятой имеет меньший приоритет, чем оператор присваивания

#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

Перво-наперво: запятая на самом деле не является оператором, для компилятора это просто токен, который получает значение в контексте с другими токенами.

Что это значит и зачем?

Пример 1:

Чтобы понять разницу между значением одного и того же токена в другом контексте, рассмотрим следующий пример:

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

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

Правильная интерпретация приведенного выше примера состоит в том, что он является шаблоном.

Пример 2:

Когда мы пишем типичный цикл for с несколькими переменными инициализации и / или несколькими выражениями, которые следует выполнять после каждой итерации цикла, мы также используем запятую:

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.