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

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

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

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

Сыроежка: Еще одно забавное высказывание одного начинающего программиста: so i have been wanting to make a OS but can anyone give some help by telling me were to start and any other info that may help? Написано следующее: Мне захотелось создать операционную систему, поэтому не мог бы кто-нибудь сказать, с чего начать, а также предоставить другую информацию, которая может быть полезной. Это только по молодости можно строить такие наполеоновские планы тем более. не имея опыта программирования. Дополнение Похоже, это непреодолимая и бессмертная тема в программировании, как тема любви в художественной литературе, касательно начинающих программистов, которая не зависит ни от национальной, ни от территориальной принадлежности. Это, можно сказать, болезнь "переходного возраста" программистов, которой начинающие программисты должны переболеть, как многие из нас переболели в детстве "ветрянкой".. На днях опять вычитал на одном, уже российском форуме такое сообщение начинающего программиста (главное достоинство этого сообщения состоит в том, что на этот раз мне не требуется переводить его с иностранного языка на русский ): Хочу написать ос на c++ но не знаю как помогите и подскажите с чего начать. А ещё лучше если кинете мне статью или видео-урок по написанию ос на c++ Вы не находите, что это сообщение является почти идентичной копией первого сообщения на английском языке, учитывая то, что первое сообщение также было написано в разделе форума, посвященном языку C++?

Сыроежка: Вот пример плодотворной дискуссии по вопросам программирования. Я лишь убрал ники участников дискуссии: потому что это был Мне не понятен ни ход твоих мыслей, ни смысл сообщения в 4# посту. Поскольку, автор сабжа так же, по видимому тебя не вразумел, то есть предложение - получше формулировать свои мысли. Если конечно, ты разговариваешь не сам с собой, и тебе хочется, что бы тебя могли понять другие. Мне не понятен ни ход твоих мыслей, ни смысл сообщения в 4# посту. это нормально, я точно также не понимаю, что пишут другие.

PSP : Сыроежка пишет: Мне не понятен ни ход твоих мыслей, ни смысл сообщения в 4# посту. это нормально, я точно также не понимаю, что пишут другие. Супер!!!


Сыроежка: Если ваш труд оценивают по количеству набранных знаков, то вы можете включить в свой арсенал средств по увеличению объема кода следующий прием, используемый при объявлении переменных: [pre2]const unsigned const long const long const int x = 10;[/pre2] На первый взгляд эта конструкция кажется ошибочной, но на самом деле это есть объявление [pre2]const unsigned long long x = 10;[/pre2] Компилятор просто отбросит лишние квалификаторы const.

Сыроежка: Часто начинающие изучать С++ задают такой вопрос: чем отличается ++count от count++. Более опытные программисты им быстро разъясняют, чем прединкриметная операция отличается от постинкриментной. Естественно акцент делается на то, какой результат возвращают эти два выражения.. Но при этом обычно забывается еще один очень важный аспект, определяющий различие этих операций. И когда указываешь на него в виде примера записи выражения, то это порой удивляет даже опытных программистов. Так что это за аспект, о котором забывают, и как его более наглядно продемонстрировать? Все просто: разница между ++count и count++ состоит в том, что в первом случае вы можете указать любое четное число плюсиков, а во втором случае вы можете указать всего лишь только два плюсика! ++++++++++count; и только count++; Это справедливо для C++. Однако в C первое предложение выражения не будет компилироваться. В C вы можете использовать только два плюсика. ++count;

Сыроежка: А знаете ли вы, что в самом большом целочисленном типе С/С++ unsigned long long может разместиться ровно 20 факториалов? 0: 1 1: 1 2: 2 3: 6 4: 24 5: 120 6: 720 7: 5040 8: 40320 9: 362880 10: 3628800 11: 39916800 12: 479001600 13: 6227020800 14: 87178291200 15: 1307674368000 16: 20922789888000 17: 355687428096000 18: 6402373705728000 19: 121645100408832000 20: 2432902008176640000 Максимальное значение самого типа unsigned long long равно 18446744073709551615. Вот как соотносятся максимально вмещаемый факториал в объект данного типа и максимальное значение объекта данного типа: 2432902008176640000 18446744073709551615

Сыроежка: А для типа unsigned int можно вычислить лишь 12 факториалов. Вот они: 0: 1 1: 1 2: 2 3: 6 4: 24 5: 120 6: 720 7: 5040 8: 40320 9: 362880 10: 3628800 11: 39916800 12: 479001600 При этом максимальное значение объекта типа unsigned int равно 4294967295. Я это потому написал здесь, что часто начинающие программисты об этом совершенно не задумываются, когда используют вычисления факториалов. Вот пример размышления начинающего программиста, взятый с форума www.cplusplus.com Его задание выглядет так: Write a program that computes the value of e-x by using the formula: e-x= 1- x/1! + x2/2! - x3/3! + … То есть требуется вычислить ряд, в знаменателе каждого члена которого используется факториал. Далее этот навичок пишет цикл и рассуждает, сколько итераций должно быть в цикле, приходя к выводу, что самое подходящее число для итераций - это 30! Причем для хранения факториалов он использует даже тип не unsigned int, а просто int! int factorial=1; for(int i=1;i<=30;i++)//I don't know which number to chose because for high values when the compiler compiles i get values which say IDK..i thought 30 was the best choice..30! for instance is high enough)

Сыроежка: Интересно отметить, что оказывается количество факториалов, которые можно разместить в объекте типа unsigned int в точности совпадает с количеством факториалов, которые можно разместить в объекте типа int, то есть 12 факториалов. Вот значения факториалов для объекта типа int: 0: 1 1: 1 2: 2 3: 6 4: 24 5: 120 6: 720 7: 5040 8: 40320 9: 362880 10: 3628800 11: 39916800 12: 479001600 Максимальное значение объекта типа int равно 2147483647. Кстати сказать, то же самое справедливо и для типа long long, то есть количество значений факториалов, которые можно разместить в объекте этого типа совпадает с количеством значений факториалов, которые можно разместить в объекте типа unsigned long long, - ровно 20 факториалов. Я оговорился, сказав, что может бть всего лишь 20 и 12 значений факториалов для объектов соответственно типов unsigned long long/long long и unsigned int/int. На самом деле число факториалов на 1 больше, так как факториалы вычисляются, начиная с 0. То есть число фаториалов для этих типов 21 и 13. 20 и 12 - это максимальные значения неотрицательных чисел, факториалы которых могут уместиться соответственно в объектах указанных типов. Те, кому интересно посмотреть значения факториалов для больших значений чисел, могут использовать следующую программу, написанную мною на C#. Она выдает значения факториалов для чисел от 1 до 50. Вам достаточно будет изменить условие цикла в статическом методе Factorial, если вы хотите подсчитать факториал для больших значений. Эта программа использует класс BigInteger, определенный в пространстве имен System.Numerics, Чтобы его можно было бы использовать, вам надо добавить в проект ссылку на System.Numerics.dll. Вот текст программы: [pre2] using System; using System.Numerics; using System.Collections.Generic; namespace Factorial { class Program { static IEnumerable<string> Factorial() { uint i = 1; BigInteger value = 1; do { value *= i; yield return string.Format( "{0,4}: {1}", i, value ); } while ( i++ != 50 ); } static void Main(string[] args) { const int LINES_PER_SCREEN = 22; int i = 0; foreach (string next_value in Factorial()) { Console.WriteLine(next_value); if ( ( i = ++i % LINES_PER_SCREEN) == 0 ) { Console.Write("\nContinue (Enter - continue)? "); var c = Console.ReadKey(); if (c.Key != ConsoleKey.Enter) break; Console.WriteLine( "\n" ); } } Console.WriteLine(); } } } [/pre2] Ее вывод на консоль может выглядеть следующим образом: [pre2] 1: 1 2: 2 3: 6 4: 24 5: 120 6: 720 7: 5040 8: 40320 9: 362880 10: 3628800 11: 39916800 12: 479001600 13: 6227020800 14: 87178291200 15: 1307674368000 16: 20922789888000 17: 355687428096000 18: 6402373705728000 19: 121645100408832000 20: 2432902008176640000 21: 51090942171709440000 22: 1124000727777607680000 Continue (Enter - continue)? 23: 25852016738884976640000 24: 620448401733239439360000 25: 15511210043330985984000000 26: 403291461126605635584000000 27: 10888869450418352160768000000 28: 304888344611713860501504000000 29: 8841761993739701954543616000000 30: 265252859812191058636308480000000 31: 8222838654177922817725562880000000 32: 263130836933693530167218012160000000 33: 8683317618811886495518194401280000000 34: 295232799039604140847618609643520000000 35: 10333147966386144929666651337523200000000 36: 371993326789901217467999448150835200000000 37: 13763753091226345046315979581580902400000000 38: 523022617466601111760007224100074291200000000 39: 20397882081197443358640281739902897356800000000 40: 815915283247897734345611269596115894272000000000 41: 33452526613163807108170062053440751665152000000000 42: 1405006117752879898543142606244511569936384000000000 43: 60415263063373835637355132068513997507264512000000000 44: 2658271574788448768043625811014615890319638528000000000 Continue (Enter - continue)? 45: 119622220865480194561963161495657715064383733760000000000 46: 5502622159812088949850305428800254892961651752960000000000 47: 258623241511168180642964355153611979969197632389120000000000 48: 12413915592536072670862289047373375038521486354677760000000000 49: 608281864034267560872252163321295376887552831379210240000000000 50: 30414093201713378043612608166064768844377641568960512000000000000 Для продолжения нажмите любую клавишу . . . [/pre2]

Сыроежка: Следующий пример кода можно в шутку предворить крылатой фразой: "Что позволено Юпитеру, то не позволено быку.":) Если написать следующее объявление int *pi = &pi: То вы неминуемо получите ошибку компиляции. Нельзя неявно преобразовать тип int **, который имеет выражение &pi, в тип int *, который имеет сама переменная pi. Но вполне можно написать void *pv = &pv; и это объявление успешно скомпилирутеся. Первой реакцией неопытного С/С++ программиста может быть недоумение: "Как же так, ведь здесь тоже имеется два различных типа: void **. выражения &pv и void * переменной pv!" Как говорится, ларчик открывается просто. Указателю void * может быть присвоено значение указателя любого типа в том числе и void **. Никаких при этом приведений типов делать нет необходимости.

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

Сыроежка: Есть опечатки, которые могут поразить своими последствиями даже опытного программиста. Вот одна из таких опечаток. Как известно в станлартном классе С++ std::string можно делать конкатенацию строк. Например: [pre2] std::string s = "Hello "; s += "World"; std:;cout << s << std::endl;[/pre2] Результатом работы этого фрагмента неполного кода программы будет вывод на консоль строки Hello World Ничего необычного здесь нет. Оператор += присоединяет операнд в правой части к строке, расположеннной слева от знака оператора. Теперь начинается забавное. Можно по невнимательности допустить ошибку и написать [pre2] std::string s = "Hello "; s =+ "World"; std:;cout << s << std::endl;[/pre2] То есть вместо перегруженного для класса std::string оператора += по ошибке написать =+. Первая реакция даже опытного программиста на такую опечатку будет утверждение, что код не будет компилироваться, так как такого оператора нет. Каково же будет его удивление, когда ему покажут, что код успешно компилируется и выводит на консоль строку World . "Что такое?!", - скажет опытный программист, - "Это скорей всего баг компилятора!" На самом деле он заблуждается. Никакого бага здесь нет. Код совершеннно корректный. Чтобы лучше было разобраться, что эта конструкция означает, желательно вставить пробел между знаками операторов = и +. s = +"World"; В правой части находится строковый литерал, который имеет тип константного символьного массива. Массивы в выражениях преобразуются к указателю на их первый элемент. А к указателям можно применять унарный оператор +! Согласно параграфу №7 раздела 5.3.1 Unary operators стандарта С++: 7 The operand of the unary + operator shall have arithmetic, unscoped enumeration, or pointer type and the result is the value of the argument. Integral promotion is performed on integral or enumeration operands. The type of the result is the type of the promoted operand. Для указателя он никакой роли не играет. Поэтому выражение s = +"World"; эквивалентно выражению s = "World"; без унарного оператора +. А это - уже знакомая всем запись присвоения символьного литерала объекту типа std::string. Замечу также, что в отличии от унарного оператора + унарный оператор - нельзя применять к указателям. Забавность этого примера состоит в том, что многие, даже опытные программисты, попадаются на этот трюк С++ с операторами =+, считая, что они обнаружили баг компилятора. Кстати сказать, такую опечатку легко пропустить, не заметив, а потом не один час потратить на поиск причины некорректной работы программы. Будьте внимательны!

Сыроежка: Вот очень простой и в то же время сложный вопрос, так как решение его не очевидно, то есть не сразу же приходит на ум. Допустим в программе на C++ объявлена структура, члены которой имеют арифметические типы или типы перечислений. [pre2]struct A { /* объявления членов структуры арифметических типов или типов перечислений */ };[/pre2] Являются ли два таких объявления объектов этой структуры, показанных ниже, эквивалентными, и может ли одно из них привести к ошибке компиляции, то есть при задании подобного объявления для некоторой сттруктуры, содержащей только члены, имеющие арифметические типы и типы перечислений, компилятор выдаст сообщение об ошибке? [pre2]A a1 = {}; A a2 = { 0 };[/pre2]

Сыроежка: Не удержался и решил привести пример самого абсурдного кода начинающего программиста, который на первый взгляд не должен компилироваться, но на самом деле с точки зрения синтаксиса он корректный. Забавно, что у автора кода программа действительно скомпилировалась и даже что-то там запросила ввести! Вот этот код. Привожу его целиком, чтобы можно было по достоинству оценить творческий порыв начинающих программистов [pre2]#include "stdafx.h" #include <iostream> #define PI 3.14159 using namespace std; void select() { cout << "Select r for radius or d for diameter: "; char choice; cin >> choice; } void calculation(void select(), char choice) { if (choice == 'r') cout << "Select a value for your radius: " << endl; double radius; cin >> radius; double circRadius; circRadius = 2 * PI * radius; if (choice == 'd') cout << "You have selected a diameter." << endl; double diameter; cin >> diameter; double circDiameter; circDiameter = 2 * PI * (diameter / 2); } int main(char choice, void calculation()) { select(); calculation(); return 0; }[/pre2] Забавно в этом примере то, что не сразу же бросается в глаза, как объявлены параметны функции main int main(char choice, void calculation()) Сначала, глядя на вызов функции calculation(); можно подумать, что код не должен компилироваться, так как количество аргументов не соответствует количеству параметров в объявлении функции void calculation(void select(), char choice) Поэтому если не обратить внимание на объявление main, то так и можно сказать, что код не будет компилироваться. Но дело в том, что объявленный параметр в main void calculation() скрывает объявление функции, и впредложении calculation(); вызывается не объявленная функция, а нечто, на что указывает этот параметр void calculation() в объявлении main. Поэтому, как это не парадоксально, код скомпилировался и даже попытался что-то сделать. Нет предела творческой фантазии человека! Вот ссылка на исходный пример

Сыроежка: Вот простой пример на сообразительность. При выполнении следующей функции [pre2] std::string ReplaceString( const std::string &input, const std::string &source, const std::string &dest ) { std::string result = input; unsigned int pos; while( true ) { pos = result.find( source ); if ( pos == std::string::npos ) break; result.replace( pos, source.size(), dest ); } return result; }[/pre2] возникло исключение std::out_of_range Найдите в коде причину возникновения этого исключения.

ололеша: 2Сыроежка Найдите в коде причину возникновения этого исключения. данный код будет кидать исключение только на тех платформах, где sizeof(unsigned int) != sizeof(string::size_type) (коим и является npos), например на x86-64. там где они равны, все будет работать нормально. меняем unsigned int pos на string::size_type pos и будет работать везде.

ололеша: Сыроежка пишет: Являются ли два таких объявления объектов этой структуры, показанных ниже, эквивалентными, и может ли одно из них привести к ошибке компиляции, то есть при задании подобного объявления для некоторой сттруктуры, содержащей только члены, имеющие арифметические типы и типы перечислений, компилятор выдаст сообщение об ошибке? A a1 = {}; A a2 = { 0 }; очевидно же, что если в структуре первый член будет являться перечислением, во второй строчке произойдет ошибка о невозможности неявного преобразования int к enum.

Сыроежка: ололеша пишет: данный код будет кидать исключение только на тех платформах, где sizeof(unsigned int) != sizeof(string::size_type) Все правильно. Данный пример демонстрирует, насколько важно использовать со стандартными контейнерами те имена типов, которые объявлены в каждом контейнере. Иначе можно столкнуться с подобной ситуацией, когда неожиданно ранее работающий код начинает вести себя непредвиденно. Смоделировать эту ситуацию легко и на пллатформе, для которой std::string::size_type определен как unsigned int. Для этого достаточно просто присвоить значение std::string::size_type переменной unsigned short. Вот пример такого кода [pre2] std::string s( "ABC" ); unsigned short n = s.find( "D" ); //std::string::size_type n = s.find( "D" ); if ( n != std::string::npos ) std::cout << "n != std::string::npos\n"; else std::cout << "n == std::string::npos\n";[/pre2] Если использовать предложение unsigned short n = s.find( "D" );, то на консоль будет выдано сообщение n != std::string::npos Если же использовать предложение std::string::size_type n = s.find( "D" );, которое закомментировано в примере, то на консоль будет выдано сообщение n == std::string::npos

Сыроежка: Вот еще одно простое задание на "освежение мозгов". Одинаков ли будет результат вывода на консоль двух выражений a - b и x - y в приведенном ниже коде [pre2] unsigned short a = 5; unsigned short b = 10; std::cout << a - b << std::endl; unsigned int x = 5; unsigned int y = 10; std::cout << x - y << std::endl;[/pre2]

ололеша: 2Сыроежка набор битов у результата будет одинаков, отображение разное. unsigned short после аддитивной операции (да и не только аддитивной) продвигается до типа int, unsigned int остается самим собой.

Сыроежка: Знаете ли вы, что MS VC++ позволяет использовать символ $ (знак доллара) в идентификаторе. Так что следующее предложение в MS VC++ будет успешно компилироваться. [pre2]char $ = '$';[/pre2] Не надо забывать, что стандард С++ разрешает в идентификаторах использовать implementation-defined символы.

Сыроежка: Существует забавная ошибка, которую программисты очень часто допускают в своем коде, не догадываясь даже о существовании этой ошибки. Сначала приведу пример формулировки одной из задач, нередко встречающейся в разных модификациях во многих заданиях, которая содержит в себе побудительную причину, приводящую к данной ошибке.: Разбить целое число, то есть типа 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] компилироваться не будет.

Сыроежка: Часто начинающие программисты получают задание от своих наставников или сами просят предоставить им подобное задание, когда изучают язык программирования 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] Изобретательности начинающих программистов нет границ!

Сыроежка: Некоторые синтаксические конструкции могут вводить в заблуждение из-за того, что мы, порой, используем их автоматически, не задумываясь. Например, обратимся к оператору sizeof, который возвращает размер в байтах объекта заданного типа. Например, обычно программисты пишут выражения с sizeof оператором следующим образом, как показано в демонстрационной программе ниже [pre2] #include <stdio.h> int main(void) { int x; printf( "%zu\n", sizeof( int ) ); printf( "%zu\n", sizeof( x ) ); return 0; } [/pre2] Оба выражения , sizeof( int ) и sizeof( x ), произведут один и тот же результат. То есть обычно программисты привыкли вызывать оператор sizeof как функцию, в круглых скобках которой задается некоторая сущность, размер которой нужно вычислить. Вот эта однотипная запись выражения с оператором sizeof может вводить в заблуждение программистов. То есть требуется некоторое время, чтобы, глядя на выражение с оператором sizeof, понять, является оно корректным или нет. Для начала рассмотрим следующую программу. [pre2] #include <stdio.h> struct A { char s[10]; }; int main(void) { printf( "%zu\n", sizeof( ( ( struct A * )0 )->s ) ); return 0; } [/pre2] Результатом работы программы будет вывод на консоль числа 10. В этой программе используется привычная для подавляющего большинства программистов запись выражения с оператором sizeof. То есть оператор sizeof записан в виде вызова функции. Теперь рассмотрим следующую программу. [pre2] #include <stdio.h> struct A { char s[10]; }; int main(void) { printf( "%zu\n", sizeof( ( struct A * )0 ) ->s ); return 0; } [/pre2] Первая реакция многих программистов будет такой, что программа не должна компилироваться, так как выражение с оператором sizeof является некорректным. То есть по привычке они рассматривают выражение с оператором sizeof, как использование оператор ->, примененного к выражению с sizeof оператором. [pre2]sizeof( ( struct A * )0 ) -> s[/pre2] То есть само выражение с оператором sizeof для них выглядит как [pre2]sizeof( ( struct A * )0 )[/pre2] и, естественно, заключают они, к целочисленному результату этого выражения нельзя применить оператор ->. На самом деле оператор sizeof для выражений определяется как [pre2]sizeof unary-expression[/pre2] То есть все, что следует за ключевым словом sizeof в приведенной конструкции из демонстрационной программы выше, является выражением. И это выражение (которое является не вычисляемым) [pre2]( ( struct A * )0 )->s[/pre2] является корректным выражением, к которому и применяется оператор sizeof .

Сыроежка: Начинающие программисты часто допускают много синтаксических ошибок. И это естественно, так как знания еще не закреплены в памяти, и для этого нет достаточной практики. В связи с этим можно встретить очень оригинальные конструкции в коде начинающих программистов. Вот, например, как один такой начинающий программист объявил массив из n элементов в программе, написанной на C [pre2] int scores = [n];[/pre2]

Сыроежка: Сегодня на сайте Stackoverflow прочитал следующий пассаж в одном из вопросов Today, I finally learned that it's possible to do "continue 2;" to "continue" an inner loop. I never knew about this syntax in 20+ years of using PHP. I could have used this in so many cases. Как говорится, нарочно не придумаешь. Человек более 20 лет использовал PHP и только на днях решил заглянуть в описание языка и сделал для себя открытие относительно такого базового оператора как continue.

Сыроежка: Встретил интересную преамбулу в одном вопросе на Stackoverflow. Вопрос начинается со следующего предисловия: «I am a 52-year-old chemical engineer who decided to change carrier, after some period of unemployment, to programming and made a mistake to choose C language to learn. In addition, English is not my native language, so be kind.» Здесь два интересных момента, заслуживающих внимание. Первое - это то, что человек в 52 года не побоялся резко изменить специальность. Это надо быть большим оптимистом. Например, в России программисты, которым уже только только за 40 лет начинают нервничать и задаваться вопросом: "А что делать, если уже на работу не будут брать в связи с возрастом?". К сожалению в России те, кто набирает программистов, то есть менеджеры и руководство фирм, крайне не компетентны и руководствуются обывательскими соображениями, не понимая, что как раз программистами становятся после 40 лет. А до этого возраста каждый, кто пишет код, думает, что он является программистом.:) Второе - это то, что человек, как только встретился с ошибкой в набранной им простейшей программе из книги для начинающих программистов по языку C (а ошибка заключалась в обыденной опечатке, когда оператор присваивания был спутан с оператором равенства) то сразу поспешно сделал вывод, что он не тот язык программирования выбрал для изучения.:) В программировании нужна усидчивость и хладнокровие. Нужно получать удовольствие от решения проблем в коде.:)

Сыроежка: Вопросы начинающих программистов порой поражают своей не ординарностью, свежестью и глубиной мысли. Вот, встретившейся мне на форуме Stackoverflow такой вопрос. Так как вопрос в конечном итоге был удален, то привожу его текст здесь полностью. Вопрос озаглавлен так: "Which is better practice? Print as int, string,char in C++?" и имеет следующее содержание: [pre2] cout << 1; //Output as Integer Literal cout << "1"; //Output as String cout << '1'; //Output as Char cout<<true; //Output using bool [/pre2] I simply want to print 1 in output, Just curious to know which one of the above ways is the best practice? Я, вот, подумал, зачем ломать голову и мучить себя этим вопросом? Вдруг, еще выберешь не лучший вариант, а затем будешь сильно переживать и казнить себя в этом! Лучше написать, используя все возможности, так сказать, никого не обижая [pre2] std::cout << 1 * true * ( '1' - '0' ) * ( "1"[0] - '0' );[/pre2] И, как говорится, и волки сыты, и овцы целы!



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