int a [] = {1,2,}; Дивні коси дозволені. Якась конкретна причина?


335

Можливо, я не з цієї планети, але мені здається, що наступною має бути помилка синтаксису:

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

Але це не так. Я був здивований , коли цей код скомпільовано на Visual Studio, але я навчився не довіряти MSVC компілятор, наскільки правила C ++ стурбовані, так що я перевірив стандарт і це допускається стандартом , а також. Ви можете бачити 8.5.1 для граматичних правил, якщо ви мені не вірите.

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

Чому це дозволено? Це може бути дурним марним питанням, але я хочу, щоб ви зрозуміли, чому я прошу. Якби це був підзакон загального правила граматики, я би зрозумів - вони вирішили не ускладнювати загальну граматику лише для того, щоб заборонити зайву кому в кінці списку ініціалізатора. Але ні, додаткова кома прямо дозволена. Наприклад, заборонено мати надлишкову кому в кінці списку аргументів функції-виклику (коли функція бере ...), що є нормальним .

Отже, знову ж таки, чи є якась конкретна причина, коли ця зайва кома явно дозволена?


10
Здається, кожен погоджується на «легкість додавання нового рядка» - але чи дійсно люди, що визначають мовні специфікації, турбують такі речі? Якщо вони справді такі, що розуміють, то чому б вони не ігнорували пропущене, ;коли зрозуміло, що наступний маркер - це насправді наступне твердження.
такиІнший користувач

35
@YetAbodyUser: Так, мовні дизайнери вважають такі речі. Допуск до крапки з комою мав би набагато більший вплив та був би неоднозначним у багатьох частинах мови (пам’ятайте, пробіл у С не є семантичним). Додаткова кома - цей випадок неоднозначний. Додаткова крапка з комою майже ніколи не є неоднозначною, і це також дозволено. У випадку, коли це неоднозначно (після аfor() прикладу), додаючи його, він видає попередження компілятора.
Роб Нап'єр

5
@Tomalak: Це неоднозначно для людського читача і часто є помилкою. Ось чому він кидає попередження. Так само 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
@ Néstor: Чому "нещасний"? Який тут недолік? Тільки тому, що певна увага приділяється генерації коду (та легкому маніпулюванню) для однієї крихітної частини мови , не означає, що вона повинна бути основною мотивацією для всіх рішень на мові. Виведення типу, видалення напівколонок тощо мають величезні наслідки для мови. Ви встановлюєте тут помилкову дихотомію, ІМО.
Джон Скіт

18
@ Néstor: Ось де прагматизм перемагає догматизм: чому це мусить бути повністю одне чи повністю інше, коли корисніше бути сумішшю обох? Як це насправді заважає, маючи змогу додати кому в кінці? Це непослідовність, яка коли-небудь перешкоджала вам у будь-якому сенсі? Якщо ні, будь-ласка, зважте цю невідповідну неелегантність у порівнянні з практичними перевагами дозволу кома в кінці.
Джон Скіт

8
@Mrchief: Справа не в швидкості введення тексту - це питання простоти при копіюванні, видаленні або переупорядкуванні елементів. Це вчора спростило моє життя. Не маючи недоліків, чому б не полегшити життя? Що стосується спроб вказувати пальцем на MS, я сильно підозрюю, що це було в С, перш ніж Microsoft навіть існувала ... Ви говорите, що це виправдання здається дивним, але я думаю, що це приносить користь тисячам розробників у сотнях компаній щодня. Хіба це не краще пояснення, ніж пошук того, що приносить користь авторам-компіляторам?
Джон Скіт

6
Це було у K&R C.
Ферруччо

126

Це корисно, якщо ви робите щось подібне:

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

6
JavaScript підтримує цей синтаксис: var a = [1, 2,];так само, як і більшість інших мов, які я знаю ... ActionScript, Python, PHP.
Шон Фуджівара

14
@Sean Це призведе до помилки розбору в JavaScript IE, тому будьте уважні!
Skilldrick

10
Це не для мене в IE9. Але це робить щось дивне ... це створює нульовий елемент. Я буду остерігатися.
Шон Фуджівара

5
@Sean До жаль, ви праві - це не помилка синтаксичного аналізу в IE, але він буде вставити додатковий набір елементів для undefined.
Skilldrick

3
Найбільш розчаровує, JSON не підтримує цей синтаксис.
Timmmm

38

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

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

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


32

Я завжди вважав, що це полегшує додавання додаткових елементів:

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

просто стає:

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

пізніше.


3
Я не думаю, що зробити редагування трохи швидше - це вагома причина для того, щоб зіпсувати синтаксис. IMHO - це лише ще одна дивна функція C ++.
Джорджіо

3
@Giorgio: Ну, це успадковане від C. Це цілком можливо, що це лише нагляд за специфікацією оригінальної мови, який, можливо, має корисний побічний ефект.
Олівер Чарльзворт

Гаразд, я не знав, що це походить від C. Я просто перевірив, чи це дозволено і на Java. Однак це здається дивним: у моїй інтуїції кома є роздільником, а не термінатором. Крім того, останню кому можна опустити. Отже, це термінатор, сепаратор чи обоє? Але добре, ця функція доступна, і це добре знати.
Джорджіо

11
@Giorgio - вихідний код призначений для людей, а не для машин. Такі дрібниці, які не дозволяють нам робити помилки простого перенесення - це благо, а не недогляд. Для довідки, він також працює таким чином у 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
Я зовсім не згоден; [На мою думку,] він знайшов свій шлях до багатьох мов, створених задовго після С саме через те, що програмісту вигідно вміти переміщувати вміст масиву, коментувати рядки вольово-невольно тощо, не турбуючись про дурні синтаксичні помилки, спричинені транспозицією. Хіба ми вже недостатньо підкреслені?
Опубліковано

12
@ Вийшов - за тією ж логікою, чому не слід дозволити трейлінг (нічого), як щодо того, int a = b + c +;чи if(a && b &&);простіше буде просто копіювати та вставляти що-небудь в кінці і простіше писати генератори коду. Це питання є і тривіальним, і суб'єктивним, у таких випадках завжди добре робити те, що найкраще для читача коду.
Джин Бушуєв

1
@Gene Bushuyev: Саме так! Я часто маю довгі вирази з + або &&, з оператором в кінці рядка, і, звичайно, мені доведеться витратити трохи додаткового часу, коли я хочу видалити останній операнд виразу. Я думаю, що цей синтаксис комами справді дивний!
Джорджіо

2
@GeneBushuyev - Я не згоден з цим. Хоча дозвол трейлінг коми через масиви тощо - це функція усунення помилок і полегшує вам життя програміста, я б із чистого розуміння вжив заходів для вилучення контингентних І (і&) операторів, плюсів та інших різних операторів з умовних заяви. Це просто некрасиво, ІМО.
Sune Rasmussen

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 написання походження залишається.

і ми можемо перейти до обґрунтування С99, щоб побачити, чому це було дозволено в С, і в ньому сказано:

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


1
Підкажіть за найбільш підкріплену літературну відповідь та справжнє джерело цієї функції.
Марко

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
};

Додавання макросів для останньої обробки ,буде великим болем. З цією невеликою зміною синтаксису це тривіально для управління. І це важливіше машинного коду, оскільки це набагато простіше зробити в повному мові Тьюрінга, ніж дуже обмежений препроцесор.


7

Єдина мова, де це - на практиці * - не дозволено, - це Javascript, і це викликає незліченну кількість проблем. Наприклад, якщо скопіювати та вставити рядок із середини масиву, вставити її в кінці та забути видалити кому, то ваш сайт буде повністю порушений для ваших відвідувачів IE.

* Теоретично це дозволено, але Internet Explorer не дотримується стандарту і трактує його як помилку


"Масиви" JavaScript (які є лише об'єктами з властивістю магічної довжини) все одно досить незвичні: var x = [,,,]законний (за винятком IE <9, але специфікація говорить, що це законно)
Пітер C,

Відповідно до специфікації ECMAScript, вона абсолютно дійсна; теоретично він повинен працювати в будь-якому браузері, який реалізує JavaScript відповідно до зазначеної специфікації, зокрема, частиною специфікації, яка тут знаходиться .
Опубліковано

1
На жаль, JavaScript - це все для створення додатків для загального користування. Так що ні, це не зовсім вірно, коли ~ 50% користувачів матимуть проблеми з вашим додатком. І так, якби я міг би заборонити IE <9 - просто занадто багато годин витрачаю на те, щоб просто там добре працював код ...
kgadek

@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, схоже, не любить пропускати крапки з комою, однорядкові штрихи. Вони застосовуються і до коми.

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


Є мови (наприклад, Pascal), які це дозволяють. Тобто ви повинні вибирати між собою; як термінатор (С) або як роздільник (Паскаль). Те саме для ','. Для мене було б нормально, якщо ',' є термінатором, але тоді {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 Overflow
Super User
Server Fault

Але з цим є одна проблема. Розумієте, колонтитул на цьому веб-сайті показує помилку сервера перед Super User. Краще виправте це, перш ніж хтось помітить.

#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

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

А що робити, якщо в початковому масиві в кінці була марна кома? Ну, лінії будуть переміщені, але такої помилки не було б. Легко пропустити щось таке маленьке, як кома. Якщо ви пам'ятаєте ставити кому після кожного елемента масиву, така помилка просто не може статися. Ви б не хотіли витрачати чотири години на щось налагодження, поки ви не знайдете кома - це причина ваших проблем .


4

Як і багато речей, кінцева кома в ініціалізаторі масиву є однією з речей C ++, успадкованих від 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? */
};

ми читаємо :

... , що Кома після остаточного ініціалізатор не помилка, але сплеск в синтаксисі , перенесений з аборигенного C . Його наявність або відсутність дозволено, але не має значення . Обгрунтування, заявлене в обґрунтуванні ANSI C, полягає в тому, що це полегшує автоматизовану генерацію C. Заява може бути більш достовірною, якби пропуски косів були дозволені у кожному списку , який оцінюється комами , наприклад у перерахунках перерахунків, або кількох деклараторах змінних в одній декларації. Вони не.

... для мене це має більше сенсу


2
Заборона проти коми ставиться enumдещо цікаво, оскільки це той випадок, коли пропущена кома створювала б найменшу неоднозначність. Дано struct foo arr[] = {{1,2,3,4,5}, {3,4,5,6,7}, }; Є два розумні значення, якими могла призначити мова: створити двоелементний масив або створити триелементний масив, де для останнього елемента є значення за замовчуванням. Якби C прийняв пізнішу інтерпретацію, я міг би бачити заборону enum foo {moe, larry, curly, };за принципом, що слід писати лише один спосіб (без коми), але ...
supercat

1
... враховуючи, що C готовий ігнорувати кому у випадку, коли це розумно могло бути (але не було) присвоєне значне значення (що було б вагомим аргументом на користь заборони її там), цікаво, що це Я не бажаю у випадку, коли кома не могла мати значення [навіть якщо інтерпретувати enum foo {moe,,larry,curly,};як пропуск числа між, moeі larry, як правило, не має значення, обробляється чи ігнорується кінцева кома. Єдиний випадок, коли це могло б мати значення, якби останній елемент був максимальним значенням для його оголошеного типу, і це ...
supercat

1
... можна вирішити, просто сказавши, що переповнення, яке виникає після останнього призначеного значення перерахування, слід ігнорувати.
supercat

@supercat Є такі мови, як C #, де апріорні дизайнерські дослідження стосуються розгляду особливостей IDE та інтеграції під час присвячення мови. C не була (і не могла бути) однією з цих мов.
Нікос Афанасьоу

Навіть у таких мовах, як C #, зміна дизайнерських цілей призвела до досить серйозних невідповідностей дизайну. Наприклад, мова утримується від підтримки будь-якої форми перевантаження типів повернення для звичайних методів та операторів (навіть незважаючи на те, що базовий фреймворк може її підтримувати), оскільки це було суперечить цілі створення простої для компіляції мови, але лямбда-оцінка включає правила виводу типу, роздільна здатність яких не відповідає NP. Додавання нового методу / правил перевантаження оператора може порушити існуючий код (хоча я думаю, що хороші правила могли мінімізувати таку небезпеку) ...
supercat

2

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


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 працює на лінійній основі.


-4

Якщо ви використовуєте масив без заданої довжини, VC ++ 6.0 може автоматично ідентифікувати його довжину, тож якщо ви використовуєте "int a [] = {1,2,};" довжина a становить 3, але останній hasn " t ініціалізовано, ви можете використовувати "cout <


Це помилка для VC6, яка не відповідає стандарту?
Томсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.