web-dev-qa-db-ja.com

Cの三項(条件付き)演算子

条件演算子の必要性は何ですか? if-elseコンストラクトを実装するため、機能的には冗長です。条件演算子が同等のif-else割り当てよりも効率的な場合、コンパイラがif-elseをより効率的に解釈できないのはなぜですか?

53
Bongali Babu

三項演算子は構文と読みやすさの利便性であり、パフォーマンスのショートカットではありません。さまざまな複雑さの条件の場合、人々はそのメリットに基づいて分割されますが、短い条件の場合、1行の式を使用すると便利です。

さらに、これは Charlie Martinが書いた のような式であるため、Cのステートメントの右側に表示できることを意味します。これは簡潔にするために価値があります。

63
John Feminella

Cでは、実際のユーティリティは、ステートメントではなくexpressionであるということです。つまり、ステートメントの右側(RHS)に配置できます。したがって、特定のことをより簡潔に書くことができます。

155
Charlie Martin

与えられた他の答えのいくつかは素晴らしいです。しかし、constの正確さをコンパクトな方法で強制するのに役立つと誰も言及していないことに驚いています。

このようなもの:

const int n = (x != 0) ? 10 : 20;

したがって、基本的にnconstであり、その初期値は条件ステートメントに依存します。最も簡単な代替方法は、nconstではなく_にすることです。これにより、通常のifで初期化できます。ただし、constにしたい場合は、通常のifではできません。最良の代替手段は、次のようなヘルパー関数を使用することです。

int f(int x) {
    if(x != 0) { return 10; } else { return 20; }
}

const int n = f(x);

ただし、バージョンがはるかにコンパクトで、間違いなく読みやすい場合は、3項です。

82
Evan Teran

次のように、コードの難読化にとって重要です。

Look->       See?!

No
:(
Oh, well
);
38
Artelius

コンパクトさとif-then-else構造を式にインライン化する機能。

11
tvanfosson

Cには、他のことに関して多少なりとも簡単に実装できるため、技術的に必要ではないものがたくさんあります。不完全なリストは次のとおりです。

  1. ながら
  2. for
  3. 関数
  4. 構造体

これらがなければコードがどのように見えるか想像してみてください。答えが見つかるかもしれません。三項演算子は、「シンタックスシュガー」の形式であり、注意してスキルを使用すると、コードの記述と理解が容易になります。

11

時々、三項演算子が仕事を成し遂げるための最良の方法です。特に、3進の結果をl値にしたい場合。

これは良い例ではありませんが、私はより良いものに空白を描いています。 1つはcertianです。3項を実際に使用する必要がある場合はあまりありませんが、私はまだ3項を使用しています。

const char* appTitle  = amDebugging ? "DEBUG App 1.0" : "App v 1.0";

しかし、私が警告することの1つは、3項を結び付けることです。彼らは本物になります
メンテナンス時の問題:

int myVal = aIsTrue ? aVal : bIsTrue ? bVal : cIsTrue ? cVal : dVal;

[〜#〜] edit [〜#〜]:これは潜在的に良い例です。三項演算子を使用して、それ以外の場合はそれを処理する関数を記述する必要がある場合に参照とconst値を割り当てることができます。

int getMyValue()
{
  if( myCondition )
    return 42;
  else
    return 314;
}

const int myValue = getMyValue();

...になる可能性があります:

const int myValue = myCondition ? 42 : 314;

どちらが良いかは議論の余地のない質問です。

9
John Dibling

まだ誰もこれに言及していないので、スマートprintfステートメントを取得する唯一の方法は、三項演算子を使用することです:

printf("%d item%s", count, count > 1 ? "s\n" : "\n");

警告:CからC++に移行するときの演算子の優先順位にはいくつかの違いがあり、そこから生じる微妙なバグに驚くかもしれません。

8
dirkgently

三項演算子がステートメントではなく式であるという事実により、式の一部として使用される関数のようなマクロのマクロ展開で使用することができます。 Constは元のCの一部ではなかったかもしれませんが、マクロプリプロセッサは以前の方法に戻ります。

私が使用した場所の1つは、バインドチェックされた配列アクセスにマクロを使用した配列パッケージです。チェックされた参照の構文はaref(arrayname, type, index)のようなもので、arraynameは実際には配列境界とデータのunsigned char配列を含む構造体へのポインターであり、typeはデータの実際の型であり、インデックスはインデックスでした。これの拡張は非常に毛むくじゃらでした(そして、私はメモリからそれをするつもりはありません)が、境界チェックを行うためにいくつかの三項演算子を使用しました。

返されたオブジェクトのポリモーフィズムが必要なため、Cで関数呼び出しとしてこれを行うことはできません。そのため、式で型キャストを行うにはマクロが必要でした。 C++では、テンプレート化されたオーバーロード関数呼び出しとして(おそらくoperator []として)これを行うことができますが、Cにはそのような機能はありません。

編集:バークレーのCAD配列パッケージ(glu 1.4エディション)から、私が話していた例があります。array_fetchの使用法のドキュメントは次のとおりです。

type
array_fetch(type, array, position)
typeof type;
array_t *array;
int position;

配列から要素を取得します。配列の境界外を参照しようとすると、ランタイムエラーが発生します。指定された位置の値が、実際に配列を参照解除するときに使用される型であるかどうかの型チェックはありません。

次に、array_fetchのマクロ定義を示します(三項演算子とコンマシーケンス演算子を使用して、単一の式の一部として正しい値で正しい値を持つすべての部分式を実行することに注意してください)。

#define array_fetch(type, a, i)         \
(array_global_index = (i),              \
  (array_global_index >= (a)->num) ? array_abort((a),1) : 0,\
  *((type *) ((a)->space + array_global_index * (a)->obj_size)))

Array_insert(必要に応じて、C++ベクトルのように配列を拡大します)の展開はさらに複雑で、複数のネストされた三項演算子が含まれます。

8
dewtell

これは構文上の砂糖であり、1つのステートメントのみを含む短いif/elseブロックの便利な略記です。機能的には、両方のコンストラクトは同じように機能するはずです。

4
Dana the Sane

三項演算子は、通常のif else句よりもパフォーマンスが高い場合があります。これは、組み込みアプリケーションでは重要かもしれませんが、コンパイラの最適化によってこの違いが崩れる可能性もあります。

dwnが言ったように、パフォーマンスは複雑なプロセッサの台頭中の利点の1つでした。MSDNブログ 非古典的なプロセッサの動作:しないことよりも速くすることができる方法 三項(条件付き)演算子とif/elseステートメントの違いを示します。

次のコードを与えます:

#include <windows.h>
#include <stdlib.h>
#include <stdlib.h>
#include <stdio.h>

int array[10000];

int countthem(int boundary)
{
 int count = 0;
 for (int i = 0; i < 10000; i++) {
  if (array[i] < boundary) count++;
 }
 return count;
}

int __cdecl wmain(int, wchar_t **)
{
 for (int i = 0; i < 10000; i++) array[i] = Rand() % 10;

 for (int boundary = 0; boundary <= 10; boundary++) {
  LARGE_INTEGER liStart, liEnd;
  QueryPerformanceCounter(&liStart);

  int count = 0;
  for (int iterations = 0; iterations < 100; iterations++) {
   count += countthem(boundary);
  }

  QueryPerformanceCounter(&liEnd);
  printf("count=%7d, time = %I64d\n",
         count, liEnd.QuadPart - liStart.QuadPart);
 }
 return 0;
}

異なる境界のコストは大きく異なり、大きくなります(元の資料を参照)。変更中の場合:

 if (array[i] < boundary) count++;

 count += (array[i] < boundary) ? 1 : 0;

次の理由により、実行時間は境界値に依存しなくなりました。

オプティマイザーは、三項式からブランチを削除できました。

しかし、私のデスクトップインテルi5 cpu/windows 10/vs2015では、私のテスト結果はmsdnブログとはかなり異なります。

デバッグモードを使用する場合、if/elseコスト:

count=      0, time = 6434
count= 100000, time = 7652
count= 200800, time = 10124
count= 300200, time = 12820
count= 403100, time = 15566
count= 497400, time = 16911
count= 602900, time = 15999
count= 700700, time = 12997
count= 797500, time = 11465
count= 902500, time = 7619
count=1000000, time = 6429

および三項演算子のコスト:

count=      0, time = 7045
count= 100000, time = 10194
count= 200800, time = 12080
count= 300200, time = 15007
count= 403100, time = 18519
count= 497400, time = 20957
count= 602900, time = 17851
count= 700700, time = 14593
count= 797500, time = 12390
count= 902500, time = 9283
count=1000000, time = 7020 

リリースモードを使用する場合、if/elseコスト:

count=      0, time = 7
count= 100000, time = 9
count= 200800, time = 9
count= 300200, time = 9
count= 403100, time = 9
count= 497400, time = 8
count= 602900, time = 7
count= 700700, time = 7
count= 797500, time = 10
count= 902500, time = 7
count=1000000, time = 7

および三項演算子のコスト:

count=      0, time = 16
count= 100000, time = 17
count= 200800, time = 18
count= 300200, time = 16
count= 403100, time = 22
count= 497400, time = 16
count= 602900, time = 16
count= 700700, time = 15
count= 797500, time = 15
count= 902500, time = 16
count=1000000, time = 16

三項演算子は、私のマシンのif/elseステートメントよりも遅いです!

そのため、さまざまなコンパイラ最適化手法に従って、内部演算子とif/elseの動作が大きく異なる場合があります。

1
Brent81

三項= if-elseの単純な形式。主に読みやすさのために利用可能です。

0
Alphaneo
  • Cのより曖昧な演算子のいくつかは、結果を返す単一の式としてさまざまな関数のようなマクロを実装できるという理由だけで存在します。これが、?:および,演算子が存在することを許可されている主な目的であると言えます。

    2つのパラメーターの最大値を返す関数のようなマクロを実装するとします。次に、たとえば次のように呼び出されます。

    int x = LARGEST(1,2);
    

    これを関数のようなマクロとして実装する唯一の方法は

    #define LARGEST(x,y) ((x) > (y) ? (x) : (y))
    

    結果値を返さないため、if ... elseステートメントでは不可能です。 注意)

  • ?:の他の目的は、場合によっては実際に読みやすさを向上させることです。ほとんどの場合、if...elseは読みやすいですが、常にそうではありません。たとえば、長い繰り返しのswitchステートメントを考えます。

    switch(something)
    {
      case A: 
        if(x == A)
        {
          array[i] = x;
        }
        else
        {
          array[i] = y;
        }
        break;
    
      case B: 
        if(x == B)
        {
          array[i] = x;
        }
        else
        {
          array[i] = y;
        }
        break;
      ...
    }
    

    これははるかに読みやすいものに置き換えることができます

    switch(something)
    {
      case A: array[i] = (x == A) ? x : y; break;
      case B: array[i] = (x == B) ? x : y; break;
      ...
    }
    
  • ?:neverを実行すると、if-elseよりもコードが高速になることに注意してください。それは混乱した初心者によって作成されたいくつかの奇妙な神話です。最適化されたコードの場合、?:は、ほとんどの場合、if-elseと同じパフォーマンスを提供します。

    どちらかといえば、?:if-elseよりもslowerになります。これは、使用されないオペランドの暗黙的な型プロモーションが必須であるためです。ただし、?:if-elseより高速になることはありません。


注意) もちろん、誰かが機能を使用しないのはなぜかと疑問に思うでしょう。実際、関数を使用できる場合は、関数のようなマクロよりもalwaysをお勧めします。ただし、関数を使用できない場合があります。たとえば、上記の例のxがファイルスコープで宣言されているとします。この場合、初期化子は定数式である必要があるため、関数呼び出しを含めることはできません。関数のようなマクロを使用する必要がある他の実用的な例には、_Genericまたは「Xマクロ」を使用したタイプセーフプログラミングが含まれます。

0
Lundin