Базовые типы данных в c. Сколько «весят» типы данных. Размеры типов данных

В языке Си различают понятия “тип данных” и “модификатор типа”. Тип данных – это целый, а модификатор – со знаком или без знака. Целое со знаком будет иметь как положительные, так и отрицательные значения, а целое без знака – только положительные значения. В языке Си можно выделить пять базовых типов.

  • char – символьный.
  • Переменная типа char имеет размер 1 байт, ее значениями являются различные символы из кодовой таблицы, например: ‘ф’, ‘:’, ‘j’ (при записи в программе они заключаются в одинарные кавычки).

  • int – целый.
  • Размер переменной типа int в стандарте языка Си не определен. В большинстве систем программирования размер переменной типа int соответствует размеру целого машинного слова. Например, в компиляторах для 16-разрядных процессоров переменная типа int имеет размер 2 байта. В этом случае знаковые значения этой переменной могут лежать в диапазоне от -32768 до 32767.

  • float – вещественный.
  • Ключевое слово float позволяет определить переменные вещественного типа. Их значения имеют дробную часть, отделяемую точкой, например: -5.6, 31.28 и т.п. Вещественные числа могут быть записаны также в форме с плавающей точкой, например: -1.09e+4. Число перед символом “е” называется мантиссой, а после “е” – порядком. Переменная типа float занимает в памяти 32 бита. Она может принимать значения в диапазоне от 3.4е-38 до 3.4e+38.

  • double – вещественный двойной точности;
  • Ключевое слово double позволяет определить вещественную переменную двойной точности. Она занимает в памяти в два раза больше места, чем переменная типа float. Переменная типа double может принимать значения в диапазоне от 1.7e-308 до 1.7e+308.

  • void – не имеющий значения.
  • Ключевое слово void используется для нейтрализации значения объекта, например, для объявления функции, не возвращающей никаких значений.

Типы переменных:

Программы оперируют с различными данными, которые могут быть простыми и структурированными. Простые данные – это целые и вещественные числа, символы и указатели (адреса объектов в памяти). Целые числа не имеют, а вещественные имеют дробную часть. Структурированные данные – это массивы и структуры; они будут рассмотрены ниже.

Переменная – это ячейка в памяти компьютера, которая имеет имя и хранит некоторое значение. Значение переменной может меняться во время выполнения программы. При записи в ячейку нового значения старое стирается.

Хорошим стилем является осмысленное именование переменных. Имя переменной может содержать от одного до 32 символов. Разрешается использовать строчные и прописные буквы, цифры и символ подчёркивания, который в Си считается буквой. Первым символом обязательно должна быть буква. Имя переменной не может совпадать с зарезервированными словами.

Тип char

char – является самым экономным типом. Тип char может быть знаковым и беззнаковым. Обозначается, как “signed char” (знаковый тип) и “unsigned char” (беззнаковый тип). Знаковый тип может хранить значения в диапазоне от -128 до +127. Беззнаковый – от 0 до 255. Под переменную типа char отводится 1 байт памяти (8 бит).

Ключевые слова signed и unsigned указывают, как интерпретируется нулевой бит объявляемой переменной, т.е., если указано ключевое слово unsigned, то нулевой бит интерпретируется как часть числа, в противном случае нулевой бит интерпретируется как знаковый.

Тип int

Целочисленная величина int может быть short (короткой) или long (длинной). Ключевое слово short ставится после ключевых слов signed или unsigned. Таким образом, есть типы: signed short int, unsigned short int, signed long int, unsigned long int.

Переменная типа signed short int (знаковая короткая целая) может принимать значения от -32768 до +32767, unsigned short int (беззнаковая короткая целая) – от 0 до 65535. Под каждую из них отводится ровно по два байта памяти (16 бит).

При объявлении переменной типа signed short int ключевые слова signed и short могут быть пропущены, и такой тип переменной может быть объявлен просто int. Допускается и объявление этого типа одним ключевым словом short.

Переменная unsigned short int может быть объявлена как unsigned int или unsigned short.

Под каждую величину signed long int или unsigned long int отводится 4 байта памяти (32 бита). Значения переменных этого типа могут находиться в интервалах от -2147483648 до 2147483647 и от 0 до 4294967295 соответственно.

Существуют также переменные типа long long int, для которых отводится 8 байт памяти (64 бита). Они могут быть знаковыми и беззнаковыми. Для знакового типа диапазон значений лежит в пределах от -9223372036854775808 до 9223372036854775807, для беззнакового – от 0 до 18446744073709551615. Знаковый тип может быть объявлен и просто двумя ключевыми словами long long.

Тип Диапазон Шестнадцатеричный диапазон Размер
unsigned char 0 … 255 0x00 … 0xFF 8 bit
signed char
или просто
char
-128 … 127 -0x80 … 0x7F 8 bit
unsigned short int
или просто
unsigned int или unsigned short
0 … 65535 0x0000 … 0xFFFF 16 bit
signed short int или signed int
или просто
short или int
-32768 … 32767 0x8000 … 0x7FFF 16 bit
unsigned long int
или просто
unsigned long
0 … 4294967295 0x00000000 … 0xFFFFFFFF 32 bit
signed long
или просто
long
-2147483648 … 2147483647 0x80000000 … 0x7FFFFFFF 32 bit
unsigned long long 0 … 18446744073709551615 0x0000000000000000 … 0xFFFFFFFFFFFFFFFF 64 bit
signed long long
или просто
long long
-9223372036854775808 … 9223372036854775807 0x8000000000000000 … 0x7FFFFFFFFFFFFFFF 64 bit

Объявление переменных

Переменные объявляют в операторе описания. Оператор описания состоит из спецификации типа и списка имён переменных, разделённых запятой. В конце обязательно должна стоять точка с запятой.

[модификаторы] спецификатор_типа идентификатор [, идентификатор] ...

Модификаторы – ключевые слова signed, unsigned, short, long.
Спецификатор типа – ключевое слово char или int, определяющее тип объявляемой переменной.
Идентификатор – имя переменной.

Char x; int a, b, c; unsigned long long y;

При объявлении переменную можно проинициализировать, то есть присвоить ей начальное значение.

Int x = 100;

В переменную x при объявлении сразу же будет записано число 100. Инициализируемые переменные лучше объявлять в отдельных строках.

Целый тип char занимает в памяти 1 байт (8 бит) и позволяет выразить в двоичной системе счисления 2^8 значений=256. Тип char может содержать как положительные, так и отрицательные значения. Диапазон изменения значений составляет от -128 до 127.

uchar

Целый тип uchar также занимает в памяти 1 байт, как и тип char, но в отличие от него, uchar предназначен только для положительных значений. Минимальное значение равно нулю, максимальное значение равно 255. Первая буква u в названии типа uchar является сокращением слова unsigned (беззнаковый).

short

Целый тип short имеет размер 2 байта(16 бит) и, соответственно, позволяет выразить множество значений равное 2 в степени 16: 2^16=65 536. Так как тип short является знаковым и содержит как положительные, так и отрицательные значения, то диапазон значений находится между -32 768 и 32 767.

ushort

Беззнаковым типом short является тип ushort, который также имеет размер 2 байта. Минимальное значение равно 0, максимальное значение 65 535.

int

Целый тип int имеет размер 4 байта (32 бита). Минимальное значение -2 147 483 648, максимальное значение 2 147 483 647.

uint

Беззнаковый целый тип uint занимает в памяти 4 байта и позволяет выражать целочисленные значения от 0 до 4 294 967 295.

long

Целый тип long имеет размер 8 байт (64 бита). Минимальное значение -9 223 372 036 854 775 808, максимальное значение 9 223 372 036 854 775 807.

ulong

Целый тип ulong также занимает 8 байт и позволяет хранить значения от 0 до 18 446 744 073 709 551 615.

Примеры:

char ch= 12 ;
short sh=- 5000 ;
int in= 2445777 ;

Так как беззнаковые целые типы не предназначены для хранения отрицательных значений, то попытка установить отрицательное значение может привести к неожиданным последствиям. Вот такой невинный скрипт приведет к бесконечному циклу:

Правильно будет так:

Результат:

Ch= -128 u_ch= 128
ch= -127 u_ch= 129
ch= -126 u_ch= 130
ch= -125 u_ch= 131
ch= -124 u_ch= 132
ch= -123 u_ch= 133
ch= -122 u_ch= 134
ch= -121 u_ch= 135
ch= -120 u_ch= 136
ch= -119 u_ch= 137
ch= -118 u_ch= 138
ch= -117 u_ch= 139
ch= -116 u_ch= 140
ch= -115 u_ch= 141
ch= -114 u_ch= 142
ch= -113 u_ch= 143
ch= -112 u_ch= 144
ch= -111 u_ch= 145
...

В этом уроке мы рассмотрим целочисленные типы данных, их диапазоны значений, деление, а также переполнение: что это такое и примеры.

Целочисленные типы данных

Целочисленный тип данных - это тип, переменные которого могут содержать только целые числа (без дробной части, например: -2, -1, 0, 1, 2). В C++ есть пять основных целочисленных типов, доступных для использования:

Примечание : Тип char — это особый случай, он является как целочисленным, так и символьным типом данных. Об этом детальнее мы поговорим в одном из следующих уроков.

Основным различием между целочисленными типами выше является их , чем он больше, тем больше значений сможет хранить переменная этого типа.

Определение целочисленных переменных

Происходит следующим образом:

char c; short int si; // допустимо short s; // предпочтительнее int i; long int li; // допустимо long l; // предпочтительнее long long int lli; // допустимо long long ll; // предпочтительнее

В то время как полные названия short int , long int и long long int могут использоваться, их сокращённые версии (без int) более предпочтительны для использования. К тому же постоянное добавление int затрудняет чтение кода (легко спутать с переменной).

Диапазоны значений и знак целочисленных типов данных

Как вы уже знаете из предыдущего урока, переменная с n-ным количеством бит может хранить 2 n возможных значений. Но что это за значения? Те, которые находятся в диапазоне. Диапазон - это значения от и до, которые может хранить определённый тип данных. Диапазон целочисленной переменной определяется двумя факторами: её размером (в битах) и её знаком (который может быть signed или unsigned ).

Целочисленный тип signed (со знаком ) означает, что переменная может содержать как положительные, так и отрицательные числа. Чтобы объявить переменную как signed, используйте ключевое слово signed:

signed char c; signed short s; signed int i; signed long l; signed long long ll;

signed char c ;

signed short s ;

signed int i ;

signed long l ;

signed long long ll ;

По умолчанию, ключевое слово signed пишется перед типом данных.

1-байтовая целочисленная переменная со знаком (signed) имеет диапазон значений от -128 до 127. Любое значение от -128 до 127 (включительно) может храниться в ней безопасно.

В некоторых случаях мы можем заранее знать, что отрицательные числа в программе использоваться не будут. Это очень часто встречается при использовании переменных для хранения количества или размера чего-либо (например, ваш рост или вес не может быть отрицательным).

Целочисленный тип unsigned (без знака ) может содержать только положительные числа. Чтобы объявить переменную как unsigned , используйте ключевое слово unsigned:

unsigned char c; unsigned short s; unsigned int i; unsigned long l; unsigned long long ll;

unsigned char c ;

unsigned short s ;

unsigned int i ;

unsigned long l ;

unsigned long long ll ;

1-байтовая целочисленная переменная без знака (unsigned) имеет диапазон значений от 0 до 255.

Обратите внимание, объявление переменной как 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-ным количеством бит имеет диапазон от -(2 n-1) до 2 n-1 -1. Переменная unsigned с n-ным количеством бит имеет диапазон от 0 до (2 n)-1. Для нематематиков: используем таблицу 🙂

Начинающие программисты иногда путаются между signed и unsigned переменными. Но есть простой способ запомнить их различия. Чем отличается отрицательное число от положительного? Правильно! Минусом спереди. Если минуса нет, значит число — положительное. Следовательно, целочисленный тип со знаком (signed) означает, что минус может присутствовать, т.е. числа могут быть как положительными, так и отрицательными. Целочисленный тип без знака (unsigned) означает, что минус спереди полностью отсутствует, т.е. числа могут быть только положительными.

Что используется по умолчанию: signed или unsigned?

Так что же произойдёт, если мы объявим переменную без указания signed или unsigned?

Все целочисленные типы данных, кроме char, являются signed по умолчанию. Тип char может быть как signed, так и unsigned (но, обычно, signed).

В большинстве случаев ключевое слово signed не пишется (оно и так используется по умолчанию), за исключением типа char (здесь лучше уточнить).

Программисты, как правило, избегают использования целочисленных типов unsigned, если в этом нет особой надобности, так как с переменными unsigned ошибок, по статистике, возникает больше, нежели с переменными signed.

Правило: Используйте целочисленные типы signed, вместо unsigned .

Переполнение

Вопрос: «Что произойдёт, если мы попытаемся использовать значение, которое находится вне диапазона значений определённого типа данных?». Ответ: «Переполнение».

Переполнение (англ. «overflow» ) случается при потере бит из-за того, что переменной не было выделено достаточно памяти для их хранения.

Примеры переполнения

Рассмотрим переменную unsigned, которая состоит из 4 битов. Любое из двоичных чисел, перечисленных в таблице выше, поместиться внутри этой переменной.

«Но что произойдёт, если мы попытаемся присвоить значение, которое занимает больше 4 битов?». Правильно! Переполнение. Наша переменная будет хранить только 4 наименее значимых (те, что справа) бита, все остальные — потеряются.

Например, если мы попытаемся поместить число 21 в нашу 4-битную переменную:

Десятичная система Двоичная система
21 10101

Число 21 занимает 5 бит (10101). 4 бита справа (0101) поместятся в переменную, а крайний левый бит (1) просто потеряется. Т.е. наша переменная будет содержать 0101, что равно 101 (нуль спереди не считается), а это уже число 5, а не 21.

Примечание : О конвертации чисел из двоичной системы в десятичную и наоборот будет отдельный урок, где мы всё детально рассмотрим и обсудим.

Теперь рассмотрим пример в коде (тип short занимает 16 бит):

#include int main() { unsigned short x = 65535; // наибольшее значение, которое может хранить 16-битная unsigned переменная std::cout << "x was: " << x << std::endl; x = x + 1; // 65536 - это число больше максимально допустимого числа из диапазона допустимых значений. Следовательно, произойдёт переполнение, так как переменнная x не может хранить 17 бит std::cout << "x is now: " << x << std::endl; return 0; }

#include

int main ()

unsigned short x = 65535 ; // наибольшее значение, которое может хранить 16-битная unsigned переменная

std :: cout << "x was: " << x << std :: endl ;

x = x + 1 ; // 65536 - это число больше максимально допустимого числа из диапазона допустимых значений. Следовательно, произойдёт переполнение, так как переменнная x не может хранить 17 бит

std :: cout << "x is now: " << x << std :: endl ;

return 0 ;

x was: 65535
x is now: 0

Что случилось? Произошло переполнение, так как мы попытались впихнуть невпихуемое в переменную x .

Для тех, кто хочет знать больше: Число 65 535 в двоичной системе счисления представлено как 1111 1111 1111 1111. 65 535 - это наибольшее число, которое может хранить 2-байтовая (16 бит) целочисленная переменная без знака, так как это число использует все 16 бит. Когда мы добавляем 1, то получаем число 65 536. Число 65 536 представлено в двоичной системе как 1 0000 0000 0000 0000, и занимает 17 бит! Следовательно, самый главный бит (которым является 1) теряется, а все 16 бит справа — остаются. Комбинация 0000 0000 0000 0000 соответствует десятичному 0, что и является нашим результатом.

Аналогичным образом, мы получим переполнение, использовав число, меньше минимального из диапазона допустимых значений:

#include int main() { unsigned short x = 0; // наименьшее значение, которое 2-байтовая unsigned переменная может хранить std::cout << "x was: " << x << std::endl; x = x - 1; // переполнение! std::cout << "x is now: " << x << std::endl; return 0; }

#include

int main ()

unsigned short x = 0 ; // наименьшее значение, которое 2-байтовая unsigned переменная может хранить

std :: cout << "x was: " << x << std :: endl ;

x = x - 1 ; // переполнение!

std :: cout << "x is now: " << x << std :: endl ;

return 0 ;

Результат выполнения программы выше:

x was: 0
x is now: 65535

Переполнение приводит к потере информации, а это никогда не приветствуется. Если есть хоть малейшее подозрение или предположение, что значением переменной может быть число, которое находится вне диапазона допустимых значений используемого типа данных - используйте тип данных побольше!

Типом данных в программировании называют совокупность двух множеств: множество значений и множество операций, которые можно применять к ним. Например, к типу данных целых неотрицательных чисел, состоящего из конечного множества натуральных чисел, можно применить операции сложения (+), умножения (*), целочисленного деления (/), нахождения остатка (%) и вычитания (−).

Язык программирования, как правило, имеет набор примитивных типов данных - типы, предоставляемые языком программирования как базовая встроенная единица. В C++ такие типы создатель языка называет фундаментальными типами . Фундаментальными типами в C++ считаются:

  • логический (bool);
  • символьный (напр., char);
  • целый (напр., int);
  • с плавающей точкой (напр., float);
  • перечисления (определяется программистом);
  • void .

Поверх перечисленных строятся следующие типы:

  • указательные (напр., int*);
  • массивы (напр., char);
  • ссылочные (напр., double&);
  • другие структуры.

Перейдём к понятию литерала (напр., 1, 2.4F, 25e-4, ‘a’ и др.): литерал - запись в исходном коде программы, представляющаясобой фиксированное значение. Другими словами, литерал - это просто отображение объекта (значение) какого-либо типа в коде программы. В C++ есть возможность записи целочисленных значений, значений с плавающей точкой, символьных, булевых, строковых.

Литерал целого типа можно записать в:

  • 10-й системе счисления. Например, 1205 ;
  • 8-й системе счисления в формате 0 + число. Например, 0142 ;
  • 16-й системе счисления в формате 0x + число. Например, 0x2F .

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 и др.

Декларатор кроме имени переменной может содержать дополнительные символы:

  • * - указатель; перед именем;
  • *const - константный указатель; перед именем;
  • & - ссылка; перед именем;
  • - массив; после имени;
  • () - функция; после имени.

Инициализатор позволяет определить для переменной её значение сразу после объявления. Инициализатор начинается с литерала равенства (=) и далее происходит процесс задания значения переменной. Вообще говоря, знак равенства в C++ обозначает операцию присваивания; с её помощью можно задавать и изменять значение переменной. Для разных типов он может быть разным.

Спецификатор задаёт дополнительные атрибуты, отличные от типа. Приведённый в примере спецификатор const позволяет запретить последующее изменение значение переменной. Такие неизменяемые переменные называют константными или константой.

Объявить константу без инициализации не получится по логичным причинам:

Const int EMPTY_CONST; // ошибка, не инициализована константная переменная const int EXAMPLE = 2; // константа со значением 2 EXAMPLE = 3; // ошибка, попытка присвоить значение константной переменной

Для именования констант принято использовать только прописные буквы, разделяя слова символом нижнего подчёркивания.

Основные типы данных в C++

Разбирая каждый тип, читатель не должен забывать об определении типа данных.

1. Целочисленный тип (char, short (int), int, long (int), long long)

Из названия легко понять, что множество значений состоит из целых чисел. Также множество значений каждого из перечисленных типов может быть знаковым (signed) или беззнаковым (unsigned). Количество элементов, содержащееся в множестве, зависит от размера памяти, которая используется для хранения значения этого типа. Например, для переменной типа char отводится 1 байт памяти, поэтому всего элементов будет:

  • 2 8N = 2 8 * 1 = 256, где N - размер памяти в байтах для хранения значения

В таком случае диапазоны доступных целых чисел следующие:

  • - для беззнакового char
  • [-128..127] - для знакового char

По умолчанию переменная целого типа считается знаковой. Чтобы указать в коде, что переменная должна быть беззнаковой, к базовому типу слева приписывают признак знаковости, т.е. unsigned:

Unsigned long values; // задаёт целый (длинный) беззнаковый тип.

Перечисленные типы отличаются только размерами памяти, которая требуется для хранения. Поскольку язык C++ достаточно машинно-зависимый стандарт языка лишь гарантирует выполнение следующего условия:

  • 1 = размер char ≤ размер short ≤ размер int ≤ размер long.

Обычно размеры типов следующие: char - 1, short - 2, int - 4, long -8, long long - 8 байт.

Со значениями целого типа можно совершать арифметические операции: +, -, *, /, %; операции сравнения: ==, !=, <=, <, >, >=; битовые операции: &, |, xor, <<, >>.
Большинство операций, таких как сложение, умножение, вычитание и операции сравнения, не вызывают проблем в понимании. Иногда, после выполнения арифметических операций, результат может оказаться за пределами диапазона значений; в этом случае программа выдаст ошибку.
Целочисленное деление (/) находит целую часть от деления одного целого числа, на другое. Например:

  • 6 / 4 = 1;
  • 2 / 5 = 0;
  • 8 / 2 = 4.

Символ процента (%) обозначает операцию определение остатка от деления двух целых чисел:

  • 6 % 4 = 2;
  • 10 % 3 = 1.

Более сложные для понимания операции - битовые: & (И), | (ИЛИ), xor (исключающее ИЛИ), << (побитовый сдвиг влево), >> (побитовый сдвиг вправо).

Битовые операции И, ИЛИ и XOR к каждому биту информации применяют соответствующую логическую операцию:

  • 1 10 = 01 2
  • 3 10 = 11 2
  • 1 10 & 3 10 = 01 2 & 11 2 = 01 2
  • 1 10 | 3 10 = 01 2 | 11 2 = 11 2
  • 1 10 xor 3 10 = 01 2 xor 11 2 = 10 2

В обработке изображения используют 3 канала для цвета: красный, синий и зелёный - плюс прозрачность, которые хранятся в переменной типа int, т.к. каждый канал имеет диапазон значений от 0 до 255. В 16-иричной системе счисления некоторое значение записывается следующим образом: 0x180013FF; тогда значение 18 16 соответствует красному каналу, 00 16 - синему, 13 16 - зелёному, FF - альфа-каналу (прозрачности). Чтобы выделить из такого целого числа определённый канал используют т.н. маску, где на интересующих нас позициях стоят F 16 или 1 2 . Т.е., чтобы выделить значение синего канала необходимо использовать маску, т.е. побитовое И:

Int blue_channel = 0x180013FF & 0x00FF0000;

После чего полученное значение сдвигается вправо на необходимое число бит.

Побитовый сдвиг смещает влево или вправо на столько двоичных разрядов числа, сколько указано в правой части операции. Например, число 39 для типа char в двоичном виде записывается в следующем виде: 00100111. Тогда:

Char binaryExample = 39; // 00100111 char result = binaryExample << 2; // сдвигаем 2 бита влево, результат: 10011100

Если переменная беззнакового типа, тогда результатом будет число 156, для знакового оно равно -100. Отметим, что для знаковых целых типов единица в старшем разряде битового представления - признак отрицательности числа. При этом значение, в двоичном виде состоящие из всех единиц соответствует -1; если же 1 только в старшем разряде, а в остальных разрядах - нули, тогда такое число имеет минимальное для конкретного типа значения: для char это -128.

2. Тип с плавающей точкой (float, double (float))

Множество значений типа с плавающей точкой является подмножеством вещественных чисел, но не каждое вещественное число представимо в двоичном виде, что приводит иногда к глупым ошибкам:

Float value = 0.2; value == 0.2; // ошибка, value здесь не будет равно 0.2.

Работая с переменными с плавающей точкой, программист не должен использовать операцию проверки на равенство или неравенство, вместо этого обычно используют проверку на попадание в определённый интервал:

Value - 0.2 < 1e-6; // ok, подбирать интервал тоже нужно осторожно

Помимо операций сравнения тип с плавающей точкой поддерживает 4 арифметические операции, которые полностью соответствуют математическим операциям с вещественными числами.

3. Булевый (логический) тип (bool)

Состоит всего из двух значений: true (правда) и false (ложь). Для работы с переменными данного типа используют логические операции: ! (НЕ), == (равенство), != (неравенство), && (логическое И), || (логическое ИЛИ). Результат каждой операции можно найти в соответствующей таблицы истинности. например:

X Y XOR 0 0 0 0 1 1 1 0 1 1 1 0

4. Символьный тип (char, wchar_t)

Тип char - не только целый тип (обычно, такой тип называют byte), но и символьный, хранящий номер символа из таблицы символом ASCII . Например код 0x41 соответствует символу ‘A’, а 0x71 - ‘t’.

Иногда возникает необходимость использования символов, которые не закреплены в таблицы ASCII и поэтому требует для хранения более 1-го байта. Для них существует широкий символ (wchar_t).

5.1. Массивы

Массивы позволяют хранить последовательный набор однотипных элементов. Массив хранится в памяти непрерывным блоком, поэтому нельзя объявить массив, не указав его размер . Чтобы объявить массив после имени переменной пишут квадратные скобки () с указанием его размера. Например:

Int myArray; // Массив из 5-и элементов целого типа

Для инициализации массива значения перечисляют в фигурных скобках. Инициализировать таким образом можно только во время объявления переменной. Кстати, в этом случае необязательно указывать размер массива:

Int odds = {1, 3, 7, 9, 11}; // Массив инициализируется 5-ю значениями

Для доступа к определённому значению в массиве (элемента массива) используют операцию доступа по индексу () с указанием номера элемента (номера начинаются с 0). Например:

Odds; // доступ к первому элементу массива. Вернёт значение 1 odds; // доступ к третьему элементу. Вернёт значение 7 odds = 13; // 5-му элементу массива присваиваем новое значение odds; // ошибка доступа

5.3. Строки

Для записи строки программисты используют идею, что строка - последовательный ряд (массив) символов. Для идентификации конца строки используют специальный символ конца строки: ‘\0’. Такие специальные символы, состоящие из обратного слэша и идентифицирующего символа, называют управляющими или escape-символами. Ещё существуют, например, ‘\n’ - начало новой строки, ‘\t’ - табуляция. Для записи в строке обратного слэша применяют экранирование - перед самим знаком ставят ещё один слэш: ‘\’. Экранирование также применяют для записи кавычек.

Создадим переменную строки:

Char textExample = {‘T’, ‘e’, ‘s’, ‘t’, ‘\0’}; // записана строка «Test»

Существует упрощённая запись инициализации строки:

Char textExample = “Test”; // Последний символ не пишется, но размер всё ещё 5

Не вдаваясь в подробности, приведём ещё один полезный тип данных - string. Строки
такого типа можно, например, складывать:

String hello = "Привет, "; string name = "Макс!"; string hello_name = hello + name; // Получится строка «Привет, Макс!»

6. Ссылка

Int a = 2; // переменная «a» указывает на значение 2 int &b = a; // переменная «b» указывает туда же, куда и «a» b = 4; // меняя значение b, программист меняет значение a. Теперь a = 4 int &c = 4; // ошибка, так делать нельзя, т.к. ссылка нельзя присвоить значение

7. Указатель

Чтобы разобраться с этим типом данных, необходимо запомнить, что множество значений этого типа - адреса ячеек памяти, откуда начинаются данные. Также указатель поддерживает операции сложения (+), вычитания (-) и разыменовывания (*).

Адреса 0x0 означает, что указатель пуст, т.е. не указывает ни на какие данные. Этот адрес имеет свой литерал - NULL:

Int *nullPtr = NULL; // пустой указатель

Сложение и вычитание адреса с целым числом или другим адресом позволяет
передвигаться по памяти, доступной программе.

Операция получения данных, начинающихся по адресу, хранящемуся в указателе, называется разыменовывания (*). Программа считывает необходимое количество ячеек памяти и возвращает значение, хранимое в памяти.

Int valueInMemory = 2; // задаём переменну целого типа int *somePtr = &valueIntMemory; // копируем адрес переменной, здесь & - возвращает адрес переменной somePtr; // адрес ячейки памяти, например, 0x2F *somePtr; // значение хранится в 4-х ячейках: 0x2F, 0x30, 0x31 и 0x32

Для указателей не доступна операция присваивания, которая синтаксически совпадает с операцией копирования. Другими словами, можно скопировать адрес другого указателя или адрес переменной, но определить значение адреса самому нельзя.

Сам указатель хранится в памяти, как и значения переменных других типов, и занимает 4 байта, поэтому можно создать указатель на указатель.

8. Перечисления

Перечисления единственный базовый тип, задаваемый программистом. По большому счёту перечисление - упорядоченный набор именованных целочисленных констант, при этом имя перечисления будет базовым типом.

Enum color {RED, BLUE, GREEN};

По умолчанию, RED = 0, BLUE = 1, GREEN = 2. Поэтому значения можно сравнивать между собой, т.е. RED < BLUE < GREEN. Программист при объявлении перечисления может самостоятельно задать значения каждой из констант:

Enum access {READ = 1, WRITE = 2, EXEC = 4};

Часто удобно использовать перечисления, значения которых являются степенью двойки, т.к. в двоичном представлении число, являющееся степенью 2-и, будет состоять из 1-й единицы и нулей. Например:

8 10 = 00001000 2

Результат сложения этих чисел между собой всегда однозначно указывает на то, какие числа складывались:

37 10 = 00100101 2 = 00000001 2 + 00000100 2 + 00100000 2 = 1 10 + 4 10 + 32 10

Void

Синтаксически тип void относится к фундаментальным типам, но использовать его можно лишь как часть более сложных типов, т.к. объектов типа void не существует. Как правило, этот тип используется для информирования о том, что у функции нет возвращаемого значения либо в качестве базового типа указателя на объекты неопределённых типов:

Void object; // ошибка, не существует объектов типа void void &reference; // ошибка, не существует ссылов на void void *ptr; // ok, храним указатель на неизвестный тип

Часто мы будем использовать void именно для обозначения того, что функция не возвращает никакого значения. С указателем типа void работают, когда программист берёт полностью на себя заботу о целостности памяти и правильном приведении типа.

Приведение типов

Часто бывает необходимо привести значение переменной одного типа к другому. В случае, когда множество значений исходного типа является подмножеством большего типа (например, int является подмножеством long, а long - double), компилятор способен неявно (implicitly ) изменить тип значения.

Int integer = 2; float floating = integer; // floating = 2.0

Обратное приведение типа будет выполнено с потерей информации, так от числа с плавающей точкой останется только целая часть, дробная будет потеряна.

Существует возможность явного (explicitly) преобразования типов, для этого слева от переменной или какого-либо значения исходного типа в круглых скобках пишут тип, к которому будет произведено приведение:

Int value = (int) 2.5;

Унарные и бинарные операции

Те операции, которые мы выполняли ранее, называют бинарными: слева и справа от символа операции находятся значения или переменные, например, 2 + 3. В языках программирования помимо бинарных операций также используют унарные операции, которые применяются к переменным. Они могу находится как слева, так и справа от переменной, несколько таких операций встречались ранее - операция разыменовывания (*) и взятие адреса переменной (&) являются унарными. Операторы «++» и «—» увеличивают и уменьшают значение целочисленной переменной на 1 соответственно, причём могу писаться либо слева, либо справа от переменной.

В C++ также применяется сокращённая запись бинарных операций на тот случай, когда в левой и правой частях выражения находится одна и та же переменная, т.е. выполняется какая-либо операция со значением переменной и результат операции заносится в ту же переменную:

A += 2; // то же самое, что и a = a + 2; b /= 5; // то же самое, что и b = b / 5; c &= 3; // то же самое, что и c = c & 3;

Теги: Си переменные. char, int, unsigned, long, long long, float, double, long double, long float, lexical scoping. Объявление переменных. Область видимости. Инициализация переменных. Имена переменных. Экспоненциальная форма.

Переменные

П еременные используются для хранения значений (sic!). Переменная характеризуется типом и именем. Начнём с имени. В си переменная может начинаться с подчерка или буквы, но не с числа. Переменная может включать в себя символы английского алфавита, цифры и знак подчёркивания. Переменная не должна совпадать с ключевыми словами (это специальные слова, которые используются в качестве управляющих конструкций, для определения типов и т.п.)

auto double int struct
break else long switch
register typedef char extern
return void case float
unsigned default for signed
union do if sizeof
volatile continue enum short
while inline
А также ряд других слов, специфичных для данной версии компилятора, например far , near , tiny , huge , asm , asm_ и пр.

Например, правильные идентификаторы
a, _, _1_, Sarkasm, a_long_variable, aLongVariable, var19, defaultX, char_type
неверные
1a, $value, a-long-value, short

Си - регистрозависимый язык. Переменные с именами a и A, или end и END, или perfectDark и PerfectDarK – это различные переменные.

Типы переменных

Т ип переменной определяет

  • 1) Размер переменной в байтах (сколько байт памяти выделит компьютер для хранения значения)
  • 2) Представление переменной в памяти (как в двоичном виде будут расположены биты в выделенной области памяти).
В си несколько основных типов. Разделим их на две группы - целые и числа с плавающей точкой.

Целые

  • char - размер 1 байт. Всегда! Это нужно запомнить.
  • short - размер 2 байта
  • int - размер 4 байта
  • long - размер 4 байта
  • long long - размер 8 байт.
Здесь следует сделать замечание. Размер переменных в си не определён явно, как размер в байтах. В стандарте только указано, что

char <= short <= int <= long <= long long

Указанные выше значения характерны для компилятора VC2012 на 32-разрядной машине. Так что, если ваша программа зависит от размера переменной, не поленитесь узнать её размер.

Теперь давайте определим максимальное и минимальное число, которое может хранить переменная каждого из типов. Числа могут быть как положительными, так и отрицательными. Отрицательные числа используют один бит для хранения знака. Иногда знак необходим (например, храним счёт в банке, температуру, координату и т.д.), а иногда в нём нет необходимости (вес, размер массива, возраст человека и т.д.). Для этого в си используется модификатор типа signed и unsigned. unsigned char - все 8 бит под число, итого имеем набор чисел от 00000000 до 11111111 в двоичном виде, то есть от 0 до 255 signed char от -128 до 128. В си переменные по умолчанию со знаком. Поэтому запись char и signed char эквивалентны.

Таб. 1 Размер целых типов в си.
Тип Размер, байт Минимальное значение Максимальное значение
unsigned char 1 0 255
signed char
(char)
1 -128 127
unsigned short 2 0 65535
signed short
(short)
2 -32768 32767
unsigned int
(unsigned)
4 0 4294967296
signed int
(int)
4 -2147483648 2147483647
unsigned long 4 0 4294967296
signed long
(long)
4 -2147483648 2147483647
unsigned long long 8 0 18446744073709551615
signed long long
(long long)
8 -9223372036854775808 9223372036854775807

sizeof

В си есть оператор, который позволяет получить размер переменной в байтах. sizeof переменная, или sizeof(переменная) или sizeof(тип). Это именно оператор, потому что функция не имеет возможности получить информацию о размере типов во время выполнения приложения. Напишем небольшую программу чтобы удостовериться в размерах переменных.

#include #include int main() { char c; short s; int i; long l; long long L; //Вызов sizeof как "функции" printf("sizeof(char) = %d\n", sizeof(c)); printf("sizeof(short) = %d\n", sizeof(s)); printf("sizeof(int) = %d\n", sizeof(i)); printf("sizeof(long) = %d\n", sizeof(l)); printf("sizeof(long long) = %d\n", sizeof(L)); //Вызов как оператора printf("sizeof(char) = %d\n", sizeof c); printf("sizeof(short) = %d\n", sizeof s); printf("sizeof(int) = %d\n", sizeof i); printf("sizeof(long) = %d\n", sizeof l); printf("sizeof(long long) = %d\n", sizeof L); _getch(); }

(Я думаю ясно, что переменные могут иметь любое валидное имя). Эту программу можно было написать и проще

#include #include int main() { printf("sizeof(char) = %d\n", sizeof(char)); printf("sizeof(short) = %d\n", sizeof(short)); printf("sizeof(int) = %d\n", sizeof(int)); printf("sizeof(long) = %d\n", sizeof(long)); printf("sizeof(long long) = %d\n", sizeof(long long)); //нельзя произвести вызов sizeof как оператора для имени типа //sizeof int - ошибка компиляции _getch(); }

В си один и тот же тип может иметь несколько названий
short === short int
long === long int
long long === long long int
unsigned int === unsigned

Типы с плавающей точкой

  • float - 4 байта,
  • long float - 8 байт
  • double - 8 байт
  • long double - 8 байт.
Здесь также приведены значения для VC2012, по стандарту размер типов float <= long float <= double <= long double все числа с плавающей точкой - со знаком.

Переполнение переменных

Си не следит за переполнением переменных. Это значит, что постоянно увеличивая значение, скажем, переменной типа int в конце концов мы "сбросим значение"

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

Постфиксное обозначение типа

П ри работе с числами можно с помощью литер в конце числа явно указывать его тип, например

  • 11 - число типа int
  • 10u - unsigned
  • 22l или 22L - long
  • 3890ll или 3890LL - long long (а также lL или Ll)
  • 80.0f или 80.f или 80.0F - float (обязательно наличие десятичной точки в записи)
  • 3.0 - число типа double
Экспоненциальная форма записи также по умолчанию обозначает число типа double. #include #include int main() { printf("sizeof(int) = %d\n", sizeof(10)); printf("sizeof(unigned) = %d\n", sizeof(10u)); printf("sizeof(long) = %d\n", sizeof(10l)); printf("sizeof(long long) = %d\n", sizeof(10ll)); printf("sizeof(float) = %d\n", sizeof(10.f)); printf("sizeof(double) = %d\n", sizeof(10.)); printf("sizeof(double) = %d\n", sizeof(10e2)); getch(); }

Следующий код, однако, не будет приводить к ошибкам, потому что происходит неявное преобразование типа

Int a = 10u; double g = 3.f;

Шестнадцатеричный и восьмеричный формат

В о время работы с числами можно использовать шестнадцатеричный и восьмеричный формат представления. Числа в шестнадцатиричной системе счисления начинаются с 0x, в восьмеричной системе с нуля. Соответственно, если число начинается с нуля, то в нём не должно быть цифр выше 7:

#include #include void main() { int x = 0xFF; int y = 077; printf("hex x = %x\n", x); printf("dec x = %d\n", x); printf("oct x = %o\n", x); printf("oct y = %o\n", y); printf("dec y = %d\n", y); printf("hex y = %x", y); getch(); }

Экспоненциальная форма представления чисел

Э кспоненциальной формой представления числа называют представление числа в виде M e ± p , где M - мантиса числа, p - степень десяти. При этом у мантисы должен быть один ненулевой знак перед десятичной запятой.
Например 1.25 === 1.25e0, 123.5 === 1.235e2, 0.0002341 === 2.341e-4 и т.д.
Представления 3.2435e7 эквивалентно 3.2435e+7
Существеут и другое представление ("инженерное"), в котором степень должна быть кратной тройке. Например 1.25 === 1.25e0, 123.5 === 123.5e0, 0.0002341 === 234.1e-6, 0.25873256 === 258.73256e-3 и т.д.

Объявление переменных

В си переменные объявляются всегда в начале блока (блок - участок кода,ограниченный фигурными скобками)

<возвращаемый тип> <имя функции> (<тип> <аргумент>[, <тип> <аргумент>]) { объявление переменных всё остальное }

При объявлении переменной пишется её тип и имя.

Int a; double parameter;

Можно объявить несколько переменных одного типа, разделив имена запятой

Long long arg1, arg2, arg3;

Например

#include #include int main() { int a = 10; int b; while (a>0){ int z = a*a; b += z; } }

Здесь объявлены переменные a и b внутри функции main , и переменная z внутри тела цикла. Следующий код вызовет ошибку компиляции

Int main() { int i; i = 10; int j; }

Это связано с тем, что объявление переменной стоит после оператора присваивания. При объявлении переменных можно их сразу инициализировать.
int i = 0;
При этом инициализация при объявлении переменной не считается за отдельный оператор, поэтому следующий код будет работать

Int main() { int i = 10; int j; }

Начальное значение переменной

О чень важно запомнить, что переменные в си не инициализируются по умолчанию нулями, как во многих других языках программирования. После объявления переменной в ней хранится "мусор" - случайное значение, которое осталось в той области памяти, которая была выделена под переменную. Это связано, в первую очередь, с оптимизацией работы программы: если нет необходимости в инициализации, то незачем тратить ресурсы для записи нулей (замечание: глобальные переменные инициализируются нулями, почему так, читайте в этой статье).

#include #include int main() { int i; printf("%d", i); getch(); }

Если выполнять эту программу на VC, то во время выполнения вылетит предупреждение
Run-Time Check Failure #3 - The variable "i" is being used without being initialized.
Если нажать "Продолжить", то программа выведет "мусор". В многих других компиляторах при выполнении программы не будет предупреждения.

Область видимости переменной

П еременные бывают локальными (объявленными внутри какой-нибудь функции) и глобальными. Глобальная переменная видна всем функциям, объявленным в данном файле. Локальная переменная ограничена своей областью видимости. Когда я говорю, что переменная "видна в каком-то месте", это означает, что в этом месте она определена и её можно использовать. Например, рассмотрим программу, в которой есть глобальная переменная

#include #include int global = 100; void foo() { printf("foo: %d\n", global); } void bar(int global) { printf("bar: %d\n", global); } int main() { foo(); bar(333); getch(); }

Будет выведено
foo: 100
bar: 333
Здесь глобальная переменная global видна всем функциям. Но аргумент функции затирает глобальную переменную, поэтому при передаче аргумента 333 выводится локальное значение 333.
Вот другой пример

#include #include int global = 100; int main() { int global = 555; printf("%d\n", global); getch(); }

Программа выведет 555. Также, как и в прошлом случае, локальная переменная "важнее". Переменная, объявленная в некоторой области видимости не видна вне её, например

#include #include int global = 100; int main() { int x = 10; { int y = 30; printf("%d", x); } printf("%d", y); }

Этот пример не скомпилируется, потому что переменная y существует только внутри своего блока.
Вот ещё пример, когда переменные, объявленные внутри блока перекрывают друг друга

#include #include int global = 100; int main() { int x = 10; { int x = 20; { int x = 30; printf("%d\n", x); } printf("%d\n", x); } printf("%d\n", x); getch(); }

Программа выведет
30
20
10
Глобальных переменных необходимо избегать. Очень часто можно услышать такое. Давайте попытаемся разобраться, почему. В ваших простых проектах глобальные переменные выглядят вполне нормально. Но представьте, что у вас приложение, которое

  • 1) Разрабатывается несколькими людьми и состоит из сотен тысяч строк кода
  • 2) Работает в несколько потоков

Во-первых, глобальная переменная, если она видна всем, может быть изменена любой частью программы. Вы изменили глобальную переменную, хотите её записать, а другая часть программы уже перезаписала в неё другое значение (на самом деле это целый класс проблем, которые возникают в многопоточной среде). Во-вторых, при больших размерах проекта не уследить, кто и когда насоздавал глобальных переменных. В приведённых выше примерах видно, как переменные могут перекрывать друг друга, то же произойдёт и в крупном проекте.

Безусловно, есть ситуации, когда глобальные переменные упрощают программу, но такие ситуации случаются не часто и не в ваших домашних заданиях, так что НЕ СОЗДАВАЙТЕ ГЛОБАЛЬНЫХ ПЕРЕМЕННЫХ!
Переменные могут быть не только целочисленными и с плавающей точкой. Существует множество других типов, которые мы будем изучать в дальнейшем.