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

335

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

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

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

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

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

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

Армен Цирунян
источник
10
Кажется, каждый согласен с «легкостью добавления новой строки», но действительно ли люди, определяющие спецификации языка, беспокоятся о таких вещах? Если они действительно так понимают, то почему они не игнорируют пропущенное, ;когда ясно, что следующий токен - это на самом деле следующее утверждение.
Ещё один пользователь
35
@YetAnotherUser: Да, дизайнеры языка учитывают такие вещи. Разрешение отбрасывать точки с запятой будет иметь гораздо большее влияние и будет весьма неоднозначным во многих частях языка (помните, что пробел не является семантическим в C). Дополнительная запятая в этом случае не является двусмысленной. Дополнительная точка с запятой почти никогда не бывает неоднозначной, и поэтому допускается. В случае, если это неоднозначно (например, после a for()), добавление его вызывает предупреждение компилятора.
Роб Нейпир
5
@ Томалак: Это неоднозначно для читателя и часто является ошибкой. Вот почему он бросает предупреждение. Точно так if (x = 1)же не является двусмысленным в грамматике, но это очень двусмысленно для людей, и поэтому выдает предупреждение.
Роб Нейпир
12
@Rob: Ваш ifпример тоже не является двусмысленным. Я не думаю, что «неоднозначно» означает то, что вы думаете, что это значит!
Гонки легкости на орбите
5
Пока мы согласны с тем, что компилятору от нас нужно что-то полезное защищать, а запятая в объявлении массива не является чем-то полезным, от чего компилятор нас защищает.
Роб Нейпир

Ответы:

436

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

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

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

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

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

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

Джон Скит
источник
89
Кроме того, при использовании VCS «diff» между двумя версиями является более чистым, поскольку при добавлении или удалении элемента изменяется только одна строка.
Кевин Панко
47
@ Нестор: почему "неудачный"? В чем здесь минус? Тот факт, что некоторое внимание было уделено созданию кода (и простому манипулированию) для одной крошечной части языка , не означает, что это должно быть основной мотивацией для всех решений в языке. Вывод типа, удаление точек с запятой и т. Д. Имеют огромное значение для языка. Вы настраиваете ложную дихотомию здесь, ИМО.
Джон Скит
18
@ Нестор: Вот где прагматизм побеждает догматизм: почему это должно быть полностью одно или полностью другим, когда более полезно быть смесью обоих? Как это на самом деле мешает, добавляя запятую в конце? Это противоречие, которое когда-либо мешало вам в каком-то смысле? Если нет, пожалуйста, сравните это несоответствующее несоответствие с практической выгодой использования запятой в конце.
Джон Скит
8
@Mrchief: Дело не в скорости набора текста, а в простоте при копировании, удалении или изменении порядка элементов. Это сделало мою жизнь проще только вчера. Без недостатков, почему бы не сделать жизнь проще? Что касается попытки указать пальцем на MS, я сильно подозреваю, что это было в C, еще до того, как Microsoft существовала ... Вы говорите, что это оправдание кажется странным, но я уверен, что оно приносит пользу тысячам разработчиков в сотнях компаний каждый день. Разве это не лучшее объяснение, чем поиск чего-то полезного для авторов компиляторов?
Джон Скит
6
Это было в K & R C.
Ферруччо
126

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

int a[] = {
  1,
  2,
  3, //You can delete this line and it's still valid
};
Skilldrick
источник
6
JavaScript поддерживает этот синтаксис: как var a = [1, 2,];и большинство других известных мне языков ... ActionScript, Python, PHP.
Шон Фудзивара
14
@Sean Это приведет к ошибке синтаксического анализа в IE JavaScript, так что будьте осторожны!
Скиллдрик
10
Это не для меня в IE9. Но он делает что-то странное ... создает нулевой элемент. Я буду осторожен
Шон Фудзивара
5
@Sean К сожалению, вы правы - это не ошибка синтаксического анализа в IE, но он будет вставить дополнительный набор элементов для undefined.
Скиллдрик
3
К сожалению, JSON не поддерживает этот синтаксис.
Тимммм
38

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

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

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

vcsjones
источник
32

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

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

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

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

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

Оливер Чарльзуорт
источник
3
Я не думаю, что редактирование немного быстрее - хорошая причина для того, чтобы испортить синтаксис. ИМХО, это просто еще одна странная особенность C ++.
Джорджио
3
@ Джорджио: Ну, он унаследован от C. Вполне возможно, что это просто упущение в исходной спецификации языка, которое может иметь полезный побочный эффект.
Оливер Чарльзуорт
Хорошо, я не знал, что это от C. Я только что проверил, что это разрешено и в Java. Хотя это немного странно: в моей интуиции запятая - это разделитель, а не терминатор. Кроме того, можно опустить последнюю запятую. Итак, это терминатор, разделитель или оба? Но хорошо, эта функция доступна, и это полезно знать.
Джорджио
11
@ Джорджио - исходный код для людей, а не машин. Такие мелочи, которые не позволяют нам совершать простые ошибки транспонирования, являются благословением, а не упущением. Для справки, он также работает таким образом в PHP и ECMAScript (и, следовательно, в JavaScript и ActionScript), хотя он недопустим в нотации объектов JavaScript (JSON) (например, [1,2,3,]в порядке, но {a:1, b:2, c:3,}нет).
прекращено
1
@Groky: Чем больше я думаю об этом, тем больше я убежден, что синтаксис языка программирования должен быть как можно более простым и непротиворечивым и с как можно меньшим количеством исключений: это облегчает изучение языка (меньше правил для запоминания ). Преимущество сохранения одного или двух нажатий клавиш при добавлении / удалении элемента в / из списка (что, кстати, я не так часто делаю по сравнению с общим временем, затрачиваемым на кодирование) кажется мне тривиальным по сравнению с имеющий четко определенный синтаксис.
Джорджио
21

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

int ints[] = {
    3,
    9
};

И предположим, что вы проверили этот код в хранилище.

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

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

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

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

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

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

амосс
источник
15

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

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

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

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

Джин Бушуев
источник
5
Я полностью не согласен; [Я считаю, что] он нашел свой путь во многих языках, созданных задолго до C, именно потому, что программисту выгодно иметь возможность перемещаться по содержимому массива, произвольно комментировать строки и так далее, не беспокоясь о глупых синтаксических ошибках, вызванных транспозицией. Разве мы недостаточно напряжены?
прекращено
12
@Dereleased - по той же логике, почему нельзя допускать конечный (что-либо), как насчет, int a = b + c +;иначе if(a && b &&);будет проще просто скопировать и вставить что-нибудь в конце и проще написать генераторы кода. Эта проблема является как тривиальной, так и субъективной, в таких случаях всегда полезно делать то, что лучше для читателя кода.
Джин Бушуев
1
@Gene Bushuyev: Точно! У меня часто бывают длинные выражения с + или &&, с оператором в конце строки, и, конечно, мне приходится тратить дополнительное время, когда я хочу удалить последний операнд выражения. Я думаю, что этот синтаксис запятой действительно странный!
Джорджио
2
@GeneBushuyev - я не согласен с этим. Хотя использование запятых в массивах и т. П. Является функцией удаления ошибок и облегчает вашу жизнь программиста, я бы ради удобства чтения принял меры для удаления концевых операторов AND (&&), плюсов и других различных операторов из условных выражений. заявления. Это просто ужасно, ИМО.
Суни Расмуссен
2
Что касается &&оператора, иногда я делаю условные выражения, например, if (true \n && b1 \n && b2)чтобы добавлять и удалять строки по мере необходимости.
Кристиан Манн
12

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

Фредрик Пихл
источник
11

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

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

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()функцию:

print("enum Items {")
print(",\n".join(items))
print("}")
Максим Егорушкин
источник
10

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

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

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

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

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

Шафик Ягмур
источник
1
Upvote за самый подтвержденный ответ литературы и истинный источник этой функции.
Марко
10

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

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

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

Yankes
источник
7

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

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

Томас Бонини
источник
JavaScript-массивы (которые являются просто объектами со свойством магической длины) довольно необычны в любом случае: var x = [,,,]допустимы (кроме IE <9, но спецификация говорит, что это разрешено)
Peter C
Согласно спецификации ECMAScript, это совершенно правильно; теоретически он должен работать в любом браузере, который реализует JavaScript в соответствии с указанной спецификацией, особенно в той части спецификации, которая здесь найдена .
прекращено
1
К сожалению, JavaScript - это создание приложений для общественности. Так что нет, это не совсем верно, когда ~ 50% пользователей будут иметь проблемы с использованием вашего приложения. И да, если бы я мог запретить IE <9 - слишком много часов было
потрачено
@Dere: да, я сказал так много в своем ответе =)
Томас Бонини
@Dereleased microsoft изобретает свои собственные спецификации и команды, которые другие соблюдают, по крайней мере, что менталитет меняется (слава богу)
Крис МакГрат,
7

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

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

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

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

    return EXIT_SUCCESS
}

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

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

Луис
источник
Есть языки (например, Паскаль), которые позволяют это. Т.е. вы должны выбирать между; в качестве терминатора (С) или в качестве разделителя (Паскаль). То же самое для «,». Было бы хорошо для меня, если ',' является терминатором, но тогда {1, 2, 3} должно быть синтаксической ошибкой.
Джорджио
6

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

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

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

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

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

Влад
источник
6

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

Марк Б
источник
5

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

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

#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 есть функция конкатенации строк, которая позволяет вам писать две строки в двойных кавычках и объединять их, не используя ничего (аналогичная проблема может возникнуть и с целыми числами, так как- знак имеет несколько значений).

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

Конрад Боровски
источник
4

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

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

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? */
};

мы читаем :

... эта запятая после финального инициализатора - не опечатка, а ошибка в синтаксисе, перенесенная с аборигенов Си . Его присутствие или отсутствие допускается, но не имеет значения . Обоснование, заявленное в обосновании ANSI, заключается в том, что оно облегчает автоматическую генерацию Си. Заявление было бы более правдоподобным, если бы в каждом списке с разделителями-запятыми были разрешены конечные запятые , например, в объявлениях enum или в нескольких деклараторах переменных в одном объявлении. Они не.

... для меня это имеет больше смысла

Никос Атанасиу
источник
2
Запрет на запятую в этом enumслучае несколько интересен, поскольку именно в этом случае пропущенная запятая будет представлять наименьшую двусмысленность. Дано struct foo arr[] = {{1,2,3,4,5}, {3,4,5,6,7}, }; Есть два разумных значения, которые язык может назначить: создать массив из двух элементов или создать массив из трех элементов, где последний элемент имеет значения по умолчанию. Если бы C принял более позднюю интерпретацию, я мог бы видеть запрещающие enum foo {moe, larry, curly, };по принципу, что должен быть только один способ написать утверждение (без запятой), но ...
суперкат
1
... учитывая, что C желает игнорировать запятую в случае, когда ей разумно было бы (но не было) присвоено значительное значение (что было бы сильным аргументом в пользу запрета ее там), любопытно, что это не так не желают в случае, когда запятая не может иметь значения [даже если кто-то интерпретирует это enum foo {moe,,larry,curly,};как пропуск числа между moeи larry, как правило, не имеет значения, обрабатывается ли запятая запятая или игнорируется. Единственный случай, когда это могло бы иметь значение, было бы, если бы последним элементом было максимальное значение для его объявленного типа, и это ...
суперкат
1
... можно обработать, просто сказав, что переполнение, которое происходит после последнего назначенного значения перечисления, должно игнорироваться.
суперкат
@supercat Существуют языки, такие как C #, в которых априорные исследования дизайна затрагивают возможности IDE и интеграцию при разработке языка. Си не был (и не мог быть) одним из этих языков.
Никос Атанасиу
Даже с такими языками, как C #, изменение целей дизайна привело к довольно серьезным несоответствиям дизайна. Например, язык воздерживался от поддержки любой формы перегрузки возвращаемого типа для обычных методов и операторов (даже если базовая структура могла это поддерживать), потому что это было сочтено противоречащим цели иметь простой для компиляции язык, но Лямбда-оценка включает в себя правила вывода типов, чье разрешение является NP-полным. Добавление новых правил перегрузки методов / операторов может нарушить существующий код (хотя я думаю, что хорошие правила могут минимизировать такую ​​опасность) ...
суперкат
2

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

Iravanchi
источник
1

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

#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

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

Скотт Лэнгхэм
источник
0

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

noɥʇʎԀʎzɐɹƆ
источник
-4

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

zhi_jian
источник
Это ошибка для VC6, которая не соответствует стандарту?
Томсон