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

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

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

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

enter image description here

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

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

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

на более поздний срок.

Ответ 5

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

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

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

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

Ответ 6

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

int ints[] = {
    3,
    9
};

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

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

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

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

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

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

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

Ответ 7

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

Ответ 8

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

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

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

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

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

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

Ответ 9

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

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

Ответ 10

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

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

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

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

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

Ответ 11

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

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

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    puts("Line 1");
    puts("Line 2");
    puts("Line 3");

    return EXIT_SUCCESS
}

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

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

Ответ 12

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

Ответ 13

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

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

#include <iostream>
#include <string>
#include <cstddef>
#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 <iostream>
#include <string>
#include <cstddef>
#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

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

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

Ответ 14

Как и многие другие, конечная запятая в инициализаторе массива является одной из вещей, которые С++ унаследовал от 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 this extra comma? */
};

мы читаем:

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

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

Ответ 15

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

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

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

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

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

Ответ 16

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

Ответ 17

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