Forループ文で使用されていますが、どこでも正しい構文です。他の場所でそれを見つけた場合、どのような用途がありますか?
C言語(およびC++)は、歴史的には2つの完全に異なるプログラミングスタイルが混在したものであり、「ステートメントプログラミング」と「式プログラミング」と呼ぶことができます。ご存知のように、すべての手続き型プログラミング言語は通常、sequencingおよびbranching( 構造化プログラミング を参照)。これらの基本的な構成は、C/C++言語には2つの形式で存在します。1つはステートメントプログラミング用、もう1つは式プログラミング用です。
たとえば、ステートメントを使用してプログラムを作成する場合、;
で区切られた一連のステートメントを使用できます。分岐を行う場合は、if
ステートメントを使用します。サイクルおよびその他の種類のコントロール転送ステートメントも使用できます。
式プログラミングでは、同じ構成体も使用できます。これは実際に,
演算子が作用する場所です。 Cの連続式の区切り記号以外の演算子,
、つまり式プログラミングの演算子,
は、ステートメントプログラミングの;
と同じ役割を果たします。式プログラミングでの分岐は、?:
演算子を介して、あるいは&&
および||
演算子の短絡評価プロパティを介して行われます。 (ただし、表現プログラミングにはサイクルはありません。再帰に置き換えるには、ステートメントプログラミングを適用する必要があります。)
たとえば、次のコード
a = Rand();
++a;
b = Rand();
c = a + b / 2;
if (a < c - 5)
d = a;
else
d = b;
これは、従来のステートメントプログラミングの例であり、式プログラミングの観点から次のように書き換えることができます。
a = Rand(), ++a, b = Rand(), c = a + b / 2, a < c - 5 ? d = a : d = b;
またはとして
a = Rand(), ++a, b = Rand(), c = a + b / 2, d = a < c - 5 ? a : b;
または
d = (a = Rand(), ++a, b = Rand(), c = a + b / 2, a < c - 5 ? a : b);
または
a = Rand(), ++a, b = Rand(), c = a + b / 2, (a < c - 5 && (d = a, 1)) || (d = b);
言うまでもなく、実際にはステートメントプログラミングは通常、はるかに読みやすいC/C++コードを生成するため、通常、非常によく測定され制限された量で式プログラミングを使用します。しかし、多くの場合、便利です。そして、許容できるものとそうでないものの間の境界線は、大部分は個人の好みの問題であり、確立されたイディオムを認識して読む能力です。
追加の注記として、言語のデザインそのものが明らかにステートメントに合わせて調整されています。ステートメントは式を自由に呼び出すことができますが、式はステートメントを呼び出すことができません(事前定義された関数の呼び出しを除く)。この状況は、GCCコンパイラでかなり興味深い方法で変更されます。GCCコンパイラは、いわゆる "ステートメント式" を拡張機能としてサポートします(標準Cの "式ステートメント"と対称)。 「ステートメント式」を使用すると、標準Cのステートメントに式ベースのコードを挿入できるように、ユーザーは式ベースのコードを式に直接挿入できます。
別の追加の注意として:C++言語では、ファンクターベースのプログラミングが重要な役割を果たします。これは、「式プログラミング」の別の形式と見なすことができます。 C++設計の現在の傾向によると、多くの状況で従来のステートメントプログラミングよりも望ましいと考えられます。
一般に、Cのコンマは、コードを読んだり、理解したり、修正しようとしたりする人や、1か月先に自分が見逃すのが非常に簡単だからという理由で、使用するのに適したスタイルではないと思います。もちろん、変数宣言とforループ以外では、慣用句です。
たとえば、複数のステートメントを三項演算子(?:)、alaにパックするために使用できます。
int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117;
しかし、私の神々、なぜ?!? (実際のコードでこのように使用されるのを見てきましたが、残念ながら表示するためにアクセスすることはできません)
C++の2つのキラーカンマ演算子機能:
a)特定の文字列が見つかるまでストリームから読み取ります(コードをDRYに保つのに役立ちます)。
while (cin >> str, str != "STOP") {
//process str
}
b)コンストラクタ初期化子で複雑なコードを記述します。
class X : public A {
X() : A( (global_function(), global_result) ) {};
};
マクロが関数のふりをして、値を返したいが、最初に他の作業を行う必要があるマクロで使用されているのを見てきました。それは常にく、しばしば危険なハックのように見えます。
簡単な例:
_#define SomeMacro(A) ( DoWork(A), Permute(A) )
_
ここでB=SomeMacro(A)
はPermute(A)の結果を「返し」、「B」に割り当てます。
Boost Assignment ライブラリは、便利で読みやすい方法でコンマ演算子をオーバーロードする良い例です。例えば:
using namespace boost::assign;
vector<int> v;
v += 1,2,3,4,5,6,7,8,9;
Mutexロックをデバッグしてメッセージを書き込むには、コンマを使用する必要がありましたbeforeロックが待機を開始します。
導出されたロックコンストラクターの本体にログメッセージを出力することしかできなかったため、初期化リストでbaseclass((log( "message")、actual_arg))を使用して、ベースクラスコンストラクターの引数にそれを配置する必要がありました。余分な括弧に注意してください。
クラスの抜粋は次のとおりです。
class NamedMutex : public boost::timed_mutex
{
public:
...
private:
std::string name_ ;
};
void log( NamedMutex & ref__ , std::string const& )
{
LOG( << " waits for " << ref__.name_ );
}
class NamedUniqueLock : public boost::unique_lock< NamedMutex >
{
public:
NamedUniqueLock::NamedUniqueLock(
NamedMutex & ref__ ,
std::string const& ,
size_t const& nbmilliseconds )
:
boost::unique_lock< NamedMutex >( ( log( ref__ , ) , ref__ ) ,
boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ),
ref_( ref__ ),
name_( )
{
}
....
};
C標準から:
コンマ演算子の左オペランドは、無効な式として評価されます。評価後にシーケンスポイントがあります。次に、右側のオペランドが評価されます。結果にはそのタイプと値があります。 (コンマ演算子は左辺値を生成しません。)コンマ演算子の結果を変更したり、次のシーケンスポイントの後にアクセスしようとした場合、動作は未定義です。
要するに、Cが1つしか期待しない場合に、複数の式を指定できます。しかし、実際にはforループで主に使用されます。
ご了承ください:
int a, b, c;
カンマ演算子ではなく、宣言子のリストです。
Forループの外側では、コードの匂いがする可能性がありますが、カンマ演算子の良い使用法として私が見た唯一の場所は、削除の一部としてです:
delete p, p = 0;
代替の唯一の値は、2行にある場合、この操作の半分だけを誤ってコピー/貼り付けできることです。
あなたが習慣からそれをやるなら、ゼロの割り当てを決して忘れないので、私もそれが好きです。 (もちろん、pがauto_ptr、smart_ptr、shared_ptrなどのラッパーの中にない理由は別の質問です。)
オーバーロードできます(この質問に「C++」タグがある限り)。オーバーロードされたコンマがマトリックスの生成に使用されたコードを見てきました。またはベクトル、私は正確に覚えていません。それはきれいではありません(少し混乱しますが):
MyVector foo = 2、3、4、5、6;
次のようなデバッグマクロなどのマクロで使用されることがあります。
#define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size)))
(しかし この恐ろしい失敗 を見てください、あなたが本当にやりすぎたときに何が起こるかについて。)
しかし、本当に必要な場合、またはコードがより読みやすく保守しやすいものであることが確実な場合を除き、コンマ演算子の使用はお勧めしません。
@Nicolas Goyの標準からの引用を考えると、次のようなループ用のワンライナーを作成できるように思えます。
int a, b, c;
for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--);
printf("%d", c);
しかし、良い神、男、あなたは本当にあなたのCコードmoreをこの方法で不明瞭にしたいですか?
ASSERT
マクロにコメントを追加するのに非常に便利です:
ASSERT(("This value must be true.", x));
ほとんどのアサートスタイルマクロは引数のテキスト全体を出力するため、これにより、アサーションに有用な情報が少し追加されます。
一般的に、カンマ演算子はコードを読みにくくするため、使用を避けます。ほとんどすべての場合、2つのステートメントを作成する方が簡単で明確です。お気に入り:
foo=bar*2, plugh=hoo+7;
以下に対する明確な利点はありません。
foo=bar*2;
plugh=hoo+7;
If/else構造で使用したループ以外の1つの場所:
if (a==1)
... do something ...
else if (function_with_side_effects_including_setting_b(), b==2)
... do something that relies on the side effects ...
関数をIFの前に置くこともできますが、関数の実行に時間がかかる場合は、不要な場合は実行を避け、a!= 1でない限り関数を実行しないようにするには、オプション。別の方法は、IFを追加のレイヤーにネストすることです。上記のコードは少しわかりにくいので、実際に私が通常行うことです。しかし、ネストも不可解であるため、時々コンマで実行しました。
私にとって、Cでコンマを使用する非常に便利なケースは、コンマを使用して条件付きで何かを実行することです。
if (something) dothis(), dothat(), x++;
これは同等です
if (something) { dothis(); dothat(); x++; }
これは「タイピングを減らす」ことではなく、時々非常に明確に見えます。
また、ループは次のようになります。
while(true) x++, y += 5;
もちろん、両方ともループの条件部分または実行可能部分が非常に小さく、2〜3の操作である場合にのみ有用です。
古典的なシングルトンでの遅延初期化の問題を避けるために、いくつかのcppファイルで静的初期化関数を実行するためによく使用します。
void* s_static_pointer = 0;
void init() {
configureLib();
s_static_pointer = calculateFancyStuff(x,y,z);
regptr(s_static_pointer);
}
bool s_init = init(), true; // just run init() before anything else
Foo::Foo() {
s_static_pointer->doStuff(); // works properly
}
qemuには、forループの条件部分内でコンマ演算子を使用するコードがあります(qemu-queue.hの QTAILQ_FOREACH_SAFE を参照)。彼らがしたことは次のように要約されます:
#include <stdio.h>
int main( int argc, char* argv[] ){
int x = 0, y = 0;
for( x = 0; x < 3 && (y = x+1,1); x = y ){
printf( "%d, %d\n", x, y );
}
printf( "\n%d, %d\n\n", x, y );
for( x = 0, y = x+1; x < 3; x = y, y = x+1 ){
printf( "%d, %d\n", x, y );
}
printf( "\n%d, %d\n", x, y );
return 0;
}
...次の出力:
0, 1
1, 2
2, 3
3, 3
0, 1
1, 2
2, 3
3, 4
このループの最初のバージョンには、次の効果があります。
&&
、割り当ては最後の反復後に評価されません配列の初期化で見つけました:
Cでは{}の代わりに()を使用して2次元配列を初期化するとどうなりますか?
配列を初期化するときa[][]
:
int a[2][5]={(8,9,7,67,11),(7,8,9,199,89)};
配列要素を表示します。
私は得る:
11 89 0 0 0
0 0 0 0 0
for
ループの外側で,
演算子が使用されるのを見たのは、3項ステートメントでアサインメントを実行することだけでした。ずっと前だったので、正確な声明を思い出すことはできませんが、それは次のようなものでした:
int ans = isRunning() ? total += 10, newAnswer(total) : 0;
明快な人がこのようなコードを書くことは明らかではありませんが、作者は、読みやすさではなく、生成したアセンブラコードに基づいてcステートメントを構築する邪悪な天才でした。例えば、彼は、それが生成したアセンブラを好むため、ifステートメントの代わりにループを使用することがありました。
彼のコードは非常に高速でしたが、維持できませんでした。これ以上作業する必要がないのはうれしいことです。
次のように、「char *が指す出力バッファーに任意の型の値を割り当て、必要なバイト数だけポインターをインクリメントする」マクロに使用しました。
#define ASSIGN_INCR(p, val, type) ((*((type) *)(p) = (val)), (p) += sizeof(type))
コンマ演算子を使用すると、マクロを式で使用したり、必要に応じてステートメントとして使用したりできます。
if (need_to_output_short)
ASSIGN_INCR(ptr, short_value, short);
latest_pos = ASSIGN_INCR(ptr, int_value, int);
send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));
それはいくつかの反復的なタイピングを減らしましたが、あなたはそれがあまりにも読めないように注意する必要があります。
この回答の長すぎるバージョンをご覧ください here 。