Что делает оператор запятой? - PullRequest
142 голосов
/ 09 сентября 2008

Что делает оператор , в C?

Ответы [ 9 ]

113 голосов
/ 09 сентября 2008

Выражение:

(expression1,  expression2)

Сначала вычисляется выражение1, затем вычисляется выражение2, и значение выражения2 возвращается для всего выражения.

106 голосов
/ 09 сентября 2008

Я видел, что чаще всего используется в while петлях:

string s;
while(read_string(s), s.len() > 5)
{
   //do something
}

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

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}
31 голосов
/ 26 августа 2013

Оператор запятой оценит левый операнд, отбросит результат, а затем оценит правый операнд, и это будет результат. Использование idiomatic , как отмечено в ссылке, происходит при инициализации переменных, используемых в цикле for, и дает следующий пример:

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

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

Из черновика стандарта C99 грамматика выглядит следующим образом:

expression:
  assignment-expression
  expression , assignment-expression

и пункт 2 говорит:

Левый операнд оператора запятой оценивается как выражение void; после его вычисления есть точка последовательности. Затем вычисляется правый операнд ; результат имеет свой тип и значение. 97) Если предпринята попытка изменить результат оператора запятой или получить к нему доступ после следующей точки последовательности, поведение не определено.

Сноска 97 говорит:

Оператор запятой не дает lvalue .

, что означает, что вы не можете присвоить результат оператора через запятую .

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

#include <stdio.h>

int main()
{
    int x, y ;

    x = 1, 2 ;
    y = (3,4) ;

    printf( "%d %d\n", x, y ) ;
}

будет иметь следующий вывод:

1 4
28 голосов
/ 12 сентября 2008

Оператор запятой объединяет два выражения по обе стороны от него в одно, оценивая их оба в порядке слева направо. Значение с правой стороны возвращается как значение всего выражения. (expr1, expr2) похоже на { expr1; expr2; }, но вы можете использовать результат expr2 при вызове функции или назначении.

В циклах for часто можно увидеть инициализацию или поддержку нескольких переменных, например:

for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh)
{
    /* do something with low and high and put new values
       in newlow and newhigh */
}

Кроме того, я использовал его «в гневе» только в одном другом случае, когда оборачивал две операции, которые всегда должны идти вместе в макросе. У нас был код, который копировал различные двоичные значения в байтовый буфер для отправки по сети, и указатель сохранялся там, где мы получили:

unsigned char outbuff[BUFFSIZE];
unsigned char *ptr = outbuff;

*ptr++ = first_byte_value;
*ptr++ = second_byte_value;

send_buff(outbuff, (int)(ptr - outbuff));

Где значения были short с или int с, мы сделали это:

*((short *)ptr)++ = short_value;
*((int *)ptr)++ = int_value;

Позже мы читаем, что это не был действительно C, потому что (short *)ptr больше не является l-значением и не может быть увеличено, хотя наш компилятор в то время не возражал. Чтобы исправить это, мы разделили выражение на две части:

*(short *)ptr = short_value;
ptr += sizeof(short);

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

#define ASSIGN_INCR(p, val, type)  ((*((type) *)(p) = (val)), (p) += sizeof(type))

Используя оператор запятой, мы могли использовать его в выражениях или выражениях так, как нам хотелось:

if (need_to_output_short)
    ASSIGN_INCR(ptr, short_value, short);

latest_pos = ASSIGN_INCR(ptr, int_value, int);

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

Я не предполагаю, что эти примеры являются хорошим стилем! В самом деле, я помню, что Стив Макконнелл Code Complete рекомендовал не использовать даже запятые в цикле for: для удобства чтения и поддержки цикл должен управляться только одной переменной, а выражения в for сама строка должна содержать только код управления циклом, а не другие дополнительные биты инициализации или обслуживания цикла.

8 голосов
/ 09 сентября 2008

Это вызывает оценку нескольких операторов, но использует только последнее из них в качестве результирующего значения (я думаю, что это значение r).

Итак ...

int f() { return 7; }
int g() { return 8; }

int x = (printf("assigning x"), f(), g() );

должно привести к тому, что для x будет установлено значение 8.

5 голосов
/ 24 марта 2015

Оператор запятой не делает ничего значимого, это 100% лишняя особенность. Основное использование этого - «люди, пытающиеся быть умными» и поэтому используют его (непреднамеренно), чтобы запутать читаемый код. Основная область использования - запутывание для петель, например:

for(int i=0, count=0; i<x; i++, count++)

Где int i=0, count=0 на самом деле не оператор запятой, а список объявлений (мы уже запутались здесь). i++, count++ - оператор запятой, который сначала вычисляет левый операнд, а затем правый операнд. Результат оператора запятой является результатом правильного операнда. Результат левого операнда отбрасывается.

Но приведенный выше код может быть написан гораздо более читабельным способом без оператора запятой:

int count = 0;
for(int i=0; i<x; i++) // readable for loop, no nonsense anywhere
{
  ...
  count++;
}

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

Итак, если у вас есть какой-то неопределенный код поведения, подобный этому:

printf("%d %d", i++, i++);

Вы можете фактически превратить его в просто неопределенное поведение (порядок оценки параметров функции), написав

printf("%d %d", (0,i++), (0,i++));

Теперь существует точка последовательности между каждой оценкой i++, так что, по крайней мере, программа больше не будет рисковать сбой и сгорание, даже если порядок оценки параметров функции остается неопределенным.

Конечно, никто не писал бы такой код в реальных приложениях, он полезен только для обсуждения языком-юристом точек последовательности в языке C.

Оператор запятой запрещен MISRA-C: 2004 и MISRA-C: 2012 с обоснованием того, что он создает менее читаемый код.

3 голосов
/ 09 сентября 2008

Как указывалось в предыдущих ответах, он оценивает все утверждения, но использует последнее в качестве значения выражения. Лично я нашел это полезным только в выражениях цикла:

for (tmp=0, i = MAX; i > 0; i--)
2 голосов
/ 09 сентября 2008

Единственное место, где я видел, что это полезно, - это когда вы пишете фанк-цикл, в котором вы хотите сделать несколько вещей в одном из выражений (возможно, в выражении init или выражении цикла. Что-то вроде:

bool arraysAreMirrored(int a1[], int a2[], size_t size)
{
  size_t i1, i2;
  for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--)
  {
    if(a1[i1] != a2[i2])
    {
      return false;
    }
  }

  return true;
}

Прошу прощения, если есть какие-либо синтаксические ошибки или я смешал что-либо, что не является строгим C. Я не утверждаю, что оператор, является хорошей формой, но это то, для чего вы могли бы его использовать. В приведенном выше случае я бы, вероятно, вместо этого использовал цикл while, поэтому множественные выражения в init и loop были бы более очевидными. (И я бы инициализировал i1 и i2 inline вместо объявления, а затем инициализации .... бла-бла-бла.)

0 голосов
/ 04 января 2019

Я возрождаю это просто для того, чтобы ответить на вопросы @Rajesh и @JeffMercado, которые, на мой взгляд, очень важны, так как это один из самых популярных поисковых запросов.

Возьмите следующий фрагмент кода, например

int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d\n", i , j);

Будет напечатано

1 5

Дело i обрабатывается, как объясняется в большинстве ответов. Все выражения оцениваются в порядке слева направо, но только последнее присваивается i. Результат выражения ( ) is 1`.

Случай j следует различным правилам приоритета, поскольку , имеет самый низкий приоритет оператора. Из-за этих правил компилятор видит выражение присваивания, константа, константа ... . Выражения снова оцениваются в порядке слева направо, и их побочные эффекты остаются видимыми, поэтому j равно 5 в результате j = 5.

Интересно, что int j = 5,4,3,2,1; не допускается спецификацией языка. Инициализатор ожидает выражение присваивания , поэтому прямой оператор , недопустим.

Надеюсь, это поможет.

...