Грокаем C++
Ir al canal en Telegram
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Mostrar más9 359
Suscriptores
-224 horas
-47 días
+3730 días
Archivo de publicaciones
9 360
Обратный код
Обратный код или ones' complement - уже более совершенное представление целых чисел в двоичном виде. Этот код позволяет очень легко выполнять операции сложения/вычитания над числами, используя только лишь операцию сложения.
Вообще говоря, в арифметике операцию вычитания можно переопределить через операцию сложения. Вместо вычитания одного числа из другого, вы инвертируете вычитаемое и складываете с уменьшаемым. Типа того:
a - b = a + (-b)Обратный код очень удобно использовать именно в контексте такого свойства, потому что в нем очень просто инвертируются числа. Как и во всех системах, поддерживающих представление отрицательных чисел, в нем есть зарезервированный знаковый бит, а затем идет "модуль" числа. Например, число 5 в двоичном виде представляется, как 101. Восьмибиное знаковое число 5 в обратном коде представляется, как 00000101. Старший бит 0 значит, что число положительное, дальше идут незначащие нули и в конце наше число. Инвертированное же число получается просто обращением всех битов в обратные. 0 в 1 и 1 в 0. То есть число -5 представляется, как 11111010. И если мы сложим 2 обратных числа, то получим естественно 0:
00000101 + 11111010 = 11111111 = 0Причем складываются два числа без учета "особенности" старшего бита. Сложение происходит просто как сложение двух двоичных чисел. Если сумма не умещается в заданное количество бит, то новый старший разряд просто откидывается. Давайте попробуем что-нибудь посчитать:
31 - 12 = 31 + (-12) = 00100000 + (-00001100) = 00100000 + 11110011 = 1'00010011(старший разряд откидываем, у нас всего 8 бит) = 00010011 = 19Получили ожидаемое положительное(знаковый бит 0) число 19. Можем уйти в минуса:
25 - 29 = 25 + (-29) = 00011001 + (-00011101) = 00011001 + 11100010 = 11111011 = -00000100 = -4Вот так все просто. Но и у этого кода есть недостатки. Главный из них вы уже могли заметить в посте. Ноль представляется, как 11111111. Точнее, у нуля 2 значения - все нули и все единички. То есть появляется +0 и -0. С точки зрения вычислений это особо никак не влияет на результат, но добавляет головной боли при разработке, так как надо тестировать софт и для отрицательного нуля. Недостаток довольно надоедливый. Поэтому, и хоть динозавристые компьютеры использовали этот код, все современный машины используют дополнительный код. О нем в следующем посте. Remove inconvenience from your life. Stay cool. #base
9 360
Знак-амплитуда
Начнем раскрывать тему вариантов представления отрицательных чисел с метода "знак-величина" или sign-magnitude. Это самый интуитивно понятный для нас способ репрезентации целых чисел. В обычной математике как: есть грубо говоря модуль числа и его знак.
И это довольно просто перенести в память. Мы просто обговариваем, что наиболее значимый бит числа мы отводим по бит знака, а остальные биты обозначают модуль числа. Если знаковый бит - 0, значит число положительное. Если 1 - отрицательное. Таким образом получается вполне естественная ассоциация с привычными нам правилами. Есть натуральный ряд чисел плюс ноль. Чтобы получить множество всех целых чисел, нужно ко множеству натуральных чисел прибавить множество чисел, которое получается из натуральных путем добавления минуса в начале числа.
Давайте на примере. В восьмибитном числе только 7 бит будут описывать модуль числа, который может находится в отрезке [0, 127]. А самый старший бит будет отведен для знака. С добавлением знака мы теперь может кодировать все числа от -127 до 127. Так, число -43 будет выглядеть как 10101011, в то время как 43 будет выглядеть как 00101011. Однако очень внимательные читатели уже догадались, что эта форма представления отрицательных чисел имеет некоторые side-effect'ы, которые усложняют внедрение этого способа в реальные архитектуры:
1️⃣ Появление двух способов изображения нуля: 00000000 и 10000000.
2️⃣ Представление числа из определения разбивается на 2 части, каждая из которых должна иметься в виду и каким-то образом обрабатываться.
3️⃣ Из предыдущего пункта выходит, что операции сложения и вычитания чисел с таким представлением требуют разной логики в зависимости от знакового бита. Мы должны в начале "отрезать" знаковый бит, сделать операцию, которая соответствует комбинации знаковых битов операндов и первоначальной операции, и потом обратно совместить результат со знаком.
4️⃣ Раз у нас 2 нуля, мы можем представлять на 1 число меньше, чем могли бы в идеале.
В общем, минусы значительные. И хоть такое представление и использовалось в ранних компьютерах из-за интуитивной понятности для инженеров, однако в наше время этот способ представления знаковых целых чисел практически не используется.
Однако "знак-величина" используется по сей день, но немного для другого. IEEE floating point использует этот метод для отображения мантиссы числа. Есть модуль мантиссы(ее абсолютное значение) и есть ее знаковый бит. Кстати поэтому у нас есть положительные и отрицательные нули, бесконечности и NaN'ы во флотах. Вот как оно оказывается работает.
Apply yourself in the proper place. Stay cool.
#cppcore #base
9 360
А вы тоже делаете важный вид, что вы такой просветленный и знаете asm, а на деле просто надеетесь на то, что понимание работы этих закорючек само собой появится у вас в голове?
9 360
Ответ на квиз
В комментариях все более менее пришли к консенсусу, однако давайте разложим все по полочкам, чтобы вопросов уже точно не осталось.
#include <iostream>
int main () {
std::cout << +-!!"" << std::endl;
return 0;
}
Начнем с того, что этот код компилируется. Это уже много дает. По крайней мере вы смотрите на валидный С++ код.
Плюс-минусы и два восклицательных знака - валидные операции над валидными тривиальными типами. Поэтому, по факту, единственное, что надо доказать - что !"" - валидное выражение.
Разберемся с кавычками. Это строковый литерал, обозначающий пустую строку. И он имеет тип const char[1]. Единичка берется из-за того, что сишные строки неявно содержат символ '\0' в конце. Так собственно и определяется конец строки.
Для массивов не определен оператор логического отрицания. Печаль...
Но зато он определен для указателей! А у массивов есть одно замечательное свойство - косить под указатель на свой первый элемент. Поэтому для выполнения !"", компилятор приведет "" к const char *, который будет указывать на какой-то конкретный участок памяти, где лежит эта пустая строка. Раз участок конкретный - тогда этот указатель считается типа true. Отрицаем true - получаем false.
Дальше остается всего-то +-!false. Отрицает false - получаем true. Также у нас есть встроеные унарные операторы для арифметических типов. А bool - тоже арифметический тип. Поэтому true - это как бы 1. +-1 в итоге дает -1(+знака числа не меняет).
Таким нехитрым образом, получаем ответ: -1.
Ставьте снеговика☃️, если ваша интуиция(или знания) вас не подвели.
Stay on positive side. Stay cool.9 360
👩💻 C++ теперь в Telegram!
Вот обширная база контента по C++, которая ежедневно пополняется новыми постами:
Обучение C++ с нуля
Обучение Qt с нуля
Обучение Asio с нуля
Обучение Boost с нуля
Обучение FLTK с нуля
Обучение wxWidgets с нуля
📌 Ресурсы, гайды, шпаргалки, задачи и книги — всё собрано в одном месте: @cpp_ready
9 360
Квиз
Вчера в комментах наш подписчик Вячеслав скинул интересный примерчик, который здорово показывает вашу плюсовую интуицию. Поэтому захотелось разобрать его в рамках поста. Ну и для интереса предлагаю вам поучаствовать в #quiz и испытать свою интуицию. Код до боли краток, и до еще большей боли бессмысленен и ужасен. Но все же.
Какой будет результат попытки компиляции и запуска следующего кода?
#include <iostream>
int main () {
std::cout << +-!!"" << std::endl;
return 0;
}
Have a meaning in your life. Stay cool.
#fun9 360
🔥 Самые нужные каналы для C/C++ разработчика, чтобы расти в доходе 💸
• C/C++ | Вопросы собесов
• C/C++ | LeetCode
• C/C++ | Тесты
• C/C++ | Удалёнка
Подпишись, чтобы не потерять ☝️
9 360
Представление отрицательных чисел в С++
Отрицательные числа - заноза в заднице компьютеров. В нашем простом мире мы различаем положительные и отрицательные числа с помощью знака. Положительные числа состоят просто из набора цифр(ну и по желанию можно добавить + слева, никто не обидится), а к отрицательным слева приписывается минус. Но компьютеры у нас имеют бинарную логику, там все представляется в виде единиц и ноликов. И там нет никаких плюсов-минусов. Тогда если у нас модуль числа уже представляется в виде единичек и ноликов, то непонятно, как туда впихнуть минус.
Вот и в комитете по стандартизации не знали, как лучше это сделать и удовлетворить всем, поэтому до С++20 они скидывали с себя этот головняк. До этого момента С++ стандарт разрешал любое представление знаковых целых чисел. Главное, чтобы соблюдались минимальные гарантии. А именно: минимальный гарантированный диапазон N-битных знаковых целых чисел был [-2^(N-1) - 1; 2^(N-1)-1]. Например, для восьмибитных чисел рендж был бы от -127 до 127. Это соответствовало трем самым распространенным способам представления отрицательных чисел: обратному коду, дополнительному коду и метод "знак-величина".
Однако все адекватные компиляторы современности юзают дополнительный код. Поэтому, начиная с С++20, он стал единственным стандартным способом представления знаковых целых чисел с минимальным гарантированным диапазоном N-битных знаковых целых чисел [-2^(N-1); 2^(N-1)-1]. Так для наших любимых восьмибитных чисел рендж стал от -128 до 127.
Кстати для восьмибитных чисел обратной код и метод "знак-амплитуда" были запрещены уже начиная с С++11. Все из-за того, что в этом стандрате сделали так, чтобы все строковые литералы UTF-8 могли быть представлены с помощью типа char. Но есть один краевой случай, когда один из юнитов кода UTF-8 равен 0x80. Это число не может быть представлен знаковым чаром, для которого используются обратной код и метод "знак-величина". Поэтому комитет просто сказал "запретить".
Для кого-то много непонятных слов и терминов, поэтому в дальнейшем будем раскрывать все секреты представления отрицательных чисел в памяти.
Stay defined. Stay cool.
#cppcore #cpp20 #cpp11
9 360
Теория заговора
Вот живет программист по С++ своей прекрасной и беззаботной жизнью. Все у него хорошо: код пишется, баги фиксятся, деньги мутятся. И имя у него такое прекрасное - Иннокентий.
Иногда он лазается по cppreference, чтобы освежить знания по каким-то фичам или узнать что-то новое. Представим себе, что он зашел просмотреть на доку std::atoi и видит там такой фрагмент:
const auto data =
{
"42",
"0x2A", // treated as "0" and junk "x2A", not as hexadecimal
"3.14159",
"31337 with words",
"words and 2",
"-012345",
"10000000000" // note: out of int32_t range
};
Ничего необычного, просто определяется std::initializer_list<const char *> и записываются туда разные строки. Ну ладно, работает дальше.
А дальше ищет статейку по std::variant. И находит там вот какой отрывок:
int main()
{
std::variant<int, float> v, w;
v = 42; // v contains int
int i = std::get<int>(v);
assert(42 == i); // succeeds
w = std::get<int>(v);
w = std::get<0>(v); // same effect as the previous line
w = v; // same effect as the previous line
...
}
Почему-то он обратил внимание на число 42. "Где-то я его уже видел.". И вспоминает, что недавно видел это же число в коде для std::atoi. Это, конечно, немного странно - подумал, он. Но решил, что это просто случайность.
Приходит наш герой домой с работы и, как каждый уважающий себя программист, вместо ужина садится писать свой пет-проект. А вы не знали, что программисты могут годами жить на диете "код+кофе"?
Пишет он какое-то многопоточное приложение. Чтобы адекватно писать такие штуки, нужны глубокие знания о модели памяти в С++ и как работает синхронизация данных в многопроцессорном мире. Поэтому кодер снова идет на cppreference и находит там статейку про std::memory_order. Читает, читает. И херак, вылупил глаза в экран. "Это уже очень странно". А увидел он следующий фрагмент:
std::vector<int> data;
std::atomic<int> flag = {0};
void thread_1()
{
data.push_back(42);
flag.store(1, std::memory_order_release);
}
Опять это 42! "Что за приколы такие? Это что, любимое число плюсовиков, что они его везде пихают?". На том и порешил. Не нервничать же по поводу чьего-то любимого числа. Может именно на этот день рождения Страуструпу подарили маленького щеночка....
В общем, затерпели и забыли.
Иннокентий только начинает писать свой проект и ему нужен хороший сборочный скрипт. Выбор языка пал на модномолодежный питухон, поэтому он полез в документацию изучать базовый синтаксис питона. И тут на тебе:
x = int(input("Please enter an integer: "))
Please enter an integer: 42
if x < 0:
x = 0
print('Negative changed to zero')
elif x == 0:
print('Zero')
elif x == 1:
print('Single')
else:
print('More')
Whatafuck? Страуструп здесь уже никак не может быть замешан. Ситуация больше похожа на массонский заговор. Кенни не выдержал и пошел распутывать тайну века.
Оказалось, что это отсылка на книгу Дугласа Адамса "Автостопом по галактике". Там люди создали супермощный супекомпьютер только с одной целью - узнать ответ на "Главный вопрос жизни, Вселенной и всего такого". Этот вопрос настолько сложный и комплексный, что на нахождение ответа суперкомпьютер потратил целых 7.5 млн лет вычислений. И в окончании выдал: "42".
Роман вышел в период расцвета sci-fi, поэтому оставил глубокий отпечаток в массовой культуре. Оно появлялось в популярных сериалах типа "Остаться в живых". Даже один из радиотелескопов НАСА использует ровно 42 тарелки в честь отсылки к произведению.
Неудивительно, что гики по всему миру начали пихать это число во всех места в качестве пасхалки. Сейчас почти где-угодно встречая 42, вы можете быть на 99% уверены, что это именно отсылка на "Автостопом по галактике".
Так и была разгадана величайшая из тайн иллюминатов и Иннокентий довольный пошел спать. The end.
Make references to the great things. Stay cool.
#fun9 360
Магическое заклинание +[]{}
Правильный ответ на квиз - программа успешно завершится. Удивились? Погнали разбирать
Давайте сразу проясним, что лямбду можно записывать без списка параметров, это разрешенная тема. Да и работает это в целом довольно интуитивно: если ваша лямбда не принимает параметров, то вы можете их не указывать и все будет работать также, как и с пустыми круглыми скобками.
Дальше
Здесь мы рассматривали, что присвоение лямбд запрещено и приводит к ошибке компиляции. Каким образом один
+ переворачивает все сверх на голову и код работает?
Стандарт нам говорит:
The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.То есть ламбды без захваченых значений имеют возможность прикидываться указателями на функции. И так вышло, что оператор+ определен для всех типов указателей в виде:
For every type T there exist candidate operator functions of the form T* operator+(T*);Он ничего не делает с указателем и просто его возвращает наружу. Когда мы ставим + спереди лямбды, компилятор пытается найти подходящую перегрузку для оператора+. Логично, что для любого замыкания он не перегружен, поэтому что он не нужен там. Но зато есть перегрузка для указателя, под который может косить лямбда. Что она успешно и делает. Поэтому для здесь для test
auto test = +[]{};
auto test2 = []{};
компилятор выведет тип указателя на функцию, а не замыкания. И ничего нам не мешает перенаправлять этот указатель на другую функцию.
Итого, один бессмысленный плюс тащит катку.
Stay amazed. Stay cool.
#cpp11 #cppcore9 360
👩💻 Программирование теперь в Telegram!
Вот 10 обучающих каналов по самым востребованным направлениям в IT.
Выбирай своё направление:
👩💻 C/C++: @cpp_ready
👩💻 C#: @csharp_ready
👩💻 Python: @python_ready
👩💻 Java: @java_ready
📖 Общее IT: @roadmap_ready
🖥 Базы Данных & SQL: @sql_ready
👩💻 Backend: @backend_ready
👩💻 Frontend: @code_ready
📋 IT Архив: @archive_ready
🖥 Design: @time_design
📌 Ресурсы, гайды, шпаргалки, книги и задачи для каждого языка программирования.
9 360
👩💻 Программирование теперь в Telegram!
Вот 10 обучающих каналов по самым востребованным направлениям в IT.
Выбирай своё направление:
👩💻 C/C++: @cpp_ready
👩💻 C#: @csharp_ready
👩💻 Python: @python_ready
👩💻 Java: @java_ready
📖 Общее IT: @roadmap_ready
🖥 Базы Данных & SQL: @sql_ready
👩💻 Backend: @backend_ready
👩💻 Frontend: @code_ready
📋 IT Архив: @archive_ready
🖥 Design: @time_design
📌 Ресурсы, гайды, шпаргалки, книги и задачи для каждого языка программирования.
9 360
Квиз
Мы с вами недавно коснулись темы лямбд, поэтому вдогонку устроим #quiz по этой теме. Как всегда, тут нужно либо хорошее знание стандарта, либо хорошая интуиция. Хотя интуиция поможет вам на квиз только правильно ответить, челюсть с пола она вам не поднимет, когда вы поймете, в чем дело.
Итак. Какой результат попытки компиляции(с одним флагом указания стандарта С++20) и выполнения этого кода?:
int main() {
auto test = +[]{};
test = []{};
return 0;
}
Ответ выйдет завтра.
Stay surprised. Stay cool.9 360
Что на самом деле представляют собой short circuit операторы?
Мы уже узнали, что операторы && и || для кастомных типов - простые функции. Для функций существует гарантия вычисления всех аргументов перед тем как функция начнет выполняться. Поэтому перегруженные версии этих операторов и не проявляют своих короткосхемных свойств. Однако операторы && и || для тривиальных типов - другое дело и имеют такие свойства. Но почему? Как это так работает в одном случае и не работает в другом? Давайте разбираться.
Во-первых(и в-единственных), операторы для тривиальных типов - это не функции. Они сразу превращаются в определенную последовательность машинных команд. Так как у нас теперь нет ограничения, что мы должны вычислить все аргументы сразу, то и похимичить можно уже знатно.
Если подумать, то логика тут очень похожа на вложенные условия. Если первое выражение правдиво, переходим в вычислению второго, если нет, то выходим из условия(это для &&). И если еще подумать, то у нас и нет никаких других средств это сделать, кроме джампов(условных переходов к метке). Покажу, во что примерно компиляторы С/С++ преобразуют выражение содержащее оператор &&. Не настаиваю на достоверность и точность. Объяснение больше для понимание происходящих процессов.
Вот есть у нас такой код
if (expr1 && expr2 && expr3) {
// cool operation
} else {
// even cooler operation
}
// the coolest operation
Он преобразуется примерно вот в такое:
if (!expr1) goto do_even_cooler_operation;
if (!expr2) goto do_even_cooler_operation;
if (!expr3) goto do_even_cooler_operation;
{
// cool operation
goto do_the_coolest_operation;
}
do_even_cooler_operation:
{
// even cooler operation
}
do_the_coolest_operation:
// the coolest operation
Что здесь происходит. Входим в первое условие и если оно ложное(то есть expr1 - true), то проваливаемся дальше в следующее условие и делаем так, пока наши выражения правдивые. Если они в итоге все оказались правдивыми, то мы входим в блок выполняющий клевую операцию и дальше прыгаем уже наружу первоначального условия и выполняем самую клевую операцию. Если хоть одно из выражений expr оказалось ложным, то мы переходим по метке и выполняем еще круче операцию и естественным образом переходим к выполнению самой крутой операции. Прикол здесь в трех условиях. Так как они абсолютно не связаны друг другом и последовательны, то следующее по счету выражение просто не будет выполняться, пока выполнение не дойдет до него. Таким образом и обеспечиваются последовательные вычисления слева направо.
То есть встроенные операторы && и || разворачиваются вот с такую гармошку условий. Надеюсь, для кого-то открыл глаза, как это работает)
See what's under the hood. Stay cool.
#compiler #cppcore9 360
Инфляция...
Эта старушка в последние годы конкретно так подъедала наши с вами реальные доходы. В последние 10 лет нестабильная внешняя обстановка привела к тому, что рубли обесценились минимум в 2 раза. А зарплаты так сильно не выросли.
Много способов есть, чтобы сохранить свои деньги. Но наверное самый простой на словах метод - получать зп в долларах.
Но для этого надо устраиваться в международные компании. А это совсем другой мир.
Поиск заботы за рубежом сильно отличается от российского рынка – прежде чем приступать к откликам на вакансии, нужно составить стратегию поисков, подготовить мастер-CV, Cover Letter, профиль LinkedIn... а еще подтянуть английский для интервью и заняться нетворком.
В общем, это только индуса нанимать, который будет за вас делать текущую работу. Иначе времени на это все не хватит.
Однако есть лучик света в этом темном царстве. С сервисом AgileFluent поиск работы за границей становится в разы легче. Команда из 50+ опытных экспертов в международном найме поможет на всех этапах отбора – от карьерной консультации до обсуждения оффера.
Ребята уже давно на рынке, у них в копилке уже больше 300 международных офферов для своих клиентов, среди стран – Германия, Нидерланды, Сербия, США, Испания. У них на сайте опубликовано куча успешных кейсов.
Есессно, ко всем клиентам они находят индивидуальный подход. Продукты есть на любой вкус и чек, вежливый менеджер поможет вам с выбором.
Традиционно осень – самый активный период найма в международных компаниях, так что обязательно присмотритесь к AgileFluent, если вы хотите сохранить свои деньги.
Для Вас они подготовили специальное предложение - скидка 7% на первую покупку по промокоду ПЛЮСЫ (кроме полного сопровождения и C-level) – сохраняйте и назовите его менеджеру при оформлении покупки до конца сентября.
Всегда обращайтесь к специалистам. Они помогут вам сохранить свое время, энергию и деньги по итогу.
Save your time and money. Stay cool.
Реклама. ИП Шульгина Дарья Владимировна. ИНН 221004057209.
9 360
Присвоение лямбды
#новичкам
Изучение лямбда выражений - не самая простая задача для начинающего плюсовика. Сложные термины, какие-то замыкания, списки захвата и прочее. В общем, непросто. И с виду можно подумать, что 2 одинаковые лямбды можно присваивать друг другу с легкостью. То есть может показаться, что такой код валидный:
int main() {
auto test = [](){};
test = [](){};
return 0;
}
Однако он генерирует примерно следующую ошибку:
In function ‘int main()’: error: no match for ‘operator=’ in ‘test = <lambda closure object>main()::<lambda()>{}’ note: candidate is: note: main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&) <deleted> no known conversion for argument 1 from ‘main()::<lambda()>’ to ‘const main()::<lambda()>&’Не нашел нужного оператора присваивания. Да и вообще, это ж все лямбды, почему я не могу их присваивать друг другу? Дело в том, что каждое отдельное лямбда-выражение генерирует свой тип, который называется замыканием. Только компилятор знает этот тип, вы его наружу никак не можете получить. Стандарт говорит:
The type of the lambda-expression [...] is a unique, unnamed non-union class type — called the closure type.Это легко проверить. Такой код выведет 0:
auto test = [](){};
auto test2 = [](){};
std::cout << std::is_same_v<decltype( test ), decltype( test2 )> << std::endl;
Типы действительно разные.
В целом, поэтому вместе с лямбдами всегда нужно использовать auto. Потому что ничего другого вы вместо типа физически не сможете написать. Но компилятор знает тип замыкания и сможет вывести правильный тип для переменной.
Ну и естественно, для двух рандомных классов неопределены операторы присваивания своих объектов друг другу. Поэтому код из начала и фейлится.
Differentiate thing apart. Stay cool.
#cpp119 360
Найди летающих друзей
Сегодня будет не по С++ контент, но я не мог мимо этого пройти. Надеюсь вы сейчас никуда не спешите и у вас есть огнетушитель под рукой, будет жарко.
В общем, недавно в твиттере завирусился японский "'экспресс" тест на деменцию. Задача очень простая - найти на картинке бабочку, летучую мышь и утку. Все это надо сделать за 10 мин. Успели - молодцы. Не успели - скорее всего ваша разработческая карьера продлится не так долго, как вы этого ожидаете.
У меня не хватает усидчивости на такие штуки. Через 3 минуты безрезультатного поиска мне захотелось с криками "лайт вейт бэйбэээ" выкинуть что-нибудь тяжелое из окна и я понял, что пора залезать в комменты и ловить спойлеры. Буду верить, что раз я искал не 10 мин, это все не считается.
❤️ - нашел всех за 10 мин.
🤬 - где эта ср*ная бабочка?!
Keep calm. Stay cool.
#fun
9 360
nullptr
#новичкам
Вероятно, каждый, кто писал код на C++03, имел удовольствие использовать NULL и постоянно ударяться мизинцем ноги об этот острый уголок тумбочки. Дело в том, что NULL использовался, как обозначение нулевого указателя, который никуда не указывает. Но если он для этого использовался - это не значит, что он таковым и являлся. Это макрос, который мог быть определен как
0 aka int zero или 0L aka zero long int, но всегда это вариация интегрального нуля. И уже эти чиселки могли быть приведены к типу указателя.
Вот в этом-то и вся проблема. NULL очень явно хочет себя видеть в виде указателя, но по факту в зеркале видит число. Допустим, у нас есть 2 перегрузки одной функции: одна для инта, вторая для указателя:
class Spell { ... };
void castSpell(Spell * theSpell);
void castSpell(int spellID);
int main() {
castSpell(NULL);
}
Намерения ясны: мы хотим вызвать перегрузку для указателя. Но это гарантировано не произойдет! В произойдет один из двух сценариев: если NULL определен как 0, то просто без объявления войны в 4 часа утра вызовется вторая перегрузка. Если как 0L, то компилятор поругается на неоднозначный вызов: 0L может быть одинаково хорошо сконвертирован и в инт, и в указатель.
Проблему можно решить енамами и передавать для нулевого spellID что-то типа NoSpell. Но надо опять городить огород. Почему все не работает из коробки?!
С приходом С++11 начало работать из коробки. Надо только забыть про NULL и использовать nullptr.
Ключевое слово nullptr обозначает литерал указателя. Это prvalue типа std::nullptr_t. И nullptr неявно приводится к нулевому значению указателя для любого типа указателя. Это объект отдельного типа, который теперь к простому инту не приводится.
Поэтому сейчас этот код отработает как надо:
class Spell { ... };
void castSpell(Spell* theSpell);
void castSpell(int spellID);
int main() {
castSpell(nullptr);
}
Так как nullptr - значение конкретного типа std::nullptr_t, то мы может принимать в функции непосредственно этот тип, а не общий тип указателя. Такая штука используется, например, в реализации std::function, конструктор которого имеет перегрузку для std::nullptr_t и делает тоже самое, что и конструктор без аргументов.
/**
* @brief Default construct creates an empty function call wrapper.
* @post !(bool)this
/
function() noexcept
: _Function_base() { }
/
* @brief Creates an empty function call wrapper.
* @post @c !(bool)*this
/
function(nullptr_t) noexcept
: _Function_base() { }
По той же причине nullptr даже при возврате через функцию может быть приведен к типу указателя. А вот обычные null pointer константны не могут похвастаться таким свойством. Они могут приводиться к указателям только в виде литералов.
template<class T>
constexpr T clone(const T& t)
{
return t;
}
void g(int *)
{
std::cout << "Function g called\n";
}
int main()
{
g(nullptr); // Fine
g(NULL) // Fine
g(0); // Fine
g(clone(nullptr)); // Fine
// g(clone(NULL)); // ERROR: non-literal zero cannot be a null pointer constant
// g(clone(0)); // ERROR: non-literal zero cannot be a null pointer constant
}
clone(nullptr) вернет тот же nullptr и все будет работать гладко. А для 0 и NULL функция вернет просто int, который сам по себе неявно не конвертится в указатель.
Думаю, что вы все и так пользуете nullptr, но этот пост обязан быть на канале.
Как говорит одна древняя мудрость: "Use nullptr instead of NULL, 0 or any other null pointer constant, wherever you need a generic null pointer."
Be a separate subject. Stay cool.
#cppcore #cpp11