C ++の6つのなぞなぞ

迷惑なオプションのレーキをもう一度踏んで、それらについての知識を体系化することにしました。 C ++でしばらく開発している場合、ここで新しいものを見つけることはできませんが、記事で提供されている資料は間違いなく誰かを助けるでしょう。 5年前にこれを知っていれば、回復不能な数日間の生命と神経細胞を確実に節約できます。



もっと面白くするために、簡単なタスクの形で資料を紹介します。 私は、与えられた例を言語の誤算とは考えないことをすぐに強調しなければなりません。 多くの点で、問題について考えると意味と論理が現れます。 これらは、特に頭が何かで詰まっている場合、直感が失敗する可能性が高いケースです。 「まあ、このコンパイラには何が必要なのか、同じことがうまくいきました!」



そして最後のポイント。 これは、「ここではセミコロンをforの直後に配置しましたが、誰も気づきませんでした」などの注意のタスクではありません。 問題はタイプミスではありません。 必要なすべてのライブラリは接続されていると見なすことができます。記事を煩雑にしないために、説明した状況に関係のないコードは省略しました。





すべてのタスクの条件。


いくつかのコードが与えられます。 場合によっては、個別のリストに分割されます。 プログラムがコンパイルされるかどうか、コンパイルされる場合、画面に表示される内容、アプリケーションがどのように動作するか、コンパイルが失敗した場合、どのような理由で答える必要があります。



問題1. 0で割る


コード表示1.1:


int g = 3; g -= 3; int f = 1 / g; std::cout << "f is " << f << std::endl;
      
      





リスト1.2:


  float g = 3; g -= 3; float f = 1 / g; std::cout << "f is " << f << std::endl;
      
      





答え
リスト1.1プログラムはクラッシュします。 ゼロで割ることは不可能です。 まさにあなたが期待するもの。

リスト1.2すべてが機能します。 結論は「f is inf」です。 どうやら、フロートは正確な型ではないため、そのように表現することはできないと考えられています。 ほんのわずか。 そして、あなたは無限に分けることができます。 これは覚えておくべきであり、ゼロで割るとプログラムがクラッシュし、エラーがすぐにわかることを期待しないでください。





タスク2.スイッチとクラス


リスト2


 class SwitchClass { public: static const int ONE; static const int TWO; void switchFun(int number) { switch(number) { case ONE: std::cout<<"1"<<std::endl; break; case TWO: std::cout<<"2"<<std::endl; break; } } }; const int SwitchClass::ONE = 1; const int SwitchClass::TWO = 2; int main() { SwitchClass object; object.switchFun(SwitchClass::ONE); return 0; }
      
      





答え
コンパイルされません。 静的定数クラスのメンバーは、switchステートメントの観点からは十分に定数ではありません。 この問題は、「 static const int 」の代わりに列挙型を使用することで解決されます





タスク3.論理式


リスト3


 bool update1() { std::cout<<"update1"<<std::endl; ...//     } bool update2() { std::cout<<"update2"<<std::endl; ... //     } int main() { bool updatedSuccessfully = update1() && update2(); return 0; }
      
      





答え
画面への出力は、update1が返す値によって異なります。 C ++では、論理式の計算が最適化されています。 つまり、ある段階で結果が明らかになると、それ以上の計算は実行されません。 この例では論理Iを使用しています。オペランドの1つがFALSEの場合、2番目のオペランドの値に関係なく、結果もFALSEになります。 つまり、update1がFALSEを返した場合、update2も呼び出されず、画面に表示されます

 update1
      
      





update1の実行結果がTRUEの場合、update2が呼び出され、プログラムが出力されます

 update1 update2
      
      





道徳 。 論理式でいくつかのサイドアクションを実行する関数は、使用しないほうがよいでしょう。 結果を維持しながら両方の関数の呼び出しを保証するには、リスト3のコードを次のように書き換える必要があります

 bool update1Result = update1(); bool update2Result = update2(); bool updatedSuccessfully = update1Result && update2Result ;
      
      









タスク4.パラメーターとしてのイテレーター


リスト4.1関数宣言


 typedef std::vector<int> MyVector; void processIterator(MyVector::iterator& i) { std::cout<<*i<<std::endl; }
      
      







リスト4.2


  MyVector v; v.push_back(1); for (MyVector::iterator i = v.begin(); i != v.end(); ++i) { processIterator(i); }
      
      







リスト4.3


  MyVector v; v.push_back(1); processIterator(v.begin());
      
      





答え
リスト4.2は正常に機能します。 画面が表示されます

 1
      
      





リスト4.3はコンパイルエラーになります。 実際には、beginは定数イテレータを返します。リスト4.2では、これを単にiにコピーしています。 ループ変数iは定数ではなくなり、通常のリンクを介して関数に自由に渡すことができます。 4.3では、値から定数修飾子を「選択」しようとします。 すべての呼び出しオプションを機能させるために、関数を次のように書き換えることができます

 void processIterator(const MyVector::iterator& i) { std::cout<<*i<<endl; }
      
      









タスク5.ポインターでオブジェクトメソッドを呼び出す


リスト5


 class Test { public: void testFun() { std::cout<<"testFun"<<std::endl; } private: int mValue; }; int main() { Test *t, *t1 = NULL; t->testFun(); t1->testFun(); return 0; }
      
      





答え
一見、多くの問題があり(nullおよび初期化されていないポインターを呼び出す)、何も機能しないはずです。 ただし、コンソールでプログラムを実行すると、

 testFun testFun
      
      





その理由は簡単です。testFunメソッドは静的ではありませんが、オブジェクトのプロパティにはアクセスしません 。 メソッドがこのように見える場合

  void testFun() { std::cout<<"testFun"<<std::endl; mValue = 0; }
      
      





それから、もちろん、それは問題なくしてはなかったでしょう。

おわりに 初期化されていない、またはNULLポインターで非静的メソッドを呼び出すと、プログラムがクラッシュすると想定しないでください。 すべてが機能することが判明する場合があります。 もちろん、「なぜオブジェクトのプロパティを非静的にしないメソッドを作成するのか」という疑問は残ります。 それに対する答えは次のとおりです。 人生では、これは起こりません。」





タスク6:仮想機能のオーバーロード


リスト6.1クラスの定義


 class TestVirtuals { public: virtual void fun(int i) { std::cout<<"int"<<std::endl; } virtual void fun(float f) { std::cout<<"float"<<std::endl; } void fun(std::string s) { std::cout<<"string"<<std::endl; } }; class TestVirtualsChild : public TestVirtuals { public: virtual void fun(int i) { std::cout<<"int child"<<std::endl; } };
      
      







リスト6.2


  TestVirtuals tv; tv.fun(1); tv.fun(1.f); tv.fun(std::string("one"));
      
      







リスト6.3


  TestVirtualsChild tvc; tvc.fun(1); tvc.fun(1.f);
      
      







リスト6.4


  TestVirtualsChild tvc; tvc.fun(std::string("one"));
      
      







答え
リスト6.2オーバーロードは期待どおりに機能します。 プログラム出力

 int float string
      
      





リスト6.3子には親のオーバーロードは表示されません。 funメソッドへのすべての呼び出しは、整数バリアント(子孫にある)にキャストされます。 プログラム出力

 int child int child
      
      





リスト6.4コンパイルは失敗します。 非仮想関数は、子孫では消えるようです。 親クラスを明示的に指定して明示的に呼び出すことのみが可能です。

  tvc.TestVirtuals::fun(std::string("one"));
      
      





おわりに 仮想関数とオーバーロードされた関数の組み合わせは避けたほうが良いでしょう。 他に解決策がない場合は、屋根を通過するだけで注意する必要があります。




All Articles