forループステートメントで使用されているのがわかりますが、どこでも有効な構文です。もしあれば、他の場所でどのような用途を見つけましたか?
20 に答える
C 言語 (および C++) は歴史的に、「ステートメント プログラミング」と「式プログラミング」と呼ばれる 2 つの完全に異なるプログラミング スタイルの混合物です。ご存じのように、すべての手続き型プログラミング言語は通常、順序付けや分岐などの基本的な構造をサポートしています(「構造化プログラミング」を参照)。これらの基本的な構成要素は、C/C++ 言語に 2 つの形式で存在します。1 つはステートメント プログラミング用、もう 1 つは式プログラミング用です。
たとえば、プログラムをステートメントで記述する場合、 で区切られた一連のステートメントを使用できます;
。分岐を行いたい場合は、if
ステートメントを使用します。また、サイクルやその他の種類の制御転送ステートメントを使用することもできます。
式プログラミングでも、同じ構造を使用できます。これは実際に,
オペレーターの出番です。C では、演算子,
は順次式の区切り記号にすぎません。つまり、式プログラミングの演算子は、ステートメント プログラミング,
と同じ役割を果たします。;
式プログラミングでの分岐は、演算子を介して行われますが、代わりにand演算子?:
の短絡評価プロパティを介して行われます。(ただし、式プログラミングにはサイクルがありません。また、それらを再帰に置き換えるには、ステートメント プログラミングを適用する必要があります。)&&
||
たとえば、次のコード
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 コンパイラーでかなり興味深い方法で変更されます (標準 C の「式ステートメント」と対称的です)。「ステートメント式」を使用すると、標準 C のステートメントに式ベースのコードを挿入できるように、ステートメントベースのコードを式に直接挿入できます。
別の補足として、C++ 言語では、ファンクターベースのプログラミングが重要な役割を果たします。これは、別の形式の「式プログラミング」と見なすことができます。C++ 設計の現在の傾向によると、多くの状況で、従来のステートメント プログラミングよりも C++ の方が好ましいと考えられます。
一般に、Cのコンマは、コードを読んだり、理解したり、修正したりしようとしている他の誰か、または1か月後に自分自身が見逃しやすいという理由だけで、使用するのに適したスタイルではないと思います。もちろん、変数宣言とforループの外では、慣用句があります。
たとえば、複数のステートメントを3項演算子(?:)、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;
ロックが待機を開始する前にメッセージを送信するために、カンマを使用してミューテックスロックをデバッグする必要がありました。
派生ロックコンストラクターの本体にログメッセージを入れるしかなかったので、初期化リストで:baseclass((log( "message")、actual_arg))を使用して基本クラスコンストラクターの引数に入れる必要がありました。余分な括弧に注意してください。
これがクラスの抜粋です:
class NamedMutex : public boost::timed_mutex
{
public:
...
private:
std::string name_ ;
};
void log( NamedMutex & ref__ , std::string const& name__ )
{
LOG( name__ << " waits for " << ref__.name_ );
}
class NamedUniqueLock : public boost::unique_lock< NamedMutex >
{
public:
NamedUniqueLock::NamedUniqueLock(
NamedMutex & ref__ ,
std::string const& name__ ,
size_t const& nbmilliseconds )
:
boost::unique_lock< NamedMutex >( ( log( ref__ , name__ ) , ref__ ) ,
boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ),
ref_( ref__ ),
name_( name__ )
{
}
....
};
C標準から:
コンマ演算子の左側のオペランドは、void式として評価されます。評価後にシーケンスポイントがあります。次に、右のオペランドが評価されます。結果にはそのタイプと値があります。(コンマ演算子は左辺値を生成しません。))コンマ演算子の結果を変更しようとした場合、または次のシーケンスポイントの後でアクセスしようとした場合、動作は定義されていません。
つまり、Cが1つだけを期待する複数の式を指定できます。しかし実際には、これは主にforループで使用されます。
ご了承ください:
int a, b, c;
はコンマ演算子ではなく、宣言子のリストです。
あなたはそれをオーバーロードすることができます(この質問に「C ++」タグがある限り)。行列の生成にオーバーロードされたコンマが使用されているコードを見たことがあります。またはベクトル、私は正確に覚えていません。それはきれいではありませんか(少し混乱しますが):
MyVector foo = 2、3、4、5、6;
これは、次のようなデバッグマクロなどのマクロで使用されることがあります。
#define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size)))
(しかし、あなたがそれをやり過ぎたときに何が起こり得るかについて、あなたが本当にこの恐ろしい失敗を見てください。)
ただし、本当に必要な場合、またはコードが読みやすく保守しやすいものになることが確実でない限り、コンマ演算子を使用しないことをお勧めします。
for ループの外では、コードの匂いがする可能性がありますが、コンマ演算子の適切な使用法として私が見た唯一の場所は、削除の一部です。
delete p, p = 0;
代替案に対する唯一の価値は、この操作が 2 行である場合、誤って半分しかコピー/貼り付けできないことです。
また、習慣的に行うとゼロ代入を忘れないので気に入っています。(もちろん、p が auto_ptr、smart_ptr、shared_ptr などのラッパーの中にない理由は別の問題です。)
標準からの@NicolasGoyの引用を考えると、次のようなループのワンライナーを記述できるように思えます。
int a, b, c;
for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--);
printf("%d", c);
しかし、良い神よ、あなたは本当にあなたのCコードをこのようにもっと曖昧にしたいですか?
ASSERT
マクロにコメントを追加するのに非常に便利です:
ASSERT(("This value must be true.", x));
ほとんどのアサート スタイル マクロは、引数のテキスト全体を出力するため、これにより、有用な情報がアサーションに追加されます。
一般に、コードが読みにくくなるだけなので、コンマ演算子の使用は避けています。ほとんどの場合、ステートメントを 2 つだけ作成する方が簡単で明確です。お気に入り:
foo=bar*2, plugh=hoo+7;
以下に対して明確な利点はありません。
foo=bar*2;
plugh=hoo+7;
ループ以外の 1 つの場所では、次のように if/else コンストラクトで使用しました。
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 の前に配置することもできますが、関数の実行に時間がかかる場合は、必要がない場合は実行しないようにすることをお勧めします。オプション。別の方法は、IF を追加レイヤーとしてネストすることです。上記のコードは少し不可解なので、実際に私が通常行っていることです。しかし、ネスティングも不可解であるため、私は時々コンマの方法でそれを行いました。
従来のシングルトンでの遅延初期化の問題を回避するために、いくつかの 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
このループの最初のバージョンには、次の効果があります。
- 2 つの割り当てを行うことが回避されるため、コードが同期しなくなる可能性が減少します。
- を使用しているため
&&
、割り当ては最後の反復後に評価されません - 代入は評価されないため、キュー内の次の要素が最後にあるときに逆参照しようとしません (上記のコードではなく、qemu のコード内)。
- ループ内では、現在および次の要素にアクセスできます
,
演算子がループの外で使用されているのを見たのfor
は、三項ステートメントで代入を実行するときだけでした。かなり前のことなので正確には覚えていませんが、次のような内容でした。
int ans = isRunning() ? total += 10, newAnswer(total) : 0;
明らかに、正気な人がこのようなコードを書くことはありませんが、作成者は、読みやすさではなく、生成したアセンブラー コードに基づいて c ステートメントを作成する邪悪な天才でした。たとえば、if ステートメントの代わりにループを使用することもありました。これは、if ステートメントが生成するアセンブラーを好んだためです。
彼のコードは非常に高速でしたが、保守が困難でした。これで作業する必要がなくなったことを嬉しく思います。
配列の初期化で見つけました:
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
次のように、マクロで「任意の型の値を 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));
これにより、繰り返し入力する回数が減りましたが、読みにくくなりすぎないように注意する必要があります。
この回答の非常に長いバージョンをこちらでご覧ください。