Что делает оператор запятой?

167

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

lillq
источник
1
Как я отмечаю в своем ответе, после оценки левого операнда есть точка последовательности. Это не похоже на запятую в вызове функции, которая является просто грамматической.
Шафик Ягмур
2
@SergeyK. - Учитывая, что об этом спрашивали и отвечали за много лет до другого, более вероятно, что другой является дубликатом этого вопроса. Однако другой также имеет двойные теги с c и c ++ , что создает неудобства. Это Q & A только для C, с достойными ответами.
Джонатан Леффлер

Ответы:

129

Выражение:

(expression1,  expression2)

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

lillq
источник
3
тогда, если я напишу i = (5,4,3,2,1,0), то в идеале он должен вернуть 0, правильно? но мне присваивается значение 5? Можете ли вы помочь мне понять, где я иду не так?
Джаеш
19
@James: значение операции с запятой всегда будет значением последнего выражения. Ни в коем случае не будет iзначений 5, 4, 3, 2 или 1. Это просто 0. Это практически бесполезно, если выражения не имеют побочных эффектов.
Джефф Меркадо
6
Обратите внимание , что есть полная точка последовательности между оценкой LHS из выражения разделителей и оценками РИТ (см Шафика Ягмур «s ответа на цитату из стандарта C99). Это важное свойство оператора запятой.
Джонатан Леффлер
5
i = b, c;эквивалентно (i = b), cпотому что, потому что присваивание =имеет более высокий приоритет, чем оператор запятой ,. Оператор запятой имеет самый низкий приоритет из всех.
cyclaminist
1
Я беспокоюсь, что круглые скобки вводят в заблуждение по двум причинам: (1) они не нужны - оператор запятой не обязательно должен быть заключен в круглые скобки; и (2) их можно спутать с круглыми скобками вокруг списка аргументов вызова функции - но запятая в списке аргументов не является оператором запятой. Однако исправить это не совсем тривиально. Может быть: в утверждении: expression1, expression2;сначала expression1оценивается, предположительно, на наличие побочных эффектов (таких как вызов функции), затем идет точка последовательности, затем expression2оценивается и возвращается значение…
Джонатан Леффлер
119

Я видел, используется больше всего в 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);
}
crashmstr
источник
21
Эй, это круто! Мне часто приходилось делать неортодоксальные вещи в цикле, чтобы решить эту проблему.
staticsan
6
Несмотря на то, что было бы , вероятно , будет менее темным и более удобным для чтения , если вы сделали что - то вроде: while (read_string(s) && s.len() > 5). Очевидно, что это не сработало бы, если бы read_stringне иметь возвращаемого значения (или не иметь значащего значения). (Изменить: Извините, не заметил, сколько лет было этому сообщению.)
jamesdlin
11
@staticsan Не бойтесь использовать while (1)с break;утверждением в теле. Попытка форсировать выделенную часть кода во время теста во время теста или во время теста, часто является пустой тратой энергии и усложняет понимание кода.
potrzebie
8
@jamesdlin ... и люди все еще читают это. Если вам есть что сказать, то скажите это. Форумы имеют проблемы с воскрешенными темами, потому что темы обычно сортируются по дате последнего сообщения. У StackOverflow таких проблем нет.
Димитр Славчев
3
@potrzebie Мне нравится запятая гораздо лучше, чем while(1)и break;
Майкл
39

Оператор запятой оценит левый операнд, отбросит результат, а затем оценит правый операнд, и это будет результат. Идиоматично использование , как указано в ссылке, когда инициализации переменных , используемые в 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 говорит:

Левый операнд оператора запятой оценивается как выражение пустоты; после его оценки есть точка последовательности. Затем вычисляется правый операнд; результат имеет свой тип и значение. 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

Оператор запятой объединяет два выражения по обе стороны от него в одно, оценивая их оба в порядке слева направо. Значение правой части возвращается как значение всего выражения. (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));

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

*((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строке должны содержать только код управления циклом, не другие дополнительные биты инициализации или обслуживания цикла.

Пол Стивенсон
источник
Спасибо! Это был мой первый ответ на StackOverflow: с тех пор я, наверное, узнал, что краткость стоит ценить :-).
Пол Стивенсон
Иногда я оцениваю немного многословия, как здесь, где вы описываете эволюцию решения (как вы туда попали).
зеленый диод
8

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

Так...

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

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

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

Бен Коллинз
источник
3

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

for (tmp=0, i = MAX; i > 0; i--)
DGentry
источник
2

Единственное место, где я увидел, что это полезно, - это когда вы пишете какой-то интересный цикл, в котором вы хотите сделать несколько вещей в одном из выражений (возможно, в выражении 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 вместо объявления, а затем инициализации .... бла-бла-бла.)

Оуэн
источник
Я полагаю, вы имеете в виду i1 = 0, i2 = размер -1
Франкстер
-2

Я возрождаю это просто для того, чтобы ответить на вопросы @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. Результат ( выражения ) is1`.

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

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

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

ViNi89
источник