NULL == nullptr в C++11?
не совcем. NULL большинством компиляторов с легкостью трактуется как 0 (на самом деле это обычно define). Но нет 100 гарантии, что это так. А вот nullptr — это известная для компилятора константа и компилятор знает, что это такое.
6 дек 2013 в 13:29
@KoVadim, можно сказать, что надежнее будет инициализировать указатели nullptr, для дальнейшей проверки на корректность, нежели NULL?
6 дек 2013 в 13:58
nullptr как раз специально ввели, что бы решить часть неоднозначных ситуаций с NULL. Это все лучше утрясется в голове, если понимать, то компилятор не видет NULL, он видит обычно вместо него 0 (препроцессор постарался). А вот nullptr виден. И компилятор может сделать анализ.
6 дек 2013 в 14:43
@SoloMio, не совсем. Везде где идет речь про указатели НУЖНО использовать null_ptr. Это гарантия того, что компилятор попробует застраховать программиста от ошибки.
nullptr (C++/CLI и C++/CX)
Ключевое слово nullptr представляет значение указателя NULL. Значение пустого указателя показывает, что тип дескриптора объекта, внутреннего указателя или собственного указателя не указывает на объект.
Используется nullptr с управляемым или машинным кодом. Компилятор выводит соответствующие, но различные инструкции для управляемых и машинных значений пустых указателей. Дополнительные сведения об использовании версии этого ключевого слова в соответствии со стандартом ISO C++ см. в разделе nullptr.
__nullptr ключевое слово — это ключевое слово, относящуюся к корпорации Майкрософт, которая имеет то же значение, что nullptr и код, но применяется только к машинному коду. Если вы используете nullptr собственный код C/C++, а затем компилируете с параметром компилятора /clr , компилятор не может определить, указывает ли nullptr собственное или управляемое значение указателя NULL. Чтобы сделать намерение понятным компилятору, используйте nullptr для указания управляемого значения или __nullptr для указания собственного значения.
Ключевое слово nullptr эквивалентен Nothing в Visual Basic и null в C#.
Использование
nullptr Ключевое слово можно использовать в любом месте дескриптора, собственного указателя или аргумента функции.
Ключевое слово nullptr не является типом и не поддерживается для использования:
- sizeof
- typeid
- throw nullptr (однако throw (Object^)nullptr; будет работать)
Ключевое слово nullptr можно использовать в инициализации следующих типов указателей:
- собственного указателя;
- дескриптора среды выполнения Windows;
- управляемого дескриптора;
- управляемого внутреннего указателя.
nullptr Ключевое слово можно использовать для проверки, имеет ли указатель или дескриптор null перед использованием ссылки.
Вызовы функций для языков, использующих значения пустых указателей для проверки ошибок, должны правильно интерпретироваться.
Невозможно инициализировать дескриптор до нуля; можно использовать только nullptr . При присвоении константы 0 дескриптору объекта создаются упакованный тип Int32 и приведение к типу Object^ .
Пример: nullptr ключевое слово
В следующем примере кода показано, что nullptr ключевое слово можно использовать везде, где можно использовать дескриптор, собственный указатель или аргумент функции. В примере показано, что nullptr ключевое слово можно использовать для проверка ссылки перед его использованием.
// mcpp_nullptr.cpp // compile with: /clr value class V <>; ref class G <>; void f(System::Object ^) <> int main() < // Native pointer. int *pN = nullptr; // Managed handle. G ^pG = nullptr; V ^pV1 = nullptr; // Managed interior pointer. interior_ptrpV2 = nullptr; // Reference checking before using a pointer. if (pN == nullptr) <> if (pG == nullptr) <> if (pV1 == nullptr) <> if (pV2 == nullptr) <> // nullptr can be used as a function argument. f(nullptr); // calls f(System::Object ^) >
Пример. Использование и ноль nullptr взаимозаменяемого использования
В следующем примере кода показано, что nullptr и ноль можно использовать взаимозаменяемо на собственных указателях.
// mcpp_nullptr_1.cpp // compile with: /clr class MyClass < public: int i; >; int main()
pMyClass == nullptr pMyClass == 0 pMyClass == nullptr pMyClass == 0
Пример. Интерпретация nullptr как дескриптор
В следующем примере кода показано, что nullptr интерпретируется как дескриптор любого типа или собственного указателя на любой тип. В случае перегрузки функции с дескрипторами различных типов создается ошибка неоднозначности. Необходимо nullptr явно привести к типу.
// mcpp_nullptr_2.cpp // compile with: /clr /LD void f(int *)<> void f(int ^)<> void f_null() < f(nullptr); // C2668 // try one of the following lines instead f((int *) nullptr); f((int ^) nullptr); >
Пример: Приведение nullptr
В следующем примере кода показано, что приведение разрешено и возвращает указатель или дескриптор типа приведения nullptr , содержащего nullptr значение.
// mcpp_nullptr_3.cpp // compile with: /clr /LD using namespace System; template void f(T) <> // C2036 cannot deduce template type because nullptr can be any type int main() < f((Object ^) nullptr); // T = Object^, call f(Object ^) // Delete the following line to resolve. f(nullptr); f(0); // T = int, call f(int) >
Пример. Передача nullptr в качестве параметра функции
В следующем примере кода показано, что nullptr можно использовать в качестве параметра функции.
// mcpp_nullptr_4.cpp // compile with: /clr using namespace System; void f(Object ^ x) < Console::WriteLine("test"); >int main()
test
Пример. Инициализация по умолчанию
В следующем примере кода показано, что при объявлении дескрипторов и не явной инициализации они инициализированы nullptr по умолчанию.
// mcpp_nullptr_5.cpp // compile with: /clr using namespace System; ref class MyClass < public: void Test() < MyClass ^pMyClass; // gc type if (pMyClass == nullptr) Console::WriteLine("NULL"); >>; int main() < MyClass ^ x = gcnew MyClass(); x ->Test(); >
NULL
Пример. Назначение собственного указателя nullptr
В следующем примере кода показано, что nullptr при компиляции с помощью собственного указателя /clr можно назначить указатель.
// mcpp_nullptr_6.cpp // compile with: /clr int main()
Требования
Параметр компилятора: необязателен; поддерживается всеми параметрами создания кода, включая /ZW и /clr .
Встроенные типы (C++)
Встроенные типы (также называемые фундаментальными типами) задаются стандартом языка C++ и встроены в компилятор. Встроенные типы не определены в файле заголовка. Встроенные типы делятся на три основные категории: целочисленные, с плавающей запятой и void. Целочисленные типы представляют целые числа. Типы с плавающей запятой могут указывать значения, которые могут содержать дробные части. Большинство встроенных типов рассматриваются как отдельные типы компилятором. Однако некоторые типы являются синонимами или рассматриваются как эквивалентные типы компилятором.
Тип void
Тип void описывает пустой набор значений. Переменная типа void не может быть указана. Тип void используется в основном для объявления функций, которые не возвращают значения или объявляют универсальные указатели на нетипизированные или произвольные типизированные данные. Любое выражение можно явно преобразовать или привести к типу void . Однако такие выражения можно использовать только в следующих операторах и операндах:
- в операторе выражения (Дополнительные сведения см. в разделе Выражения.)
- в левом операнде оператора запятой (Дополнительные сведения см. в разделе Оператор запятой.)
- во втором и третьем операндах условного оператора ( ? : ). (Дополнительные сведения см. в разделе Выражения с условным оператором.)
std::nullptr_t
Ключевое слово nullptr является константой std::nullptr_t типа null-указателя, которая преобразуется в любой необработанный тип указателя. Дополнительные сведения см. в разделе nullptr .
Тип Boolean
Тип bool может иметь значения true и false . Размер bool типа зависит от реализации. Дополнительные сведения о реализации см. в разделе «Размеры встроенных типов «.
Символьные типы
Тип char — это тип представления символов, который эффективно кодирует элементы базового набора символов выполнения. Компилятор C++ обрабатывает переменные типа char , signed char и unsigned char как переменные разных типов.
Корпорация Майкрософт: переменные типа char по умолчанию действуют как int будто из типа signed char , если /J только не используется параметр компиляции. В этом случае они рассматриваются как тип unsigned char и повышаются до int без расширения знака.
Переменная типа wchar_t — это расширенный или многобайтовый тип символов. L Используйте префикс перед символом или строковым литералом, чтобы указать тип расширенных символов.
Корпорация Майкрософт: по умолчанию wchar_t — это собственный тип, но вы можете использовать /Zc:wchar_t- его для unsigned short определения wchar_t типа. __wchar_t — синоним для машинного типа wchar_t для систем Майкрософт.
Тип char8_t используется для представления символов UTF-8. Он имеет то же представление, что unsigned char и , но рассматривается как отдельный тип компилятором. Тип char8_t новый в C++20. Корпорация Майкрософт: для использования char8_t требуется /std:c++20 параметр компилятора или более поздней версии (например /std:c++latest , ).
Тип char16_t используется для представления символов UTF-16. Оно должно быть достаточно большим, чтобы представить любую единицу кода UTF-16. Он рассматривается как отдельный тип компилятором.
Тип char32_t используется для представления символов UTF-32. Оно должно быть достаточно большим, чтобы представить любую единицу кода UTF-32. Он рассматривается как отдельный тип компилятором.
Типы с плавающей запятой
Типы с плавающей запятой используют представление IEEE-754 для обеспечения приближения дробных значений по широкому диапазону величин. В следующей таблице перечислены типы с плавающей запятой в C++ и относительные ограничения на размеры типов с плавающей запятой. Эти ограничения являются обязательными стандартом C++ и не зависят от реализации Майкрософт. Абсолютный размер встроенных типов с плавающей запятой не указан в стандарте.
| Тип | Содержимое |
|---|---|
| float | Тип float является наименьшим типом с плавающей запятой в C++. |
| double | double — это тип с плавающей запятой, размер которого больше или равен размеру типа float , но меньше или равен размеру типа long double . |
| long double | long double — это тип с плавающей запятой, размер которого больше или равен размеру типа double . |
Корпорация Майкрософт: представление long double и double идентично. long double Однако и double рассматриваются как отдельные типы компилятором. Компилятор Microsoft C++ использует представления с плавающей запятой 4 и 8-байтов IEEE-754. Дополнительные сведения см. в представлении с плавающей запятой IEEE.
Целочисленные типы
Тип int — базовый целочисленный тип по умолчанию. Он может представлять все целые числа по определенному диапазону реализации.
Целочисленное представление со знаком — это одно из них, которое может содержать как положительные, так и отрицательные значения. Он используется по умолчанию или signed при наличии модификатора ключевое слово. Модификатор unsigned ключевое слово указывает неподписаемое представление, которое может содержать только неотрицательных значений.
Модификатор размера указывает ширину в битах используемого целочисленного представления. Язык поддерживает short и long long long модификаторы. Тип short должен быть не менее 16 бит ширины. Тип long должен быть не менее 32 битов ширины. Тип должен быть по крайней long long мере 64-разрядным. Стандарт задает связь размера между целочисленными типами:
Реализация должна поддерживать как минимальные требования к размеру, так и отношение размера для каждого типа. Однако фактические размеры могут отличаться между реализацией. Дополнительные сведения о реализации см. в разделе «Размеры встроенных типов «.
Ключевое слово int могут быть опущены при signed unsigned указании модификаторов размера или размера. Модификаторы и int тип, если они присутствуют, могут отображаться в любом порядке. Например, short unsigned и unsigned int short ссылаться на тот же тип.
Синонимы целочисленного типа
Следующие группы типов считаются синонимами компилятора:
Типы целых чисел, зависящие от Майкрософт, включают определенные ширины __int8 , __int16 __int32 а также __int64 типы. Эти типы могут использовать signed модификаторы и unsigned модификаторы. Тип данных __int8 аналогичен типу char , __int16 — типу short , __int32 — типу int , а __int64 — типу long long .
Размеры встроенных типов
Большинство встроенных типов имеют определенные реализацией размеры. В следующей таблице перечислены объем хранилища, необходимый для встроенных типов в Microsoft C++. В частности, long 4 байта даже в 64-разрядных операционных системах.
| Тип | Size |
|---|---|
| bool , char , char8_t , unsigned char , signed char , __int8 | 1 байт |
| char16_t , __int16 , short , unsigned short , wchar_t , __wchar_t | 2 байта |
| char32_t , float , __int32 , int , unsigned int , long , unsigned long | 4 байта |
| double , __int64 , long double , long long , unsigned long long | 8 байт |
Сведения о диапазоне типов данных см. в сводке по диапазону значений каждого типа.
Дополнительные сведения о преобразовании типов см. в разделе «Стандартные преобразования».
С лёгким налётом ржавчины или куда делся NULL
Давно начал следить за языком Rust, кажется, ещё до выхода версии 1.0. За это время язык сильно изменился, оформился и стал совсем взрослым, можно в производство. При этом из коробки программисту предлагается довольно много интересных концепций для разработки надёжного ПО с длительным жизненным циклом. Однако сфера промышленной автоматизации не так динамична (как нам иногда бы хотелось), поэтому пока приходится только присматриваться к Rust. Тем не менее надо знакомиться поближе. Просто так читать книжки или заметки не продуктивно, надо что-то пробовать делать. Например, можно начать с решения задачек на LeetCode (что я и решил попробовать). А при решении таких задачек иногда натыкаешься на что-то такое, с чем и Stack Overflow может не помочь, не только книжки. В результате поисков дополнительной информации наткнулся на серию заметок, которой хотелось бы поделиться с общественностью (да-да, он воровал тексты у богатых и переводил их бедным). Под катом перевод первой маленькой заметки про (отсутствие) NULL в Rust.
Большинство ключевых функциональных возможностей Rust не являются новыми или уникальными — функциональное программирование, объектно-ориентированное программирование, метапрограммирование, отсутствие NULL, управление памятью без сборщика мусора — всё это существовало и раньше. Но Rust выгодно отличается в той области, для которой он изначально задумывался — безопасная работа с памятью и производительность во время выполнения. В этой серии заметок поговорим о некоторых из перечисленных функциональных возможностях.
Как и обещал, начнем с NULL. Что на самом деле означает NULL? В большинстве языков он обозначает ничто — пустое, недействительное, нулевое или условное значение, которое может быть заменой для любого типа. NULL был придуман сэром Тони Хоаром в 1965 году и с тех пор пользуется популярностью. Rust является одним из немногих языков программирования, которые не поддерживают NULL, и небезосновательно.
Начнём с того, что сам сэр Хоар назвал NULL ошибкой в миллиард долларов. Мне, как разработчику преимущественно на C (или на C++, как автору оригинальной заметки) хорошо знакома боль от необходимости повсеместно добавлять проверки на NULL. Отсутствие хотя бы одной проверки может стать фатальным. Идея избегать ошибок, сбоев и уязвимостей звучит довольно привлекательно.
Вместо NULL язык Rust предоставляет перечисление Option, которое является общим для любого типа и имеет два варианта Some и None. Если есть вероятность, что объект типа T не имеет значения, то есть может быть None, его тип становится Option вместо T. Если попытаться использовать такой объект как T напрямую, компилятор будет люто (но понятно) ругаться. Поэтому не получится использовать объекты или ссылки, которые не определены, что устраняет целый класс ошибок.
Рассмотрим следующую программу на C++ в качестве примера:
#include using namespace std; struct Wrapper < int value; void print() < cout >; int main() < cout print(); cout
Программа замечательно компилируется, но её запуск приводит к сбою. Ошибка сегментации памяти возникает, когда разыменовывается нулевой указатель. Программу легко исправить, потому что она короткая и простая. Но в реальности это часто не так. С увеличением сложности, при передаче указателя в другие функции, ошибиться становится проще простого. Это позволит ошибке проявиться у заказчика, и может потребоваться несколько дней (хорошо если так) для отладки.
Буквальный перевод программы на Rust будет выглядеть следующим образом, но, конечно, он не будет компилироваться:
fn main() < println!("Entrance"); let w: Wrapper = None; w.print(); println!("Exit"); >struct Wrapper < value: i32 >impl Wrapper < fn print(&self) < println!("<>", self.value); > >
Объект w не может иметь тип Wrapper и иметь значение None. Если не получается присвоить ему значение, он должен иметь тип Option, который затем необходимо будет развернуть, чтобы получить объект Wrapper. Простой и безопасный способ — оператор if (в данном случае if let), который использует w, если это какое-то значение, и игнорирует его в противном случае. Следующая версия программы делает именно это и работает корректно:
fn main() < println!("Entrance"); let w: Option= None; if let Some(v) = w < v.print(); >println!("Exit"); >
И хотя в C++ есть аналогичный необязательный тип Optional со значением NULL, он настолько же полезный, насколько обязательный. Компилятор Rust, наоборот, не будет компилировать программу без всех необходимых проверок, если используется Option (а без него нельзя использовать None, т.к. NULL же нет).
Итак, какое впечатление от Rust на данный момент? Неудобно для идеального результата? Больше чем просто разница в синтаксисе? Чрезмерно усердный компилятор? Всё вышеперечисленное? Верно. Следите за обновлениями и мы вместе узнаем, что делает Rust таким, какой он есть.
Другие заметки цикла:
- С лёгким налётом ржавчины или куда делся NULL
- С лёгким налётом ржавчины или немного о владении