2

三項演算子の 2 番目または 3 番目の引数を空白にしたり、コンテキスト固有でなく「何もしない」ことを意味する引数を使用したりすることは可能ですか? 次の例では、三項演算子で、整数変数が偶数の場合は 2 で乗算し、それ以外の場合は何もしないようにします。3 番目の引数については、自己代入、0 の加算または減算、または 1 の乗算または除算以外に何も考えられません。とにかく、それらはすべてコンテキスト固有です。すべての三項演算子に対して「何もしない」ことを意味するものが必要です。引数を空白にしてみましたが、コンパイルされません。

#include <iostream>

int main()
{
    int x = 5;
    (x % 2 == 0) ? x *= 2 : x = x; // or x += 0, x -= 0, x *= 1, or x /= 1
    std::cout << x << std::endl;
    return 0;
}

既に存在する変数、関数、またはオブジェクトの再記述を伴わない解決策はありますか? たとえば、 が返す2 つの関数fooと を考えてみましょう。後続の呼び出しで状態が変化するなど、何らかの理由でどちらの関数も再度呼び出すことができないと仮定します。goofoobool

int main()
{
    ( foo() ) ? goo() : ; // won't compile
    return 0;
}
4

4 に答える 4

7

三項演算子を使用する場合は、おそらくその結果を代入する必要があります。

x = (x % 2 == 0) ? 2 * x : x;

個人的には、ちょっとした数学を使うだけだと思います:

x <<= ((x & 1) ^ 1);

x&1の最下位ビットを与え、xx が奇数の場合は 1、偶数の場合は 0 になります。パーツはその^ 1ビットを反転するため、偶数の場合は 1、奇数のx場合は 0 になります。次に、そのビット数だけ左xにシフトします。x0 ビットでシフトすると、明らかに変更されxません。1 ビット左にシフトすると 2 が乗算xされます。

後者が望ましい (または少なくとも望ましい) 理由については、ほとんどの場合、この状況でパフォーマンスを本当に気にするかどうかという問題に帰着します。パフォーマンスが問題にならない状況にある場合は、if ((x%2)==0) x *= 2;おそらく次のようなものが最良の選択です。

私は少なくとも、質問の理由の少なくとも一部は効率への懸念であると推測していました. もしそうなら、純粋に数学的方法がより良い選択である可能性が高い. たとえば、VC++ が 2 つに対して生成するコードを考えてみましょう。

; mathematical version:
mov eax, DWORD PTR _x$[esp-4]
mov ecx, eax
not ecx
and ecx, 1
shl eax, cl

[注: このソース コードでは、g++ はほぼ同一のオブジェクト コードを生成します]。

メモリからロードする (可能な) 時間を無視するとx、これは 386 前後のほぼすべての Intel プロセッサで 4 クロック サイクル以内に実行されるはずです。かなり似通った結果 - ソース コードからアセンブリ言語への直訳的で文字通りの変換は、ほぼすべての合理的なプロセッサで、実際の計算を 4 つの命令で実行します。各命令は可能な限り単純かつ高速です。

ステートメントを使用したバージョンifは次のようになります。

; Line 2
    mov ecx, DWORD PTR _x$[esp-4]
    mov eax, ecx
    and eax, -2147483647            ; 80000001H
    jns SHORT $LN5@f
    dec eax
    or  eax, -2                 ; fffffffeH
    inc eax
$LN5@f:
    lea eax, DWORD PTR [ecx+ecx]
    je  SHORT $LN1@f
; Line 3
    mov eax, ecx
$LN1@f:

コンパイルが進むにつれて、これはそれほど悪くはありません。少なくともdiv、実装の明白な方法である を回避してい%2ます。残念ながら、それはまだ競争力があるほどスマートではありません。まだいくつかの分岐があり、そのうちの 1 つはおそらくあまり予測できません。

コンパイラによっては、これよりもよく見えることがあります。たとえば、代わりに g++ を使用すると、次のようになります。

    mov eax, DWORD PTR [ebp+8]
    and eax, 1
    test    eax, eax
    jne L2
    sal DWORD PTR [ebp+8]
L2:
    mov eax, DWORD PTR [ebp+8]

このコードで VC++ が行ったよりは確かに優れていますが、それでも数学的なバージョンにはほど遠いものです特に、その最下位ビットがかなり予測可能な偶数または奇数でない限り、その分岐は約半分の時間で誤予測される可能性があります。

結論: 最良の場合、これは数学的バージョンにほぼ一致する可能性がありますが、それはコンパイラと入力データの連携に依存します。コンパイラと入力データの最も偶然の組み合わせ以外では、ほぼ確実に少なくとも 2 倍遅くなり、10 倍であっても驚くことではありません。

もちろん、使用するフラグ、コンパイラのバージョンなどによっては、どちらのコンパイラからも実際よりも良い結果が得られる場合があります。ある程度の粘り強さがあれば、コードの数学的バージョンと同等の結果が得られるかもしれません。ターゲットのコンパイラと CPU について十分に理解していない限り、同じような結果が得られるかどうかさえかなり確信が持てないでしょう。

于 2013-09-07T02:30:12.730 に答える
2
(x % 2 == 0) ? x *= 2 : x = x;

条件演算子はif ステートメントではありません。値を生成するためのものです。上記のように値が必要ない場合は、次のように入力するだけifです。

if (x%2==0) x*=2;

これは、読みやすく維持しやすく、他のすべての点で同等です。コードが何らかの形で改善されると思われる場合は、再検討してください。

質問のコメントから、奇妙に見えるコードを書くだけでなく、それがどのように機能するかを学びたいようです。だとすればオペレーターは非常に興味深いのですが、興味深いのは3つまたは2つのオペレーターで使用できるかどうかではありません。むしろ、演算子の最も興味深い動作は、2 番目と 3 番目の引数が同じ型でない場合に生成されるものです (ここでも、値を生成するように設計されています)

他のすべての値を生成する式と同様に、三項演算子の型は条件の値に関係なく固定されているため、コンパイラは式全体の型が何であるかを判断する必要があり、両方の引数と互換性のある型である必要があります。 . ルールは非常に興味深く、複雑すぎてここで説明することはできませんが、Conditional Love: FOREACH Reduxを参照してください。

また、それはあなたの質問に適合しませんが、これが私が以前にそれを持ち出さなかった理由ですが、両方のブランチが値を生成する必要はありません.一方または両方がthrow式である可能性があり、その場合、型は他の引数。さらに、両方がthrow式または型voidの式である場合、三項演算子の型はvoidそれ自体です。

于 2013-09-07T02:29:22.653 に答える
1

三項演算子のパラメータをスキップする方法はありません。関数を一度呼び出す必要がある場合は、このように使用できます。foo() と goo() の両方の戻り値がブール値の場合、単純な or 演算子を使用して最終結果を取得できます。

bool ResultOfFoo =  foo();
bool ResultOfGoo = goo();

bool RequiredResult = ResultOfFoo ? ResultOfGoo : ResultOfFoo ;

最適化

bool RequiredResult = ResultOfFoo || ResultOfGoo;
于 2013-09-07T02:10:25.530 に答える
0

現在、(x % 2 == 0)? x *= 2 : x = xif ステートメントに勝るものはありません。

if (x % 2 == 0) x *= 2; // This is okay for one-liners.

x = x三項演算子を保持したい場合は、書く必要はありません。行全体を代入ステートメントにして、三項演算子を使用して値を選択できます。

x = (x % 2 == 0)? x * 2 : x;

ただし、ifステートメントに固執します。より明確に見えます。

于 2013-09-07T01:59:13.977 に答える