В языках программирования C и C++ оператор запятая (представленный токеном ) ,
— это бинарный оператор , который оценивает свой первый операнд и отбрасывает результат, а затем оценивает второй операнд и возвращает это значение (и тип); между этими оценками есть точка последовательности .
Использование токена-запятой в качестве оператора отличается от его использования в вызовах и определениях функций, объявлениях переменных, объявлениях перечислений и подобных конструкциях, где он действует как разделитель .
Оператор запятая разделяет выражения (которые имеют значение) аналогично тому, как точка с запятой завершает операторы, а последовательности выражений заключаются в круглые скобки аналогично тому, как последовательности операторов заключаются в фигурные скобки: [1] (a, b, c)
— это последовательность выражений, разделенных через запятую, результатом которого является последнее выражение c
, while {a; b; c;}
представляет собой последовательность операторов и не возвращает никакого значения. Запятая может стоять только между двумя выражениями – запятые разделяют выражения – в отличие от точки с запятой, которая появляется в конце (неблочного) оператора – точка с запятой завершает операторы.
Оператор запятая имеет самый низкий приоритет среди всех операторов C и действует как точка последовательности . В сочетании запятых и точек с запятой точки с запятой имеют более низкий приоритет, чем запятые, поскольку точки с запятой разделяют операторы, но запятые встречаются внутри операторов, что согласуется с их использованием в качестве обычных знаков пунктуации: a, b; c, d
сгруппировано как, (a, b); (c, d)
поскольку это два отдельных оператора.
Оператор запятая устарел в выражениях индексации (начиная с C++20 ); [2] , чтобы уменьшить путаницу и открыть в будущем возможность перепрофилирования синтаксиса для индексации многомерных массивов. В C++23 была добавлена возможность перегрузки operator[]
с использованием нескольких аргументов, что сделало выражения с запятыми без скобок непригодными для использования в нижних индексах. [3] Оператор запятой по-прежнему можно использовать и он не устарел в этом контексте, если выражение с запятой заключено в круглые скобки (как в a[(b,c)]
).
В этом примере различное поведение второй и третьей строк связано с тем, что оператор запятой имеет более низкий приоритет, чем оператор присваивания. Последний пример также отличается тем, что возвращаемое выражение должно быть полностью вычислено, прежде чем функция сможет вернуться.
/** * Запятые в этой строке действуют как разделители, а не как оператор. * Результаты: a=1, b=2, c=3, i=0 */ int a = 1 , b = 2 , c = 3 , i = 0 ; /** * Присваивает значение b элементу i. * Запятые действуют как разделители в первой строке и как оператор во второй строке. * Результаты: a=1, b=2, c=3, i=2 */ int a = 1 , b = 2 , c = 3 ; интервал я знак равно ( а , б ); /** * Присваивает значение a элементу i. * Эквивалентно: int i = a; интервал б; * Запятые служат разделителями в обеих строках. * Фигурные скобки во второй строке позволяют избежать повторного объявления переменных в том же блоке, * что может привести к ошибке компиляции. * Второй объявленный b не имеет начального значения. * Результаты: a=1, b=2, c=3, i=1 */ int a = 1 , b = 2 , c = 3 ; { int я знак равно а , б ; } /** * Увеличивает значение a на 2, затем присваивает значение результирующей операции a + b в i. * Запятые действуют как разделители в первой строке и как оператор во второй строке. * Результаты: a=3, b=2, c=3, i=5 */ int a = 1 , b = 2 , c = 3 ; int я знак равно ( а += 2 , а + б ); /** * Увеличивает значение a на 2, затем сохраняет значение a в i и отбрасывает * неиспользованные значения результирующей операции a + b. * Эквивалентно: (i = (a += 2)), a + b; * Запятые действуют как разделители в первой строке и как оператор в третьей строке. * Результаты: a=3, b=2, c=3, i=3 */ int a = 1 , b = 2 , c = 3 ; интервал я ; я знак равно а += 2 , а + б ; /** * Присваивает значение a элементу i. * Запятые служат разделителями в обеих строках. * Фигурные скобки во второй строке позволяют избежать повторного объявления переменных в том же блоке, * что может привести к ошибке компиляции. * Вторым объявленным b и c не присваивается начальное значение. * Результаты: a=1, b=2, c=3, i=1 */ int a = 1 , b = 2 , c = 3 ; { int я знак равно а , б , c ; } /** * Запятые действуют как разделители в первой строке и как оператор во второй строке. * Присваивает значение c значению i, отбрасывая неиспользуемые значения a и b. * Результаты: a=1, b=2, c=3, i=3 */ int a = 1 , b = 2 , c = 3 ; int я знак равно ( а , б , с ); /** * Возвращает 6, а не 4, поскольку точки последовательности операторов-запятых, следующие за ключевым словом * return, считаются одним выражением, вычисляющим значение rvalue окончательного * подвыражения c=6. * В этой строке в качестве операторов выступают запятые. */ return a = 4 , b = 5 , c = 6 ; /** * Возвращает 3, а не 1 по той же причине, что и предыдущий пример. * В этой строке в качестве операторов выступают запятые. */ возврат 1 , 2 , 3 ; /** * Возвращает 3, а не 1, по той же причине, что и выше. Этот пример работает так, * потому что return — это ключевое слово, а не вызов функции. Несмотря на то, что компиляторы * допускают использование конструкции return(value), круглые скобки относятся только к «значению» * и не оказывают особого влияния на ключевое слово return. * Return просто получает выражение, и здесь это выражение «(1), 2, 3». * В этой строке в качестве операторов выступают запятые. */ возврат ( 1 ), 2 , 3 ;
Оператор запятая имеет относительно ограниченные случаи использования. Поскольку он отбрасывает свой первый операнд, он обычно полезен только в том случае, если первый операнд имеет желательные побочные эффекты , которые необходимо располагать перед вторым операндом. Кроме того, поскольку он редко используется за пределами определенных идиом и его легко спутать с другими запятыми или точкой с запятой, он потенциально может сбивать с толку и подвержен ошибкам. Тем не менее, существуют определенные обстоятельства, когда он обычно используется, особенно в циклах for и SFINAE . [4] Для встроенных систем, которые могут иметь ограниченные возможности отладки, оператор запятая может использоваться в сочетании с макросом, чтобы плавно переопределить вызов функции и вставить код непосредственно перед вызовом функции.
Наиболее распространенное использование — разрешить несколько операторов присваивания без использования оператора блока, в первую очередь в выражениях инициализации и приращения цикла for . Это единственное идиоматическое использование в элементарном программировании на C. В следующем примере порядок инициализаторов цикла имеет значение:
void rev ( char * s , size_t len ) { char * first ; for ( first = s , s += len ; s >= first ; -- s ) { putchar ( * s ); } }
Альтернативным решением этой проблемы в других языках является параллельное присваивание , которое позволяет выполнять несколько присваиваний внутри одного оператора, а также использует запятую, хотя и с другим синтаксисом и семантикой. Это используется в Go в аналогичном цикле for. [5]
За исключением инициализаторов цикла for (в которых точки с запятой используются особым образом), вместо точки с запятой можно использовать запятую, особенно когда рассматриваемые операторы действуют аналогично увеличению цикла (например, в конце цикла while):
++ п , ++ q ; ++ п ; ++ д ;
Запятую можно использовать в макросах препроцессора для выполнения нескольких операций в пространстве одного синтаксического выражения.
Одним из распространенных вариантов использования является предоставление пользовательских сообщений об ошибках в неудачных утверждениях. Это делается путем передачи в assert
макрос списка выражений в скобках, где первое выражение представляет собой строку ошибки, а второе выражение — утверждаемое условие. Макрос assert
дословно выводит свой аргумент в случае сбоя утверждения. Ниже приведен пример:
#include <stdio.h> #include <assert.h> int main ( void ) { int я ; for ( я = 0 ; я <= 9 ; я ++ ) { утверждения ( ( «я слишком большой!» , я <= 4 ) ); printf ( "i = %i \n " , я ); } вернуть 0 ; }
Выход:
я = 0я = 1я = 2я = 3я = 4Assert: Assert.c:6: test_assert: Утверждение `( "i слишком велико!", i <= 4)' не выполнено.Прервано
Однако макрос Assert обычно отключен в рабочем коде, поэтому используйте его только в целях отладки.
Запятую можно использовать внутри условия (if, while, do while или for), чтобы разрешить вспомогательные вычисления, в частности вызов функции и использование результата с областью действия блока :
if ( y = f ( x ), y > x ) { ... // операторы, включающие x и y }
Похожая идиома существует в Go , где синтаксис оператора if явно допускает использование необязательного оператора. [6]
Запятую можно использовать в операторах возврата для присвоения глобальной переменной или выходного параметра (передаваемого по ссылке). Эта идиома предполагает, что присваивания являются частью возврата, а не вспомогательными присваиваниями в блоке, который завершается фактическим возвратом. Например, при установке глобального номера ошибки:
if ( ошибка ) return ( errno = EINVAL , -1 );
Более подробно это можно записать так:
если ( ошибка ) { errno = EINVAL ; вернуть -1 ; }
Для краткости можно использовать запятую, чтобы избежать блока и связанных с ним фигурных скобок, например:
если ( Икс == 1 ) у знак равно 2 , z знак равно 3 ;
если ( Икс == 1 ) у знак равно 2 , z знак равно 3 ;
вместо:
если ( Икс == 1 ) { y знак равно 2 ; г = 3 ;}
если ( Икс == 1 ) { y знак равно 2 ; г = 3 ; }
В языках программирования OCaml и Ruby для этой цели используется точка с запятой («;»). В JavaScript [7] и Perl [8] оператор запятая используется так же, как в C/C++. В Java запятая — это разделитель, используемый для разделения элементов списка в различных контекстах. [9] Это не оператор, и он не возвращает результат к последнему элементу в списке. [10]
Два выражения, разделенные запятой, оцениваются слева направо. Левый операнд всегда оценивается, и все побочные эффекты выполняются до вычисления правого операнда.
Вы можете использовать оператор запятая, если хотите включить несколько выражений в место, где требуется одно выражение.