Форум » C/C++ » Шутка - ложь, но в ней намек, добрым молодцам урок. » Ответить

Шутка - ложь, но в ней намек, добрым молодцам урок.

Сыроежка: Это не выдумка, а реальное сообщение, написанное на одном сайте: [quote]Решил я изучать C++. Но вот беда - перепробовал уже 9 книг - в каждой один и тот же первый пример - Hello world. И в каждой он не верен, если верить visual studio c++. [/quote] Перефразируя известное выражение, что если раз дали в морду, второй раз дали в морду, третий раз дали в морду, то может быть дело в морде?!

Ответов - 63, стр: 1 2 3 4 All

Сыроежка: Существует забавная ошибка, которую программисты очень часто допускают в своем коде, не догадываясь даже о существовании этой ошибки. Сначала приведу пример формулировки одной из задач, нередко встречающейся в разных модификациях во многих заданиях, которая содержит в себе побудительную причину, приводящую к данной ошибке.: Разбить целое число, то есть типа int, на составляющие его ццфры. Опытные прогаммисты сразу же принимают во внимание, что в общем случае число может быть отрицательным, а потому начинают решение этой задачи с переводом значения исходного числа в его абсолютное значение. Делают они это обычно с помощью функции abs. И вот как их код часто может выглядеть: [pre2]int SomeFunction( int x ) { x = abs( x ); // ...other stuff with x }[/pre2] Вы уже видете ошибку? Проблема в том, что отрицательных значений на единицу больще, чем положительных для любого знакового целого числа. Например, для типа signed char максимальное положительное значение равно 127, в то время, как минимальное отрицательное значение равно -128. А следовательно вы не можете получить абсолютное значение для этого минимального отрицательного значения в том же самом типе объекта! То есть 128 (положительное значение) не может храниться в signed char. Если вы примените функцию abs к этому минимальному значению, то снова получите тоже самое значение! Алгоритм работы abs прост. Он выполняет операцию дополнения до двойки и прибавляет к результату 1. Например, если имеется значение, записанное в шестнадцатиричном виде как 0x80, то есть -128 в десятичном виде, то abs сначала заменяет единицы на нули, а нули на единицы и получает 0x7F, а затем к результату прибавляет единицу и получает...снова 0x80!, которое, как и раньше, равно -128, так как тип исходного объекта остался прежним Чтобы убедиться в этом, достаточно выполнить простой пример [pre2]#include <limits> #include <iostream> #include <cstdlib> int main() { signed char x = std::numeric_limits<signed char>::min(); std::cout << "Before x = " << x << std::endl; x = abs( x ); std::cout << "After x = " << x << std::endl; }[/pre2] На консоль будет выведено два раза -128! Данная ошибка настолько прижилась среди программистов, что ее нередко можно встретить в профессиональном коде! Забавно, что подобная ошибка присутствует в книге Nicolai M. Josuttis "The C++ Standard Library" (second edition) . В 11-ой главе в разделе, посвященном описанию алгоритмов std::max_element, std::min_element и std::minmax_element, он приводит такую функцию для сравнения двух целых чисел по абсолютной величине, используемую в качестве предиката для указанных алгоритмов для нахождения минимального и максимального значений по абсолютной величине заданной последовательности целых чисел: [pre2] bool absLess( int elem1, int elem2 ) { return abs( elem1 ) < abs( elem2 ); } [/pre2] Как мы уже знаем, эта функция некорректна. Она не будет возвращать правильное значение, если в качестве аргумента для параметра elem1 задать минимальное значение для типа int. Это наглядно можно увидеть на следующем демонстрационном примере: [pre2] #include <iostream> #include <iomanip> #include <cstdlib> #include <limits> bool absLess( int elem1, int elem2 ) { return std::abs( elem1 ) < std::abs( elem2 ); } bool absLess1( int elem1, int elem2 ) { return ( unsigned int )std::abs( elem1 ) < ( unsigned int )std::abs( elem2 ); } int main() { std::cout << std::boolalpha << absLess( std::numeric_limits<int>::min(), std::numeric_limits<int>::max() ) << std::endl; std::cout << std::boolalpha << absLess1( std::numeric_limits<int>::min(), std::numeric_limits<int>::max() ) << std::endl; return 0; } [/pre2] Вывод на консоль будет [pre2] true false [/pre2]

Сыроежка: C++ богат различными синтаксическими ловушками, которые могут ввести в заблуждение программиста Вот один из таких примеров. [pre2] int *a = new int[10]; int *b = new int[10]; // some stuff with variables a and b delete [] a, b;[/pre2] Вопрос: каков будет результат выполнения предложения delete [] a, b; и что, вообще, означает эта конструкция? Не каждый программист сходу готов ответить на этот вопрос. Во-первых, возникают сомнения: является ли эта конструкция синтаксически корректной? Да, является! Если она является синтаксически корректной, то можно предположить, что она удаляет объекты из памяти, на которые указывают указатели a и b. Если программист считает, что это предположение верно, то перед ним возникает следующий вопрос: как интерпретировать эту конструкцию? Либо как [pre2]delete [] a; delete [] b;[/pre2] либо как [pre2]delete [] a; delete b;[/pre2] Увы, это предположение изначально неверно! На самом деле имеет место использования выражения с оператором запятая! Сначала будет удален объект, на который указывает указатель a, а затем будет возвращено значение указателя b. Сам объект, на который указывает указатель b никак не будет затронут! Чтобы убедиться в этом достаточно выполнить простой код. [pre2]#include <iostream> int main() { int *a = new int[10]; int *b = new int[10]; std::cout << ( delete [] a, b ) << std::endl; delete [] b; }[/pre2] На моем компьютере было выведено на консоль 003D4D08 Это ничто иное, как значение, хранящееся ы указателе b. А что делать, если нужно удалить лямбда-выражение, которое начинается с символов []? Для этого лямбда-выражение нужно заключить в круглые скобки. Например, [pre2]delete ( [] { return ( new int() ); }() );[/pre2] или [pre2]delete [] ( [] { return ( new int[10] ); }() );[/pre2]

Сыроежка: Встретил один необычный вопрос на многих форумах программистов от одного назойливого молодого человека. На первый взгляд вопрос кажется совершенно глупым и бесммысленным. Формулировка вопроса звучит следующим образом: Написать програмку, которая выводит на экран консоли такую фигуру (здесь символ 0 используется лишь для демонстрации выравнивания полей; используется только звездочка) 0000* 00*000* *000000* При этом нельзя использовать ни циклы, ни сивол пробела (или другой непечатаемый символ), как, например, символ с кодом 255. Действительно, как можно вывести такую фигуру на экран консоли, совершенно не используя символ пробела?! Кажется, что это сделать совершенно невозможно. Лучшее, что смогли придумать программисты на тех форумах, где задавался вопрос этим молодым человеком, это использовать манипулятор std:;setw при выводе строки. Но этот манипулятор на самом деле задействует символ пробела, так как он использует символ-заполнитель, а по умолчанию таким символом-заполнителем является символ пробела. Так, что, действительно задание глупое и невыполнимое? На самом дееле есть остроумное решение, которое я, не долго думая, все же нашел. Действительно, в этом решении символ пробела или другой непечатаемый символ совершенно не используется ни в одной строчке кода. Вот это решение. [pre2]#include <iostream> int main() { std::cout << "\t\b\b\b\b*\n"; std::cout << "\t\b\b\b\b\b\b*\t\b\b*\n"; std::cout << "*\t*\n"; return 0; }[/pre2] Как видите, символа пробела в коде действительно нет! Вместо этого используется лишь управляющие символы табуляции и забоя знака. Без изящества, но зато условие задачи выполнено!


ололеша:

Сыроежка: ололеша , очевидно, что автор вопроса имел в виду те непечатаемые символы, которые вы используете как символы-заполнители, помещая их в конкретную позицию Без управляющих символов просто не обойтись, так как по крайней мере нужно использовать сомвол новой строки. Но этот символ не является символов-заполнителем, а цправляет поведением консоли. То же самое имеет место и в отношении символов табуляции и забоя знака. А иначе без управляющих символов задача не имеет смысла, так как вы только и сможете, что делать, как выводить последовательно символ звездочки пока буфер клаквиатуры не будет заполнен.

Сыроежка: Начинающте программисты часто допускают такую ошибку, когда пытаются присвоить объекту типа char строковый литерал, либо сравнить объект типа char со строковым литералом, как показано ниже [pre2]if ( c == "A" ) { /* ... */ }[/pre2] где c - это переменная, объявленная с типом char. Конечно такую ошибку исправить легко, заменив у строкового литерала двойные кавычки в одинарные, тем самым превратив его в символьный литерал: [pre2]if ( c == 'A' ) { /* ... */ }[/pre2] Но мало кто из начинающих программистов знает, что можно и не менять кавычки, а использовать в этом выражении строковый литерал! Например, можно то же самое выражение записать так [pre2]if ( c == "A"[0] ) { /* ... */ }[/pre2] Или даже так: [pre2]if ( c == *"A" ) { /* ... */ }[/pre2] Обе эти конструкции со строковым литералом корректные и семантически эквивалентны первой конструкции, где используется символьный литерал. Эти конструкции выглядят довольно вычурно, и может показаться, что их никогда в коде не придется использовать. На самом деле это не совсем так. Если вам достался унаследованный код на языке программирования C, то там часто можно встретить, что строковым литералам назначаются символические имена с помощью макроопределений. В этом случае вполне приемлемо использовать такие вычурные конструкции вмсето магического символьного литерала, по которому трудно сказать, связан ли он с символическим определением строкового литерала или нет. Например, [pre2]#define YES_NO "y/n" // some oither stuff if ( c == YES_NO[0] ) { /*... */ }[/pre2] Такой код нередко можно встретить в проектах, разрабатываемых на языке C. Конечно, если вы начинаете писать проект с самого начала, то лучше использовать константные объекты вместо макроопределений. Так что предыдущий код можно переписать как [pre2]const char *YES_NO = "y/n"; // some oither stuff if ( c == YES_NO[0] ) { /*... */ }[/pre2]

Сыроежка: Язык C++ настолько перегружен различными семантическими правилами, что, казалось бы, синтаксически неверные на первый взгляд конструкции успешно компилируются компилятором. Забавность подобных ситуаций заключается в их двойной природе ошибочности. То есть когда вы видите такую конструкцию, вам кажется, что синтаксически она неверная. Затем вы убеждаетесь, что на самом деле синтаксически конструкция совершенно корректная. Но тем не менее она ошибочная! Потому что она делает совсем не то, что ожидал от нее программист. Но, чтобы растянуть удовольствие от произведенного на вас впечатления от этой конструкции, начну с предварительного примера. Рассмотрим предложение [pre2]int i = new int();[/pre2] Вы уже нашли ошибку? Да, действительно, вместо int i, то есть вместо объявления объекта целочисленного типа мы должны написать int *i, то есть объявить указатель на тип int. Это не страшно, если вы допустите такую опечатку. Компилятор сам быстро обнаружит эту ошибку и сообщит о ней. Поэтому никаких последствий от этой ошибки для программы не будет. Разве лишь вам придется потратить время, чтобы ее исправить на этапе компиляции. А теперь самое интересное. В C++ есть скалярный тип, для которого подобная запись не является ошибкой! То есть компилятор не обнаружит ошибки, хотя вы на самом деле сделали опечатку и хотели написать совершенно не то! Этим типом является тип bool! Посмотрите на следующий код [pre2]bool b = new bool();[/pre2] На первый взгляд предложение кажется ошибочным. Но ничего подобного! Это совершенно корректное предложение, хотя объявляется не указатель на bool, а объект типа bool Трагикомичность этой ситуации состоит в том, что на самом деле вы не этого хотели и сделали элементарную опечатку, забыв вставить символ * перед именем переменной 'b'.. При этом компилятор сделает вид, что предложение корректное! Ошибка может быть обнарпужена лишь где-нибудь в другом месте, если вы, например, попробуете применить оператор индексирования к переменной 'b'. Проблема в том, что это предложение действительно корректно с точки зрения стандарта языка C++. Любое скалярное выражение может быть неявно преобразовано к типу bool. Если оно отлично от нуля, то есть если в приведенном примере память будет успешно выделена, то переменной 'b' будет присвоено значение true. Такую ошибку легко пропустить в коде, а сообщение компилятора об успешной компиляции программы может ввести вас в заблуждение.

Сыроежка: Вопрос на засыпку: является ли следующая программа корректной? [pre2] #include <iostream> int main() { const char *s = "Hello"; std::cout << s[[]{ return 1; }()] << std::endl; return 0; }[/pre2]

Сыроежка: Наверное, чтобы ответить на поставленный в предыдущем сообщении вопрос, многие поступят очень просто, особо себя не утруждая. Они наберут код программы в редакторе своего компилятора и запустят его на выполнение. И если компилятор скомпилирует код успешно и выполнит его, то они ответят, что программа корректная. И даже удивятся: а в чем, собственно говоря, здесь подвох?! Действительно, данная программа успешно скомпилируется многими компиляторами и выполнится без каких-либо проблем, выведя на консоль вторую букву строкового литерала "Hello", то есть букву 'e'. Чтобы убедиться в этом, попробуйте проделать это с помощью компилятора MS VC++ 2010 или GCC 4.7.2 (например, используя он-лайновый компилятор www.ideone.com) Так в чем заключается некорректность программы, если таковая имеет место быть? На вид программа написана синтаксически и семантически корректно: внутри оператора индексирования используется лямбда выражение, возвращающее целочиселннное значение равное 1. И тем не менее программа некорректная, даже не смотря на то, что код успешно компилируется многими компиляторами и предсказуемо выполняется! Этот код не соответствует требованиям стандарта C++ 2011. Проблема в том, что большинство компиляторов или их более ранние версии не полностью соответствуют новому стандарту C++. Код из приведенной программы конфликтует с определением аттрибутов. Аттрибуты синтаксически начинаются с двух следующих друг за другом открывающихся квадратных скобок. И если такая последовательность скобок встречается в коде, то компилятор обязан их рассматривать, как определение аттрибута, даже если в противном случае конструкция синтаксически выгядет корректно в другом контексте, как, например, в приведенном примере программы. Вот что по этому поводу говорится в стандарте C++ в параграфе №6 раздела 7.6.1 Attribute syntax and semantics: 6 Two consecutive left square bracket tokens shall appear only when introducing an attribute-specifier. [ Note: If two consecutive left square brackets appear where an attribute-specifier is not allowed, the program is ill formed even if the brackets match an alternative grammar production. —end note ] Поэтому чтобы сделать программу корректной, нужно лямбда выражение заключить в круглые скобки: [pre2] #include <iostream> int main() { const char *s = "Hello"; std::cout << s[ ( []{ return 1; }() )] << std::endl; return 0; }[/pre2]

Сыроежка: Мало, кто знает, что идентификаторы деклараторов и сами деклараторы можно заключать в круглые скобки. То есть на самом деле круглые скобки многие используют для заключения в них деклараторв, когда имеют дело, например, с указателем на массив или на функцию, как в примере ниже. int ( *a )[10]; int ( *fp )( int, int ); Но при этом теряются, когда встречают следующее объявление: int ( ( f )( int, int ) ); Это объявление совершенно корректно и согласуется с грамматикой языка C++. Вот демонстрационный код использования подобного рода объявления: [pre2] #include <iostream> int ( ( f )( int, int ) ); int main() { std::cout << f( 1, 2 ) << std::endl; return 0; } int ( ( f )( int x, int y ) ) { return x + y; }[/pre2]

Сыроежка: Вот еще один каверзный вопрос на детальное знание C++: Всегда ли спецификаторы типа как, например, int и signed int, или unsigned и unsigned int эквивалентны? То есть всегда ли вместо одного спецификатора (спецификаторов) можно использовать другой (другие)?

Сыроежка: Нередко возникает необходимость инициализировать массив различными наборами значений в зависимости от некоторых условий. Как это можно проще сделать, чтобы не писать длинный перечень предложений с присваиванием каждому отдельному элементу массива его значения в виде, представленном ниже? [pre2] a[0] = vqlue0; a[1] = vqlue1; ... a[n] = vqluen; [/pre2] Порой такое задание ставит в тупик немало программистов. Однако задача просто решается, если вспомнить про класс std::array. В отличии от классических массивов этот класс обладает одним очень замечательным свойством: объекту класса можно присваивать список инициализации, чего нельзя делать для классических массивов. Об этом полезном свойстве класса std::array многие программисты порой забывают. Вот пример использования этого свойства в решении поставленной задачи [pre2] #include <iostream> #include <string> #include <array> int main() { const size_t N = 3; std::array<std::string, N> a; char c; std::cout << "Enter 'm' for male or 'f' for female: "; std::cin >> c; switch ( c ) { case 'm': a = { "Bob", "Tom", "Jack" }; break; case 'f': a = { "Ann", "Jane", "Lisa" }; break; default: a = { "Cat", "Dog", "Cow" }; break; } for ( const std::string &s : a ) std::cout << s << ' '; std::cout << std::endl; return 0; } [/pre2] Инициализация массива в этом примере выглядит достаточно изящно

Сыроежка: В стандарте C++ можно встретить забавные алогизмы. Например, такой: initialization by a trivial copy/move constructor is non-trivial initialization Выглядит как: тривиальное не всегда является тривиальным.

Сыроежка: Оказывается, что не только в тексте стандарта C++ можно встретить забавные алогизмы, но похожие алогизмы можно увидеть и в коде программ, если попытаться прочитать вслух действия, которые выполняются кодом. Например, как вам такое выражение: "Вставить элемент после перед"? Казалось бы, что это всего лишь чьи-то фантазии по интерпретации кода, но на самом деле это дословное описание того, что делает функция insert класса std::forward_list. Рассмотрим задачу: вставить новый элемент в список std::forward_list перед первым элементом списка. Сделать это очень просто, используя метод класса push_front. Например, [pre2] std::forward_list<int> l; l.push_front( 1 ); [/pre2] Но эту же операцию можно сделать с помощью метода insert. На самом деле если быть точным, такого метода в классе std::forward_list нет. Есть метод insert_after. Теперь возникает вопрос, если есть только метод insert_after (вставить после), то как с его помощью вставить новый элемент в список перед самым первым элементом списка? Сделать это просто, если вспомнить про еще один метод этого класса cbefore_begin, который возвращает итератор перед самым первым элементом списка. Итак, если собрать эти два метода вместе, то получится следующее предложение [pre2] l.insert_after( l.cbefore_begin(), 0 ); [/pre2] Нетрудно убедиться, что это предложение эквивалентно вызову метода push_front. Для этого достаточно к этим двум фрагментам кода добавить еще пару строчек с выводом результата на консоль [pre2] for ( int x : l ) std::cout << x << ' '; std::cout << std::endl; [/pre2] Ну, а теперь если вы внимательно посмотрите на предложение [pre2] l.insert_after( l.cbefore_begin(), 0 ); [/pre2] и попробуете произнести вслух используемые в строке слова, входящие в название используемых методов, то вы в итоге получите забавный алогизм: "Вставить элемент после перед"

Сыроежка: Известно ли вам, что если некоторый класс имеет закрытые члены, то вы можете обращаться к ним в функции main..., если объявите функцию main дружественной функцией классу! Мне не приходилось с таким встречаться, но тем не менее следующий код вполне корректный [pre2] #include <iostream> class A { friend int main(); int x; }; int main() { A a; a.x = 10; std::cout << "a.x = " << a.x << std::endl; return 0; } [/pre2] Программа успешно скомпилируется. и на консоль будет выведено [pre2] a.x = 10 [/pre2]

Сыроежка: Как инициализировать массив в C++ в зависимости от условий, было показано выше. А можно ли тоже самое сделать в C? В C нельзя присваивать списки инициализации агрегатам, как это можно делать в C++. Тем не менее достичь аналогичного результата можно и в C, и в этом нам помогут составные литералы. Вот как будет выглядеть соответствующий код в C. [pre2] #include <stdio.h> #define N 3 int main(void) { struct array_t { char name[N][10]; } a; char c; printf( "Enter 'm' for male or 'f' for female: " ); scanf( " %c", &c ); switch ( c ) { case 'm': a = ( struct array_t ){ { "Bob", "Tom", "Jack" } }; break; case 'f': a = ( struct array_t ){ { "Ann", "Jane", "Lisa" } }; break; default: a = ( struct array_t ){ { "Cat", "Dog", "Cow" } }; break; } for ( size_t i = 0; i < N; i++ ) printf( "%s ", a.name ); puts( "" ); return 0; } [/pre2] Этот код очень похож на код аналогичной программы, написанный на C++. Только вместо списков инициализации, используемых в C++-программе, в C-программе используются составные литералы, которые отсутствуют в языке C++.

Сыроежка: Допустим, имеется следующее объявление структуры в программе [pre2] struct A { int i, j, k; }; [/pre2] Являются ли следующие определения объектов a1 и a2 [pre2] A a1; A a2 = A(); [/pre2] эквивалентными? Неопытный программист может посчитать, что определение вида [pre2] A a2 = A(); [/pre2] является вычурным и "исправит" код на [pre2] A a2; [/pre2] В результате "исправленная" программа может повести себя непредсказуемым образом вплоть до аварийного завершения. И причиной этому будет то, что указанные выше два определения объектов не являются эквивалентными. В первом случае, то есть при определения объекта как [pre2] A a1; [/pre2] объект a1 инициализируется по умолчанию. Это означает, что для данной структуры члены данных i, j, k будут иметь неопределенные значения. Во втором случае, то есть при определении объекта как [pre2] A a2 = A(); [/pre2] создается временный объект, который инициализируется по значению. Это означает, что все члены данных объекта, имеющие фундаментальные скалярные типы, будут инициализированы нулем. А затем этот временный объект копируется в объект a2. Точнее сказать он не копируется, а создается на месте объекта a2, то есть вызов либо конструктора копирования либо конструктора перемещения исключается из операции с целью оптимизации, хотя тем не менее они должны быть доступными. То есть второе объявление позволяет инициализировать объект a2 по значению. Вы не можете просто написать [pre2] A a2(); [/pre2] так как это будет не определение объекта, а объявление функции без параметров и имеющий тип возвращаемого значения A. Ниже приведена демонстрационная программа, которая позволяет увидеть различие между этими двумя определениями. [pre2] #include <iostream> struct A { int i, j, k; }; int main() { A a1; std::cout << a1.i << ' ' << a1.j << ' ' << a1.k << std::endl; A a2 = A(); std::cout << a2.i << ' ' << a2.j << ' ' << a2.k << std::endl; } [/pre2] Для второго определения гарантировано, что i, j, k будут равны нулю, тогда как для первого определения эти члены данных объекта могут иметь произвольные значения. Так что если увидите подобное "вычурное" определение объекта вида, [pre2] A a2 = A(); [/pre2] то не пытайтесь его исправить.

Сыроежка: Начинающих программистов, приступивших к изучению C или C++, могут обескуражить следующие операторы ---, +++ или -->. На самом деле в каждой из этих конструкций не один, а два следующих друг за другом оператора такие, как -- -, ++ + и -- >. Но если эти операторы писать слитно, то может получиться такой забавный код, как показано ниже, который повергнет начинающего программиста в смятение. [pre2] #include <stdio.h> int main(void) { int x = 1; int y = 2; int z = 3; z = x --- y +++ z; if ( z --> 1 ) printf( "%d\n", z ); return 0; } [/pre2] Или [pre2] #include <stdio.h> int main(void) { int x = 1; int y = 1; int z = 1; if ( x --- y +++ z --> 0 ) printf( "%d\n", z ); return 0; } [/pre2] Ради смеха попробуйте показать одну из этих программ начинающему программисту и попросите его ответить на вопрос: "Будет ли код компилироваться? Если код будет компилироваться, тогда что будет выведено на консоль?"

Сыроежка: Из переписки на одном форуме: > И да, код должен быть понятен любому дураку. >Не могу согласиться. Зачем это? Зачем дураков до проекта допускать? Конечно слово "дурак" нужно понимать образно. Каждый из нас может оказаться в роле "дурака", когда сталкивается с незнакомым ему проектом, предметной областью или новым инструментальным средством. Что для одного, кто уже имел дело с предметом, может быть очевидным, то у другого может вызывать вопросы, которые будут выглядеть очень наивными. Самый наглядный пример - это вопросы программистов, имеющих ранее дело, например, с Java и пытающихся написать впервые в жизни программу на C++.. Их вопросы порой выглядят дурацкими для опытных C++ программистов. Бытует такое мнение у низкоквалифицированных программистов, что если писать код качественно, то не уложишься в сроки, а если нужно уложиться в сжатые сроки, то вполне закономерно, что код будет плохой. Это глубокое заблуждение, ничего общего не имеющего с действительностью. На самом деле свойство писать качественный код - это просто неотъемлемое качество квалифицированного программиста. Оно не зависит от каких-то там сроков. Это как у пианистов: либо руки "поставлены", либо нет. И именно плохой код не позволяет уложиться в сроки, или же просто происходит откровенный обман, когда представленный код на самом деле не выполняет поставленную задачу, так как содержит многочисленные баги и не приемлем для его дальнейшей эксплуатации. Просто ответственность за код перекладывается на плечи других, кто придет следом на смену нерадивых программистов.

Сыроежка: С самого начала изучения языка программирования C++ мы настолько привыкли использовать выражение std::endl с операторами вывода (operator <<) в стандартные потоки, что даже не задумываемся, что оно означает и как работает с потоками. Например, обычно самая первая программа в большинстве книг для начинающих по C++ выглядит следующим образом: [pre2] #include <iostream> int main() { std::cout << "Hello, World!" << std::endl; } [/pre2] Это настолько вошло в привычку писать предложения вида [pre2] std::cout << std::endl; [/pre2] что когда неожиданно вместо данного предложения программист встречает выражение вида [pre2] std::endl( std::cout ); [/pre2] то оно ставит его в тупик. На самом деле в обоих случаях используется функция std::endl, объявленная как [pre2] template <class charT, class traits> basic_ostream<charT,traits>& endl(basic_ostream<charT,traits>& os); [/pre2] только в первом случае она передается как аргумент оператору operator <<, а во втором случае вызывается непосредственно. Еще большее удивление у программистов порой вызывает тот факт, что можно не указывать префикс с именем пространства имен перед endl, даже не включая в код директиву using namespace std;. То есть следующее предложение будет успешно компилироваться [pre2] endl( std::cout ); [/pre2] Дело в том, что в этом случае в дело вступает зависящий от аргумента поиск неквалифицированных имен (Argument Dependent Lookup). Так как аргумент std::cout объявлен в пространстве имен std, то компилятор будет искать неквалифицированное имя endl в этом же пространстве. Также мало кто знает, что если заключить имя функции в круглые скобки, то зависящий от аргумента поиск (ADL) уже не будет действовать. Поэтому следующая запись [pre2] ( endl )( std::cout ); [/pre2] компилироваться не будет.



полная версия страницы