Що робить оператор комами?


167

Що робить ,оператор в C?



1
Як я зазначаю у своїй відповіді, після оцінки лівого операнда є точка послідовності. Це на відміну від коми у виклику функції, який є просто граматичним.
Шафік Ягмур

2
@SergeyK. - З огляду на те, що на це запитали і відповіли за роки до іншого, більш імовірно, що інший є дублікатом цього питання. Однак інший також має подвійний тег як c, так і c ++ , що є неприємністю. Це питання, які стосуються лише C, з гідними відповідями.
Джонатан Леффлер

Відповіді:


129

Вираз:

(expression1,  expression2)

Спочатку оцінюється вираз1, потім оцінюється вираз2, а значення виразу2 повертається для всього виразу.


3
то якщо я напишу i = (5,4,3,2,1,0), то в ідеалі він повинен повернути 0, правильно? але мені присвоюється значення 5? Чи можете ви допомогти мені зрозуміти, де я помиляюся?
Jayesh

19
@James: значення операції з комою завжди буде значенням останнього виразу. Ні в якому разі не буде iзначень 5, 4, 3, 2 або 1. Це просто 0. Це практично марно, якщо вирази не мають побічних ефектів.
Джефф Меркадо

6
Зверніть увагу , що є повна точка послідовності між оцінкою LHS з виразу роздільників і оцінками РІТ (див Шафіка Ягмур «s відповіді на цитату зі стандарту C99). Це важлива властивість оператора комами.
Джонатан Леффлер

5
i = b, c;еквівалентна (i = b), cтому, що присвоєння =має більший пріоритет, ніж оператор коми ,. Оператор комами має найнижчий пріоритет із усіх.
цикламін

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);
}

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;
Майкл

38

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

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

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

Від проекту стандарту С99 граматика така:

expression:
  assignment-expression
  expression , assignment-expression

а в пункті 2 сказано:

Лівий операнд оператора коми оцінюється як вираз порожнечі; після її оцінки є пункт послідовності. Потім оцінюється правий операнд; результат має свій тип та значення. 97) Якщо робиться спроба змінити результат оператора комами або отримати доступ до нього після наступної точки послідовності, поведінка не визначена.

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

Оператор з комами не дає значення .

а це означає, що ви не можете призначити результат оператора комами .

Важливо зауважити, що оператор з комами має найнижчий пріоритет, і тому є випадки, коли використання ()може призвести до значних змін, наприклад:

#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));

Я не припускаю, що жоден із цих прикладів є гарним стилем! Дійсно, я, мабуть, пам’ятаю Кодекс Стіва МакКоннелла Повна порада щодо навіть використання операторів коми в forциклі: для читабельності та ремонтопридатності цикл повинен контролюватися лише однією змінною, а вирази в самій forрядку повинні містити лише код керування циклом, не інші зайві біти ініціалізації чи обслуговування циклу.


Дякую! Це була моя перша відповідь на StackOverflow: з тих пір я, мабуть, дізнався, що лаконічність слід цінувати :-).
Пол Стефенсон

Іноді я ціную трохи багатослів’я, як це стосується тут, де ви описуєте еволюцію рішення (як ви потрапили туди).
зелений діод

8

Він викликає оцінку декількох висловлювань, але використовує лише останнє як отримане значення (rvalue, я думаю).

Так...

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--)

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;
}

Вибачте мене, якщо є якісь синтаксичні помилки або якщо я змішав що-небудь, що не є суворим С. Я не стверджую, що оператор - це хороша форма, але для цього ви можете його використовувати. У вищенаведеному випадку я б, мабуть, використовував whileцикл замість цього, так що декілька виразів на init та циклі будуть більш очевидними. (І я би ініціалізував i1 та i2 inline, а не декларував, а потім ініціалізував .... bla bla bla.)


Я припускаю, що ви маєте на увазі 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;заборонено мовними специфікаціями. Ініціалізатор очікує присвоєння самовираження тому прямий ,оператор не допускаються.

Сподіваюся, це допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.