Оператор-запятая - Comma operator

В C и C ++ языков программирования, с оператором запятой ( в лице маркера , ) представляет собой бинарный оператор , который оценивает свой первый операнд и отбрасывает результат, а затем оценивает второй операнд и возвращает это значение (и типа); между этими оценками есть точка последовательности .

Использование токена запятой в качестве оператора отличается от его использования в вызовах и определениях функций , объявлениях переменных, объявлениях перечислений и подобных конструкциях, где он действует как разделитель .

Синтаксис

Оператор запятая разделяет выражения (которые имеют значение) аналогично тому, как точка с запятой завершает операторы, а последовательности выражений заключаются в круглые скобки аналогично тому, как последовательности операторов заключаются в фигурные скобки: (a, b, c)это последовательность выражений, разделенных запятыми, который оценивает последнее выражение, cа {a; b; c;}является последовательностью операторов и не возвращает никакого значения. Запятая может находиться только между двумя выражениями - запятые отдельные выражения - в отличие от точки с запятой, которая ставится в конце (неблочного) оператора - точки с запятой завершают операторы.

Оператор-запятая имеет самый низкий приоритет среди всех операторов C и действует как точка последовательности . В сочетании запятых и точек с запятой, точки с запятой имеют более низкий приоритет, чем запятые, поскольку точки с запятой разделяют операторы, но запятые встречаются внутри операторов, что соответствует их использованию в качестве обычной пунктуации: a, b; c, dгруппируется как, (a, b); (c, d)потому что это два отдельных оператора.

Примеры

В этом примере различное поведение между второй и третьей строками связано с тем, что оператор запятой имеет более низкий приоритет, чем присваивание. Последний пример также отличается, поскольку возвращаемое выражение должно быть полностью вычислено, прежде чем функция сможет вернуться.

/**
 *  Commas act as separators in this line, not as an operator.
 *  Results: a=1, b=2, c=3, i=0
 */
int a=1, b=2, c=3, i=0;

/**
 *  Assigns value of b into i.
 *  Commas act as separators in the first line and as an operator in the second line.
 *  Results: a=1, b=2, c=3, i=2
 */
int a=1, b=2, c=3;              
int i = (a, b);           
                      
/**
 *  Assigns value of a into i.
 *  Equivalent to: int i = a; int b;
 *  Commas act as separators in both lines.
 *  The braces on the second line avoid variable redeclaration in the same block,
 *  which would cause a compilation error.
 *  The second b declared is given no initial value.
 *  Results: a=1, b=2, c=3, i=1
 */
int a=1, b=2, c=3;                                
{ int i = a, b; }

/**
 *  Increases value of a by 2, then assigns value of resulting operation a + b into i.
 *  Commas act as separators in the first line and as an operator in the second line.
 *  Results: a=3, b=2, c=3, i=5
 */
int a=1, b=2, c=3;
int i = (a += 2, a + b);
          
/**
 *  Increases value of a by 2, then stores value of a to i, and discards unused
 *  values of resulting operation a + b.
 *  Equivalent to: (i = (a += 2)), a + b;
 *  Commas act as separators in the first line and as an operator in the third line.
 *  Results: a=3, b=2, c=3, i=3
 */
int a=1, b=2, c=3;
int i;
i = a += 2, a + b;

/**
 *  Assigns value of a into i.
 *  Commas act as separators in both lines.
 *  The braces on the second line avoid variable redeclaration in the same block,
 *  which would cause a compilation error.
 *  The second b and c declared are given no initial value.
 *  Results: a=1, b=2, c=3, i=1
 */
int a=1, b=2, c=3;
{ int i = a, b, c; }

/**
 *  Commas act as separators in the first line and as an operator in the second line.
 *  Assigns value of c into i, discarding the unused a and b values.
 *  Results: a=1, b=2, c=3, i=3
 */
int a=1, b=2, c=3;
int i = (a, b, c);

/**
 *  Returns 6, not 4, since comma operator sequence points following the keyword 
 *  return are considered a single expression evaluating to rvalue of final 
 *  subexpression c=6.
 *  Commas act as operators in this line.
 */
return a=4, b=5, c=6;

/**
 *  Returns 3, not 1, for same reason as previous example.
 *  Commas act as operators in this line.
 */
return 1, 2, 3;

/**
 *  Returns 3, not 1, still for same reason as above. This example works as it does
 *  because return is a keyword, not a function call. Even though compilers will 
 *  allow for the construct return(value), the parentheses are only relative to "value"
 *  and have no special effect on the return keyword.
 *  Return simply gets an expression and here the expression is "(1), 2, 3".
 *  Commas act as operators in this line.
 */
return(1), 2, 3;

Использует

Варианты использования оператора запятой относительно ограничены. Поскольку он отбрасывает свой первый операнд, он обычно полезен только тогда, когда первый операнд имеет желательные побочные эффекты, которые должны быть упорядочены перед вторым операндом. Кроме того, поскольку он редко используется вне определенных идиом и легко ошибочно используется с другими запятыми или точкой с запятой, он потенциально сбивает с толку и подвержен ошибкам. Тем не менее, есть определенные обстоятельства, при которых он обычно используется, особенно в циклах for и в SFINAE . Для встроенных систем, которые могут иметь ограниченные возможности отладки, оператор запятой может использоваться в сочетании с макросом, чтобы легко переопределить вызов функции, чтобы вставить код непосредственно перед вызовом функции.

Для петель

Чаще всего используется для разрешения нескольких операторов присваивания без использования оператора блока, в первую очередь в выражениях инициализации и приращения цикла for . Это единственное идиоматическое использование в элементарном программировании на C. В следующем примере важен порядок инициализаторов цикла:

void rev(char *s, size_t len)
{
    char *first;
    for (first = s, s += len; s >= first; --s) {
        putchar(*s);
    }
}

Альтернативным решением этой проблемы на других языках является параллельное присваивание , которое позволяет выполнять несколько присваиваний в одном операторе, а также использует запятую, хотя и с другим синтаксисом и семантикой. Это используется в Go в аналогичном цикле for.

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

++p, ++q;
++p; ++q;

Макросы

Запятую можно использовать в макросах препроцессора для выполнения нескольких операций в пространстве одного синтаксического выражения.

Одним из распространенных способов использования является предоставление настраиваемых сообщений об ошибках в неудачных утверждениях. Это делается путем передачи в assertмакрос списка выражений в скобках , где первое выражение является строкой ошибки, а второе выражение - утвержденным условием. assertМакро выдает свой аргумент дословно на неудачу утверждение. Ниже приведен пример:

#include <stdio.h>
#include <assert.h>

int main ( void )
{
    int i;
    for (i=0; i<=9; i++)
    {
        assert( ( "i is too big!", i <= 4 ) );
        printf("i = %i\n", i);
    }
    return 0;
}

Выход:

i = 0
i = 1
i = 2
i = 3
i = 4
assert: assert.c:6: test_assert: Assertion `( "i is too big!", i <= 4 )' failed.
Aborted

Однако макрос assert обычно отключен в производственном коде, поэтому используйте его только для целей отладки.

Условие

Запятую можно использовать в условии (if, while, do while или for), чтобы разрешить вспомогательные вычисления, в частности вызов функции и использование результата, с областью видимости блока :

if (y = f(x), y > x) {
    ... // statements involving x and y
}

Похожая идиома существует в Go , где синтаксис оператора if явно допускает необязательный оператор.

Комплексный возврат

Запятую можно использовать в операторах возврата, чтобы присвоить глобальной переменной или параметру out (передаваемому по ссылке). Эта идиома предполагает, что присваивания являются частью возврата, а не вспомогательными присваиваниями в блоке, который завершается фактическим возвратом. Например, при установке глобального номера ошибки:

if (failure)
    return (errno = EINVAL, -1);

Более подробно это можно записать как:

if (failure) {
    errno = EINVAL;
    return -1;
}

Избегайте блокировки

Для краткости можно использовать запятую, чтобы избежать блока и связанных фигурных скобок, например:

if (x == 1) y = 2, z = 3;
if (x == 1)
    y = 2, z = 3;

вместо:

if (x == 1) {y = 2; z = 3;}
if (x == 1) {
    y = 2; z = 3;
}

Другие языки

В языках программирования OCaml и Ruby для этой цели используется точка с запятой («;»). В JavaScript и Perl оператор запятой используется так же, как в C / C ++. В Java запятая - это разделитель, используемый для разделения элементов в списке в различных контекстах. Это не оператор и не оценивает последний элемент в списке.

Смотрите также

Рекомендации

Библиография

  • Рамаджаран В. (1994), Компьютерное программирование на языке Си , Нью-Дели: Prentice Hall of India
  • Диксит, JB (2005), Основы компьютеров и программирования на C , Нью-Дели: Laxmi Publications
  • Керниган, Брайан В .; Ричи, Деннис М. (1988), Язык программирования C (2-е изд.), Englewood Cliffs, NJ: Prentice Hall

Внешние ссылки