カンマ演算子は何をしますか?


167

何をしない,オペレータはCでいますか?



1
私の回答で述べたように、左のオペランドの評価の後にシーケンスポイントがあります。これは、単なる文法的な関数呼び出しのコンマとは異なります。
Shafik Yaghmour 2014年

2
@SergeyK。—これは他の人より何年も前に尋ねられ、回答されたとすると、他の人がこの質問の複製である可能性が高くなります。ただし、もう1つはcc ++の両方で二重タグ付けされているため、迷惑です。これはCのみのQ&Aで、適切な回答が含まれています。
ジョナサンレフラー

回答:


129

表現:

(expression1,  expression2)

最初にexpression1が評価され、次にexpression2が評価され、式全体のexpression2の値が返されます。


3
次に、i =(5,4,3,2,1,0)と書くと、理想的には0を返すはずです。しかし、私は5の値が割り当てられていますか?私がどこで間違っているのかを理解するのを手伝ってくれませんか?
Jayesh、2010年

19
@James:コンマ操作の値は常に最後の式の値になります。どの時点でiも、5、4、3、2、1の値はありません。これは単に0です。式に副作用がない限り、これは実質的に役に立ちません。
ジェフメルカド2010年

6
コンマ式のLHSの評価とRHSの評価の間に完全なシーケンスポイントがあることに注意してください(C99標準からの引用については、Shafik Yaghmour回答を参照してください)。これは、カンマ演算子の重要なプロパティです。
ジョナサンレフラー

5
i = b, c;(i = b), c代入=はカンマ演算子よりも優先されるため、と同等です,。カンマ演算子は、すべての中で最も優先順位が低くなっています。
シクラミニスト

1
括弧が2つの点で誤解を招くのではないかと心配しています。(1)括弧は必要ありません。コンマ演算子を括弧で囲む必要はありません。(2)関数呼び出しの引数リストを囲む括弧と混同される可能性がありますが、引数リストのコンマはコンマ演算子ではありません。ただし、それを修正することは完全に簡単ではありません。たぶん:ステートメント内:expression1, expression2;最初にexpression1、おそらくその副作用(関数の呼び出しなど)expression2について評価され、次にシーケンスポイントがあり、次に評価されて値が返されます…
Jonathan Leffler

119

私はwhileループで最も使用されるのを見てきました:

string s;
while(read_string(s), s.len() > 5)
{
   //do something
}

操作を行ってから、副作用に基づいてテストを行います。もう1つの方法は、次のようにすることです。

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}

21
ねえ、それは気の利いたです!私はその問題を修正するために、ループで正統でないことをしなければならないことがよくありました。
staticsan 2009

6
ただし、次のようにすると、おそらくあいまいさが少なくなり、読みやすくなりますwhile (read_string(s) && s.len() > 5)。明らかにread_string、戻り値がない(または意味のある値がない)場合は機能しません。(編集:申し訳ありませんが、この投稿の古さに気づきませんでした。)
jamesdlin 2010年

11
@staticsan Doが使用することを恐れてはいけないwhile (1)break;、本体内のステートメント。コードのブレークアウト部分をwhileテストに強制的に入れたり、do-whileテストに押し下げたりしようとすると、多くの場合エネルギーが無駄になり、コードの理解が難しくなります。
potrzebie 2012

8
@jamesdlin ...そして人々はまだそれを読んでいます。何か言いたいことがあれば、それを言ってください。スレッドは通常、最後の投稿の日付でソートされるため、フォーラムでは復活スレッドに問題があります。StackOverflowにはそのような問題はありません。
Dimitar Slavchev

3
より良いよりもコンマアプローチのようにIを@potrzebie while(1)break
マイケル

38

コンマ演算子は、左のオペランドを評価し、結果を破棄し、右側のオペランドを評価し、それが結果になります。慣用的に使用される変数の初期化時にリンクに記載されているように使用されるforループを、それが次の例を与えます。

void rev(char *s, size_t len)
{
  char *first;
  for ( first = s, s += len - 1; s >= first; --s)
      /*^^^^^^^^^^^^^^^^^^^^^^^*/ 
      putchar(*s);
}

それ以外の場合は、コンマ演算子の多くの優れた使用法はありませんが、読み取りや保守が難しいコードを乱用することは簡単です。

ドラフトC99標準以下のような文法は以下のとおりです。

expression:
  assignment-expression
  expression , assignment-expression

そしてパラグラフ2は言う:

コンマ演算子左のオペランドは、void式として評価されます。評価後にシーケンスポイントがあります。次に、右側のオペランドが評価されます。結果にはタイプと値があります。 97)コンマ演算子の結果を変更しようとしたり、次のシーケンスポイントの後にアクセスしたりした場合の動作は未定義です。

脚注97は言う:

コンマ演算子は左辺値を生成しませ

つまり、コンマ演算子の結果に割り当てることはできません。

コンマ演算子の優先順位最も低いため、使用()すると大きな違いが生じる場合があることに注意してください。次に例を示します。

#include <stdio.h>

int main()
{
    int x, y ;

    x = 1, 2 ;
    y = (3,4) ;

    printf( "%d %d\n", x, y ) ;
}

次の出力が表示されます。

1 4

28

コンマ演算子は、その両側の2つの式を1つに結合し、左から右の順序で両方を評価します。右側の値は、式全体の値として返されます。 (expr1, expr2)のよう{ expr1; expr2; }ですがexpr2、関数呼び出しまたは代入で結果を使用できます。

forループでは、次のように複数の変数を初期化または維持することがよくあります。

for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh)
{
    /* do something with low and high and put new values
       in newlow and newhigh */
}

これとは別に、私はそれを「怒って」、他の1つのケースでのみ使用しました。これは、常にマクロで一緒に実行される2つの操作をラップする場合です。ネットワーク上で送信するために、さまざまなバイナリ値をバイトバッファーにコピーするコードと、取得した場所で維持されるポインターがありました。

unsigned char outbuff[BUFFSIZE];
unsigned char *ptr = outbuff;

*ptr++ = first_byte_value;
*ptr++ = second_byte_value;

send_buff(outbuff, (int)(ptr - outbuff));

値がshortsまたはints の場合、これを行いました:

*((short *)ptr)++ = short_value;
*((int *)ptr)++ = int_value;

後で(short *)ptrL値ではなくなり、インクリメントできないため、これは本当に有効なCではないことを後で読みましたが、当時のコンパイラは気にしていませんでした。これを修正するには、式を2つに分割します。

*(short *)ptr = short_value;
ptr += sizeof(short);

ただし、このアプローチは、すべての開発者が両方のステートメントを常に入力することを忘れないようにしていました。出力ポインタ、値、値の型を渡すことができる関数が必要でした。これはCであり、テンプレートを使用するC ++ではないため、関数が任意の型をとることができなかったため、マクロを使用しました。

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

これらの例はどれも良いスタイルだとは思わない!確かに、ループでカンマ演算子を使用することに対してもアドバイスするSteve McConnellのコードコンプリートを覚えているようですfor。読みやすさと保守性のために、ループは1つの変数だけで制御され、for行自体の式にはループ制御コードのみが含まれている必要があります。初期化やループメンテナンスの他の余分なビットはありません。


ありがとう!それはStackOverflowに対する私の最初の答えでした。それ以来、おそらく簡潔さを重視する必要があることを学びました:-)。
ポールスティーブンソン、

ここでソリューションの進化を説明する場合(どのようにしてそこにたどり着いたか)を説明する場合のように、少し冗長性を重視することがあります。
緑の日程2016

8

これは複数のステートメントの評価を引き起こしますが、最後のステートメントのみを結果の値として使用します(rvalue、だと思います)。

そう...

int f() { return 7; }
int g() { return 8; }

int x = (printf("assigning x"), f(), g() );

xは8に設定されます。


3

以前の回答で述べたように、すべてのステートメントを評価しますが、最後のステートメントを式の値として使用します。個人的には、ループ式でのみ役立つことがわかりました。

for (tmp=0, i = MAX; i > 0; i--)

2

式の1つ(おそらくinit式またはループ式)で複数のことを実行するファンキーなループを作成するときに、私がそれが役立つと私が見た唯一の場所は、次のようなものです。

bool arraysAreMirrored(int a1[], int a2[], size_t size)
{
  size_t i1, i2;
  for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--)
  {
    if(a1[i1] != a2[i2])
    {
      return false;
    }
  }

  return true;
}

構文エラーがある場合や、厳密なC以外のものが混在している場合はご容赦ください。、演算子が適切な形式であるとは主張していませんが、それを使用できます。上記の場合、おそらくwhile代わりにループを使用するので、initとループの複数の式がより明確になります。(そして、宣言してから初期化するのではなく、インラインでi1とi2を初期化します...何とか何とか何とか。)


i1 = 0、i2 =サイズ-1
フランクスター、2009

-2

これは、@ Rajeshと@JeffMercadoからの質問に対処するために、これを復活させています。これは、検索エンジンの上位ヒットの1つであるため、非常に重要だと思います。

たとえば、次のコードスニペットを考えてみましょう

int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d\n", i , j);

印刷されます

1 5

iほとんどの回答で説明したようなケースが処理されます。すべての式は左から右の順序で評価されますが、最後の式のみがに割り当てられiます。( is1` の結果。

j以降場合は、異なる優先順位の規則に従う,最小演算子の優先順位を有しています。これらの規則により、コンパイラーは割り当て式、定数、定数...を認識します。式は再び左から右の順に評価し、その副作用は、目に見えるとどまるので、されjている5の結果としてj = 5

興味深いことに、int j = 5,4,3,2,1;言語仕様では許可されていません。初期化子が期待し代入式を直接ので、,オペレータが許可されていません。

お役に立てれば。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.