Ответ 1
Выражение:
(expression1, expression2)
Первое выражение1 оценивается, затем вычисляется выражение 2, и значение выражения2 возвращается для всего выражения.
Что делает оператор ,
в C?
Выражение:
(expression1, expression2)
Первое выражение1 оценивается, затем вычисляется выражение 2, и значение выражения2 возвращается для всего выражения.
Я видел больше всего в циклах 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);
}
Оператор запятой будет оценивать левый операнд, отбрасывать результат и затем оценивать правый операнд, и это будет результат. Идиоматическое использование, как указано в ссылке, - это инициализация переменных, используемых в цикле 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
Оператор запятой объединяет два выражения с обеих сторон в один, оценивая их как в порядке слева направо. Значение правой части возвращается как значение всего выражения.
(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, а не С++ с шаблонами, мы не могли бы иметь функцию, берущую произвольный тип, поэтому мы установили макрос:
#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));
Я не предлагаю, чтобы какой-либо из этих примеров был хорошим стилем! Действительно, я, кажется, помню, что Steve McConnell Code Complete консультирует даже против использования операторов запятой в цикле for
: для удобства чтения и ремонтопригодности цикл должен управляться только одной переменной, а выражения в строке for
должны содержат код управления контуром, а не другие дополнительные биты инициализации или обслуживания цикла.
Он вызывает оценку нескольких операторов, но использует только последнее в качестве результирующего значения (например, значение rvalue).
Итак...
int f() { return 7; }
int g() { return 8; }
int x = (printf("assigning x"), f(), g() );
должно привести к тому, что x будет установлено в 8.
Оператор запятой не делает ничего значимого, это 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++;
}
Единственное реальное использование оператора запятой, которое я видел, - это искусственное обсуждение точек последовательности, поскольку оператор запятой имеет точку последовательности между оценкой левого и правого операндов.
Итак, если у вас есть код поведения undefined, как это:
printf("%d %d", i++, i++);
Фактически вы можете превратить его в просто неопределенное поведение (порядок оценки параметров функции), написав
printf("%d %d", (0,i++), (0,i++));
Теперь существует точка последовательности между каждой оценкой i++
, поэтому, по крайней мере, программа не будет рисковать для сбоя и записи дольше, хотя порядок оценки параметров функции остается неопределенным.
Конечно, никто не напишет такой код в реальных приложениях, он полезен только для разговоров по языку-юристу о точках последовательности на языке C.
Оператор запятой запрещен MISRA-C: 2004 и MISRA-C: 2012 с обоснованием того, что он создает менее читаемый код.
Как уже говорилось ранее, он оценивает все утверждения, но использует последнее как значение выражения. Лично я нашел его полезным в выражениях цикла:
for (tmp=0, i = MAX; i > 0; i--)
Единственное место, где я видел его полезным, - это когда вы пишете цикл фанки, в котором вы хотите сделать несколько вещей в одном из выражений (возможно, выражение 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 вместо объявления и затем инициализации.... бла-бла-бла.)
Я возрождаю это просто для того, чтобы ответить на вопросы @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;
не допускается языковой спецификацией. Инициализатор ожидает присвоения выражения, так прямой ,
оператор не допускается.
Надеюсь это поможет.