たぶん私はこの惑星から来たのではないかもしれませんが、以下は構文エラーであるべきだと思われます:
int a[] = {1,2,}; //extra comma in the end
しかし、そうではありません。このコードがVisual Studioでコンパイルされたときは驚きましたが、C++ルールに関する限りMSVCコンパイラを信頼しないことを学んだので、標準を確認しましたis標準でも許可されています。あなたが私を信じないなら、文法規則のために8.5.1を見ることができます。
なぜこれが許可されているのですか?これは愚かな役に立たない質問かもしれませんが、なぜ私が尋ねているのか理解してほしい。それが一般的な文法規則のサブケースである場合、私は理解するだろう-初期化子リストの最後に冗長なコンマを許可しないために、一般的な文法をこれ以上難しくしないことを決めた。ただし、追加のコンマはexplicitlyが許可されています。たとえば、関数呼び出し引数リストの末尾に冗長コンマを含めることは許可されていません(関数が...
を使用する場合)、これは正常です。
だから、再び、この冗長コンマがexplicitly許可されている特定の理由がありますか?
これにより、ソースコードの生成が容易になり、後日簡単に拡張できるコードを記述できるようになります。以下に追加のエントリを追加するために必要なものを検討してください。
int a[] = {
1,
2,
3
};
...既存の行にコンマを追加する必要がありますand新しい行を追加します。 3つのalreadyの後にカンマがあり、行を追加するだけの場合と比較してください。同様に、行を削除する場合は、それが最後の行であるかどうかを心配せずに削除できます。また、コンマをいじらずに行を並べ替えることができます。基本的には、線の処理方法に均一性があることを意味します。
次に、コードの生成について考えます。 (擬似コード)のようなもの:
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"; });
プログラマーにとって実際には何の利点もありません。
追伸この方法でコードを自動生成する方が簡単ですが、実際には、末尾のコンマを入れないように常に注意しました。労力は最小限で、読みやすさが向上しています。それがより重要です。コードを一度書くだけで、何度も読むことができます。
私が知る限り、これが許可されている理由の1つは、コードを自動生成するのが簡単であるべきだということです。最後の要素に特別な処理は必要ありません。
これにより、配列または列挙を吐き出すコードジェネレーターが簡単になります。
想像してみてください:
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("}")
私のお気に入りのマクロである、他の回答で言及されていないユースケースがあります:
int a [] = {
#ifdef A
1, //this can be last if B and C is undefined
#endif
#ifdef B
2,
#endif
#ifdef C
3,
#endif
};
最後の,
を処理するマクロを追加するのは大きな苦痛です。構文のこの小さな変更により、これを管理するのは簡単です。そして、これは、非常に限られた前処理よりもチューリング完全な言語で行うのが通常ずっと簡単であるため、マシン生成コードよりも重要です。
注釈付きC++リファレンスマニュアル (ARM)を引用している人がいないことに驚いています。[dcl.init]を強調して:
初期化のための表記法は明らかに多すぎますが、それぞれが特定のスタイルの使用に適しているようです。 = {initializer_list、opt}表記はCから継承され、データの初期化に役立ちます構造と配列。 [...]
ARMが記述されて以来、文法は進化していますが、Originは残っています。
そして、 C99の根拠 に移動して、これがCで許可された理由を確認できます。
K&Rでは、初期化リストの末尾にある初期化子の末尾にコンマを使用できます。 は初期化リストにメンバーを追加または削除する際の柔軟性を提供し、そのようなリストのマシン生成を簡素化するため、標準はこの構文を保持しています。
マシンにとっては、つまりコードの解析と生成が簡単です。また、一貫性による変更、コメントアウト、視覚的優雅さなど、人間にとっても簡単です。
Cと仮定して、次のように記述しますか?
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
puts("Line 1");
puts("Line 2");
puts("Line 3");
return EXIT_SUCCESS
}
いいえ。最終ステートメントがエラーであるだけでなく、一貫性がないためです。では、なぜ同じことをコレクションにもしますか?最後のセミコロンとコンマを省略できる言語であっても、コミュニティは通常それを好みません。たとえば、Perlコミュニティは、セミコロン、バーワンライナーを省略することを好まないようです。それらはコンマにも適用されます。
複数行のコードブロックのセミコロンを省略しないのと同じ理由で、複数行のコレクションでコンマを省略しないでください。つまり、たとえ言語で許可されていても、あなたはそれをしないでしょう?右?
-実際には*-許可されていない唯一の言語はJavascriptであり、無数の問題を引き起こします。たとえば、配列の中央から行をコピーして貼り付け、最後に貼り付け、コンマを削除するのを忘れた場合、IE訪問者のサイトは完全に壊れます。
*理論上は許可されますが、Internet Explorerは標準に準拠せず、エラーとして扱います
理由は簡単です:行の追加/削除の容易さ。
次のコードを想像してください:
int a[] = {
1,
2,
//3, // - not needed any more
};
これで、末尾のコンマを追加/削除しなくても、リストに項目を簡単に追加/削除できるようになりました。
他の答えとは対照的に、リストの生成の容易さは正当な理由だとは本当に思っていません。結局、コードが最後の(または最初の)行を特殊なケースにするのは簡単なことです。コード生成プログラムは1回作成され、何度も使用されます。
すべての行が同じ形式に従うことができます。まず、これにより、新しい行の追加が容易になり、バージョン管理システムが変更を有意に追跡できるようになります。また、コードをより簡単に分析できるようになります。技術的な理由は考えられません。
これは、長いリスト内で要素を移動することによって引き起こされる間違いから保護するために許可されています。
たとえば、次のようなコードがあるとします。
#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サイトの元の3部作を示しているので、素晴らしいです。
Stack Overflow
Super User
Server Fault
しかし、それには1つの問題があります。ご覧のとおり、このWebサイトのフッターには、スーパーユーザーの前にサーバー障害が表示されています。誰もが気付く前にそれを修正します。
#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に文字列連結機能があり、2つの二重引用符付き文字列を記述し、何も使用せずに連結できることです(-
記号には複数の意味があるため、整数でも同様の問題が発生する可能性があります)。
元の配列の最後に役に立たないコンマがあった場合はどうでしょうか?さて、行は移動しますが、そのようなバグは発生しませんでした。コンマのような小さなものを見逃すのは簡単です。すべての配列要素の後にコンマを置くことを忘れない場合、このようなバグは発生しません。 コンマが問題の原因であることがわかるまで、何かをデバッグするのに4時間も無駄にしたくないでしょう 。
多くの事柄と同様に、配列初期化子の末尾のコンマは、C++がCから継承したものの1つです(そして、永遠にサポートする必要があります)。 ここに配置されているものとはまったく異なるビューが本に記載されています「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の自動生成を容易にすることです。 カンマ区切りのすべてのリストで末尾のコンマが許可されている場合、クレームはより信頼性が高くなります(enum宣言や単一の宣言での複数の変数宣言子など)。ではない。
...私にとってこれはより理にかなっています
コードの生成と編集が簡単であることに加えて、パーサーを実装する場合、このタイプの文法は実装がより簡単で簡単です。 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
使用するのは非常に厄介です。