-インストラクターQ [605892]スペアσσ[8099468] [すべて・スカイ・ファイン・正確・プラン・プラン]【10年の経験がもたらすもの】

  フォーラムやブログでは、Cのオペレーターの混乱や、誤解さえもよく見かけます。この混乱または解釈のほとんどは、式に複雑な副作用がある場合に発生します。しかし、本質的には、それはまだ概念的な理解の逸脱です。この記事では、3つの典型的な式を分析して、演算子の優先度と結合性に焦点を当て、それらと評価プロセスの違いと関係を説明します。
  
  優先順位は、式内のさまざまな演算子が機能する順序を決定し、関連性は、隣接する演算子が同じ優先順位を持つ場合に式が結合される方向を決定します。
  
  (1)a = b = c;
  
  優先順位と結合性の古典的な例の1つは、上記の「連続代入」式です。
  
  bの両側は代入演算であり、優先順位は当然同じです。割り当て式には「右に結合」という特性があり、この式の意味構造は「(a = b)= c」ではなく「a =(b = c)」であると判断します。つまり、cのbへの割り当てが最初に完了し(型が異なる場合、昇格、切り捨て、強制などが発生する可能性があります)、次に式 "b = c"の値がaに割り当てられます。代入式の値は、代入が完了した後の左側のオペランドの値であることがわかります。最も単純なケースでは、a、b、cの型はまったく同じで、 "b = c; a = b;と同じです。 「このように書くことの効果は同じです。
  
  一般的に言えば、2項演算子forについて、「左に結合」されている場合、「x▽y▽z」は「(x▽y)▽z」と解釈され、それ以外の場合は、 「X▽(y▽z)」。隣接する2つの演算子は異なっていてもかまいませんが、優先順位が同じである限り、上記の結論が適用されます。別の例では、「a * b / c」は「a *(b / c)」ではなく「(a * b)/ c」として解釈されます。これは完全に異なる結果をもたらす可能性があることを知っています。
  
  単項演算子の結合性の問題は、一般に単純です。たとえば、「* ++ p」は「*(++ p)」としてのみ解釈できます。三項演算子については後述します。
  
  (2)* p ++;
  
  このようなstrcpy関数を実装するサンプルコードはどこでも見ることができます:
  
  [cpp] view plaincopy
  
  char * strcpy(char * dest、const char * src){
  
  char * p = dest;
  
  while(* p ++ = * src ++);
  
  return dest;
  
  }
  
  この実装を理解するための鍵は、「* p ++」の意味を理解することです。
  
  まず、逆参照演算子「*」はポストインクリメント演算子「++」よりも優先順位が低いため、この式は「(* p)+ではなく「*(p ++)」と意味的に同等です。 + "。
  
  フォーラムには、「p ++」が括弧の有無にかかわらず同じ効果をもたらす理由を理解していない友達がよくいます。これが答えです。ポストインクリメントの優先度が逆参照より高く、括弧も冗長であるためです。(ここでは、意味の冗長性のみを参照しています。プログラムの読みやすさを考慮することは冗長ではないと考える人もいます。それは別の問題です。)
  
  しかし、混乱する別の問題、つまりポストインクリメント演算子のセマンティクスがあります。多くの本は、「自己インクリメントの後、最初に値を取得し、次に1を追加することです」と述べています。これは確かに本当ですが、このようなwhileステートメントでは、人々はまだ簡単に混乱しています。式に自己インクリメント、逆参照、および代入が含まれ、最終的にループを制御する条件として使用される場合、いわゆる「最初の値」および「最初」とは何ですか?C言語標準を見てみましょう。以下は、C99標準からの抜粋です:ISO / IEC 9899:1999:
  
  6.5.2.4-2:後置++演算子の結果は、オペランドの値です。結果が取得された後、オペランドの値がインクリメントされます.......オペランドの格納された値を更新することによる副作用が発生します。前と次のシーケンスポイントの間、
  
  つまり、インクリメント後の式の結果値は、インクリメントされる前の値であり、結果値が決定された後、オペランドの値がインクリメントされます。この「自己増加」の副作用は、最後の「シーケンスポイント」と次の「シーケンスポイント」の間で完了します。
  
  この記事では、シーケンスポイントについて詳しく説明することは意図していません。興味のある読者は標準を読むことができます。代入演算はC言語のシーケンスポイントではないため、上記のwhileステートメントでは、代入の前にsrcの自己増加効果を完了する必要がないことに注意してください。しかし、whileの制御式全体の終わりはシーケンスポイントです。
  
  「while(* p ++ = * src ++);」は次のように解析できます。最初に、whileの条件変数は代入式、左のオペランドは「* p ++」、右のオペランドは「* src ++」、式全体です数式の値は、割り当てが完了した後の左側のアイテムの値になります。左側と右側は、2つのポストインクリメント式を間接参照します。逆参照はpまたはsrcだけではなく、ポストインクリメント式全体に作用するため、上記の標準によれば、それらはそれぞれポインターpおよびsrcの現在の値を「取得」します。自己増加する副作用は、次のシーケンスポイントの前に完了する必要があるだけです。
  
  要約すると、コンパイラーはポインターpとsrcの現在の値をそれぞれ取得し、この値に基づいて「* src」から「* p」への割り当てを完了する必要があります。同時に、この割り当ての結果は、割り当て式全体の値になり、 whileループを終了します。次に、式全体の最後のある時点で(前のステートメントに影響を与えずに)、pとsrcがそれぞれ1ずつ増分されます。
  
  つまり、式全体が完全に終わった時点で、pとsrcの古い値に基づく代入とループ条件判定が完了し、pとsrcのインクリメントも完了しています。
  
  明らかに、この説明はまだめまいです。ポストインクリメント(ポストデクリメント)操作に関する他の2つの「ステートメント」を見たことがありますが、それらはC言語標準と完全に一貫しているわけではありませんが、最終的なセマンティック効果はまったく同じです。これらの2つのステートメントは、次のとおりです。
  
  (1)「x ++」をインクリメントした後は、コンマ式と同等です:「tmp = x、++ x、tmp」;
  
  (2)インクリメント後は、オペランドに1を加算してから、addに戻ります1の前の値が式全体の値として使用されます。
  
  相対的に言えば、コンパイラの実装(特に最適化)のためのスペースを残すのは、依然として標準のステートメントですが、上記の2つの「ステートメント」は、人々が理解しやすく、最終的な効果が適切に使用されます。上記は一貫しています。C ++言語では、オーバーロード後に演算子をインクリメントする必要がある場合、一般的に使用されるメカニズムは上記の2つのステートメントに基づいています。
  
  これらの理解があれば、次のようなstrlen実装を理解することは問題ありません:
  
  [cpp] view plaincopy
  
  size_t strlen(const char * str){
  
  const char * p = str;
  
  while(* p ++);
  
  return p-str- 1;
  
  }
  
  上記の関数の最後のマイナス1に注意してください。whileループを終了するかどうかは、pの現在の値の逆参照によって決定されますが、whileを終了する場合でも、「公式」の終了前に、ポストインクリメント(「++」)プラス1の副作用を反映する必要があります。いわゆる「exitループ」は「ループ本体を実行しない」ことを意味しますが、制御式はループ本体の一部ではなく、そのすべての副作用は式全体が終了する前に有効になります。したがって、最後に、ループの終了時に実行される余分なステップを減らします。
  
  私も繰り返します:* p ++は*(p ++)であり、読みやすさ以外は違いがないため、括弧を追加すると1が追加され、逆参照が間違っているという考えがあります。その効果を達成するには、「* ++ p」を使用できます。
  
  (3)x> y?100:++ y> 2?20:30
  
  この式は少し怖いです。まず、より多くのコンテキストを与えましょう:
  
  [cpp]プレーンコピーを表示
  
  int x = 3;
  
  int y = 2;
  
  int z = x> y?100:++ y> 2?20:30;
  
  この時点で、z値は何ですか?
  
  ネストされた2つの条件演算子(?:、「三項演算子」とも呼ばれます)です。多くの人が条件演算子の特性を確認し、それが「右に結合されている」ことを知るので、右側が"++ y> 2?20:30"の内部条件演算が最初に評価され、最初にyが1増加し、2より大きい条件が確立されるため、2番目の条件演算は結果 "20"を取得し、次に評価します。条件式全体。このとき、yが3になったため、「x> y」は成り立たなくなりました。全体の結果は当然20です。
  
  この考えは間違っています。
  
  エラーの理由は、優先度、結合性、評価順序が完全に混乱するためです。
  
  まず、ほとんどの場合、C言語では式の部分式の評価順序が厳密に規定されていません。次に、評価順序が決定されても、最初に式の意味構造を決定する必要があります。特定のセマンティクスの後でのみ、「評価順序」について話すことができます。
  
  上記の例では、条件演算子「join right」は、内部の条件式が最初に評価されることを決定しませんが、上記の式の意味構造が「x> y?100」と同等であると決定します。 :(++ y> 2?20:30)「代わりに」(x> y?100:++ y)> 2?20:30 "。-これが「右に組み合わせる」の真の意味です。
  
  コンパイラは式の構造を決定した後、式の実行時の動作を正確に生成できます。条件演算子は、評価の順序を明確に指定するC言語の数少ない演算子の1つです(他のビットは、論理および「&&」、論理または「||」およびコンマ演算子である3桁です)、 ")。
  
  C言語は、条件式が最初に条件部分を評価することを規定しています。条件部分がtrueの場合、疑問符の後のコロンの前の部分が評価され、得られた結果が式全体の結果値として使用されます。それ以外の場合は、コロンの後の値部分的に評価され、結果値として使用されます。
  
  したがって、式 "x> y?100:(++ y> 2?20:
  

  

  

  

  
  [PS-1] WikipediaにはC / C ++言語の演算子テーブルがあります。http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B
  
  [PS-2] Sina Weiboでbenbearchenについて言及したことがあります会社のコード要件:whileループの本体が空のステートメントである場合は、continueステートメントで置き換える必要があり、セミコロンのみを書き込むことはできません。私はこれに個人的に同意します。上記の2つのstrcpyとstrlenの例は、「流れをたどる」ためにそれほど有用ではありません。これらの2つの関数の実装例は、多くの人々と多くの本によって書かれているためです。
  
  演算子の優先順位
  
  優先順位の演算子のためのC ++入門、本の中では、それをこのように説明:
  
  どのようにオペランドの優先順位の指定は、オペランドで外部リンクを評価される順序..で約グループ化されたこと(PIC)何もしない
  
  意識の平均優先レベルは、オペランドの組み合わせ方法を指定しますが、オペランドが計算される順序は指定しません。例:
  
  6 + 3 * 4 + 2
  
  計算順序を左から右に直接たどると、結果は38になりますが、C / C ++ではその値は20です。
  
  乗算演算子の優先度は加算の優先度よりも高いため、3は6と3ではなく4とグループ化されます。これが演算子の優先順位の意味です。
  
  結合II。オペレータ
  
  にどのグループオペレーターに結合性を指定し 、同じ優先レベル。
  
  結合オペレータの指定方法と同じプライオリティグループ。
  
  たとえば、次のとおりです。
  
  a = b = c = d;
  
  この式には複数の代入演算子があるため、どのようにグループ化しますか?この場合、それは代入演算子の結合性に依存します。代入演算子は右結合なので、式は(a =(b = c)= d)ではなく(a =(b =(c = d)))と同等です。
  
  m = a + b + cの場合も
  
  同じです。m= a +(b + c);ではなく、m =(a + b)+ c; と同じです。3.
  
  オペランドの評価順序は、
  
  すべてC / C ++で指定します演算子の優先順位と結合性。ただし、すべての演算子がオペランドの計算順に指定されているわけではありません。C / C ++では、オペランドの計算順に4つの演算子(&&、||、コンマ演算子(、)、および条件演算子(?:))のみが指定されています。
  
  たとえば、m = f1()+ f2();
  
  最初にf1()またはf2()が呼び出されますか?同じコンパイラの異なるバージョンでも、コンパイラによって呼び出し順序が異なることは明らかではありません。異なる呼び出しシーケンス。最終結果が呼び出し順序と関係がない限り、このステートメントは正しいです。オペランドの評価順序と演算子の結合性の2つの概念を明確に区別する必要があります。加算演算子の結合性は結合性のままなので、最初にf1()を呼び出してから呼び出します。 f2()、この理解は正しくありません。凝集度は演算子を決定するオブジェクトであり、オペランドの評価順序ではありません。
  
  同様に、2 + 3 * 4 + 5;
  
  そのバインディングは(2+(3 * 4))+ 5ですが、3 * 4が最初に計算されるという意味ではなく、その計算順序は不明であり、未定義です。
  
  たとえば、3 * 4-> 2 + 3 * 4-> 2 + 3 * 4 + 5
  
  および2-> 3 * 4-> 2 + 3 * 4-> 2 + 3 * 4 + 5および5-> 3 * 4 -> 2 + 3 * 4-> 2 + 3 * 4 + 5これらのシーケンスは可能です。計算順序は異なりますが、最終結果には影響しません。

おすすめ

転載: www.cnblogs.com/zhenhua1618/p/12729375.html