質問
この質問にはすでに回答があります:
- コンマ演算子は何をしますか? 9つの答え
forループ文で使用されていますが、どこでも正しい構文です。他の場所でそれを見つけた用途はありますか?
解決
C言語(およびC ++)は、歴史的には2つの完全に異なるプログラミングスタイルの組み合わせであり、「ステートメントプログラミング」と呼ばれることがあります。および「式プログラミング」。ご存知のように、すべての手続き型プログラミング言語は通常、シーケンスやブランチなどの基本的な構成要素をサポートしています(構造化プログラミング)。これらの基本的なコンストラクトは、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 ++コードを生成するため、通常、非常によく測定され制限された量で式プログラミングを使用します。しかし、多くの場合、それは便利です。そして、受け入れられるものと受け入れられないものとの境界は、大部分は個人的な好みの問題であり、確立されたイディオムを認識して読む能力です。
追加の注意事項として、言語のデザインそのものは明らかにステートメントに合わせて調整されています。ステートメントは式を自由に呼び出すことができますが、式はステートメントを呼び出すことができません(事前定義された関数の呼び出しを除く)。この状況は、いわゆる&quot;ステートメント式&quot; を拡張機能として(標準Cの「式ステートメント」と対称)。 &quot;ステートメント式&quot;ユーザーが標準Cのステートメントに式ベースのコードを挿入できるように、ステートメントベースのコードを式に直接挿入できるようにします。
別の追加メモとして:C ++言語では、ファンクターベースのプログラミングが重要な役割を果たします。これは、「式プログラミング」の別の形式として見ることができます。 C ++設計の現在の傾向によると、多くの状況で従来のステートメントプログラミングよりも望ましいと考えられます。
他のヒント
一般に、Cのコンマは、コードを読んだり、理解したり、修正しようとしたりする人、または自分で1か月先に見逃すのが非常に簡単なため、使用するのに適したスタイルではないと思います。もちろん、変数宣言とforループ以外では、慣用句です。
たとえば、複数のステートメントを三項演算子(?:)、ala:にパックするために使用できます。
int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117;
しかし、私の神、なぜ?!? (実際のコードでこのように使用されているのを見ましたが、残念ながら表示するためにアクセスすることはできません)
マクロが関数のふりをしており、値を返したいが、他の作業を最初に行う必要があるマクロで使用されているのを見ました。それは常にく、多くの場合、危険なハックのように見えます。
簡単な例:
#define SomeMacro(A) ( DoWork(A), Permute(A) )
ここ B = SomeMacro(A)
&quot; returns&quot; Permute(A)の結果を&quot; B&quot;に割り当てます。
C ++の2つのキラーカンマ演算子機能:
a)特定の文字列が見つかるまでストリームから読み取ります(コードをDRYに保つのに役立ちます):
while (cin >> str, str != "STOP") {
//process str
}
b)コンストラクタ初期化子で複雑なコードを記述します:
class X : public A {
X() : A( (global_function(), global_result) ) {};
};
相互排他ロックをデバッグするためにコンマを使用して、ロックが待機を開始する前にメッセージを配置する必要がありました。
派生したロックコンストラクターの本体にログメッセージはありませんでしたので、ベースクラスコンストラクターの引数にそれを使用する必要がありました:baseclass((log(&quot; message&quot;)、actual_arg))in初期化リスト。余分な括弧に注意してください。
クラスの抜粋:
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__ )
{
}
....
};
Boost Assignment ライブラリは良い例です便利で読みやすい方法でコンマ演算子をオーバーロードします。例:
using namespace boost::assign;
vector<int> v;
v += 1,2,3,4,5,6,7,8,9;
C標準から:
コンマ演算子の左オペランドは、無効な式として評価されます。評価後にシーケンスポイントがあります。次に、右側のオペランドが評価されます。結果にはそのタイプと値があります。 (コンマ演算子は左辺値を生成しません。)コンマ演算子の結果を変更したり、次のシーケンスポイントの後にアクセスしようとした場合、動作は未定義です。
要するに、Cが1つだけを期待する場合に、複数の式を指定できます。しかし、実際にはforループで主に使用されます。
注意:
int a, b, c;
はカンマ演算子ではなく、宣言子のリストです。
これは、次のようなデバッグマクロなどのマクロで使用されることがあります。
#define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size)))
(ただし、この恐ろしい失敗、本当にあなたがそれをやり過ぎたときに何が起こるかについて。
しかし、本当に必要な場合、またはコードがより読みやすく保守しやすいものであることが確かな場合を除き、コンマ演算子の使用はお勧めしません。
これをオーバーロードできます(この質問に&quot; C ++&quot;タグがある限り)。オーバーロードされたコンマが行列の生成に使用されたコードを見ました。またはベクトル、私は正確に覚えていません。それはきれいではありません(少し混乱しますが):
MyVector foo = 2、3、4、5、6;
forループの外で、コードの匂いがすることがありますが、コンマ演算子の良い使用法として私が見た唯一の場所は、削除の一部としてです:
delete p, p = 0;
代替案に対する唯一の値は、2行にある場合、この操作の半分だけを誤ってコピー/貼り付けできることです。
また、習慣を外してやると、ゼロの割り当てを決して忘れないので気に入っています。 (もちろん、pがauto_ptr、smart_ptr、shared_ptrなどのラッパーの中にない理由は別の質問です。)
@Nicolas Goyの標準からの引用を考えると、次のようなループ用のワンライナーを書くことができるように思えます:
int a, b, c;
for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--);
printf("%d", c);
しかし、良い神よ、この方法であなたのCコードをもっとあいまいにしたいですか?
一般に、コードを読みにくくするだけなので、コンマ演算子の使用は避けます。ほとんどすべての場合、2つのステートメントを作成する方が簡単で明確です。いいね:
foo=bar*2, plugh=hoo+7;
以上の明確な利点はありません:
foo=bar*2;
plugh=hoo+7;
ループのほかに、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の前に関数を置くこともできますが、関数の実行に時間がかかる場合は、必要でない場合や、a!= 1でない限り関数を実行しない場合は実行しないようにすることができます。それはオプションではありません。別の方法は、IFを追加のレイヤーにネストすることです。上記のコードは少しわかりにくいので、実際に私が通常行うことです。しかし、ネストも不可解であるため、時々コンマで行いました。
ASSERT
マクロにコメントを追加するのに非常に便利です:
ASSERT(("This value must be true.", x));
ほとんどのアサートスタイルマクロは引数のテキスト全体を出力するため、これによりアサーションに有用な情報が少し追加されます。
古典的なシングルトンの遅延初期化の問題を避けるために、いくつかの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
}
私にとって、Cでコンマを使用する非常に便利なケースは、コンマを使用して条件付きで何かを実行することです。
if (something) dothis(), dothat(), x++;
これは
と同等です if (something) { dothis(); dothat(); x++; }
これは「タイピングを少なくする」ことではなく、時々非常に明確に見えるだけです。
ループも同様です:
while(true) x++, y += 5;
もちろん、両方ともループの条件部分または実行可能部分が非常に小さく、2〜3回の操作である場合にのみ有用です。
for
ループの外側で使用される、
演算子を見たことがあるのは、三項ステートメントでアシングを実行することだけでした。ずっと前だったので、正確な声明を思い出すことはできませんが、次のようなものでした:
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));
繰り返しの入力をいくつか減らしましたが、読みにくくなりすぎないように注意する必要があります。
この回答の非常に長いバージョンをご覧くださいこちら。
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つの割り当てを行わないため、コードが同期しなくなる可能性が低くなります
-
&amp;&amp;
を使用するため、最後の反復後に割り当ては評価されません - 割り当ては評価されないため、キューの次の要素が最後にあるとき(上記のコードではなく、qemuのコードで)、参照を解除しようとしません。
- ループ内で、現在および次の要素にアクセスできます
配列の初期化で見つかりました:
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