int a [] = {1,2,}; Dziwny przecinek dozwolony. Jakiś konkretny powód?

335

Może nie jestem z tej planety, ale wydaje mi się, że następujący błąd powinien być błędem składni:

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

Ale nie jest. Byłem zaskoczony, gdy ten kod został skompilowany w Visual Studio, ale nauczyłem się nie ufać kompilatorowi MSVC, jeśli chodzi o zasady C ++, więc sprawdziłem ten standard i jest on również dozwolony. Możesz zobaczyć 8.5.1 dla reguł gramatyki, jeśli mi nie wierzysz.

wprowadź opis zdjęcia tutaj

Dlaczego to jest dozwolone? To może być głupie bezużyteczne pytanie, ale chcę, żebyś zrozumiał, dlaczego pytam. Zrozumiałbym, gdyby był to pod-przypadek ogólnej reguły gramatycznej - postanowili nie utrudniać gramatyki ogólnej po prostu nie dopuszczać zbędnego przecinka na końcu listy inicjalizującej. Ale nie, dodatkowy przecinek jest wyraźnie dozwolony. Na przykład niedozwolone jest umieszczanie nadmiarowego przecinka na końcu listy argumentów wywołania funkcji (gdy funkcja przyjmuje ...), co jest normalne .

Czy zatem istnieje jakiś szczególny powód, dla którego ta nadmiarowa przecinek jest wyraźnie dozwolona?

Armen Tsirunyan
źródło
10
Wydaje się, że wszyscy zgadzają się na „łatwość dodawania nowej linii” - ale czy ludzie definiujący specyfikacje językowe naprawdę przejmują się takimi rzeczami? Jeśli naprawdę rozumieją, dlaczego nie zignorują zaginięcia, ;gdy jest jasne, że następny token jest tak naprawdę kolejnym stwierdzeniem.
YetAnotherUser,
35
@YetAnotherUser: Tak, projektanci języków uważają takie rzeczy. Umożliwienie upuszczenia średników miałoby znacznie większy wpływ i byłoby bardzo niejednoznaczne w wielu częściach języka (pamiętaj, że spacja nie jest semantyczna w C). Dodatkowy przecinek w tym przypadku nie jest dwuznaczny. Dodatkowy średnik prawie nigdy nie jest dwuznaczny, dlatego też jest dozwolony. W przypadku, gdy jest niejednoznaczny ( for()na przykład po dodaniu), dodanie powoduje wygenerowanie ostrzeżenia kompilatora.
Rob Napier,
5
@Tomalak: Jest niejednoznaczny dla ludzkiego czytelnika i często jest pomyłką. Dlatego rzuca ostrzeżenie. Podobnie if (x = 1)nie jest dwuznaczny w gramatyce, ale jest bardzo niejednoznaczny dla ludzi, a zatem rzuca ostrzeżenie.
Rob Napier
12
@Rob: Twój ifprzykład też nie jest niejednoznaczny. Nie uważam, że „dwuznaczny” oznacza to, co według ciebie oznacza!
Wyścigi lekkości na orbicie
5
Tak długo, jak zgadzamy się, że kompilator jest dla nas przydatny, a przecinek końcowy w deklaracji tablicowej nie jest przydatny dla kompilatora.
Rob Napier

Odpowiedzi:

436

Ułatwia generowanie kodu źródłowego, a także pisanie kodu, który można łatwo rozszerzyć w późniejszym terminie. Zastanów się, co jest wymagane, aby dodać dodatkowy wpis do:

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

... musisz dodać przecinek do istniejącej linii i dodać nową linię. Porównaj to z przypadkiem, w którym trzy mają już przecinek, po prostu musisz dodać linię. Podobnie, jeśli chcesz usunąć linię, możesz to zrobić, nie martwiąc się o to, czy jest to ostatnia linia, czy nie, i możesz zmienić kolejność linii bez zbędnego wprowadzania przecinków. Zasadniczo oznacza to jednolitość w traktowaniu linii.

Pomyśl teraz o wygenerowaniu kodu. Coś w stylu (pseudo-kod):

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

Nie musisz się martwić, czy zapisywany element jest pierwszym, czy ostatnim. O wiele prostsze.

Jon Skeet
źródło
89
Ponadto w przypadku korzystania z VCS „różnica” między dwiema wersjami jest czystsza, ponieważ zmienia się tylko jedna linia, gdy element jest dodawany lub usuwany.
Kevin Panko,
47
@ Néstor: Dlaczego „niefortunne”? Co jest tutaj wadą? To, że poświęcono trochę uwagi generowaniu kodu (i łatwej manipulacji) dla jednej niewielkiej części języka , nie oznacza, że ​​musi to być główna motywacja wszystkich decyzji w tym języku. Wnioskowanie typu, usuwanie średników itp. Ma ogromne znaczenie dla języka. Ustawiasz tutaj fałszywą dychotomię, IMO.
Jon Skeet,
18
@ Néstor: To tutaj pragmatyzm wygrywa z dogmatyzmem: dlaczego musi to być w pełni jedno lub całkowicie drugie, skoro bardziej użyteczne jest połączenie obu? Jak to naprawdę przeszkadza, ponieważ na końcu można dodać przecinek? Czy jest to niespójność, która kiedykolwiek przeszkadzała ci w jakimś sensie? Jeśli nie, należy porównać tę nieistotną nieistotność z praktycznymi korzyściami wynikającymi z dopuszczenia przecinka na końcu.
Jon Skeet,
8
@Mrchief: Nie chodzi o szybkość pisania - chodzi o prostotę podczas kopiowania, usuwania lub zmiany kolejności elementów. Wczoraj moje życie stało się prostsze. Bez wad, dlaczego nie ułatwić życia? Jeśli chodzi o próbę wskazania palcem na MS, mocno podejrzewam, że było to w C, zanim jeszcze Microsoft istniał ... Mówisz, że to uzasadnienie wydaje się dziwne, ale założę się, że przynosi to korzyści tysiącom programistów w setkach firm każdego dnia. Czy nie jest to lepsze wytłumaczenie niż szukanie czegoś, co byłoby korzystne dla autorów kompilatorów?
Jon Skeet,
6
To było w K&R C.
Ferruccio,
126

Jest to przydatne, jeśli robisz coś takiego:

int a[] = {
  1,
  2,
  3, //You can delete this line and it's still valid
};
Skilldrick
źródło
6
JavaScript obsługuje tę składnię: var a = [1, 2,];podobnie jak większość innych języków, które znam ... ActionScript, Python, PHP.
Sean Fujiwara,
14
@Sean To spowoduje błąd analizy w IE JavaScript, więc uważaj!
Skilldrick
10
Nie dotyczy mnie to w IE9. Ale robi coś dziwnego ... tworzy element zerowy. Będę uważał.
Sean Fujiwara,
5
@Sean Niestety, pan rację - nie jest to błąd parse w IE, ale to będzie wstawić dodatkowy element do zestawu undefined.
Skilldrick
3
Najbardziej frustrujące jest to, że JSON nie obsługuje tej składni.
Timmmm,
38

Myślę, że łatwość użycia dla programisty.

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

Dodatkowo, jeśli z jakiegokolwiek powodu masz narzędzie, które wygenerowało dla ciebie kod; narzędzie nie musi dbać o to, czy jest to ostatni element w inicjalizacji, czy nie.

vcsjones
źródło
32

Zawsze zakładałem, że ułatwia to dodanie dodatkowych elementów:

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

po prostu staje się:

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

w późniejszym terminie.

Oliver Charlesworth
źródło
3
Nie sądzę, aby nieco szybsze edytowanie było dobrym powodem do zepsucia składni. IMHO to kolejna dziwna funkcja C ++.
Giorgio
3
@Giorgio: Cóż, odziedziczył po C. Jest całkiem możliwe, że jest to tylko przeoczenie specyfikacji oryginalnej wersji językowej, co może mieć użyteczny efekt uboczny.
Oliver Charlesworth,
Ok, nie wiedziałem, że pochodzi z C. Właśnie sprawdziłem, czy jest to dozwolone także w Javie. To trochę dziwne: w mojej intuicji przecinek jest separatorem, a nie terminatorem. Ponadto można pominąć ostatni przecinek. Czy jest to terminator, separator, czy jedno i drugie? Ale OK, ta funkcja jest dostępna i warto o tym wiedzieć.
Giorgio,
11
@Giorgio - kod źródłowy jest przeznaczony dla ludzi, a nie maszyn. Małe rzeczy, takie jak ta, które powstrzymują nas od popełniania prostych błędów transpozycji, są błogosławieństwem, a nie przeoczeniem. W celach informacyjnych działa również w ten sposób w PHP i ECMAScript (a zatem JavaScript i ActionScript), chociaż jest niepoprawny w notacji obiektowej JavaScript (JSON) (np. [1,2,3,]Jest OK, ale {a:1, b:2, c:3,}nie jest).
Opuszczono
1
@Groky: Im więcej o tym myślę, tym bardziej jestem przekonany, że składnia języka programowania powinna być tak prosta i spójna, jak to możliwe, z jak najmniejszą liczbą wyjątków: ułatwia to naukę języka (mniej zasad do zapamiętania ). Zaleta zaoszczędzenia jednego lub dwóch naciśnięć klawiszy podczas dodawania / usuwania elementu do / z listy (która, nawiasem mówiąc, nie robię tego często w porównaniu z całkowitą ilością czasu spędzonego na kodowaniu) wydaje mi się raczej trywialna w porównaniu do mając jasno zdefiniowaną składnię.
Giorgio
21

Wszystko, co wszyscy mówią o łatwości dodawania / usuwania / generowania linii, jest poprawne, ale prawdziwym miejscem, w którym ta składnia się świeci, jest łączenie plików źródłowych. Wyobraź sobie, że masz tę tablicę:

int ints[] = {
    3,
    9
};

Załóżmy, że sprawdziłeś ten kod w repozytorium.

Następnie twój kolega edytuje go, dodając do końca:

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

I jednocześnie edytujesz go, dodając na początku:

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

Semantycznie tego rodzaju operacje (dodawanie na początku, dodawanie na końcu) powinny być całkowicie bezpieczne, a twoje oprogramowanie do wersji (mam nadzieję, że git) powinno być w stanie zautomatyzować. Niestety tak nie jest, ponieważ twoja wersja nie ma przecinka po 9, a twój kumpel ma. Podczas gdy gdyby oryginalna wersja miała końcową liczbę 9, automatycznie by się pojawiły.

Zatem moja ogólna zasada brzmi: użyj przecinka końcowego, jeśli lista obejmuje wiele linii, nie używaj go, jeśli lista znajduje się w jednej linii.

amoss
źródło
15

Przecinek końcowy Uważam, że jest dozwolony ze względu na kompatybilność wsteczną. Istnieje wiele istniejących kodów, głównie generowanych automatycznie, co powoduje wstawienie przecinka. Ułatwia napisanie pętli bez specjalnych warunków na końcu. na przykład

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

Praktycznie nie ma żadnej przewagi dla programisty.

PS Chociaż w ten sposób łatwiej jest wygenerować kod automatycznie, zawsze starałem się nie wstawiać przecinka, wysiłki są minimalne, poprawia się czytelność, a to jest ważniejsze. Piszesz kod raz, czytasz go wiele razy.

Gene Bushuyev
źródło
5
Całkowicie się nie zgadzam; [Moim zdaniem] znalazł drogę do wielu języków stworzonych długo po C właśnie dlatego, że jest korzystne, aby programista mógł przesuwać się po zawartości tablicy, komentować wiersze nie chcąc, i tak dalej, bez obawy o głupie błędy składniowe wywołane transpozycją. Czy nie jesteśmy już wystarczająco zestresowani?
Zrezygnowano
12
@Dereleased - według tej samej logiki, dlaczego nie powinno się dopuścić do końca (cokolwiek), jak to zrobić int a = b + c +;lub if(a && b &&);łatwiej będzie po prostu skopiować i wkleić cokolwiek na końcu i łatwiej napisać generatory kodu. Ten problem jest zarówno trywialny, jak i subiektywny, w takich przypadkach zawsze dobrze jest robić to, co najlepsze dla czytnika kodów.
Gene Bushuyev,
1
@Gene Bushuyev: Dokładnie! Często mam długie wyrażenia z + lub &&, z operatorem na końcu linii i, oczywiście, muszę spędzić trochę czasu, kiedy chcę usunąć ostatni operand wyrażenia. Myślę, że ta składnia przecinków jest naprawdę dziwna!
Giorgio
2
@GeneBushuyev - Nie zgadzam się z nimi. Chociaż zezwalanie na końcowe przecinki w tablicach itp. Jest funkcją usuwania błędów i ułatwia życie programistom, dla czystej czytelności podjąłbym środki w celu usunięcia instrukcji warunkowych ORAZ (i&), plusów i innych różnych operatorów z warunków sprawozdania. To po prostu brzydkie, IMO.
Sune Rasmussen
2
Jeśli chodzi o &&operatora, czasami robię takie operacje warunkowe, if (true \n && b1 \n && b2)aby móc dodawać i usuwać wiersze według potrzeb.
Christian Mann
12

O ile mi wiadomo, jest to jeden z powodów, dla których automatyczne generowanie kodu powinno być proste; nie potrzebujesz żadnej specjalnej obsługi ostatniego elementu.

Fredrik Pihl
źródło
11

Ułatwia to generowanie kodów, które wypluwają tablice lub wyliczenia.

Wyobrażać sobie:

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

Tj. Nie ma potrzeby specjalnego obchodzenia się z pierwszym lub ostatnim elementem, aby uniknąć plucia końcowego przecinka.

Jeśli na przykład generator kodu jest napisany w języku Python, łatwo jest uniknąć plucia końcowego przecinka za pomocą str.join()funkcji:

print("enum Items {")
print(",\n".join(items))
print("}")
Maxim Egorushkin
źródło
10

Dziwi mnie, że przez cały ten czas nikt nie zacytował Annotated C ++ Reference Manual ( ARM ), mówi on o [dcl.init] z naciskiem na mój:

Istnieje wyraźnie zbyt wiele oznaczeń do inicjalizacji, ale wydaje się, że każda z nich dobrze pasuje do określonego stylu użycia. W = {initializer_list OPT} oznaczenie zostało odziedziczone C i służy również do inicjalizacji struktur danych i tablic. [...]

chociaż gramatyka ewoluowała od czasu napisania ARM, pochodzenie pozostaje.

i możemy przejść do uzasadnienia C99, aby zobaczyć, dlaczego było to dozwolone w C, i mówi:

K&R zezwala na końcowy przecinek w inicjalizatorze na końcu listy inicjalizacyjnej. Standard zachował tę składnię, ponieważ zapewnia elastyczność dodawania lub usuwania elementów z listy inicjalizującej i upraszcza generowanie maszynowe takich list.

Shafik Yaghmour
źródło
1
Głosuj za najbardziej popartą odpowiedzią literaturą i prawdziwym źródłem tej funkcji.
Marko
10

Widzę jeden przypadek użycia, który nie został wymieniony w innych odpowiedziach, nasze ulubione makra:

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

Dodanie makr do obsługi ostatniego ,byłoby dużym bólem. Przy tej małej zmianie w składni zarządzanie tym jest trywialne. Jest to ważniejsze niż kod generowany maszynowo, ponieważ zwykle łatwiej jest to zrobić w pełnym języku Turinga niż w bardzo ograniczonym preprocesorze.

Jankesi
źródło
7

Jedynym językiem, w którym - w praktyce * - jest niedozwolony, jest JavaScript i powoduje on niezliczoną ilość problemów. Na przykład, jeśli skopiujesz i wkleisz wiersz ze środka tablicy, wkleisz go na końcu i zapomnisz usunąć przecinek, Twoja witryna zostanie całkowicie zepsuta dla użytkowników IE.

* Teoretycznie jest to dozwolone, ale Internet Explorer nie przestrzega standardu i traktuje go jako błąd

Thomas Bonini
źródło
„Tablice” JavaScript (które są tylko obiektami o właściwościach o magicznej długości) i tak są raczej niezwykłe: var x = [,,,]są legalne (z wyjątkiem IE <9, ale specyfikacja mówi, że jest legalne)
Peter C
Zgodnie ze specyfikacją ECMAScript jest on całkowicie poprawny; teoretycznie powinien on działać w każdej przeglądarce, która implementuje JavaScript zgodnie ze wspomnianą specyfikacją, szczególnie w części specyfikacji tutaj .
Opuszczono
1
Niestety JavaScript polega na tworzeniu aplikacji dla społeczeństwa. Więc nie, nie jest to w pełni poprawne, gdy ~ 50% użytkowników miałoby problemy z używaniem Twojej aplikacji. I tak, gdybym mógł, zabroniłbym IE <9 - po prostu zbyt wiele godzin spędzonych na tworzeniu dobrego kodu działającego tam ...
kgadek
@Dere: tak, powiedziałem tyle w mojej odpowiedzi =)
Thomas Bonini
@ Odrzucone Microsoft wymyśla swoje własne specyfikacje i polecenia, które inni przestrzegają przynajmniej, że mentalność się zmienia (dzięki Bogu)
Chris McGrath
7

Jest to łatwiejsze dla maszyn, tj. Parsowania i generowania kodu. Jest to również łatwiejsze dla ludzi, tj. Modyfikacja, komentowanie i wizualna elegancja dzięki spójności.

Zakładając C, czy napisałbyś następujące?

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

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

    return EXIT_SUCCESS
}

Nie. Nie tylko dlatego, że końcowe stwierdzenie jest błędem, ale także dlatego, że jest niespójne. Dlaczego więc to samo dotyczy kolekcji? Nawet w językach, w których można pominąć ostatnie średniki i przecinki, społeczność zwykle tego nie lubi. Na przykład społeczność Perla nie lubi pomijać średników, poza linijkami. Stosują to również do przecinków.

Nie pomijaj przecinków w zbiorach wielowierszowych z tego samego powodu, dla którego nie odrzucasz średników dla wielowierszowych bloków kodu. To znaczy, nie zrobiłbyś tego, nawet gdyby język na to pozwalał, prawda? Dobrze?

Louis
źródło
Istnieją języki (np. Pascal), które to umożliwiają. Tzn. Musisz wybrać między; jako terminator (C) lub jako separator (Pascal). To samo dla „,”. Byłoby dla mnie ok, jeśli „,” jest terminatorem, ale wtedy {1, 2, 3} musi być błędem składni.
Giorgio
6

Powód jest trywialny: łatwość dodawania / usuwania linii.

Wyobraź sobie następujący kod:

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

Teraz możesz łatwo dodawać / usuwać elementy do listy bez konieczności dodawania / usuwania końcowego przecinka.

W przeciwieństwie do innych odpowiedzi, tak naprawdę nie uważam, że łatwość generowania listy jest ważnym powodem: w końcu kod zajmuje specjalne miejsce w ostatniej (lub pierwszej) linii. Generatory kodu są pisane raz i używane wiele razy.

Vlad
źródło
6

Pozwala każdej linii na przyjęcie tego samego formularza. Po pierwsze, ułatwia to dodawanie nowych wierszy i pozwala systemowi kontroli wersji w znaczący sposób śledzić zmianę, a także umożliwia łatwiejszą analizę kodu. Nie mogę wymyślić przyczyny technicznej.

Mark B
źródło
5

Pozwala to zabezpieczyć się przed błędami powodowanymi przez przenoszenie elementów na długiej liście.

Załóżmy na przykład, że mamy kod wyglądający tak.

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

I jest świetny, ponieważ pokazuje oryginalną trylogię witryn Stack Exchange.

Stack Overflow
Super User
Server Fault

Ale jest z tym jeden problem. Widzisz, stopka na tej stronie pokazuje awarię serwera przed superużytkownikiem. Lepiej to napraw, zanim ktokolwiek zauważy.

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

W końcu przesuwanie linii nie może być takie trudne, prawda?

Stack Overflow
Server FaultSuper User

Wiem, że nie ma witryny o nazwie „Server FaultSuper User”, ale nasz kompilator twierdzi, że istnieje. Problem polega na tym, że C ma funkcję konkatenacji łańcuchów, która pozwala napisać dwa łańcuchy z podwójnym cudzysłowem i połączyć je przy użyciu niczego (podobny problem może się również zdarzyć z liczbami całkowitymi, ponieważ -znak ma wiele znaczeń).

A co jeśli oryginalna tablica miała na końcu bezużyteczny przecinek? Cóż, linie byłyby przesuwane, ale taki błąd nie miałby miejsca. Łatwo przeoczyć coś tak małego jak przecinek. Jeśli pamiętasz, aby po każdym elemencie wstawić przecinek, taki błąd po prostu nie może się zdarzyć. Nie chcesz tracić czterech godzin na debugowanie czegoś, dopóki nie znajdziesz przecinka, który jest przyczyną twoich problemów .

Konrad Borowski
źródło
4

Podobnie jak wiele innych, przecinek końcowy w inicjalizatorze tablicowym jest jedną z rzeczy odziedziczonych przez C ++ po C (i będzie musiał być obsługiwany na zawsze). Pogląd całkowicie odmienny od przedstawionego tutaj jest wspomniany w książce „Deep C Secrets” .

Oto przykład z kilkoma „paradoksami przecinków”:

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

czytamy :

... że trailing przecinek po ostatnim inicjatora nie jest literówka, ale blip w składni przeniesione z rodowity C . Jego obecność lub brak jest dozwolony, ale nie ma znaczenia . Uzasadnieniem uzasadnienia ANSI C jest to, że ułatwia zautomatyzowane generowanie C. Twierdzenie byłoby bardziej wiarygodne, gdyby końcowe przecinki były dozwolone na każdej liście rozdzielanej przecinkami , na przykład w deklaracjach wyliczonych lub w wielu deklaratorach zmiennych w jednej deklaracji. Oni nie są.

... dla mnie ma to większy sens

Nikos Athanasiou
źródło
2
Zakaz przecinka w tej enumsprawie jest dość interesujący, ponieważ w tym przypadku brakujący przecinek stanowiłby najmniejszą dwuznaczność. Biorąc pod uwagę struct foo arr[] = {{1,2,3,4,5}, {3,4,5,6,7}, }; język ma dwa sensowne znaczenia: utwórz tablicę dwuelementową lub stwórz tablicę trzyelementową, w której ostatni element ma wartości domyślne. Gdyby C przyjął późniejszą interpretację, widziałbym zakazanie enum foo {moe, larry, curly, };na zasadzie, że powinien istnieć tylko jeden sposób na napisanie oświadczenia (bez przecinka), ale ...
supercat
1
... biorąc pod uwagę, że C jest skłonny zignorować przecinek w przypadku, gdy można było (ale nie) przypisać mu znaczące znaczenie (co byłoby silnym argumentem za zakazaniem go tam), to ciekawe, że nie jest nie jest chętny w przypadku, gdy przecinek nie może mieć znaczenia [nawet jeśli interpretuje się go enum foo {moe,,larry,curly,};jako pomijanie liczby pomiędzy, moei larrygeneralnie nie ma znaczenia, czy przecinek końcowy został przetworzony czy zignorowany. Jedyny przypadek, w którym mogłoby mieć znaczenie, to gdyby ostatnia pozycja była maksymalną wartością dla zadeklarowanego typu i że ...
supercat
1
... można sobie poradzić po prostu mówiąc, że przepełnienie, które występuje po ostatniej przypisanej wartości wyliczenia, powinno zostać zignorowane.
supercat
@supercat Istnieją języki, takie jak C #, w których a priori badania projektowe idą tak daleko, jak rozważanie funkcji IDE i integracji podczas rozwijania języka. C nie był (i nie mógł być) jednym z tych języków.
Nikos Athanasiou
Nawet w przypadku języków takich jak C # zmiana celów projektowych spowodowała dość poważne niespójności projektowe. Na przykład język nie obsługiwał żadnej formy przeciążenia typu powrotu dla normalnych metod i operatorów (nawet jeśli podstawowa struktura może to obsługiwać), ponieważ był postrzegany jako sprzeczny z celem posiadania prostego w kompilacji języka, ale Ocena lambda obejmuje reguły wnioskowania typu, których rozdzielczość jest NP-pełna. Dodanie nowej metody / operatora przeciążenia zasady może przełamać istniejący kod (chociaż myślę, że dobre przepisy mogłyby zminimalizować takie niebezpieczeństwo) ...
Supercat
2

Oprócz łatwości generowania i edycji kodu, jeśli chcesz zaimplementować analizator składni, ten typ gramatyki jest prostszy i łatwiejszy do wdrożenia. C # przestrzega tej zasady w kilku miejscach, w których znajduje się lista elementów oddzielonych przecinkami, takich jak elementy w enumdefinicji.

Iravanchi
źródło
1

Ułatwia generowanie kodu, ponieważ wystarczy dodać tylko jeden wiersz i nie trzeba traktować dodawania ostatniego wpisu, jakby to był szczególny przypadek. Jest to szczególnie prawdziwe w przypadku używania makr do generowania kodu. Próbuje się wyeliminować potrzebę używania makr z danego języka, ale wiele języków ewoluowało równolegle z dostępnymi makrami. Dodatkowy przecinek umożliwia zdefiniowanie i użycie makr, takich jak następujące:

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

Stosowanie:

LIST_BEGIN
   LIST_ENTRY(1)
   LIST_ENTRY(2)
LIST_END

To bardzo uproszczony przykład, ale często ten wzór jest używany przez makra do definiowania takich rzeczy, jak mapy i tabele wysyłki, wiadomości, zdarzenia lub tłumaczenia. Jeśli przecinek nie byłby dozwolony na końcu, potrzebowalibyśmy specjalnego:

#define LIST_LAST_ENTRY(x) x

i byłoby to bardzo niewygodne w użyciu.

Scott Langham
źródło
0

Tak więc, gdy dwie osoby dodają nowy element do listy w oddzielnych gałęziach, Git może poprawnie scalić zmiany, ponieważ Git działa w trybie liniowym.

noɥʇʎԀʎzɐɹƆ
źródło
-4

Jeśli użyjesz tablicy bez określonej długości, VC ++ 6.0 może automatycznie zidentyfikować jej długość, więc jeśli użyjesz „int a [] = {1,2,};” długość a wynosi 3, ale ostatnia nie ma „ t został zainicjowany, możesz użyć „cout <

zhi_jian
źródło
Czy to błąd w VC6, który nie jest zgodny ze standardem?
Thomson