Что делает оператор , в C?
Как я отмечаю в своем ответе, после вычисления левого операнда есть точка последовательности. Это не похоже на запятую в вызове функции, которая носит чисто грамматический характер.
@SergeyK. - Учитывая, что этот вопрос был задан и ответил на него за много лет до другого, более вероятно, что второй является дубликатом этого вопроса. Однако другой также имеет двойные теги как c, так и C++, что доставляет неудобства. Это вопросы и ответы только для Си, с достойными ответами.





Выражение:
(expression1, expression2)
Сначала вычисляется выражение1, затем вычисляется выражение2, и значение выражения2 возвращается для всего выражения.
тогда, если я напишу i = (5,4,3,2,1,0), тогда в идеале он должен вернуть 0, правильно? но мне присваивается значение 5? Не могли бы вы помочь мне понять, в чем я ошибаюсь?
@James: значение операции с запятой всегда будет значением последнего выражения. Ни в коем случае i не будет иметь значения 5, 4, 3, 2 или 1. Это просто 0. Это практически бесполезно, если выражения не имеют побочных эффектов.
Обратите внимание, что существует точка полной последовательности между оценкой LHS выражения запятой и оценкой RHS (см. отвечатьШафик Ягмур для цитаты из стандарта C99). Это важное свойство оператора запятой.
Глядя на docs.microsoft.com/en-us/cpp/cpp/comma-operator, кажется, есть разница, если скобки не используются. В случае i = b, c; b присваивается i. Как это происходит? В случае параметра функции использование круглых скобок является обязательным, и Microsoft четко объясняет это.
i = b, c; эквивалентен (i = b), c, поскольку назначение = имеет более высокий приоритет, чем оператор запятой ,. Оператор запятой имеет самый низкий приоритет из всех.
Я беспокоюсь, что круглые скобки вводят в заблуждение по двум причинам: (1) они не нужны - оператор запятой не обязательно должен быть окружен круглыми скобками; и (2) их можно спутать со скобками вокруг списка аргументов вызова функции, но запятая в списке аргументов не является оператором запятой. Однако исправить это не совсем тривиально. Может быть: В заявлении: expression1, expression2; сначала оценивается expression1, предположительно для его побочных эффектов (таких как вызов функции), затем есть точка последовательности, затем оценивается expression2 и возвращается значение ...
Он вызывает оценку нескольких операторов, но использует только последний в качестве результирующего значения (я думаю, rvalue).
Так...
int f() { return 7; }
int g() { return 8; }
int x = (printf("assigning x"), f(), g() );
должен привести к тому, что x будет установлен в 8.
Оно делает. И он будет установлен на 11, если вы не укажете внешние скобки. Довольно интересно и определенно заслуживает предупреждения компилятора в некоторых случаях.
Как указывалось в предыдущих ответах, он оценивает все операторы, но использует последний в качестве значения выражения. Лично я нашел это полезным только в выражениях цикла:
for (tmp=0, i = MAX; i > 0; i--)
Единственное место, где я видел, что это полезно, - это когда вы пишете забавный цикл, в котором вы хотите сделать несколько вещей в одном из выражений (возможно, в выражении инициализации или выражении цикла. Что-то вроде:
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 = size -1
Я видел больше всего используемых в петлях 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);
}
Эй, это здорово! Мне часто приходилось делать неортодоксальные вещи в цикле, чтобы решить эту проблему.
Хотя, вероятно, он был бы менее неясным и более читабельным, если бы вы сделали что-то вроде: while (read_string(s) && s.len() > 5). Очевидно, это не сработает, если read_string не имеет возвращаемого значения (или не имеет значимого). (Обновлено: извините, не заметил, сколько лет было этому сообщению.)
@staticsan Не бойтесь использовать while (1) с оператором break; в теле. Попытки заставить начальную часть кода перейти к тесту while или вниз к тесту do-while, часто являются пустой тратой энергии и затрудняют понимание кода.
@jamesdlin ... и люди до сих пор его читают. Если вы хотите сказать что-то полезное, скажите это. На форумах возникают проблемы с восстановленными обсуждениями, потому что обсуждения обычно сортируются по дате последнего сообщения. У StackOverflow таких проблем нет.
@potrzebie Мне больше нравится использование запятой, чем while(1) и break;
Да, это отличный DRY * и оправдание для редко используемого оператора. (* для чего 2-й пример крайне оскорблен!)
Он также красиво разделяет код на «настройку и проверку» и «выполнение чего-нибудь», что IMO улучшает читаемость. Тем более, что read_string(s) в теле заявления while не имеет фиксированной позиции в принципе или даже может менять позиции от редакции к редакции.
Обратите внимание, что это недопустимый код C, так как вопросы задаются для C.
Оператор запятой объединяет два выражения по обе стороны от него в одно, оценивая их в порядке слева направо. Значение правой части возвращается как значение всего выражения.
(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));
Я не считаю, что ни один из этих примеров является хорошим стилем! В самом деле, я, кажется, помню, как Стив МакКоннелл Код завершен советовал даже не использовать операторы запятой в цикле for: для удобства чтения и поддержки цикл должен управляться только одной переменной, а выражения в самой строке for должны содержать только код управления циклом. , а не другие дополнительные биты инициализации или обслуживания цикла.
Спасибо! Это был мой первый ответ на StackOverflow: с тех пор я, наверное, научился ценить лаконичность :-).
Иногда я ценю некоторую многословность, как в случае, когда вы описываете эволюцию решения (как вы к этому пришли).
оператор запятой будет оценивать левый операнд, отбрасывать результат, а затем оценивать правый операнд, и это будет результатом. Использование идиоматический, как указано в ссылке, происходит при инициализации переменных, используемых в цикле 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 говорит:
The left operand of a comma operator is evaluated as a void expression; there is a sequence point after its evaluation. Then the right operand is evaluated; the result has its type and value.97) If an attempt is made to modify the result of a comma operator or to access it after the next sequence point, the behavior is undefined.
Сноска 97 говорит:
A comma operator does not yield an lvalue.
это означает, что вы не можете присвоить результат оператор запятой.
Важно отметить, что оператор запятой имеет самый низкий приоритет, и поэтому есть случаи, когда использование () может иметь большое значение, например:
#include <stdio.h>
int main()
{
int x, y ;
x = 1, 2 ;
y = (3,4) ;
printf( "%d %d\n", x, y ) ;
}
будет следующий вывод:
1 4
Я возрождаю это просто для того, чтобы ответить на вопросы @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; не допускается спецификацией языка. инициализатор ожидает присваивание-выражение, поэтому прямой оператор , не разрешен.
Надеюсь это поможет.
возможный дубликат Как правильно использовать оператор запятой?