Чтобы было интереснее, материал представлю в виде простых задачек. Сразу подчеркну, что я не считаю приведенные примеры просчетами языка. Во многом появляется смысл и логика, если вопрос обдумать. Это скорее случаи, когда может отказать интуиция, особенно если голова забита чем-нибудь еще. Есть и пара примеров вида «Ну чего этому компилятору надо, только что то же самое работало!»
И последнее замечание. Это не будут задачи на внимательность типа «Тут я поставил точку с запятой сразу после 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.2 Все работает. Вывод «f is inf». По всей видимости, считается, что float не является точным типом, потому и нуля как такового в нем представлено быть не может. Только бесконечно малое. А на бесконечно малое делить можно. Об этом нужно помнить и не надеяться, что в случае деления на ноль программа упадет, и вы сразу узнаете об ошибке.
Задача 2. Switch и класс
Листинг 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; }
Задача 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
Если же результатом выполнения update1 будет ИСТИНА, то будет вызвана 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<<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());
1
Листинг 4.3 Приведет к ошибке компиляции. Дело в том, что begin возвращает константный итератор, который в листинге 4.2 просто копируется в i. Переменная цикла i уже не является константой и может быть свободно передана в нашу функцию по обычной ссылке. В 4.3 же мы пытаемся «отобрать» у значения спецификатор (qualifier) константности. Чтобы заставить все варианты вызова работать, функцию можно переписать следующим образом
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; }
testFun testFun
А причина проста: хоть метод testFun не является статическим, обращения к свойствам объекта в нем не происходит. Если бы метод выглядел следующим образом
void testFun() { std::cout<<"testFun"<<std::endl; mValue = 0; }
то без проблем бы, конечно, не обошлось.
Вывод. Не рассчитывайте, что вызов нестатического метода по неинициализированному или даже нулевому указателю приведет к аварийному завершению программы. Может сложиться так, что все отработает. Конечно, остается вопрос «Зачем делать метод, не использующий свойства объекта, нестатическим». Ответом на него будет: «Мало ли. В жизни и не такое случается».
Задача 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"));
int float string
Листинг 6.3 Потомок «не видит» перегрузки родителя. Все вызовы метода fun приводятся к целочисленному варианту (который есть в потомке). Вывод программы
int child int child
Листинг 6.4 Компиляция завершается с ошибкой. Невиртуальная функция как будто исчезает у потомка. Остается возможным только явный ее вызов через явное указание родительского класса.
tvc.TestVirtuals::fun(std::string("one"));
Вывод. От совмещения виртуальных функций с перегруженными лучше держаться подальше. Если другого выхода нет, осторожность должна просто зашкаливать.
ссылка на оригинал статьи http://habrahabr.ru/post/197164/
Добавить комментарий