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


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.