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