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


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

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

20 87

20 ответов:

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

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

в программировании выражений вам также доступны те же конструкции. Это на самом деле где , оператор вступает в игру. Оператор , ни в чем другом, кроме a разделитель последовательных выражений в 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, который поддерживает so называется "заявление выражений" как расширение (симметрично "выражениям выражения" в стандарте C). "Выражения операторов" позволяют пользователю напрямую вставлять код на основе операторов в выражения, так же как они могут вставлять код на основе выражений в операторы в стандартном C.

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

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

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

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

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

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

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

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

две функции оператора запятых убийцы в C++:

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

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

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

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

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

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

  ....

};

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

using namespace boost::assign;

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

от стандарта C:

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

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

внимание:

int a, b, c;

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

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

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

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

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

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

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

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

 delete p, p = 0;

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

Мне тоже нравится, потому что если вы делаете это по привычке, вы никогда не забудете нулевой назначения. (Конечно, почему p не находится внутри somekind 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 больше скрывать таким образом?

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

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

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

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, но если функция занимает много времени для запуска, вы можете избежать этого если это не нужно, и если функция не должна осуществляться, если!=1, то это не вариант. Альтернативой является вложение IF в дополнительный слой. Это на самом деле то, что я обычно делаю, потому что приведенный выше код немного загадочный. Но я делал это запятыми время от времени, потому что вложенность также загадочна.

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

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

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

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

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

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

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

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

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

Это может быть удобно для "гольф"код:

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

The , на if(i>0)t=i,i=0; сохраняет два символа.

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

#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