Использование оператора запятой C [дубликат]

StackOverflow https://stackoverflow.com/questions/1613230

  •  06-07-2019
  •  | 
  •  

Вопрос

На этот вопрос уже есть ответ здесь:

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

Это было полезно?

Решение

Язык C (как и C++) исторически представляет собой смесь двух совершенно разных стилей программирования, которые можно назвать «программированием операторов» и «программированием выражений».Как вы знаете, каждый процедурный язык программирования обычно поддерживает такие фундаментальные конструкции, как последовательность действий и ветвление (видеть Структурированное программирование).Эти фундаментальные конструкции присутствуют в языках 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/C++, поэтому мы обычно используем программирование выражений в очень хорошо измеренных и ограниченных количествах.Но во многих случаях это бывает полезно.И грань между тем, что приемлемо, а что нет, в значительной степени зависит от личных предпочтений и способности распознавать и читать устоявшиеся идиомы.

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

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

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

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

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

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

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

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

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

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

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

Две потрясающие возможности оператора-запятой в C++:

а) Чтение из потока до тех пор, пока не встретится определенная строка (помогает сохранить код СУХИМ):

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

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

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

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

Я не мог оставить сообщение журнала в теле производного конструктора блокировки, поэтому мне пришлось поместить его в аргументы конструктора базового класса, используя:baseclass((log("message"), fact_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__ )
    {
    }

  ....

};

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

using namespace boost::assign;

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

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

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

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

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

int a, b, c;

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

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

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

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

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

Вы можете перегрузить его (если этот вопрос имеет тег «C++»).Я видел код, в котором для генерации матриц использовалась перегруженная запятая.Или векторы, точно не помню.Не правда ли, красиво (хотя и немного сбивает с толку):

МойВектор foo = 2, 3, 4, 5, 6;

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

 delete p, p = 0;

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

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

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

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

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

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

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

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

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

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

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

Я часто использую его для запуска статической функции инициализации в некоторых файлах 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
}

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

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

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

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

Речь идет не о том, чтобы «меньше печатать», просто иногда это выглядит очень понятно.

Также циклы такие:

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

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

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

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

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

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

Я использовал его для макроса, чтобы «присвоить значение любого типа выходному буферу, на который указывает символ 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));

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

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

Это может быть удобно для «кодового гольфа»:

Код гольфа:Игра в кубики

А , в if(i>0)t=i,i=0; сохраняет двух персонажей.

В 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, а не в приведенном выше коде).
  • Внутри цикла у вас есть доступ к текущему и следующему элементу.

Нашел это при инициализации массива:

Что именно произойдет в C, если я использую () для инициализации двумерного массива вместо {}?

Когда я инициализирую массив a[][]:

int a[2][5]={(8,9,7,67,11),(7,8,9,199,89)};

а затем отобразить элементы массива.

Я получил:

11 89 0 0 0 
0 0 0 0 0
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top