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

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

возможный дубликат Как правильно использовать оператор запятой?

Sergey K. 28.08.2013 12:00

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

Shafik Yaghmour 25.06.2014 15:46

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

Jonathan Leffler 18.05.2019 03:18
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
177
3
42 053
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Ответ принят как подходящий

Выражение:

(expression1,  expression2)

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

тогда, если я напишу i = (5,4,3,2,1,0), тогда в идеале он должен вернуть 0, правильно? но мне присваивается значение 5? Не могли бы вы помочь мне понять, в чем я ошибаюсь?

Jayesh 13.11.2010 09:55

@James: значение операции с запятой всегда будет значением последнего выражения. Ни в коем случае i не будет иметь значения 5, 4, 3, 2 или 1. Это просто 0. Это практически бесполезно, если выражения не имеют побочных эффектов.

Jeff Mercado 13.11.2010 10:06

Обратите внимание, что существует точка полной последовательности между оценкой LHS выражения запятой и оценкой RHS (см. отвечатьШафик Ягмур для цитаты из стандарта C99). Это важное свойство оператора запятой.

Jonathan Leffler 21.06.2014 00:47

Глядя на docs.microsoft.com/en-us/cpp/cpp/comma-operator, кажется, есть разница, если скобки не используются. В случае i = b, c; b присваивается i. Как это происходит? В случае параметра функции использование круглых скобок является обязательным, и Microsoft четко объясняет это.

Rajesh 26.04.2018 07:25

i = b, c; эквивалентен (i = b), c, поскольку назначение = имеет более высокий приоритет, чем оператор запятой ,. Оператор запятой имеет самый низкий приоритет из всех.

cyclaminist 02.02.2019 09:10

Я беспокоюсь, что круглые скобки вводят в заблуждение по двум причинам: (1) они не нужны - оператор запятой не обязательно должен быть окружен круглыми скобками; и (2) их можно спутать со скобками вокруг списка аргументов вызова функции, но запятая в списке аргументов не является оператором запятой. Однако исправить это не совсем тривиально. Может быть: В заявлении: expression1, expression2; сначала оценивается expression1, предположительно для его побочных эффектов (таких как вызов функции), затем есть точка последовательности, затем оценивается expression2 и возвращается значение ...

Jonathan Leffler 18.05.2019 03:22

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

Так...

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

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

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

Оно делает. И он будет установлен на 11, если вы не укажете внешние скобки. Довольно интересно и определенно заслуживает предупреждения компилятора в некоторых случаях.

sebkraemer 12.10.2020 13:57

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

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

frankster 13.08.2009 18:05

Я видел больше всего используемых в петлях 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);
}

Эй, это здорово! Мне часто приходилось делать неортодоксальные вещи в цикле, чтобы решить эту проблему.

staticsan 04.09.2009 05:47

Хотя, вероятно, он был бы менее неясным и более читабельным, если бы вы сделали что-то вроде: while (read_string(s) && s.len() > 5). Очевидно, это не сработает, если read_string не имеет возвращаемого значения (или не имеет значимого). (Обновлено: извините, не заметил, сколько лет было этому сообщению.)

jamesdlin 25.03.2010 11:05

@staticsan Не бойтесь использовать while (1) с оператором break; в теле. Попытки заставить начальную часть кода перейти к тесту while или вниз к тесту do-while, часто являются пустой тратой энергии и затрудняют понимание кода.

potrzebie 27.09.2012 10:20

@jamesdlin ... и люди до сих пор его читают. Если вы хотите сказать что-то полезное, скажите это. На форумах возникают проблемы с восстановленными обсуждениями, потому что обсуждения обычно сортируются по дате последнего сообщения. У StackOverflow таких проблем нет.

Dimitar Slavchev 29.11.2012 17:36

@potrzebie Мне больше нравится использование запятой, чем while(1) и break;

Michael 11.03.2016 17:19

Да, это отличный DRY * и оправдание для редко используемого оператора. (* для чего 2-й пример крайне оскорблен!)

underscore_d 17.04.2016 00:43

Он также красиво разделяет код на «настройку и проверку» и «выполнение чего-нибудь», что IMO улучшает читаемость. Тем более, что read_string(s) в теле заявления while не имеет фиксированной позиции в принципе или даже может менять позиции от редакции к редакции.

NOhs 20.06.2019 16:01

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

RobertS supports Monica Cellio 19.02.2020 19:05

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

Paul Stephenson 16.07.2009 21:36

Иногда я ценю некоторую многословность, как в случае, когда вы описываете эволюцию решения (как вы к этому пришли).

green diod 06.12.2016 13:19

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

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

Другие вопросы по теме