Использование C-запятой

Вы видите, что он используется в операторах цикла, но это юридический синтаксис в любом месте. Какое использование вы нашли для него в другом месте, если таковые имеются?

Ответ 1

Язык C (как и С++) исторически представляет собой сочетание двух совершенно разных стилей программирования, которые можно назвать "программированием операторов" и "программированием выражений". Как вы знаете, каждый процедурный язык программирования обычно поддерживает такие фундаментальные конструкции, как последовательность и ветвление. Эти фундаментальные конструкции присутствуют на языках C/С++ в двух формах: один для программирования операторов, другой для программирования выражений.

Например, когда вы пишете свою программу в терминах операторов, вы можете использовать последовательность операторов, разделенных символом ;. Когда вы хотите сделать какое-либо ветвление, вы используете операторы if. Вы также можете использовать циклы и другие виды операторов передачи управления.

В программировании выражений вам также доступны те же конструкции. На самом деле это оператор ,. Оператор , не что иное, как разделитель последовательных выражений в C, то есть оператор , в программировании выражений выполняет ту же роль, что и ; в программировании операторов. Ветвление в программировании выражений выполняется с помощью оператора ?: и, альтернативно, с помощью характеристик оценки короткого замыкания операторов && и ||. (Однако для программирования выражений нет циклов. И чтобы заменить их рекурсией, вам нужно применить программирование операторов.)

Например, следующий код

a = rand();
++a;
b = rand();
c = a + b / 2;
if (a < c - 5)
  d = a;
else
  d = b;

который является примером традиционного программирования инструкций, может быть переписан в терминах программирования выражений как

a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? d = a : d = b;

или

a = rand(), ++a, b = rand(), c = a + b / 2, d = a < c - 5 ? a : b;

или

d = (a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? a : b);

или

a = rand(), ++a, b = rand(), c = a + b / 2, (a < c - 5 && (d = a, 1)) || (d = b);

Излишне говорить, что на практике программирование операторов обычно создает гораздо более читаемый код C/С++, поэтому мы обычно используем программирование выражений в очень хорошо измеренных и ограниченных количествах. Но во многих случаях это пригодится. И линия между тем, что приемлемо, а что нет, в значительной степени зависит от личных предпочтений и способности распознавать и читать установленные идиомы.

В качестве дополнительного примечания: сам дизайн языка, очевидно, скроен в отношении утверждений. Выражения могут свободно вызывать выражения, но выражения не могут вызывать утверждения (помимо вызова предопределенных функций). Эта ситуация в GCC компиляторе довольно интересна, что поддерживает так называемый выражение выражения как расширение (симметричное для "операторов выражения" в стандарте C). "Выражения операторов" позволяют пользователю напрямую вставлять операторный код в выражения, точно так же, как они могут вставлять код на основе выражений в операторы в стандартном C.

В качестве еще одной заметки: на языке С++ программирование на основе функций играет важную роль, что можно рассматривать как еще одну форму "программирования выражений". Согласно текущим тенденциям в дизайне С++, во многих ситуациях его можно считать предпочтительным по сравнению с традиционным программированием.

Ответ 2

Я думаю, что в общем случае C-запятая не является хорошим стилем для использования просто потому, что ее очень легко пропустить - либо кто-то другой пытается прочитать/понять/исправить ваш код, либо вы сами в месяц на линии. Разумеется, вне деклараций переменных и для циклов, где это идиоматично.

Вы можете использовать его, например, для упаковки нескольких операторов в тройной оператор (?:), ala:

int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117;

но мои боги, почему?!? (Я видел, как он использовался таким образом в реальном коде, но к сожалению не имеет к нему доступа)

Ответ 3

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

Упрощенный пример:

#define SomeMacro(A) ( DoWork(A), Permute(A) )

Здесь B=SomeMacro(A) "возвращает" результат Permute (A) и назначает его "B".

Ответ 4

Две функции оператора запятой в С++:

a) Чтение из потока до тех пор, пока не встретится определенная строка (помогает сохранить код DRY):

 while (cin >> str, str != "STOP") {
   //process str
 }

b) Напишите сложный код в инициализаторах конструктора:

class X : public A {
  X() : A( (global_function(), global_result) ) {};
};

Ответ 5

Мне пришлось использовать запятую для отладки блокировок mutex, чтобы поместить сообщение до, когда блокировка начинает ждать.

Я не мог, кроме сообщения журнала в теле производного конструктора блокировки, поэтому мне пришлось поместить его в аргументы конструктора базового класса, используя: baseclass ((log ( "message" ), actual_arg)) в список инициализации. Обратите внимание на дополнительные скобки.

Вот выдержка из классов:

class NamedMutex : public boost::timed_mutex
{
public:
    ...

private:
    std::string name_ ;
};

void log( NamedMutex & ref__ , std::string const& name__ )
{
    LOG( name__ << " waits for " << ref__.name_ );
}

class NamedUniqueLock : public boost::unique_lock< NamedMutex >
{
public:

    NamedUniqueLock::NamedUniqueLock(
        NamedMutex & ref__ ,
        std::string const& name__ ,
        size_t const& nbmilliseconds )
    :
        boost::unique_lock< NamedMutex >( ( log( ref__ , name__ ) , ref__ ) ,
            boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ),
            ref_( ref__ ),
            name_( name__ )
    {
    }

  ....

};

Ответ 6

Библиотека Boost Assignment является хорошим примером перегрузки оператора запятой полезным и понятным способом. Например:

using namespace boost::assign;

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

Ответ 7

Из стандарта C:

Левый операнд оператора запятой оценивается как выражение void; после его оценки появляется точка последовательности. Затем оценивается правый операнд; результат имеет свой тип и значение. (Оператор запятой не дает lvalue.)) Если делается попытка изменить результат работы с запятой или получить доступ к ней после следующей точки последовательности, то поведение undefined.

Короче говоря, это позволяет указать более одного выражения, где C ожидает только одного. Но на практике он чаще всего используется для циклов.

Обратите внимание, что:

int a, b, c;

НЕ является оператором запятой, это список деклараторов.

Ответ 8

Вы можете перегрузить его (если этот вопрос имеет тег "С++" ). Я видел некоторый код, где для генерации матриц использовалась перегруженная запятая. Или векторы, я точно не помню. Разве это не довольно (хотя и немного запутанно):

MyVector foo = 2, 3, 4, 5, 6;

Ответ 9

Он иногда используется в макросах, таких как макросы отладки, такие как:

#define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size)))

(Но посмотрите на этот ужасный провал, по-вашему, за то, что может произойти, когда вы переусердствовали.)

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

Ответ 10

За пределами цикла for, и даже в нем есть запах запаха кода, единственное место, которое я видел как хорошее использование для оператора запятой, является частью удаления:

 delete p, p = 0;

Единственное значение по сравнению с альтернативой - вы можете случайно скопировать/вставить только половину этой операции, если она находится на двух строках.

Мне также нравится, потому что, если вы это делаете по привычке, вы никогда не забудете нулевое задание. (Конечно, почему p не находится внутри какого-то файла auto_ptr, smart_ptr, shared_ptr и т.д. Другой вопрос.)

Ответ 11

Учитывая @Nicolas Goy цитата из стандарта, тогда звучит так, как будто вы можете написать однострочный цикл для таких циклов, как:

int a, b, c;
for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--);
printf("%d", c);

Но добрый Бог, мужик, ты действительно хочешь сделать свой код С более неясным таким образом?

Ответ 12

Очень полезно добавить комментарий в макросы ASSERT:

ASSERT(("This value must be true.", x));

Так как большинство макросов assert style выдают весь текст своего аргумента, это добавляет дополнительный бит полезной информации в утверждение.

Ответ 13

В общем, я избегаю использования оператора запятой, потому что он просто делает код менее читаемым. Почти во всех случаях было бы проще и понятнее делать два утверждения. Как:

foo=bar*2, plugh=hoo+7;

не имеет явного преимущества:

foo=bar*2;
plugh=hoo+7;

Одно место, кроме циклов, где я использовал его в if/else, например:

if (a==1)
... do something ...
else if (function_with_side_effects_including_setting_b(), b==2)
... do something that relies on the side effects ...

Вы можете поместить функцию перед IF, но если для выполнения этой функции требуется много времени, вам может понадобиться избежать ее, если это не необходимо, и если функция не должна выполняться, кроме a!= 1, тогда это не вариант. Альтернативой является вложение IF дополнительного слоя. Это на самом деле то, что я обычно делаю, потому что приведенный выше код немного загадочен. Но я делал это через запятую, а затем потому, что вложенность также загадочна.

Ответ 14

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

void* s_static_pointer = 0;

void init() {
    configureLib(); 
    s_static_pointer = calculateFancyStuff(x,y,z);
    regptr(s_static_pointer);
}

bool s_init = init(), true; // just run init() before anything else

Foo::Foo() {
  s_static_pointer->doStuff(); // works properly
}

Ответ 15

Для меня один действительно полезный случай с запятыми в C использует их для выполнения чего-то условного.

  if (something) dothis(), dothat(), x++;

это эквивалентно

  if (something) { dothis(); dothat(); x++; }

Это не о том, чтобы "печатать меньше", иногда это выглядит очень ясно.

Также петли такие же:

while(true) x++, y += 5;

Конечно, оба могут быть полезны только тогда, когда условная часть или исполняемая часть цикла довольно мала, две-три операции.

Ответ 16

Единственный раз, когда я когда-либо видел оператор ,, используемый вне цикла for, заключался в том, чтобы выполнить оспаривание в тройном выражении. Это было давно, поэтому я не могу повторить точное утверждение, но это было что-то вроде:

int ans = isRunning() ? total += 10, newAnswer(total) : 0;

Очевидно, что здравомыслящий человек не писал бы такой код, но автор был злым гением, который строит c-выражения на основе кода ассемблера, который они генерировали, а не читаемости. Например, он иногда использовал циклы вместо утверждений if, потому что предпочитал сборщик, сгенерированный им.

Его код был очень быстрым, но недостижимым, я рад, что мне больше не нужно работать с ним.

Ответ 17

Я использовал его для макроса, чтобы "назначить значение любого типа в выходной буфер, на который указывает char *, а затем увеличивать указатель на необходимое количество байтов", например:

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

Это уменьшило некоторую повторяющуюся типизацию, но вам нужно быть осторожным, чтобы она не стала слишком нечитаемой.

Пожалуйста, просмотрите мою длинную версию этого ответа здесь.

Ответ 19

qemu имеет некоторый код, который использует оператор запятой в условной части цикла for (см. QTAILQ_FOREACH_SAFE в qemu-queue.h). То, что они сделали, сводится к следующему:

#include <stdio.h>

int main( int argc, char* argv[] ){
  int x = 0, y = 0;

  for( x = 0; x < 3 && (y = x+1,1); x = y ){
    printf( "%d, %d\n", x, y );
  }

  printf( "\n%d, %d\n\n", x, y );

  for( x = 0, y = x+1; x < 3; x = y, y = x+1 ){
    printf( "%d, %d\n", x, y );
  }

  printf( "\n%d, %d\n", x, y );
  return 0;
}

... со следующим выходом:

0, 1
1, 2
2, 3

3, 3

0, 1
1, 2
2, 3

3, 4

Первая версия этого цикла имеет следующие эффекты:

  • Он избегает выполнения двух назначений, поэтому вероятность выхода кода из синхронизации снижается.
  • Поскольку он использует &&, назначение не оценивается после последней итерации
  • Поскольку присваивание не оценивается, оно не будет пытаться де-ссылаться на следующий элемент в очереди, когда он находится в конце (в коде qemu, а не в коде выше).
  • Внутри цикла у вас есть доступ к текущему и следующему элементу