int a = {1,2,}; Разрешена странная запятая. Любая конкретная причина?

Возможно, я не с этой планеты, но мне кажется, что следующая синтаксическая ошибка:

int a[] = {1,2,}; //extra comma in the end 

Но это не так. Я был удивлен, когда этот код скомпилирован в Visual Studio, но я научился не доверять компилятору MSVC в отношении правил C ++, поэтому я проверил стандарт, и он также разрешен стандартом. Вы можете видеть 8.5.1 для правил грамматики, если вы мне не верите.

введите описание изображения здесь

Почему это разрешено? Это может быть глупый бесполезный вопрос, но я хочу, чтобы вы поняли, почему я спрашиваю. Если бы это был подзаголовок общего правила грамматики, я бы понял – они решили не затруднять общую грамматику, чтобы просто запретить избыточную запятую в конце списка инициализаторов. Но нет, дополнительная запятая явно разрешена. Например, не разрешается иметь избыточную запятую в конце списка аргументов функции-вызова (когда функция принимает ... ), что является нормальным .

Итак, опять же, есть ли какая-то конкретная причина, в которой явно разрешена избыточная запятая?

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

 int a[] = { 1, 2, 3 }; 

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

Теперь подумайте о генерации кода. Что-то вроде (псевдокод):

 output("int a[] = {"); for (int i = 0; i < items.length; i++) { output("%s, ", items[i]); } output("};"); 

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

Это полезно, если вы делаете что-то вроде этого:

 int a[] = { 1, 2, 3, //You can delete this line and it's still valid }; 

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

 int a[] = { 1, 2, 2, 2, 2, 2, /*line I could comment out easily without having to remove the previous comma*/ } 

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

Я всегда предполагал, что упростить добавление дополнительных элементов:

 int a[] = { 5, 6, }; 

просто становится:

 int a[] = { 5, 6, 7, }; 

впоследствии.

Все, что все говорят о простоте добавления / удаления / генерации строк, правильно, но реальное место синтаксиса сияет, когда вы объединяете исходные файлы. Представьте, что у вас есть этот массив:

 int ints[] = { 3, 9 }; 

И предположим, вы проверили этот код в репозитории.

Затем ваш приятель редактирует его, добавляя к концу:

 int ints[] = { 3, 9, 12 }; 

И вы одновременно редактируете его, добавляя к началу:

 int ints[] = { 1, 3, 9 }; 

Семантически эти виды операций (добавление к началу, добавление к концу) должны быть полностью безопасными слиянием, и ваше программное обеспечение для управления версиями (надеюсь, git) должно быть способно авторизоваться. К сожалению, это не так, потому что ваша версия не имеет запятой после 9 и ваших приятелей. Принимая во внимание, что если исходная версия имела завершающий 9, они бы прошли автоматизацию.

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

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

 for_each(my_inits.begin(), my_inits.end(), [](const std::string& value) { std::cout << value << ",\n"; }); 

Для программиста нет никакого преимущества.

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

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

Он создает генераторы кода, которые легче выдают массивы или enums.

Представить:

 std::cout << "enum Items {\n"; for(Items::iterator i(items.begin()), j(items.end); i != j; ++i) std::cout << *i << ",\n"; std::cout << "};\n"; 

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

Например, если генератор кода написан на Python, легко избежать str.join() запятой с помощью функции str.join() :

 print("enum Items {") print(",\n".join(items)) print("}") 

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

* Теоретически это разрешено, но Internet Explorer не соответствует стандарту и рассматривает его как ошибку

Причина тривиальна: простота добавления / удаления строк.

Представьте следующий код:

 int a[] = { 1, 2, //3, // - not needed any more }; 

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

В отличие от других ответов, я действительно не считаю, что легкость генерации списка является веской причиной: в конце концов, тривиальным для кода является специальный случай последней (или первой) строки. Генераторы кода записываются один раз и используются много раз.

Это проще для машин, т.е. parsingа и генерации кода. Это также легче для людей, т. Е. Модификация, комментирование и визуальная элегантность через согласованность.

Предполагая, что вы писали бы следующее?

 #include  #include  int main(void) { puts("Line 1"); puts("Line 2"); puts("Line 3"); return EXIT_SUCCESS } 

Нет. Не только потому, что последнее утверждение является ошибкой, но и потому, что оно противоречиво. Итак, зачем делать то же самое с коллекциями? Даже на языках, которые позволяют пропустить последние точки с запятой и запятыми, сообществу обычно это не нравится. Сообщество Perl, например, похоже, не хочет пропускать точки с запятой, выровнять однострочные. Они применяют это также к запятым.

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

Я удивлен после того, как все это время никто не цитировал аннотированное справочное руководство по C ++ ( ARM ), он говорит следующее о [dcl.init] с акцентом мое:

Есть явно слишком много обозначений для инициализаций, но каждый, кажется, хорошо обслуживает определенный стиль использования. Обозначение = {initializer_list, opt} было унаследовано от C и хорошо служит для инициализации структур данных и массивов. […]

хотя грамматика развивалась с момента написания ARM, происхождение остается.

и мы можем перейти к обоснованию C99, чтобы понять, почему это было разрешено на C, и в нем говорится:

K & R разрешает конечную запятую в инициализаторе в конце списка инициализаторов. Стандарт сохранил этот синтаксис, поскольку он обеспечивает гибкость при добавлении или удалении членов из списка инициализаторов и упрощает создание таких списков.

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

Это позволяет защитить от ошибок, вызванных перемещением элементов в длинном списке.

Например, предположим, что у нас есть код, похожий на этот.

 #include  #include  #include  #define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array)) int main() { std::string messages[] = { "Stack Overflow", "Super User", "Server Fault" }; size_t i; for (i = 0; i < ARRAY_SIZE(messages); i++) { std::cout << messages[i] << std::endl; } } 

И это здорово, так как показывает оригинальную трилогию сайтов Stack Exchange.

 Stack Overflow Super User Server Fault 

Но есть одна проблема с этим. Как видите, нижний колонтитул на этом веб-сайте показывает «Ошибка сервера перед суперпользователем». Лучше исправить это, прежде чем кто-нибудь заметит.

 #include  #include  #include  #define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array)) int main() { std::string messages[] = { "Stack Overflow", "Server Fault" "Super User", }; size_t i; for (i = 0; i < ARRAY_SIZE(messages); i++) { std::cout << messages[i] << std::endl; } } 

В конце концов, движущиеся линии вокруг не могут быть такими тяжелыми, не так ли?

 Stack Overflow Server FaultSuper User 

Я знаю, нет веб-сайта под названием «Сервер FaultSuper User», но наш компилятор утверждает, что он существует. Теперь проблема заключается в том, что C имеет функцию конкатенации строк, которая позволяет писать две строки с двойными кавычками и конкатенировать их, используя ничего (аналогичная проблема также может возникать с целыми числами, так как знак имеет несколько значений).

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

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

 int a [] = { #ifdef A 1, //this can be last if B and C is undefined #endif #ifdef B 2, #endif #ifdef C 3, #endif }; 

Добавление макросов для обработки последней , было бы большой болью. С этим небольшим изменением синтаксиса это тривиально для управления. И это более важно, чем машинный код, потому что обычно это намного проще сделать в полной версии Turing, чем очень ограниченный препроцессор.

Как и многие вещи, конечная запятая в инициализаторе массива является одной из вещей, которые C ++ унаследовал от C (и им придется поддерживать навсегда). Взгляд, совершенно отличный от тех, что здесь указаны, упоминается в книге «Deep C secrets» .

В этом случае после примера с более чем одним «парамодическим знаком»:

 char *available_resources[] = { "color monitor" , "big disk" , "Cray" /* whoa! no comma! */ "on-line drawing routines", "mouse" , "keyboard" , "power cables" , /* and what's this extra comma? */ }; 

мы читаем :

… эта конечная запятая после окончательного инициализатора не является опечаткой, а является провалом в синтаксисе, перенесенном из аборигена C. Его присутствие или отсутствие разрешено, но не имеет никакого значения . Обоснование, заявленное в обосновании ANSI C, заключается в том, что упрощает автоматическую генерацию C. Претензия была бы более надежной, если бы конечные запятые разрешались в каждом списке с разделителями-запятыми , например, в объявлениях перечислений или нескольких деклараторах переменных в одной декларации. Они не.

… для меня это имеет смысл

В дополнение к генерации кода и простому редактированию, если вы хотите реализовать парсер, этот тип грамматики проще и проще реализовать. C # следует за этим правилом в нескольких местах, где есть список разделенных запятыми элементов, таких как элементы в определении enum .

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

 #define LIST_BEGIN int a[] = { #define LIST_ENTRY(x) x, #define LIST_END }; 

Применение:

 LIST_BEGIN LIST_ENTRY(1) LIST_ENTRY(2) LIST_END 

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

 #define LIST_LAST_ENTRY(x) x 

и это было бы очень неудобно использовать.

Если вы используете массив без указанной длины, VC ++ 6.0 может автоматически идентифицировать его длину, поэтому, если вы используете «int a [] = {1,2,};» длина a равна 3, но последняя имеет hasn ‘ t, вы можете использовать «cout <

  • Когда это действительно для доступа к указателю на «мертвый» объект?
  • Используя std :: bind с функцией-членом, используйте указатель объекта или нет для этого аргумента?
  • При использовании заголовков C в C ++ следует ли использовать функции из std :: или глобального пространства имен?
  • Является ли std :: abs (0u) плохо сформированным?
  • Вычисления без последствий (ака последовательности)
  • В C ++ 11, имеет ли `i + = ++ i + 1` неопределенное поведение?
  • Удаленный конструктор по умолчанию. Объекты все еще могут быть созданы ... иногда
  • Это действительно нарушает правила строгого сглаживания?
  • «Создание» объекта с возможностью копирования с возможностью memcpy
  • Давайте будем гением компьютера.