Использование оператора с запятой [дубликат]
этот вопрос уже есть ответ здесь:
- что делает оператор запятая , не? 8 ответов
вы видите, что он используется в операторах цикла, но это юридический синтаксис в любом месте. Какую пользу вы нашли для него в другом месте, если таковые имеются?
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;
конечно, оба могут быть полезны только тогда, когда условная часть или исполняемая часть цикла довольно мала, две-три операции.
единственный раз, когда я когда-либо видел
,
оператор, используемый вне afor
цикл был выполнить назначение в тернарный оператор. Это было давно, поэтому я не могу вспомнить точное утверждение, но это было что-то вроде: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));
это уменьшило некоторые повторяющиеся набрав, но вы должны быть осторожны, он не становится слишком нечитаемым.
пожалуйста, смотрите мою чрезмерно длинную версию этого ответа здесь.
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, а не в коде выше).
- внутри цикла у вас есть доступ к текущему и следующему элементу