Примитивные типы в java
Содержание:
- Общие понятия
- Типизация переменных
- Work with Exponential Numbers
- Целочисленный тип данных int
- Float ranges and precision
- Переполнение переменных
- Деление целочисленных переменных
- Диапазоны значений и знак целочисленных типов данных
- Целочисленные типы
- Строки
- Динамическое выделение памяти для структур
- Моя критика
- Nullable-типы (нулевые типы) и операция ??
- Объединения
Общие понятия
Типом данных в программировании называют совокупность двух множеств: множество значений и множество операций, которые можно применять к ним. Например, к типу данных целых неотрицательных чисел, состоящего из конечного множества натуральных чисел, можно применить операции сложения (+), умножения (*), целочисленного деления (/), нахождения остатка (%) и вычитания (−).
Язык программирования, как правило, имеет набор примитивных типов данных — типы, предоставляемые языком программирования как базовая встроенная единица. В C++ такие типы создатель языка называет фундаментальными типами. Фундаментальными типами в C++ считаются:
- логический ();
- символьный (напр., );
- целый (напр., );
- с плавающей точкой (напр., );
- перечисления (определяется программистом);
- .
Поверх перечисленных строятся следующие типы:
- указательные (напр., );
- массивы (напр., );
- ссылочные (напр., );
- другие структуры.
Перейдём к понятию литерала (напр., 1, 2.4F, 25e-4, ‘a’ и др.): литерал — запись в исходном коде программы, представляющаясобой фиксированное значение. Другими словами, литерал — это просто отображение объекта (значение) какого-либо типа в коде программы. В C++ есть возможность записи целочисленных значений, значений с плавающей точкой, символьных, булевых, строковых.
Литерал целого типа можно записать в:
- 10-й системе счисления. Например, ;
- 8-й системе счисления в формате 0 + число. Например, ;
- 16-й системе счисления в формате 0x + число. Например, .
24, 030, 0x18 — это всё записи одного и того же числа в разных системах счисления.
Для записи чисел с плавающей точкой используют запись через точку: 0.1, .5, 4. — либо в
экспоненциальной записи — 25e-100. Пробелов в такой записи быть не должно.
Имя, с которым мы можем связать записанные литералами значения, называют переменной. Переменная — это поименованная либо адресуемая иным способом область памяти, адрес которой можно использовать для доступа к данным. Эти данные записываются, переписываются и стираются в памяти определённым образом во время выполнения программы. Переменная позволяет в любой момент времени получить доступ к данным и при необходимости изменить их. Данные, которые можно получить по имени переменной, называют значением переменной.
Для того, чтобы использовать в программе переменную, её обязательно нужно объявить, а при необходимости можно определить (= инициализировать). Объявление переменной в тексте программы обязательно содержит 2 части: базовый тип и декларатор. Спецификатор и инициализатор являются необязательными частями:
const int example = 3; // здесь const — спецификатор // int — базовый тип // example — имя переменной // = 3 — инициализатор.
Имя переменной является последовательностью символов из букв латинского алфавита (строчных и прописных), цифр и/или знака подчёркивания, однако первый символ цифрой быть не может. Имя переменной следует выбирать таким, чтобы всегда было легко догадаться о том, что она хранит, например, «monthPayment». В конспекте и на практиках мы будем использовать для правил записи переменных нотацию CamelCase. Имя переменной не может совпадать с зарезервированными в языке словами, примеры таких слов: if, while, function, goto, switch и др.
Декларатор кроме имени переменной может содержать дополнительные символы:
- — указатель; перед именем;
- — константный указатель; перед именем;
- — ссылка; перед именем;
- — массив; после имени;
- — функция; после имени.
Инициализатор позволяет определить для переменной её значение сразу после объявления. Инициализатор начинается с литерала равенства (=) и далее происходит процесс задания значения переменной. Вообще говоря, знак равенства в C++ обозначает операцию присваивания; с её помощью можно задавать и изменять значение переменной. Для разных типов он может быть разным.
Спецификатор задаёт дополнительные атрибуты, отличные от типа. Приведённый в примере спецификатор const позволяет запретить последующее изменение значение переменной. Такие неизменяемые переменные называют константными или константой.
Объявить константу без инициализации не получится по логичным причинам:
const int EMPTY_CONST; // ошибка, не инициализована константная переменная const int EXAMPLE = 2; // константа со значением 2 EXAMPLE = 3; // ошибка, попытка присвоить значение константной переменной
Для именования констант принято использовать только прописные буквы, разделяя слова символом нижнего подчёркивания.
Типизация переменных
Именно так работали бы переменные, если бы в не существовало типизации. Типизация – это возможность разделить коробочки по возможному содержимому. То есть, когда мы создаем коробочку, мы кроме имени указываем, что в ней может располагаться. И тогда, в коробочку для IPhone котеночка ты уже не засунешь.
Это позволяет дополнительно защититься от ошибок, потому что ты будешь заранее знать, что будет в коробочке, и будешь готов к тому, как тебе нужно будет себя вести с содержимым.
Языки программирования условно можно разделить на два больших типа:
Сильнотипизированные – те, где вся ответственность за указание типа переменных ложится на программиста
Слаботипизированные – те, где компьютер сам решает, какой тип используется в конкретном случае.
Язык C# относится к первым. Возможно, это лишает его такой гибкости как тот же самый JavaScript (который относится ко вторым), но при этом дает большую защищенность от ошибок.
Work with Exponential Numbers
As mentioned above, and can also be used to represent exponential numbers. For example,
C++ outputs exponential numbers and very large numbers in a format called the scientific format. The variable ex will be outputted in this format by default since it is a very large number.
In order to force C++ to display our floating-point numbers in the format regardless of the size of the number, we use the format specifier inside of .
In addition to this, there is another format specifier known as , which displays floating-point numbers in the decimal format.
It is similar to displaying floating-point numbers by only using without , except for the fact that displays numbers up to 6 decimal points.
On the other hand, only using displays digits according to the specific compiler (6 total digits in the case of MinGW compiler, including the digits before the decimal point).
Example 4: Fixed and Scientific Formats
Output
Displaying Output With fixed: Double Type Number 1 = 3.912348 Double Type Number 2 = 32500.000000 Float Type Number 1 = 3.912348 Float Type Number 2 = 32500.000000 Displaying Output With scientific: Double Type Number 1 = 3.912348e+000 Double Type Number 2 = 3.250000e+004 Float Type Number 1 = 3.912348e+000 Float Type Number 2 = 3.250000e+004
Целочисленный тип данных int
Тип данных int происходит от английского слова integer (“целое число”). Этот тип предназначен для хранения целых чисел различного диапазона.
Под этот тип, обычно, выделяется 4 байта, а это значит, что диапазон допустимых значений составляет от -2 147 483 648 до 2 147 483 647.
Тип int встречается довольно часто и обычно является типом данных по-умолчанию.
Далее, прежде чем продолжить разговор о типе данных int, да и вообще обо всех остальных типах данных, я хочу пояснить такое понятие, как модификаторы типов.
Модификаторы типов
Модификаторы типа — это специальные служебные слова, которые можно указывать перед основным типом, для его изменения.
В языке С++ есть следующие модификаторы типов:
signed (знаковый) — этот модификатор означает, что переменная такого типа может содержать как положительные значения, так и отрицательные значения.
Например: применив этот модификатор к типу int, мы ничего нового не получим, потому что тип int и так является знаковым типом по-умолчанию. То есть ничего не поменяется, если в коде программы вы напишите signed int и просто int. Просто signed, обычно, опускают, потому что зачем писать больше, если можно написать просто int. Иногда, если хотят выделить то, что тип является знаковым, то могут добавить слово signed.
unsigned (беззнаковый) — этот модификатор сдвигает диапазон принимаемых значений в положительном направлении, таким образом увеличивая диапазон в 2 раза.
Например: если мы применим unsigned к типу int, то что мы получим? У типа int уже диапазон принимаемых значений будет не от -2 147 483 648 до 2 147 483 647, а от 0 до 4 294 967 295.
Есть еще 2 спецификатора, которые могут быть применены к типу int — это short (короткий) и long (длинный). Соответственно, short укорачивает диапазон принимаемых значений, а long расширяет.
Точно, на сколько диапазон принимаемых значений увеличивается, а на сколько уменьшается, сказать сложно. Потому что все зависит от компилятора и операционной системы.
Таким образом, могут быть следующие варианты записи идентификатора int: signed int, int, unsigned int, signed short int, short int, unsigned short int, signed long int, long int, unsigned long int.
Программа с примером использования типа данных int
Float ranges and precision
In order to find the value ranges of the floating-point number in your platform, you can use the header file. This header file defines macros such as and that store the float value ranges and precision of the type.
You can also find the corresponding macros for and with the prefixes and
The following program illustrates the storage size and precision of floating-point numbers in your system.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* intmain() { printf(«Storage size: %d bytes\n» «Minimum float value: %E\n» «Maximum float value: %E\n» «Precision: %d decimal digits\n», sizeof(float), FLT_MIN, FLT_MAX, FLT_DIG); puts(«\nExample of float precision:\n»); doubled=12345.6; floatf=(float)d; printf(«The floating-point number d « «%18.10f\n»,d); printf(«Stored in a variable f\n» «of type float as the value « «%18.10f\n»,f); return; } |
The output of the program in our system is as follows:
1 2 3 4 5 6 7 8 9 10 |
Storage size4bytes Minimum floatvalue1.175494E-038 Maximum floatvalue3.402823E+038 Precision6decimal digits Example of floatprecision The floating-point numberd12345.6000000000 Stored inavariablef of type floatasthe value12345.5996093750 |
In this tutorial, we have introduced you various C float types and explained what precision means to each type of float in C.
- Was this tutorial helpful ?
Переполнение переменных
Си не следит за переполнением переменных. Это значит, что постоянно увеличивая значение, скажем, переменной типа int в конце концов мы «сбросим значение»
#include <conio.h> #include <stdio.h> void main() { unsigned a = 4294967295; int b = 2147483647; //Переполнение беззнакового типа printf("%u\n", a); a += 1; printf("%u", a); //Переполнение знакового типа printf("%d\n", b); b += 1; printf("%d", b); getch(); }
Вообще, поведение при переполнении переменной определено только для типа unsigned: Беззнаковое целое сбросит значение.
Для остальных типов может произойти что угодно, и если вам необходимо следить за переполнением, делайте это вручную, проверяя аргументы,
либо используйте иные способы, зависящие от компилятора и архитектуры процессора.
Деление целочисленных переменных
В языке C++ при делении двух целых чисел, где результатом является другое целое число, всё довольно предсказуемо:
#include <iostream>
int main()
{
std::cout << 20 / 4 << std::endl;
return 0;
}
1 |
#include <iostream> intmain() { std::cout<<204<<std::endl; return; } |
Результат:
Но что произойдет, если в результате деления двух целых чисел мы получим дробное число? Например:
#include <iostream>
int main()
{
std::cout << 8 / 5 << std::endl;
return 0;
}
1 |
#include <iostream> intmain() { std::cout<<85<<std::endl; return; } |
Результат:
В языке C++ при делении целых чисел результатом всегда будет другое целое число. А такие числа не могут иметь дробь (она просто отбрасывается, не округляется!).
Рассмотрим детально вышеприведенный пример: . Но как мы уже знаем, при делении целых чисел результатом является другое целое число. Таким образом, дробная часть () значения отбрасывается и остается .
Правило: Будьте осторожны при делении целых чисел, так как любая дробная часть всегда отбрасывается.
Диапазоны значений и знак целочисленных типов данных
Как вы уже знаете из предыдущего урока, переменная с n-ным количеством бит может хранить 2n возможных значений. Но что это за значения? Это значения, которые находятся в диапазоне. Диапазон — это значения от и до, которые может хранить определенный тип данных. Диапазон целочисленной переменной определяется двумя факторами: её размером (измеряется в битах) и её знаком (который может быть signed или unsigned).
Целочисленный тип signed (со знаком) означает, что переменная может содержать как положительные, так и отрицательные числа. Чтобы объявить переменную как signed, используйте ключевое слово :
signed char c;
signed short s;
signed int i;
signed long l;
signed long long ll;
1 |
signedcharc; signedshorts; signedinti; signedlongl; signedlonglongll; |
По умолчанию, ключевое слово пишется перед типом данных.
1-байтовая целочисленная переменная со знаком (signed) имеет диапазон значений от -128 до 127, т.е. любое значение от -128 до 127 (включительно) может храниться в ней безопасно.
В некоторых случаях мы можем заранее знать, что отрицательные числа в программе использоваться не будут. Это очень часто встречается при использовании переменных для хранения количества или размера чего-либо (например, ваш рост или вес не может быть отрицательным).
Целочисленный тип unsigned (без знака) может содержать только положительные числа. Чтобы объявить переменную как unsigned, используйте ключевое слово :
unsigned char c;
unsigned short s;
unsigned int i;
unsigned long l;
unsigned long long ll;
1 |
unsignedcharc; unsignedshorts; unsignedinti; unsignedlongl; unsignedlonglongll; |
1-байтовая целочисленная переменная без знака (unsigned) имеет диапазон значений от 0 до 255.
Обратите внимание, объявление переменной как unsigned означает, что она не сможет содержать отрицательные числа (только положительные). Теперь, когда вы поняли разницу между signed и unsigned, давайте рассмотрим диапазоны значений разных типов данных:
Теперь, когда вы поняли разницу между signed и unsigned, давайте рассмотрим диапазоны значений разных типов данных:
Размер/Тип | Диапазон значений |
1 байт signed | от -128 до 127 |
1 байт unsigned | от 0 до 255 |
2 байта signed | от -32 768 до 32 767 |
2 байта unsigned | от 0 до 65 535 |
4 байта signed | от -2 147 483 648 до 2 147 483 647 |
4 байта unsigned | от 0 до 4 294 967 295 |
8 байтов signed | от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 |
8 байтов unsigned | от 0 до 18 446 744 073 709 551 615 |
Для математиков: Переменная signed с n-ным количеством бит имеет диапазон от -(2n-1) до 2n-1-1. Переменная unsigned с n-ным количеством бит имеет диапазон от 0 до (2n)-1.
Для нематематиков: Используем таблицу
Начинающие программисты иногда путаются между signed и unsigned переменными. Но есть простой способ запомнить их различия. Чем отличается отрицательное число от положительного? Правильно! Минусом спереди. Если минуса нет, значит число — положительное. Следовательно, целочисленный тип со знаком (signed) означает, что минус может присутствовать, т.е. числа могут быть как положительными, так и отрицательными. Целочисленный тип без знака (unsigned) означает, что минус спереди отсутствует, т.е. числа могут быть только положительными.
Целочисленные типы
В языке C существует несколько типов целых чисел. Они различаются между собой объемом памяти, отводимым под переменную, а также возможностью присваивания положительных и отрицательных значений. От объема памяти, т. е. от количества выделяемых байтов под переменную, зависит, каким может быть максимально возможное значение, записанное в данную переменную. Следует отметить, что в языке Си объем памяти, выделяемый под конкретный тип, может зависеть от операционной системы.
Так, если под переменную какого-либо целочисленного типа выделяется 2 байта, что составляет 16 бит, и ей можно присваивать только положительные числа и ноль, то эти числа будут в диапазоне от 0 до 65535, т. к. 216 = 65536, но одна вариация забирается на нуль. Если же тип допускает отрицательные числа, то диапазон допустимых значений уже будет лежать в пределах от -32768 до +32767.
Часто в программах используется тип int. Вот пример, где происходит объявление и определение (присваивание значений) целочисленных переменных, а также вывод их значений на экран:
#include <stdio.h> int main() { int lines, i; int count = ; lines = 100; i = -1; printf("%5d %5d %5d\n", i, count+10, lines); }
Обратите внимание, что в языке C присваивать значение можно при объявлении переменных. Обычно под переменную типа int, которая может принимать как положительные так и отрицательные значения, отводится 4 байта, что равно 32-м битам
Отсюда допустимый диапазон значений будет лежать в пределах от -2 147 483 648 до 2 147 483 647. Если в исходном коде на C мы объявим переменную int max, присвоим ей максимально допустимое значение, а потом будем его увеличивать, то сообщений об ошибке не будет ни на этапе компиляции, ни на этапе выполнения
Обычно под переменную типа int, которая может принимать как положительные так и отрицательные значения, отводится 4 байта, что равно 32-м битам. Отсюда допустимый диапазон значений будет лежать в пределах от -2 147 483 648 до 2 147 483 647. Если в исходном коде на C мы объявим переменную int max, присвоим ей максимально допустимое значение, а потом будем его увеличивать, то сообщений об ошибке не будет ни на этапе компиляции, ни на этапе выполнения.
#include <stdio.h> int main() { int max = 2147483647; printf("%d\n", max+1); printf("%d\n", max+2); printf("%d\n", max+10); }
Результат будет таким:
-2147483648 -2147483647 -2147483639
Чтобы понять, почему такое происходит, представьте себе числовую ось не в виде прямой, а в виде окружности. Когда мы достигаем конца, двигаясь например по часовой стрелке, то это значит, что мы пришли в начало. Поэтому, продолжая движение по часовой стрелке, следующее число, которое мы увидим за максимально возможным, – это самое минимальное. Данную особенность языка Си следует иметь в виду при выполнении арифметических действий.
То же самое с минимумом int. Если мы начнем из него вычитать, т. е. двигаться против часовой стрелки, то перескочим максимальную границу и будем идти в направлении уменьшения уже от нее:
#include <stdio.h> int main() { int min = -2147483648; printf("%d\n", min-1); printf("%d\n", min-2); printf("%d\n", min-10); }
Результат:
2147483647 2147483646 2147483638
Помимо типа int в языке программирования C существуют другие (модифицированные) целочисленные типы:
-
short — отводится меньше байтов, чем на int;
-
long — отводится больше байтов, чем на int (не всегда, зависит от системы);
-
unsigned — столько же байт как у int, но без отрицательных чисел; в результате чего знаковый разряд освобождается, и количество положительных значений увеличивается;
-
unsigned short;
-
unsigned long.
При выводе длинных чисел следует дополнять спецификацию формата буквой l перед буквой формата. Например:
printf("%ld\n", i); printf("%15ld\n", i);
Строки
В языке программирования С нет отдельного строкового типа данных, хотя формат вывода строки есть (%s). Строки в C – это массивы символов, последний элемент которых является первым (с номером 0) символом в таблице ASCII. В этом месте таблицы стоит «ничто», имеющее символьное обозначение ‘\0’.
С другой стороны, строки — это необычные массивы в том смысле, что работа с ними в языке программирования C несколько отличается от работы с числовыми массивами. В этом мы убедимся позже.
Выше мы объявили и определили массив vowels. Если бы мы его определили вот так:
char vowels = {'a', 'e', 'i', 'o', 'u', 'y', '\0'};
или так:
char vowels1 = "aeiouy";
то он был бы строкой. Во втором случае сами двойные кавычки «говорят» что это строка, и символ окончания строки ‘\0’ записывается в память автоматом.
Массивы символов можно выводить на экран, просто указав имя переменной, а вот с массивами чисел такой номер не пройдет:
printf("%s\n", vowels); printf("%f\n", f_arr); // ошибка
Динамическое выделение памяти для структур
Динамически выделять память под массив структур необходимо в том случае, если заранее неизвестен размер массива. Для определения размера структуры в байтах используется операция sizeof(ИмяСтруктуры).Пример Библиотека из 3 книг
12345678910111213141516171819202122232425262728293031323334
#include <stdio.h>#include <stdlib.h>#include <malloc.h>struct book{ char title; char author; int value;};int main(){ struct book *lib; int i; system(«chcp 1251»); system(«cls»); lib = (struct book*)malloc(3 * sizeof(struct book)); for (i = 0; i<3; i++) { printf(«Введите название %d книги : «, i + 1); gets_s((lib + i)->title); printf(«Введите автора %d книги : «, i + 1); gets_s((lib + i)->author); printf(«Введите цену %d книги : «, i + 1); scanf_s(«%d», &(lib + i)->value); getchar(); } for (i = 0; i<3; i++) { printf(«\n %d. %s «, i + 1, (lib + i)->author); printf(«%s %d», (lib + i)->title, (lib + i)->value); } getchar(); return 0;}
Язык Си
Моя критика
Если вкратце, то знание и постоянное использование всех этих правил сильно нагружает мышление. Допущение же ошибки в их применении приводит к риску написания неверного или непортируемого кода. При этом такие ошибки могут как всплыть сразу, так и таиться в течение дней или даже долгих лет.
Сложности начинаются с битовой ширины базовых целочисленных типов, которая зависит от реализации. Например, может иметь 16, 32, 64 бита или другое их количество. Всегда нужно выбирать тип с достаточным диапазоном. Но иногда использование слишком обширного типа (например, необычного 128-битного ) может вызвать сложности или даже внести уязвимости. Усугубляется это тем, что такие типы из стандартных библиотек, как , не имеют связи с другими типами вроде беззнакового или ; стандарт позволяет им быть шире или уже.
Правила преобразования совершенно безумны. Что еще хуже, практически везде допускаются неявные преобразования, существенно затрудняющие аудит человеком. Беззнаковые типы достаточно просты, но знаковые имеют очень много допустимых реализаций (например, обратный код, создание исключений). Типы с меньшим рангом, чем , продвигаются автоматически, вызывая труднопонимаемое поведение с диапазонами и переполнение. Когда операнды отличаются знаковостью и рангами, они преобразуются в общий тип способом, который зависит от определяемой реализацией битовой ширины. Например, выполнение арифметики над двумя операндами, как минимум один из которых имеет беззнаковый тип, приведет к преобразованию их обоих либо в знаковый, либо в беззнаковый тип в зависимости от реализации.
Арифметические операции изобилуют неопределенным поведением: знаковое переполнение в , деление на нуль, битовые сдвиги. Несложно создать такие условия неопределенного поведения по случайности, но сложно вызвать их намеренно или обнаружить при выполнении, равно как выявить их причины. Необходима повышенная внимательность и усилия для проектирования и реализации арифметического кода, исключающего переполнение/UB. Стоит учитывать, что впоследствии становится сложно отследить и исправить код, при написании которого не соблюдались принципы защиты от переполнения/UB.
Присутствие и версии каждого целочисленного типа удваивает количество доступных вариантов. Это создает дополнительную умственную нагрузку, которая не особо оправдывается, так как типы со знаком способны выполнять практически все те же функции, что и беззнаковые.
Ни в одном другом передовом языке программирования нет такого числа правил и подводных камней касательно целочисленных типов, как в С и C++. Например:В Java целые числа ведут себя одинаково в любой среде. В этом языке определено конкретно 5 целочисленных типов (в отличие от C/C++, где их не менее 10). Они имеют фиксированную битовую ширину, практически все из них имеют знаки (кроме ), числа со знаком должны находиться в дополнительном коде, неявные преобразования допускают только их варианты без потерь, а вся арифметика и преобразования определяются точно и не вызывают неоднозначного поведения. Целочисленные типы в Java поддерживают быстрое вычисление и эффективное упаковывание массивов в сравнении с языками вроде Python, где есть только переменного размера.
Java в значительной степени опирается на 32-битный тип , особенно для перебора массивов. Это означает, что этот язык не может эффективно работать на малопроизводительных 16-битных ЦПУ (часто используемых во встраиваемых микроконтроллерах), а также не может непосредственно работать с большими массивами в 64-битных системах
К сравнению, C/C++ позволяет писать код, эффективно работающий на 16, 32 и/или 64-битных ЦПУ, но при этом требует от программиста особой осторожности.
В Python есть всего один целочисленный тип, а именно. В сравнении с C/C++ это сводит на нет все рассуждения на тему битовой ширины, знаковости и преобразований, так как во всем коде правит один тип
Тем не менее за это приходится платить низкой скоростью выполнения и несогласованным потреблением памяти.
В JavaScript вообще нет целочисленного типа. Вместо этого в нем все выражается через математику (в C/C++). Из-за этого битовая ширина и числовой диапазон оказываются фиксированными, числа всегда имеют знаки, преобразования отсутствуют, а переполнение считается нормальным.
Язык ассемблера для любой конкретной машинной архитектуры (x86, MIPS и т.д.) определяет набор целочисленных типов фиксированной ширины, арифметические операции и преобразования – с редкими случаями неопределенного поведения или вообще без них.
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); }
Объединения
Объединениями называют сложный тип данных, позволяющий размещать в одном и том же месте оперативной памяти данные различных типов.
Размер оперативной памяти, требуемый для хранения объединений, определяется размером памяти, необходимым для размещения данных того типа, который требует максимального количества байт.
Когда используется элемент меньшей длины, чем наиболее длинный элемент объединения, то этот элемент использует только часть отведенной памяти. Все элементы объединения хранятся в одной и той же области памяти, начиная с одного адреса.
Общая форма объявления объединения
union ИмяОбъединения{ тип ИмяОбъекта1; тип ИмяОбъекта2; . . . тип ИмяОбъектаn;};
Объединения применяются для следующих целей:
- для инициализации объекта, если в каждый момент времени только один из многих объектов является активным;
- для интерпретации представления одного типа данных в виде другого типа.
Например, удобно использовать объединения, когда необходимо вещественное число типа float представить в виде совокупности байтов
123456789101112131415161718
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>union types{ float f; unsigned char b;};int main(){ types value; printf(«N = «); scanf(«%f», &value.f); printf(«%f = %x %x %x %x», value.f, value.b, value.b, value.b, value.b); getchar(); getchar(); return 0;}
Пример
123456789101112131415161718192021222324
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>int main() { char temp; system(«chcp 1251»); system(«cls»); union { unsigned char p; unsigned int t; } type; printf(«Введите число : «); scanf(«%d», &type.t); printf(«%d = %x шестн.\n», type.t, type.t); // Замена байтов temp = type.p; type.p = type.p; type.p = temp; printf(«Поменяли местами байты, получили\n»); printf(«%d = %x шестн.\n», type.t, type.t); getchar(); getchar(); return 0;}
Результат выполнения