Типы данных в языке си
Содержание:
- Размер основных типов данных в C++
- Символы (тип char)
- Структуры
- Характеристики типов с плавающей запятойCharacteristics of the floating-point types
- 2 Целые типы
- Виды типов значенийKinds of value types
- Символы
- Таблицы
- Приоритет и ассоциативность операторовOperator precedence and associativity
- Nullable-типы (нулевые типы) и операция ??
Размер основных типов данных в C++
Возникает вопрос: «Сколько памяти занимают переменные разных типов данных?». Вы можете удивиться, но размер переменной с любым типом данных зависит от компилятора и/или архитектуры компьютера!
Язык C++ гарантирует только их минимальный размер:
Категория | Тип | Минимальный размер |
Логический тип данных | bool | 1 байт |
Символьный тип данных | char | 1 байт |
wchar_t | 1 байт | |
char16_t | 2 байта | |
char32_t | 4 байта | |
Целочисленный тип данных | short | 2 байта |
int | 2 байта | |
long | 4 байта | |
long long | 8 байт | |
Тип данных с плавающей запятой | float | 4 байта |
double | 8 байт | |
long double | 8 байт |
Фактический размер переменных может отличаться на разных компьютерах, поэтому для его определения используют оператор sizeof.
Оператор sizeof — это унарный оператор, который вычисляет и возвращает размер определенной переменной или определенного типа данных в байтах. Вы можете скомпилировать и запустить следующую программу, чтобы выяснить, сколько занимают разные типы данных на вашем компьютере:
#include <iostream>
int main()
{
std::cout << «bool:\t\t» << sizeof(bool) << » bytes» << std::endl;
std::cout << «char:\t\t» << sizeof(char) << » bytes» << std::endl;
std::cout << «wchar_t:\t» << sizeof(wchar_t) << » bytes» << std::endl;
std::cout << «char16_t:\t» << sizeof(char16_t) << » bytes» << std::endl;
std::cout << «char32_t:\t» << sizeof(char32_t) << » bytes» << std::endl;
std::cout << «short:\t\t» << sizeof(short) << » bytes» << std::endl;
std::cout << «int:\t\t» << sizeof(int) << » bytes» << std::endl;
std::cout << «long:\t\t» << sizeof(long) << » bytes» << std::endl;
std::cout << «long long:\t» << sizeof(long long) << » bytes» << std::endl;
std::cout << «float:\t\t» << sizeof(float) << » bytes» << std::endl;
std::cout << «double:\t\t» << sizeof(double) << » bytes» << std::endl;
std::cout << «long double:\t» << sizeof(long double) << » bytes» << std::endl;
return 0;
}
1 |
#include <iostream> intmain() { std::cout<<«bool:\t\t»<<sizeof(bool)<<» bytes»<<std::endl; std::cout<<«char:\t\t»<<sizeof(char)<<» bytes»<<std::endl; std::cout<<«wchar_t:\t»<<sizeof(wchar_t)<<» bytes»<<std::endl; std::cout<<«char16_t:\t»<<sizeof(char16_t)<<» bytes»<<std::endl; std::cout<<«char32_t:\t»<<sizeof(char32_t)<<» bytes»<<std::endl; std::cout<<«short:\t\t»<<sizeof(short)<<» bytes»<<std::endl; std::cout<<«int:\t\t»<<sizeof(int)<<» bytes»<<std::endl; std::cout<<«long:\t\t»<<sizeof(long)<<» bytes»<<std::endl; std::cout<<«long long:\t»<<sizeof(longlong)<<» bytes»<<std::endl; std::cout<<«float:\t\t»<<sizeof(float)<<» bytes»<<std::endl; std::cout<<«double:\t\t»<<sizeof(double)<<» bytes»<<std::endl; std::cout<<«long double:\t»<<sizeof(longdouble)<<» bytes»<<std::endl; return; } |
Вот результат, полученный на моем компьютере:
Ваши результаты могут отличаться, если у вас другая архитектура, или другой компилятор
Обратите внимание, оператор sizeof не используется с типом void, так как последний не имеет размера
Если вам интересно, что значит в коде, приведенном выше, то это специальный символ, который используется вместо клавиши TAB. Мы его использовали для выравнивания столбцов. Детально об этом мы еще поговорим на соответствующих уроках.
Интересно то, что sizeof — это один из 3-х операторов в языке C++, который является словом, а не символом (еще есть new и delete).
Вы также можете использовать оператор sizeof и с переменными:
#include <iostream>
int main()
{
int x;
std::cout << «x is » << sizeof(x) << » bytes» << std::endl;
}
1 |
#include <iostream> intmain() { intx; std::cout<<«x is «<<sizeof(x)<<» bytes»<<std::endl; } |
Результат выполнения программы:
На следующих уроках мы рассмотрим каждый из фундаментальных типов данных языка С++ по отдельности.
Символы (тип char)
Для хранения символов Java использует специальный тип char. Он отличается от типа char в языках C/C++, где представляет собой целочисленный тип с размером 8 бит. В Java для char используется кодировка Unicode и для хранения Unicode-символов используется 16 бит или 2 байта. Диапазон допустимых значений — от 0 до 65536 (отрицательных значений не существует).
Из примера выше видно, что переменной можно присвоить код символа или непосредственно сам символ, который следует окружить одинарными кавычками. Попробуйте запустить пример и посмотреть, какое слово получится из трёх указанных символов.
Не следует путать символ ‘a’ со строкой «a», состоящей из одного символа. На экране монитора они выглядят одинаково, но в программах ведут себя по разному.
Стандартные символы ASCII можно выводить сразу. Если нужно вывести специальный символ из Unicode, то можно воспользоваться шестнадцатеричным представлением кода в escape-последовательности — вы указываете обратную наклонную черту и четыре цифры после u. Например:
Хотя тип char используется для хранения Unicode-символов, его можно использовать как целочисленный тип, используя сложение или вычитание.
В результате получим:
Если вы думаете, что увеличив значение переменной ch1 ещё на одну единицу, получите символ «й», то глубоко заблуждаетесь.
Чтобы узнать, какой символ содержится в значении переменной, заданной как int, можно воспользоваться двумя специальными методами из класса EncodingUtils:
Для стандартных символов ASCII:
Для расширенной таблицы символов:
Методы работают со строками, но если мы используем строку из одного символа, то получим то, что нам нужно.
В упрощённом виде, если работаем со стандартными символами ASCII (on 0 до 127), то можно получить символ из int ещё проще.
Класс Character
Класс Character является оболочкой вокруг типа char. Чтобы получить значение типа char, содержащее в объекте класса Character, вызовите метод charValue().
С классом Character редко имеют дело в Android, но помните, что класс содержит огромное количество констант и методов. Например, можно определить, является ли символ цифрой или буквой, или написан ли символ в нижнем или в верхнем регистре.
Структуры
Структуры в Си позволяют хранить несколько полей и в одной переменной. В других языках могут называться записями или кортежами. Например, данная структура хранит в себе имя человека и дату рождения:
struct birthday { char name20]; int day; int month; int year; };
Объявление структур в теле программы всегда должно начинаться с ключевого struct (необязательно в C++). Доступ к элементам структуры осуществляется с помощью оператора . или ->, если мы работаем с указателем на структуру. Структуры могут содержать указатели на самих себя, что позволяет реализовывать многие структуры данных, основанных на связных списках. Такая возможность может показаться противоречивой, однако все указатели занимают одинаковое число байт, поэтому размер этого поля не изменится от числа полей структуры.
Структуры не всегда занимают число байт, равное сумме байт их элементов. Компилятор обычно выравнивает элементы в блоки по 4 байта. Также есть возможность ограничить число бит, отводимое на конкретное поле, для этого надо после имени поля через двоеточие указать размер поля в битах. Такая возможность позволяет создавать битовые поля.
Некоторые особенности структур:
- Адрес памяти первого поля структуры равен адресу самой структуры
- Структуры могут быть инициализированы или приведены к какому-либо значению, с помощью составных литералов
- Пользовательские функции могут возвращать структуру, хотя часто не очень эффективны во время выполнения. С C99, структура может оканчиваться массивом переменного размера.
Характеристики типов с плавающей запятойCharacteristics of the floating-point types
C# поддерживает следующие предварительно определенные типы с плавающей запятой:C# supports the following predefined floating-point types:
Ключевое слово или тип C#C# type/keyword | Приблизительный диапазон значенийApproximate range | ТочностьPrecision | РазмерSize | Тип .NET.NET type |
---|---|---|---|---|
От ±1,5 x 10−45 до ±3,4 x 1038±1.5 x 10−45 to ±3.4 x 1038 | 6–9 цифр~6-9 digits | 4 байта4 bytes | System.Single | |
от ±5,0 × 10−324 до ±1,7 × 10308±5.0 × 10−324 to ±1.7 × 10308 | 15–17 цифр~15-17 digits | 8 байт8 bytes | System.Double | |
от ±1,0 x 10-28 до ±7,9228 x 1028±1.0 x 10-28 to ±7.9228 x 1028 | 28-29 знаков28-29 digits | 16 байт16 bytes | System.Decimal |
В приведенной выше таблице каждый тип ключевого слова C# из крайнего левого столбца является псевдонимом для соответствующего типа .NET.In the preceding table, each C# type keyword from the leftmost column is an alias for the corresponding .NET type. Они взаимозаменяемые.They are interchangeable. Например, следующие объявления объявляют переменные одного типа:For example, the following declarations declare variables of the same type:
По умолчанию все типы с плавающей запятой имеют значение .The default value of each floating-point type is zero, . Все типы с плавающей запятой имеют константы и с минимальным и максимальными итоговыми значениями этого типа.Each of the floating-point types has the and constants that provide the minimum and maximum finite value of that type. Типы и также предоставляют константы, обозначающие бесконечные и нечисловые значения.The and types also provide constants that represent not-a-number and infinity values. Например, тип предоставляет следующие константы: Double.NaN, Double.NegativeInfinity и Double.PositiveInfinity.For example, the type provides the following constants: Double.NaN, Double.NegativeInfinity, and Double.PositiveInfinity.
Так как тип характеризуется более высокой точностью и меньшим диапазоном, чем и , он подходит для финансовых расчетов.Because the type has more precision and a smaller range than both and , it’s appropriate for financial and monetary calculations.
В одном и том же выражении можно сочетать и целочисленные типы, и типы и .You can mix integral types and the and types in an expression. В этом случае целочисленные типы неявно преобразуются в один из типов с плавающей запятой. При необходимости тип неявно преобразуется в .In this case, integral types are implicitly converted to one of the floating-point types and, if necessary, the type is implicitly converted to . Выражение вычисляется следующим образом.The expression is evaluated as follows:
- Если в выражении есть тип , оно оценивается как или в реляционных сравнениях или сравнениях на равенство.If there is type in the expression, the expression evaluates to , or to in relational and equality comparisons.
- Если в выражении нет типа , оно оценивается как или в реляционных сравнениях или сравнениях на равенство.If there is no type in the expression, the expression evaluates to , or to in relational and equality comparisons.
Можно также смешивать целочисленные типы и тип в выражении.You can also mix integral types and the type in an expression. В этом случае целочисленные типы неявно преобразуются в тип , а выражение вычисляется как или в реляционных сравнениях и сравнениях на равенство.In this case, integral types are implicitly converted to the type and the expression evaluates to , or to in relational and equality comparisons.
Тип нельзя смешивать с типами и в выражении.You cannot mix the type with the and types in an expression. В этом случае, если требуется выполнить арифметические операции или операции сравнения или равенства, необходимо явно преобразовать операнды из типа или в тип , как показано в следующем примере:In this case, if you want to perform arithmetic, comparison, or equality operations, you must explicitly convert the operands either from or to the type, as the following example shows:
Можно использовать строки стандартных числовых форматов или строки пользовательских числовых форматов для форматирования значения с плавающей запятой.You can use either standard numeric format strings or custom numeric format strings to format a floating-point value.
2 Целые типы
В языке Java аж 4 целых типа: , , и . Они отличаются размером и диапазоном значений, которые могут хранить.
Тип
Самым часто используемым является тип . Его название происходит от Integer (целый). Все целочисленные литералы в коде имеют тип (если в конце числа не указана буква , или ).
Переменные этого типа могут принимать значение от до .
Это достаточно много и хватает почти для всех случаев жизни. Почти все функции, которые возвращают число, возвращают число типа .
Примеры:
Код | Пояснение |
---|---|
Метод возвращает длину строки | |
Поле содержит длину массива. |
Тип
Тип получил свое название от . Его еще называют короткое целое. В отличие от типа , его длина всего два байта и возможный диапазон значений от до .
То есть в нем даже число миллион не сохранишь. Даже 50 тысяч. Это самый редко используемый целочисленный тип в Java. В основном его используют, когда хотят сэкономить на памяти.
Допустим, у вас ситуация, когда заранее известно, что значения с которыми вы работаете не превышает 30 тысяч, и таких значений миллионы.
Например, вы пишете приложение, которое обрабатывает картинки сверхвысокой четкости: на один цвет приходится бит. А точек у вас в картинке — миллион. И вот тут уже играет роль, используете вы тип или .
Тип
Этот тип получил свое название от — его еще называют длинное целое. В отличие от типа , у него просто гигантский диапазон значений: от до
Почему же он не является основным целым типом?
Все дело в том, что Java появилась еще в середине 90-х, когда большинство компьютеров были 32-х разрядными. А это значило, что все процессоры были заточены под работу с числами из 32-х бит. С целыми числами из 64-х бит процессоры работать уже умели, но операции с ними были медленнее.
Поэтому программисты разумно решили сделать стандартным целым типом тип , ну а тип использовать только тогда, когда без него действительно не обойтись.
Тип
Это самый маленький целочисленный тип в Java, но далеко не самый редко используемый. Его название совпадает со словом — минимальная адресуемая ячейка памяти в Java.
Размер допустимых значений типа не так уж велик: от до . Но не в этом его сила. Тип чаще всего используется, когда нужно хранить в памяти большой блок обезличенных данных. Массив типа просто идеально подходит для этих целей.
Например, вам нужно куда-то скопировать файл.
Вам не нужно обрабатывать содержимое файла: вы просто хотите создать область памяти (буфер), скопировать в нее содержимое файла, а затем записать эти данные из буфера в другой файл. Массив типа — то, что нужно для этих целей.
Тем более, что в переменной-типа-массив хранится только ссылка на область памяти. При передаче значения этой переменной в какой-то метод произойдет только передача адреса в памяти, а сам блок памяти копироваться на будет.
Виды типов значенийKinds of value types
Тип значения может относится к одному из двух следующих видов:A value type can be one of the two following kinds:
- тип структуры, который инкапсулирует данные и связанные функции;a structure type, which encapsulates data and related functionality
- тип перечисления, который определяется набором именованных констант и представляет выбор или сочетание вариантов для выбора.an enumeration type, which is defined by a set of named constants and represents a choice or a combination of choices
Тип значений , допускающий значение NULL, представляет все значения своего базового типа значения , а также дополнительное значение NULL.A nullable value type represents all values of its underlying value type and an additional null value. Вы не можете назначить переменной типа значения, если только это не тип, допускающий значение NULL.You cannot assign to a variable of a value type, unless it’s a nullable value type.
Символы
В C# символы представлены не 8-разрядным кодом, как во многих других языках
программирования, например , а 16-разрядным кодом, который называется юникодом (Unicode). В юникоде набор символов представлен настолько широко, что он охватывает символы практически из всех естественных языков на свете. Если для многих
естественных языков, в том числе английского, французского и немецкого, характерны
относительно небольшие алфавиты, то в ряде других языков, например китайском,
употребляются довольно обширные наборы символов, которые нельзя представить
8-разрядным кодом. Для преодоления этого ограничения в C# определен тип char,
представляющий 16-разрядные значения без знака в пределах от 0 до 65 535. При этом
стандартный набор символов в 8-разрядном коде ASCII является подмножеством юникода в пределах от 0 до 127. Следовательно, символы в коде ASCII по-прежнему остаются действительными в C#.
Для того чтобы присвоить значение символьной переменной, достаточно заключить это значение (т.е. символ) в одинарные кавычки:
Несмотря на то что тип char определен в C# как целочисленный, его не следует
путать со всеми остальными целочисленными типами. Дело в том, что в C# отсутствует автоматическое преобразование символьных значений в целочисленные и обратно.
Например, следующий фрагмент кода содержит ошибку:
Наравне с представлением char как символьных литералов, их можно представлять
как 4-разрядные шестнадцатеричные значения Unicode (например, ‘\u0041’), целочисленные значения с приведением (например, (char) 65) или же шестнадцатеричные значения (например, ‘\x0041’). Кроме того, они могут быть представлены в виде .
Таблицы
наиболее точной суммыабсолютного
Порядок | Naive | Kahan | Rump–Ogita–Oishi |
---|---|---|---|
~ | 4.86 21 |
0 0 |
0 0 |
↑ | 4.97 14 |
0 0 |
0 0 |
↓ | 4.50 19 |
0 0 |
0 0 |
Порядок | Naive | Kahan | Rump–Ogita–Oishi |
---|---|---|---|
±~ | 158.96 7936 |
0 0 |
0 0 |
±↑ | 86.35 2560 |
0 0 |
0 0 |
±↓ | 175.70 11776 |
0 0 |
0 0 |
6
Порядок | Naive | Kahan | Rump–Ogita–Oishi |
---|---|---|---|
~ | 143.00 562 |
0 0 |
0 0 |
↑ | 126.60 473 |
0 0 |
0 0 |
↓ | 161.91 482 |
0 0 |
0 0 |
±~ | 2015.41 38889 |
0 0 |
0 0 |
±↑ | 1520.84 33965 |
0 0 |
0 0 |
±↓ | 1672.76 36513 |
0 0 |
0 0 |
6
Порядок | Naive | Kahan | Rump–Ogita–Oishi |
---|---|---|---|
~ | 4277.17 4662 |
0 0 |
0 0 |
↑ | 29.17 111 |
0 0 |
0 0 |
↓ | 7508.68 7915 |
0 0 |
0 0 |
±~ | 475.68 8221 |
1.09 21 |
0 0 |
±↑ | 65.39 861 |
0.01 1 |
0 0 |
±↓ | 270.34 1736 |
0 0 |
0 0 |
о
Порядок | Naive | Kahan | Rump–Ogita–Oishi |
---|---|---|---|
~ | 4.84 19 |
0.01 1 |
0 0 |
↑ | 2.81 11 |
0.01 1 |
0 0 |
↓ | 6.53 26 |
0 0 |
0 0 |
±~ | 33.83 1650 |
1.26 23 |
0 0 |
±↑ | 12.76 422 |
0.31 6 |
0 0 |
±↓ | 18.54 548 |
0 0 |
0 0 |
Порядок | Naive | Kahan | Rump–Ogita–Oishi |
---|---|---|---|
~ | 17.77 483 |
1.46 61 |
0 0 |
↑ | 10.92 243 |
0.34 19 |
0 0 |
↓ | 19.71 734 |
0 0 |
0 0 |
Порядок | Всё подряд | Отдельно + и – |
---|---|---|
±~ | 78.65 2336 |
2121.61 50048 |
±↑ | 76.27 2496 |
2465.55 66432 |
±↓ | 52.74 480 |
2863.61 99200 |
6
Порядок | Naive | Kahan | Rump–Ogita–Oishi |
---|---|---|---|
~ | 210 | 522 | |
↑ | 410 | ||
↓ | 42 |
для себя
- Если нужно испортить сумму, получаемую наивным алгоритмом, то можно складывать отдельно положительные и отдельно отрицательные числа. Иногда, но не всегда, сортировка чисел по убыванию модуля также «ломает» наивный алгоритм, лучше сортировать по возрастанию, но и это в ряде случаев даёт более плохой результат, чем в случае хаотичного порядка.
- Если нужно получить ожидаемо хороший результат, следует взять алгоритм Rump–Ogita–Oishi, я не нашёл случайных тестов, при которых он выдавал бы не наиболее точную сумму. Недостаток алгоритма в том, что работать он будет медленнее наивного сложения. Ускорить такой алгоритм, вероятно, можно, пользуясь битовым представлением числа с плавающей запятой и кое-какими трюками, но у нас такой задачи на этот обзор не было.
- Алгоритм Кэхэна значительно лучше наивного суммирования и гораздо проще алгоритма Rump–Ogita–Oishi, поэтому наиболее целесообразен на практике, где точность наивного метода вам не подходит. Просто убедитесь заранее, что ваши числа не обладают какими-то экстраординарными свойствами, которые «завалят» данный алгоритм.
- Наивное суммирование в общем-то не такое страшное, как могло показаться. Ну, подумаешь, потеряли мы 5 десятичных цифр (если N не более миллиона). Много? Если их у вас 16, то вроде бы не страшно, а если 7 (для float), то вроде бы… хотя, друзья, неужели кто-то будет складывать миллион чисел в типе float? Вы серьёзно? Это допустимо только если вам нужна одна правильная цифра, либо если массив обладает какой-то особой спецификой, и то я бы не рискнул.
- Это творческий процесс, я создал свою субъективную схему тестирования и не считаю это научно-достоверным знанием, а потому не выношу на суд общественности. Коды алгоритмов я дал выше, если вам очень нужно, то не составит труда написать своё сравнение алгоритмов и получить свои данные.
- Мой код не автоматизирован, чтобы его запускать для получения разных тестов, нужно ковыряться в программе и менять какие-то параметры, это просто неприлично — давать такой код. А написание полноценной системы тестирования в задачи публикации не входило.
Приоритет и ассоциативность операторовOperator precedence and associativity
В следующем списке перечислены арифметические операторы в порядке убывания приоритета:The following list orders arithmetic operators starting from the highest precedence to the lowest:
- Постфиксный инкремент и декремент Postfix increment and decrement operators
- Префиксный инкремент и декремент , унарные операторы и Prefix increment and decrement and unary and operators
- Мультипликативные операторы , , и Multiplicative , , and operators
- Аддитивные операторы и Additive and operators
Бинарные арифметические операторы имеют левую ассоциативность.Binary arithmetic operators are left-associative. То есть операторы с одинаковым приоритетом вычисляются в направлении слева направо.That is, operators with the same precedence level are evaluated from left to right.
Порядок вычисления, определяемый приоритетом и ассоциативностью операторов, можно изменить с помощью скобок ().Use parentheses, , to change the order of evaluation imposed by operator precedence and associativity.
Полный список операторов C#, упорядоченный по уровню приоритета, можно найти в разделе статьи Операторы C#.For the complete list of C# operators ordered by precedence level, see the section of the C# operators article.
Nullable-типы (нулевые типы) и операция ??
Объявление и инициализация Nullable-переменных
В работе с типами-значениями есть одна особенность, они не могут иметь значение null. При наличии любой из следующих строк кода, компиляция программы не будет выполнена:
int nv = null; bool bv = null;
На практике, особенно при работе с базами данных, может возникнуть ситуация, когда в записи из таблицы пропущены несколько столбцов (нет данных), в этом случае, соответствующей переменной нужно будет присвоить значение null, но она может иметь тип int или double, что приведет к ошибке.
Можно объявить переменную с использованием символа ? после указания типа, тогда она станет nullable-переменной – переменной поддерживающей null-значение:
int? nv1 = null; bool? bv1 = null;
Использование символа ? является синтаксическим сахаром для конструкции Nullable<T>, где T – это имя типа. Представленные выше примеры можно переписать так:
Nullable<int> nv1 = null; Nullable<bool> bv1 = null;
Проверка на null. Работа с HasValue и Value
Для того чтобы проверить, что переменная имеет значение null можно воспользоваться оператором is с шаблоном типа:
bool? flagA = true; if(flagA is bool valueOfFlag) { Console.WriteLine("flagA is not null, value: {valueOfFlag}"); }
Также можно воспользоваться свойствами класса Nullable
-
Nullable<T>.HasValue
Возвращает true если переменная имеет значение базового типа. То есть если она не null.
-
Nullable<T>.Value
Возвращает значение переменной если HasValue равно true, иначе выбрасывает исключение InvalidOperationException.
bool? flagB = false; if(flagB.HasValue) { Console.WriteLine("flagB is not null, value: {flagB.Value}"); }
Приведение Nullable-переменной к базовому типу
При работе с Nullable-переменными их нельзя напрямую присваивать переменным базового типа. Следующий код не будет скомпилирован:
double? nvd1 = 12.3; double nvd2 = nvd1; // error
Для приведения Nullable-переменной к базовому типу можно воспользоваться явным приведением:
double nvd3 = (double) nvd1;
В этом случае следует помнить, что если значение Nullable-переменной равно null, то при выполнении данной операции будет выброшено исключение InvalidOperationException.
Второй вариант – это использование оператора ??, при этом нужно дополнительно задаться значением, которое будет присвоено переменной базового типа если в исходной лежит значение null
double nvd4 = nvd1 ?? 0.0; Console.WriteLine(nvd4); bool? nvb1 = null; bool nvb2 = nvb1 ?? false; Console.WriteLine(nvb1); Console.WriteLine(nvb2);
Второй вариант позволяет более лаконично обрабатывать ситуацию, когда вызов какого-то метода может возвращать null, а результат его работы нужно присвоить типу-значению, при этом заранее известно, какое значение нужно присвоить переменной в этой ситуации:
static int? GetValue(bool flag) { if (flag == true) return 1000; else return null; } static void Main(string[] args) { int test1 = GetValue(true) ?? 123; Console.WriteLine(test1); int test2 = GetValue(false) ?? 123; Console.WriteLine(test2); }