📜 ⬆️ ⬇️

Six puzzles in C ++

Once again, stepping on the annoying unnecessary rake, I decided to systematize my knowledge about them. If you have been developing in C ++ for some time, you may not find anything new here, but the material cited in the article will definitely help someone. If I knew this five years ago, I would definitely save a few irretrievably lost days of life and nerve cells.

To make it more interesting, I will present the material in the form of simple tasks. Immediately, I emphasize that I do not consider the examples given as language miscalculations. In many ways, there is a sense and logic, if you think about the question. These are more likely instances when intuition can refuse, especially if the head is crammed with something else. There are a couple of examples of the form “Well, what does this compiler need, just the same thing worked!”

And the last remark. It will not be attentive tasks like "Here I put a semicolon immediately after for - and no one noticed." The problems are not typos. All the necessary libraries can be considered connected — I omitted the code that is not relevant to the situation being described in order not to overload the article.

')
The condition of all tasks.

Given some code. Sometimes it is broken down into separate listings. It is necessary to answer whether the program will be compiled, and if it is compiled, what will be displayed on the screen, and how the application will behave, if the compilation ends with an error, then for what reason.

Task 1. Divide by 0

Listing 1.1:

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

Listing 1.2:

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

Answer
Listing 1.1 The program crashes. It is impossible to divide by zero. Exactly what you expect.
Listing 1.2 Everything works. The output is “f is inf”. Apparently, it is believed that float is not an exact type, therefore, zero cannot be represented as such in it. Only infinitely small. And it is possible to divide into infinitely small. This should be remembered and not hoped that in the case of division by zero, the program will fall, and you will immediately learn about the error.


Task 2. Switch and class

Listing 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; } 

Answer
Not compiled. Static constant class members are not sufficiently constant from the point of view of the switch operator. The problem is solved by using enums (instead of " static const int ")


Task 3. Logical expressions

Listing 3

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

Answer
The output to the screen will depend on which value returns update1. In C ++, the calculation of logical expressions is optimized. That is, if at some stage the result becomes obvious - further calculations are not performed. The example uses logical I. If one of its operands is FALSE, the result will also be FALSE, regardless of the value of the second operand. That is, if update1 returns FALSE, then update2 will not even be called, and the output to the screen will be
 update1 

If the result of the execution of update1 is TRUE, then update2 will be called and the program will display
 update1 update2 

Morality It is better not to use functions that perform some side effects in logical expressions. To ensure that both functions are called up with the result preserved, the code in Listing 3 needs to be rewritten as follows.
 bool update1Result = update1(); bool update2Result = update2(); bool updatedSuccessfully = update1Result && update2Result ; 



Task 4. Iterator as a parameter

Listing 4.1 Function declaration

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


Listing 4.2

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


Listing 4.3

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

Answer
Listing 4.2 will work fine. The screen will display
 1 

Listing 4.3 will result in a compilation error. The fact is that begin returns a constant iterator, which in Listing 4.2 is simply copied to i. The loop variable i is no longer a constant and can be freely passed to our function via a normal link. In 4.3, we are trying to “take away” a qualifier of constancy from a value. To make all call options work, the function can be rewritten as follows.
 void processIterator(const MyVector::iterator& i) { std::cout<<*i<<endl; } 



Task 5. Calling the object's method by pointer

Listing 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; } 

Answer
At first glance, there are a lot of problems (calling on zero and uninitialized pointers), and nothing should work. However, after running the program in the console, we will see
 testFun testFun 

And the reason is simple: although the testFun method is not static, it does not access the properties of the object in it . If the method looked like this
  void testFun() { std::cout<<"testFun"<<std::endl; mValue = 0; } 

it would not have done without problems, of course.
Conclusion . Do not expect that calling a non-static method using an uninitialized or even null pointer will cause the program to crash. It may happen that everything will work. Of course, the question remains “Why make a method that does not use the properties of an object non-static”. The answer to it will be: “Who knows? In life, and this does not happen.


Task 6. Virtual functions overload

Listing 6.1 Defining classes

 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; } }; 


Listing 6.2

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


Listing 6.3

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


Listing 6.4

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


Answer
Listing 6.2 Overload will work as expected. Program output
 int float string 

Listing 6.3 A child "does not see" the overload of the parent. All calls to the fun method are cast to an integer variant (which is in the descendant). Program output
 int child int child 

Listing 6.4 Compilation fails. The non-virtual function seems to disappear in the child. It is only possible to explicitly call it through an explicit indication of the parent class.
  tvc.TestVirtuals::fun(std::string("one")); 

Conclusion . It is better to stay away from combining virtual functions with overloaded ones. If there is no other way out, caution should just go off scale.

Source: https://habr.com/ru/post/197164/


All Articles