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

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

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

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

Сыроежка: Часто начинающие программисты получают задание от своих наставников или сами просят предоставить им подобное задание, когда изучают язык программирования C самостоятельно, где нужно использовать указатели вместо индексов с символьными массивами и рекурсию. Мне пришло в голову такое забавное задание, где оба эти требования присутствуют: вывести на консоль в обратном порядке имя файла запускаемой программы. Наверное, это одна из самых коротких рекурсивных функций, гда от польователя не требуется вводить никаких данных. Вот эта программа [pre2] #include <stdio.h> int main( int argc, char *argv[] ) { if ( *argv && **argv ) { char c = *( *argv )++; main( argc, argv ); printf( "%c", c ); } } [/pre2] Хотя эта программа достаточно короткая, но для начинающего программиста потребуется немало умственных усилий, чтобы разобраться, что же программа делает.

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

Сыроежка: Многие программисты придерживаются стиля написания кода, когда выражение, используемое в предложении return, заключается в круглые скобки. Это делает предложение return похожим на другие предложения языка C++, как, например, if, switch и многие другие, у которых согласно правилам грамматики C++ выражения заключаются в круглые скобки. Например, [pre2] size_t max_element( const int a[], size_t n ) { size_t max = 0; for ( size_t i = 1; i < n; i++ ) { if ( a[ max ] < a[ i ] ) max = i; } return ( max ); } [/pre2] В этой функции нахождения индекса максимального элемента массива переменная max, используемая в качестве возвращаемого значения в предложении return, заключена в круглые скобки. Я сам был поклонником такого стиля написания предложения return. Круглые скобки позволяют визуально более отчетливо выделить выражение, используемое в return, что повышает читабельность программы. Но теперь, с принятием стандарта C++ 2014, похоже, от такого стиля придется отказаться. Почему? Потому что имеется ситуация, когда выражения variable_name и ( variable_name ) имеют различный семантический смысл! Такая ситуация возникает, например, когда вы используете конструкцию decltype( auto ) в качестве спецификатора типа возвращаемого значения функции. Оказываются, что следующие два определения функции [pre2] decltype( auto ) f() { Int i; i.x = 10; return ( i ); } [/pre2] и [pre2] decltype( auto ) f() { Int i; i.x = 10; return i; } [/pre2] (где Int - некоторый тип, определение которого я дам ниже, и который в данном случае носит лишь демонстрационный характер) различны и определяют разные функции! Дело в том, что первая функция возвращает ссылку на объект i, в то время как вторая функция возвращает временный объект, являющийся копией объекта i. То есть у первой функции типом возвращаемого значения является Int &, тогда как у второй функции - Int. Чтобы в этом убедиться, запустите следующую программу, в которой я определил тип Int таким образом, чтобы было легче увидеть различие между определениями функций. Если вы запустите следующую программу [pre2] #include <iostream> struct Int { ~Int() { x = -1; } int x = 0; }; decltype( auto ) f() { Int i; i.x = 10; return ( i ); } int main() { std::cout << f().x << std::endl; } [/pre2] то компилятор либо выдаст сообщение об ошибке, что функция f возвращает ссылку на локальный объект i, либо ее поведение будет неопределенным, то есть либо на консоль будет выдано значение -1, которое устанавливает деструктор класса Int для члена данных x, когда локальная переменная i будет разрушена при завершении работы функции f, либо некоторое случайное значение. То есть в любом случае вы не увидите на консоле вывода числа 10, как вы могли бы предполагать. Ежели вы удалите круглые скобки вокруг имени i в предложении return функции f, то вы увидите ожидаемое число 10, выведенное на консоль. [pre2] #include <iostream> struct Int { ~Int() { x = -1; } int x = 0; }; decltype( auto ) f() { Int i; i.x = 10; return i; } int main() { std::cout << f().x << std::endl; } [/pre2] Наверное, это самое "темное" место в языке C++, которое полностью расходится с естественным и интуитивным представлением об использовании круглых скобок в выражениях.


Сыроежка: Как говорится, Сократ - мне друг, но истина дороже! Я это к тому, что даже признанные авторитетные авторы книг по C++, порой допускают оплошности, оговорки или не совсем точные однозначные формулировки. На мой взгляд не следует их попрекать за это. Написать книгу - это тяжелый труд. Особенно тяжело писать книги по программированию, так как к каждому твоему слову могут придраться различные настоящие или считающие сами себя таковыми гуру по программированию. Тем не менее я хотел бы указать на одну такую оплошность в книге Скотта Майерса "Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11", которую настоятельно рекомендуют всем программистам, имеющим дело с языком программирования C++, прочитать. Я потому решил указать на эту оплошность, что многие программисты о возможности, которая по забывчивости не упомянута в ниже-цитируемой фразе автором книги, не подозревают или, порой, забывают. В разделе, озаглавленном как "Item 10: Prefer scoped enums to unscoped enums." и посвященном перечислениям, Скотт Майерс пишет: "The names of such enumerators belong to the scope containing the enum, and that means that nothing else in that scope may have the same name:" И далее он приводит такой пример [pre2] enum Color { black, white, red }; // black, white, red are // in same scope as Color auto white = false; // error! white already // declared in this scope [/pre2] На самом деле это его высказывание не совсем точное! Вы можете объявлять типы, классы и перечисления, в той же самой области объявления и с теми же самыми именами, что и перечислители в ранее (или позже) объявленном перечислении. Просто имена типов будут скрыты перечислителями. Тем не менее вы сможете обращаться к именам этих типов, если будете использовать их уточненные имена. Ниже приведена программа, которая демонстрирует верность мною сказанного. В этой программе объявляются структуры с такими же именами, как и имена перечислителей в перечислении Color, объявленном в той же самой области объявления. [pre2] #include <iostream> enum Сolor { Red, Green, Blue }; struct Red { Red() { std::cout << "Red" << std::endl; } }; struct Green { Green() { std::cout << "Green" << std::endl; } }; struct Blue { Blue() { std::cout << "Blue" << std::endl; } }; int main() { struct Red red; } [/pre2] Программа будет успешно компилироваться, в чем вы можете убедиться сами.

Сыроежка: А знаете ли вы, какие самые длинные стандартные имена имеются в C++? Самыми длинными именами являются следующие определения имен в классе struct allocator_traits: [pre2] propagate_on_container_copy_assignment propagate_on_container_move_assignment [/pre2] Каждое из этих имен содержит 38 знаков!

Сыроежка: В C++ 2014 для большинства типов из библиотеки <type_traits> ввели псевдонимы, или, используя английское слово, алиасы типов. Это очень удобно и упрощает написание шаблонных параметров. Например, для структуры std::enable_if ввели алиас типа std::enable_if_t. Он определяется следующим образом [pre2] template <bool b, class T = void> using enable_if_t = typename enable_if<b,T>::type; [/pre2] Внешнее отличие структуры и введенного алиаса состоит в суффиксе _t, добавленному к имени структуры. Однако эти алиасы могут быть источниками трудно-находимых ошибок! Допустим вы решили написать шаблонную функцию, которой для ее параметра разрешается использовать только аргументы целочисленных типов. Ваша функция может выглядеть следующим образом, как показано в этой демонстрационной программе. [pre2] #include <iostream> #include <type_traits> template <typename T, typename = std::enable_if_t<std::is_integral<std::decay_t<T>>::value>> void f( T t ) { std::cout << t << std::endl; } int main() { f( 'A' ); // f( "A" ); ошибка компиляции } [/pre2] В этой программе вызов функции с символьным литералом будет успешным, так как тип char в C++ (в отличии, например, от C#) относится к целочисленным типам. Вызов же функции со строковым литералом, если раскомментировать соответствующее предложение в программе, не будет компилироваться, так как строковые литералы имеют типы, которые не являются целочисленными типами. Именно в этом и состоял ваш замысел. Все хорошо, пока вы случайно не допустите опечатку и вместо std::enable_if_t в списке шаблонных параметров функции по ошибке напишите std::enable_if, то есть тогда, когда вы забудете приписать вышеназванный суффикс _t к имени структуры. [pre2] template <typename T, typename = std::enable_if<std::is_integral<std::decay_t<T>>::value>> void f( T t ) { std::cout << t << std::endl; } [/pre2] Компилятор молча скомпилирует вашу программу, и, если вы укажете вызов функции со строковым литералом, то и этот вызов успешно скомпилируется и выполнится! Такие ошибки, когда в результате опечатки получается корректная с точки зрения языка C++ программа, очень трудно находить, особенно если проект очень большой.

Сыроежка: Как известно из Стандарта языка C++ размеры целочисленных типов long int и long long int удовлетворяют следующему соотношению: sizeof( long int ) <= sizeof( long long int ) Спросите любого программиста, чему равен размер sizeof( long long int ), и он вам незамедлительно ответит, что он равен 8. То есть на современных платформах тип long long int представляет собой 64-битовое число. С другой стороны, если вы хотите быть уверенными, что вы имеете дело с целыми числами нужной разрядности, независящей от платформы, то вы можете предпочесть иметь дело с типам такими, как std::int64_t и std::int32_t, определенными в заголовке <cstdint>. И тут вас могут поджидать сюрпризы, от которых, как вам казалось, вы себя застраховали, перейдя на использование типов с явно указанной разрядностью! Эти сюрпризы могут ввести вас в шоковое состояние! Действительно, допустим у вас есть перегруженные функции. Одна имеет параметр с типом std::int64_t и другая - с типом std::int32_t. И вы решили вызвать именно первую функцию с целочисленным литералом в качестве аргумента. Допустим значение этого литерала равно 0. Как вы ее вызовете? Я просто убежден, что вы ее вызовете, указав целочисленный литерал следующего вида: 0ll не так ли? То есть указав литерал типа long long int, так как вы хорошо знаете, что этот тип имеет 64-битовую разрядность. И в результате компилятор сообщит вам об ошибке компиляции, так как вызов функции будет неоднозначным! Не верите? Тогда запустите следующую демонстрационную программу, используя либо компилятор gcc 5.1.0 либо clang 3.6.0 [pre2] #include <iostream> #include <cstdint> void f( std::int32_t ) { std::cout << "void f( std::int32_t )" << std::endl; } void f( std::int64_t ) { std::cout << "void f( std::int64_t )" << std::endl; } int main() { f( 0ll ); } [/pre2] Как же так, удивитесь вы, в чем проблема?! А проблема в том, что эти компиляторы определяют тип std::int64_t не как алиас для типа long long int, как вы очевидно предполагали, а как алиас для типа long int! Чтобы в этом убедиться, достаточно запустить следующую тестовую программу: [pre2] #include <iostream> #include <iomanip> #include <cstdint> #include <type_traits> int main() { std::cout << sizeof( long long ) << std::endl; std::cout << sizeof( long ) << std::endl; std::cout << std::boolalpha << std::is_same<std::int64_t, long long>::value << std::endl; std::cout << std::boolalpha << std::is_same<std::int64_t, long>::value << std::endl; } [/pre2] Результатом работы этой программы будет следующий вывод на консоль: [pre2] 8 8 false true [/pre2] Как видно из вывода, тип std::int64_t не является тем же самым, что и тип long long int. Он является алиасом для типа long int. Теперь понятно, почему первая программа не компилировалась. Вы задали аргумент функции, имеющий тип long long int, в то время как функции имеют параметры, типы которых соответственно представляют собой алиасы для типов int и long int. Поэтому компилятор не знает, какую функцию вызывать.

Сыроежка: Пока мало кто из программистов знает, что в новом стандарте C++ теперь разрешается в целочисленных литералах вставлять одинарный апостроф. Это бывает удобно, когда целочисленный литерал довольно большой и трудно подсчитать его разрядность Так что не удивляйтесь, когда встретите такое написание целочисленного литерала. Данная демонстрационная программа будет успешно компилироваться [pre2] #include <iostream> int main() { int x = 1'000'000; std::cout << x << std::endl; } [/pre2] и выведет на консоль [pre2] 1000000 [/pre2]

Сыроежка: Аргументация из сообщения на форуме программистов: Я не считаю, что надо начинать в 16 лет учить язык СИ, когда еще не всю алгебру знаешь. Я молчу уже про высшую математику. Мне 25 лет и я сам еще не знаю СИ, вот и не советую учить СИ. Трудно удержаться и не добавить: "А что уж говорить про квантовую механику?! Если не знаешь квантовую механику, то о каком C может, вообще, идти речь?!" Кстати сказать, если кто-то сам и в 50 лет не знает C, следует ли из этого, что другим не надо приступать к изучению C в 16 лет?

Сыроежка: Тему в этом сообщении можно назвать метаморфозы инициализации В настоящее время существует пять форм инициализации переменных: [pre2] T x = expression; T x = ( expression ); T x ( expression ); T x = { expression }; T x { expression };. [/pre2] Уже беглый взгляд на этот список вызывает вопросы. Ну, например, чем отличается инициализация вида [pre2] T x = expression; [/pre2] от инициализации вида [pre2] T x = ( expression ); [/pre2] И в первом случае и во втором случае используется выражение. Если заключить выражение в круглые скобки, разве оно перестанет быть выражением? Давайте последовательно разберемся в этом запутанном лабиринте инициализации переменных. Для примеров кода, используемых далее, предположим, что в глобальном пространстве имен имеются следующие объявления: [pre2] int x; void f( int x ) { ::x = x; } int g() { int x = 10; return x; } long h() { long x = 10; return x; } [/pre2] В main вы могли бы, например, объявить переменную следующим образом [pre2] int main() { int x ( g() ); } [/pre2] И ваш код будет успешно компилироваться. Но по случайности вы можете допустить опечатку - никто не застрахован от ошибок - и написать: [pre2] int main() { int x; ( g() ); ^^ } [/pre2] и...ваш код успешно скомпилируется! Однако если бы вы изначально использовали форму инициализации со знаком присваивания [pre2] int main() { int x = ( g() ); } [/pre2] то при сделанной опечатке [pre2] int main() { int x; = ( g() ); ^^ } [/pre2] ваш код уже не будет компилироваться! Теперь предположим, что прежде, чем инициализировать переменную x значением, возвращаемом функцией g(), вы решили назначить глобальной переменной ::x новое значение. Вы могли бы записать [pre2] int main() { int x ( f( 10 ), g() ); } [/pre2] Но, увы, этот код не будет компилироваться. А что если вставить знак присваивания? [pre2] int main() { int x = ( f( 10 ), g() ); } [/pre2] На этот раз код успешно компилируется! Но если вы попробуете заменить в обоих примерах круглые скобки на фигурные, то ни в первом случае [pre2] int main() { int x { f( 10 ), g() }; } [/pre2] ни во втором случае со знаком присваивания [pre2] int main() { int x = { f( 10 ), g() }; } [/pre2] ваш код компилироваться не будет! Теперь допустим, что вы решили инициализировать локальную переменную x значением, возвращаемом функцией h(). Вы можете написать [pre2] int main() { int x ( h() ); } [/pre2] и ваша программа успешно скомпилируется! А что если заменить круглые скобки на фигурные? Сказано - сделано! [pre2] int main() { int x { h() }; } [/pre2] Оп-па! Компилятор выдает сообщение об ошибке: error: non-constant-expression cannot be narrowed from type 'long' to 'int' in initializer list Теперь давайте попробуем использовать спецификатор типа auto и посмотрим, что из этого получится. Итак, начнем. Запишем инициализацию локальной переменной x с использованием спецификатора типа auto и с использованием фигурных скобок. В одном случае мы будем использовать знак присваивания, а в другом нет. [pre2] int main() { auto x { 10 }; x = 20; } [/pre2] и [pre2] int main() { auto x = { 10 }; x = 20; } [/pre2] И результат компиляции будет непредсказуемым! Так онлайновый компилятор Майкрософт успешно скомпилирует первую программу, но выдаст сообщение об ошибке для второй программы! А такие компиляторы, как, например, Clang, не будут компилировать обе программы! Вы хотите знать, в чем проблема? Проблема в том, ч то в соответствии с текущим стандартом C++, в первом случае выводимый тип переменной x будет int, а во втором случае - std::initializer_list<int>. Тогда вы можете спросить: "А почему некоторые компиляторы не компилирует обе программы?" Дело в том, что Комитеты по стандартизации C++ хотят унифицировать обе формы инициализации, и некоторые компиляторы уже компилируют программы в соответствии с этим предложением. Да, чуть не забыл, а что по поводу другого спецификатора - decltype? Тут тоже свои причуды. Если вы напишите, например, следующие объявления [pre2] int main() { int a[] = { 1, 2 }; decltype( auto ) b = a; } [/pre2] то программа не будет компилироваться! Но если вы заключите в круглые скобки переменную a в правой части от знака присваивания [pre2] int main() { int a[] = { 1, 2 }; decltype( auto ) b = ( a ); } [/pre2] то программа успешно скомпилируется! Так как язык C++ относится к классу объектно-ориентированных языков, то было бы несправедливо оставить без внимания инициализацию определенных пользователем типов. Итак, допустим, что вы определили простой класс: [pre2] struct Int { Int( int x = 0 ) : x( x ) {} int x; }; int main() { Int x = { 10 }; } [/pre2] Данный код скомпилируется успешно. Но вы, обогатив свои знания по C++, узнав, что имеется спецификатор функций explicit, решили его использовать в определении конструктора: [pre2] struct Int { explicit Int( int x = 0 ) : x( x ) {} int x; }; int main() { Int x = { 10 }; } [/pre2] И ваш код перестал компилироваться! Вполне возможно, что вы так и задумывали. Но другой хитрый программист решил вас обмануть и убрал знак присваивания в объявлении переменной x: [pre2] struct Int { explicit Int( int x = 0 ) : x( x ) {} int x; }; int main() { Int x { 10 }; } [/pre2] Теперь код успешно компилируется! Как известно, в соответствии с новым стандартом C++ можно инициализировать члены данных класса внутри определения самого класса. Следующий код, использующий эту предоставляемую возможность, будет успешно компилироваться: [pre2] struct Int { explicit Int() {} int x = 10; }; int main() { Int x; } [/pre2] Но если вы уберете знак присваивания и будете использовать круглые скобки, как показано ниже [pre2] struct Int { explicit Int() {} int x( 10 ); }; int main() { Int x; } [/pre2] То программа перестанет компилироваться! Но если круглые скобки заменить фигурными скобками, то программа снова будет компилироваться! [pre2] struct Int { explicit Int() {} int x{ 10 }; }; int main() { Int x; } [/pre2] Как видите, не всегда широкий выбор средств облегчает жизнь программистов!

Сыроежка: По телевизору часто для детей (а также для их родителей) показывают юмористическую передачу "Ералаш", в которой обыгрывают забавные эпизоды из жизни школьников. Следующее интервью . которое проводилось с одним начинающим программистом, и которое он описал в этом своем сообщении на форуме для начинающих программистов, вполне того заслуживает, чтобы было включено в один из выпусков "Ералаша"! Этого начинающего программиста интервьюировало несколько, как я понимаю, "мастистых" программистов. Правда, прочитав эти личные впечатления самого интервьюируемого, у меня возник вопрос: кто на самом деле из этой компании на интервью ,больше подходит на роль начинающего программиста. Автору сообщения было предложено написать функцию, которая реверсирует строку. Я не знаю, что имелось в виду под строкой, но автор решил, что надо реверсировать объект типа std::string, и он написал следующий код: [pre2] string f(string &s) { string temp; copy(s.rbegin(), s.rend(), temp.begin()); return temp; } [/pre2] Теперь передадим слово самому автору этой истории, поведанной на форуме, ссылку на которую я привел выше: но, только после собеседования вспомнил, что ошибся, правильно так: [pre2] string f(string &s) { string temp; copy(s.rbegin(), s.rend(), back_inserter(temp)); return temp; } [/pre2] хотя, они по-моему ошибки не заметили - переглянулись, покивали головами. Это же насколько надо быть "мастистым" программистом, чтобы, не моргнув и глазом, "проглотить" код функции из первой ее реализации? И кому, вообще, на фирмах доверяют проводить интервью? Отдадим должное автору этой истории. Ему не давало покоя чувство того, что он написал что-то некорректно в реализации функции. Это хорошее качество программиста: не успокаиваться, пока не будет ощущения, что все сделал правильно, и написанный тобою код тебя удовлетворяет. Но, очевидно, этого нельзя сказать о тех, кто проводил интервью. Ни один профессиональный программист не должен оставить этот код без замечаний. И даже понимание того, что имеешь дело с начинающим программистом, который, естественно, может допускать серьезные огрехи, не должно удерживать от того, чтобы не указать начинающему программисту на его ошибки даже на интервью. Тем самым вы оказываете помощь этому начинающему программисту. на мой взгляд это вообще вопрос этики поведения программиста. На интервью приходят не только, чтобы продемонстрировать свои знания, но и чтобы обогатить друг друга этими знаниями, что-то вынести для себя полезное из интервью. На самом деле обе указанные выше реализации функции некорректны. Для первой функции достаточно указать, что нельзя копировать данные в пустую строку, используя итератор, полученный с помощью begin(). это приведет к неопределенному поведению программы. Во-второй реализации эта ошибка исправлена, и вместо итератора, полученного с помощью begin(), используется адаптер итераторов std::back_insert_iterator. Но тем не менее функцию нельзя считать корректной. Ее параметр объявлен как неконстантная ссылка на исходную строку. А это нарушает контракт между кодом, который использует функцию, и самой функцией о том, что функция гарантирует, что исходная строка не может быть изменена. После вызова этой функции вы уже не можете быть уверены, что исходная строка осталась прежней. Более того, вы не сможете вызывать эту функцию для константных объектов (например, для строковых литералов или символьных массивов, для которых будет создан временный объект типа std::string). Поэтому правильно было бы объявить параметр как const std::string &s. И надо указывать квалифицированное имя типа. Иначе функцию нельзя будет использовать в программе, где нет ни директивы using, ни объявления using для класса std::string. Кроме того, внутри самой функции при выполнении алгоритма std::copy постоянно происходит реаллокация памяти, используемой объектом temp, так как изначально память для объекта не была зарезервирована. Поэтому по крайней мере следовало вставить предложение перед вызовом алгоритма [pre2]temp.reserve( s.size() );[/pre2] И нет никакой надобности отдельно объявлять локальный объект temp, а затем в него копировать исходную строку. Функция могла бы выглядеть следующим образом: [pre2] std::string copy_reversed( const std::string &s ) { return { s.rbegin(), s.rend() }; } [/pre2] Обратите внимание на имя функции. Дело в том, что когда перед вами встает такая задача, то она может рассматриваться двояким образом: либо вы должны написать функцию, которая реверсирует саму исходную строку, то есть делает операцию "на месте", либо создает новую строку из исходной строки, как это пытался сделать автор данной забавной истории. Поэтому имя функции должно отражать тип операции, которую она выполняет. Вполне возможно, что имя, которое я подобрал для этой функции, не совсем удачное (в стандарте C++ аналогичная функция называется std::reverse_copy), но, тем не менее, оно позволяет отличить эту функции от функции, которая бы реверсировала строку "на месте" и называлась бы reverse. По крайней мере это имя позволяет заключить программисту, что исходная строка не изменяется, а вы будете иметь дело с копией строки. Если вы хотите реверсировать саму исходную строку, то функция может выглядеть следующим образом: [pre2] std::string & reverse( std::string &s ) { std::reverse( s.begin(), s.end() ); return s; } [/pre2] Ниже приведена демонстрационная программа, которая показывает работу обеих функций. [pre2] #include <iostream> #include <string> #include <algorithm> std::string & reverse( std::string &s ) { std::reverse( s.begin(), s.end() ); return s; } std::string copy_reversed( const std::string &s ) { return { s.rbegin(), s.rend() }; } int main() { std::string s( "Hello, World!" ); std::cout << "\"" << s << "\"" << std::endl; std::cout << "\"" << reverse( s ) << "\"" << std::endl; std::cout << "\"" << copy_reversed( s ) << "\"" << std::endl; std::cout << "\"" << s << "\"" << std::endl; } [/pre2] Ее вывод на консоль будет следующим: [pre2] "Hello, World!" "!dlroW ,olleH" "Hello, World!" "!dlroW ,olleH" [/pre2] Я бы согласился делать на интервью различные задания при условии, что и вторая сторона будет также обязана делать мои задания. И если мои задания не будут выполнены в соответствии с моим заключением, то тех, кто проводил интервью, уволят!

Сыроежка: Как известно, в стандарте C++ 2011 появился литерал указателя nullptr. Казалось бы, этот литерал должен облегчить жизнь программистам. Действительно, ранее, когда этот литерал отсутствовал, возникала следующая проблема при вызове перегруженной функции. Допустим вы имеете две перегруженные функции [pre2] void f( int * ) { std::cout << "f( int * )" << std::endl; } void f( int ) { std::cout << "f( int )" << std::endl; } [/pre2] Тогда если вы зададите в качестве аргумента константу NULL, то к вашему разочарованию будет вызвана функция [pre2]void f( int ) { std::cout << "f( int )" << std::endl; }[/pre2] а не функция [pre2]void f( int * ) { std::cout << "f( int * )" << std::endl; }[/pre2] как вам того бы, очевидно, хотелось. Вот простая демонстрационная программа [pre2] #include <iostream> void f( int * ) { std::cout << "f( int * )" << std::endl; } void f( int ) { std::cout << "f( int )" << std::endl; } int main() { f( NULL ); } [/pre2] На консоль будет выведено [pre2]f( int )[/pre2] Если же теперь благодаря стандарту C++ 2011 использовать литерал nullptr, [pre2] #include <iostream> void f( int * ) { std::cout << "f( int * )" << std::endl; } void f( int ) { std::cout << "f( int )" << std::endl; } int main() { f( nullptr ); } [/pre2] то получится желаемый результат [pre2]f( int * )[/pre2] Проблема разрешилась...Разрешилась ли? На самом деле одна проблема заменилась на другую проблему! Рассмотрите ту же самую программу, но уже с шаблонными функциями [pre2] #include <iostream> template <class T> void f( T ) { std::cout << "f( T )" << std::endl; } template <class T> void f( T * ) { std::cout << "f( T * )" << std::endl; } int main() { int *p = nullptr; f( p ); } [/pre2] Сначала все хорошо. Программа работает так, как предполагалось. Вывод на консоль [pre2]f( T * ) [/pre2] А теперь давайте заменим вызов f( p ); на, казалось бы, эквивалентный ему вызов f( nullptr ); [pre2] #include <iostream> template <class T> void f( T ) { std::cout << "f( T )" << std::endl; } template <class T> void f( T * ) { std::cout << "f( T * )" << std::endl; } int main() { f( nullptr ); } [/pre2] И что получаем? На консоль выводится сообщение [pre2]f( T )[/pre2] Это совсем не то, что мы ожидали! Увы, литерал nullptr имеет тип std::nullptr_t, и для него выбирается шаблонная функция с параметром T. Итак, новое - это хорошо забытое старое? Нас просто отвлекли от одной проблемы, подсунув другую проблему.

Сыроежка: Из отклика на объявление о вакансии программиста со знанием C++ на одном из форумов программистов: Maria_iss, без BOOST и STL стоит ли отправлять резюме (т.е. одну книжку по STL видел)? Я думаю, что учитывая положительное качество кандидата - его честность - и то обстоятельство, что он уже видел одну книжку по STL не важно где, в магазине или в интернет, его несомненно надо брать на данную вакансию. У него большой потенциал! Может оказаться так, что он очень скоро увидет еще одну книжку по STL, если очень постарается, и тогда он заслуженно может претендовать на должность руководителя группы! А теперь попробуйте догадаться о реакции HR-менеджера на данный отклик. Если хорошо знаете С++ - да Трудно сказать, что понимает этот HR-менеджер под термином "хорошо знать C++". Могу лишь предположить, что он имеет в виду, что кандидат также видел одну книжку по C++.

Сыроежка: Ранее в C++ 2003 программисты перегружали функции-члены классов для константных и не константных объектов следующим образом [pre2] #include <iostream> struct A { void f() { std::cout << "A::f()" << std::endl; } void f() const { std::cout << "A::f() const" << std::endl; } }; int main() { A a1; const A a2; a1.f(); a2.f(); A().f(); return 0; } [/pre2] Вывод данной демонстрационной программы выглядит следующим образом: [pre2] A::f() A::f() const A::f() [/pre2] Как видно из вывода в предложениях [pre2] a1.f(); [/pre2] и [pre2] A().f(); [/pre2] вызывается одна и та же перегруженная функция-член класса [pre2] void f() { std::cout << "A::f()" << std::endl; } [/pre2] А что если у вас имеется интерес также в объявлении еще одной перегруженной функции-члена класса, чтобы была возможность различать эти два вызова? Это легко сделать используя новый синтаксис объявления функций-членов класса, введенный в C++ 2011. Ниже показано, как легко это сделать, используя этот новый формат. [pre2] #include <iostream> struct A { void f() & { std::cout << "A::f() &" << std::endl; } void f() const & { std::cout << "A::f() const &" << std::endl; } void f() && { std::cout << "A::f() &&" << std::endl; } void f() const && { std::cout << "A::f() const &&" << std::endl; } }; int main() { A a1; const A a2; a1.f(); a2.f(); A().f(); const_cast<const A &&>( A() ).f(); return 0; } [/pre2] Вывод этой программы следующий [pre2] A::f() & A::f() const & A::f() && A::f() const && [/pre2]

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

Сыроежка: Вот еще один пример противоречивости фраз в документациях по информационным технологиям. В спецификации Java 11 по языку имеется следующая фраза (14.7 Labeled Statements): If the statement is labeled by an Identifier and the contained Statement completes abruptly because of a break with the same Identifier, then the labeled statement completes normally. То есть, грубо говоря, если предложение с меткой прерывается не естественным (не нормальным) способом, то оно завершается нормально.

Сыроежка: Переводчик google порой забавно переводит предложения с английского на русский, если в предложении встречаются термины из программирования. Так следующее простое предложение This if statement is redundant. переводчик google переводит как Это если заявление является избыточным.

Сыроежка: Самое длинное if предложение, когда-либо виденное мною, я встретил в вопросе начинающего программиста на Stackoverflow My code is supposed to reject inputs that do not contain all letters of the alphabet. It rejects everything. (C programming). Так как вопрос может быть удален на Stackoverflow, то привожу это if предложение из вопроса здесь, чтобы каждый мог им полюбоваться [pre2] if (!(strchr(argv[1], ('A' | 'a') & ('B' | 'b') & ('C' | 'c') & ('D' | 'd') & ('E' | 'e') & ('F' | 'f') & ('G' | 'g') & ('H' | 'h') & ('I' | 'i') & ('J' | 'j') & ('K' | 'k') & ('L' | 'l') & ('M' | 'm') & ('N' | 'n') & ('O' | 'o') & ('P' | 'p') & ('Q' | 'q') & ('R' | 'r') & ('S' | 's') & ('T' | 't') & ('U' | 'u') & ('V' | 'v') & ('W' | 'w') & ('X' | 'x') & ('Y' | 'y') & ('Z' | 'z')))) [/pre2] Изобретательности начинающих программистов нет границ!



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