Переменные
Переменные
П еременные используются для хранения значений (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_ и пр.
Типы переменных
Целые
Указанные выше значения характерны для компилятора VC2012 на 32-разрядной машине. Так что, если ваша программа зависит от размера переменной, не поленитесь узнать её размер.
| Тип | Размер, байт | Минимальное значение | Максимальное значение |
|---|---|---|---|
| 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(тип). Это именно оператор, потому что функция не имеет возможности получить информацию о размере типов во время выполнения приложения. Напишем небольшую программу чтобы удостовериться в размерах переменных.
(Я думаю ясно, что переменные могут иметь любое валидное имя). Эту программу можно было написать и проще
В си один и тот же тип может иметь несколько названий
short === short int
long === long int
long long === long long int
unsigned int === unsigned
Типы с плавающей точкой
Переполнение переменных
Си не следит за переполнением переменных. Это значит, что постоянно увеличивая значение, скажем, переменной типа int в конце концов мы «сбросим значение»
Вообще, поведение при переполнении переменной определено только для типа unsigned: Беззнаковое целое сбросит значение. Для остальных типов может произойти что угодно, и если вам необходимо следить за переполнением, делайте это вручную, проверяя аргументы, либо используйте иные способы, зависящие от компилятора и архитектуры процессора.
Постфиксное обозначение типа
Следующий код, однако, не будет приводить к ошибкам, потому что происходит неявное преобразование типа
Шестнадцатеричный и восьмеричный формат
В о время работы с числами можно использовать шестнадцатеричный и восьмеричный формат представления. Числа в шестнадцатиричной системе счисления начинаются с 0x, в восьмеричной системе с нуля. Соответственно, если число начинается с нуля, то в нём не должно быть цифр выше 7:
Экспоненциальная форма представления чисел
Объявление переменных
При объявлении переменной пишется её тип и имя.
Можно объявить несколько переменных одного типа, разделив имена запятой
Здесь объявлены переменные a и b внутри функции main, и переменная z внутри тела цикла. Следующий код вызовет ошибку компиляции
Это связано с тем, что объявление переменной стоит после оператора присваивания. При объявлении переменных можно их сразу инициализировать.
int i = 0;
При этом инициализация при объявлении переменной не считается за отдельный оператор, поэтому следующий код будет работать
Начальное значение переменной
Область видимости переменной
П еременные бывают локальными (объявленными внутри какой-нибудь функции) и глобальными. Глобальная переменная видна всем функциям, объявленным в данном файле. Локальная переменная ограничена своей областью видимости. Когда я говорю, что переменная «видна в каком-то месте», это означает, что в этом месте она определена и её можно использовать. Например, рассмотрим программу, в которой есть глобальная переменная
Будет выведено
foo: 100
bar: 333
Здесь глобальная переменная global видна всем функциям. Но аргумент функции затирает глобальную переменную, поэтому при передаче аргумента 333 выводится локальное значение 333.
Вот другой пример
Программа выведет 555. Также, как и в прошлом случае, локальная переменная «важнее». Переменная, объявленная в некоторой области видимости не видна вне её, например
Этот пример не скомпилируется, потому что переменная y существует только внутри своего блока.
Вот ещё пример, когда переменные, объявленные внутри блока перекрывают друг друга
Программа выведет
30
20
10
Глобальных переменных необходимо избегать. Очень часто можно услышать такое. Давайте попытаемся разобраться, почему. В ваших простых проектах глобальные переменные выглядят вполне нормально. Но представьте, что у вас приложение, которое
Во-первых, глобальная переменная, если она видна всем, может быть изменена любой частью программы. Вы изменили глобальную переменную, хотите её записать, а другая часть программы уже перезаписала в неё другое значение (на самом деле это целый класс проблем, которые возникают в многопоточной среде). Во-вторых, при больших размерах проекта не уследить, кто и когда насоздавал глобальных переменных. В приведённых выше примерах видно, как переменные могут перекрывать друг друга, то же произойдёт и в крупном проекте.
Безусловно, есть ситуации, когда глобальные переменные упрощают программу, но такие ситуации случаются не часто и не в ваших домашних заданиях, так что НЕ СОЗДАВАЙТЕ ГЛОБАЛЬНЫХ ПЕРЕМЕННЫХ!
Переменные могут быть не только целочисленными и с плавающей точкой. Существует множество других типов, которые мы будем изучать в дальнейшем.
Урок №28. Инициализация, присваивание и объявление переменных
Обновл. 11 Сен 2021 |
Этот урок является более детальным продолжением урока №10.
Адреса и переменные
Как вы уже знаете, переменные — это имена кусочков памяти, которые могут хранить информацию. Помним, что компьютеры имеют оперативную память, которая доступна программам для использования. Когда мы определяем переменную, часть этой памяти отводится ей.
Поскольку все данные компьютера — это лишь последовательность битов, то мы используем тип данных (или просто «тип»), чтобы сообщить компилятору, как интерпретировать содержимое памяти. Вы уже видели пример типа данных: int (целочисленный тип данных). Когда мы объявляем целочисленную переменную, то мы сообщаем компилятору, что «кусочек памяти, который находится по такому-то адресу, следует интерпретировать как целое число».
Когда вы указываете тип данных для переменной, то компилятор и процессор заботятся о деталях преобразования вашего значения в соответствующую последовательность бит определенного типа данных. Когда вы просите ваше значение обратно, то оно «восстанавливается» из этой же последовательности бит.
Кроме int, есть много других типов данных в языке C++, большинство из которых мы детально рассмотрим на соответствующих уроках.
Фундаментальные типы данных в С++
В языке C++ есть встроенная поддержка определенных типов данных. Их называют основными типами данных (или «фундаментальные/базовые/встроенные типы данных»).
Вот список основных типов данных в языке C++:
| Категория | Тип | Значение | Пример |
| Логический тип данных | bool | true или false | true |
| Символьный тип данных | char, wchar_t, char16_t, char32_t | Один из ASCII-символов | ‘c’ |
| Тип данных с плавающей запятой | float, double, long double | Десятичная дробь | 3.14159 |
| Целочисленный тип данных | short, int, long, long long | Целое число | 64 |
| Пустота | void | Пустота |
Объявление переменных
Вы уже знаете, как объявить целочисленную переменную:
Инициализаторы
Инициализатор определяет начальное значение переменной. Можно инициализировать переменные в этих контекстах:
В определении переменной:
В качестве одного из параметров функции:
В качестве возвращаемого типа функции:
Инициализаторы могут принимать эти формы:
Выражение (или разделенный запятыми список выражений) в скобках:
Знак равенства с последующим выражением:
Список инициализации в фигурных скобках. Список может быть пустым или может состоять из набора списков как в приведенном ниже примере.
Типы инициализации
Существует несколько типов инициализации, которые могут встречаться на различных этапах выполнения программы. Различные типы инициализации не является взаимоисключающими, например, инициализация списка может активировать инициализацию значений, а в других условиях она может активировать агрегатную инициализацию.
Нулевая инициализация
Нулевая инициализация — задание для переменной нулевого значения, неявно преобразованного в тип:
Числовые переменные инициализируются значением 0 (или 0,0; 0,0000000000 и т.п.).
Нулевая инициализация выполняется в разное время:
При запуске программы — для всех именованных переменных, имеющих статическую длительность. Далее эти переменные могут быть инициализированы повторно.
Во время инициализации значений — для скалярных типов и типов класса POD, которые инициализируются с помощью пустых фигурных скобок.
Для массивов, у которых инициализировано только подмножество членов.
Ниже приведены некоторые примеры нулевой инициализации:
Инициализация по умолчанию
Инициализация по умолчанию для классов, структур и объединений — это инициализация с помощью конструктора по умолчанию. Конструктор по умолчанию можно вызвать без выражения инициализации или с помощью new ключевого слова:
Если класс, структура или объединение не имеет конструктор по умолчанию, компилятор выдает ошибку.
Скалярные переменные инициализируются по умолчанию, если при их определении не указываются выражения инициализации. Они имеют неопределенные значения.
Массивы инициализируются по умолчанию, если при их определении не указываются выражения инициализации. Если массив инициализируется по умолчанию, его члены инициализируются по умолчанию и приобретают неопределенные значения как в приведенном ниже примере.
Если члены массива не имеют конструктор по умолчанию, компилятор выдает ошибку.
Инициализация по умолчанию константных переменных
Константные переменные необходимо объявлять вместе с инициализатором. Если они относятся к скалярным типам, они вызывают ошибку компилятора, а если они относятся к типам классов, имеющим конструктор по умолчанию, они вызывают предупреждение:
Инициализация по умолчанию статических переменных
Статические переменные, объявленные без инициализатора, инициализируются значением 0 (с неявным преобразованием к соответствующему типу).
Дополнительные сведения об инициализации глобальных статических объектов см. в разделе функция Main и аргументы командной строки.
Инициализация значения
Инициализация значения происходит в следующих случаях:
Именованное значение инициализируется с использованием пустых фигурных скобок.
Анонимный временный объект инициализируется с помощью пустых круглых или фигурных скобок.
Объект инициализируется с помощью new ключевого слова, а также пустых круглых скобок или фигурных скобок
При инициализации значения выполняются следующие действия:
Для классов, имеющих хотя бы один открытый конструктор, вызывается конструктор по умолчанию.
В случае классов, не относящихся к объединениям, у которых нет объявленных конструкторов, объект инициализируется нулевым значением, и вызывается конструктор по умолчанию.
В случае массивов каждый элемент инициализируется значением.
Во всех остальных случаях переменная инициализируется нулевым значением.
Инициализация копированием
Инициализация копированием — это инициализация одного объекта с использованием другого объекта. Она выполняется в следующих случаях:
Переменная инициализируется с помощью знака равенства.
Аргумент передается в функцию.
Объект возвращается функцией.
Возникает или перехватывается исключение.
Нестатический элемент данных инициализируется с помощью знака равенства.
Следующий код демонстрирует несколько примеров инициализации копированием.
Инициализация копированием не может вызывать явные конструкторы.
В некоторых случаях, если конструктор копии класса удален или недоступен, копируемая инициализация вызывает ошибку компилятора.
Прямая инициализация
Прямая инициализация — это инициализация с использованием (непустых) круглых или фигурных скобок. В отличие от копируемой инициализации она может вызывать явные конструкторы. Она выполняется в следующих случаях:
Переменная инициализируется с помощью непустых круглых или фигурных скобок.
переменная инициализируется с помощью new ключевого слова, а также непустых фигурных скобок или круглых скобок
переменная инициализируется с помощью static_cast
В конструкторе базовые классы и нестатические члены инициализируются с помощью списка инициализации.
В копии захваченной переменной в лямбда-выражении.
Приведенный ниже код демонстрирует несколько примеров прямой инициализации.
Инициализация списка
Инициализация списком выполняется, когда переменная инициализируется с помощью списка инициализации в фигурных скобках. Списки инициализации в фигурных скобках можно использовать в следующих случаях:
класс инициализируется с помощью new ключевого слова
Объект возвращается функцией.
Аргумент передается функции.
Один из аргументов при прямой инициализации.
В инициализаторе нестатических элементов данных.
В списке инициализации конструктора.
Приведенный ниже код демонстрирует несколько примеров инициализации списком.
Агрегатная инициализация
Агрегатная инициализация — форма инициализации списка для массивов и типов классов (часто структур и объединений), со следующими характеристиками:
Отсутствие закрытых или защищенных членов.
Отсутствие заданных пользователем конструкторов кроме явно заданных по умолчанию или удаленных конструкторов.
Отсутствие базовых классов.
Отсутствие виртуальных функций-членов.
в Visual Studio 2015 и более ранних версиях статистическое выражение не может содержать инициализаторы с фигурными или равными скобками для нестатических членов. это ограничение было удалено в стандарте c++ 14 и реализовано в Visual Studio 2017.
Агрегатные инициализаторы состоят из списка инициализации в фигурных скобках со знаком равенства или без него как в приведенном ниже примере:
Вы должны увидеть следующий результат.
Элементы массива, объявляемые, но не инициализированные во время инициализации агрегата, инициализируются нулем, как показано myArr3 выше.
Инициализация объединений и структур
Если объединение не имеет конструктора, его можно инициализировать одним значением (или другим экземпляром объединения). Значение используется для инициализации первого нестатического поля. Это отличается от инициализации структур, где первое значение в инициализаторе используется для инициализации первого поля, второе — для инициализации второго поля и т. д. Сравните инициализацию объединений и структур в следующем примере:
Инициализация статистических выражений, содержащих статистические выражения
Агрегатные типы могут содержать другие агрегатные типы, например массивы массивов, массивы структур и т. п. Эти типы инициализируются с помощью вложенных наборов фигурных скобок, как показано в следующем примере:
Инициализация ссылок
Переменные ссылочного типа должны инициализироваться объектом типа, на котором основан ссылочный тип, или объектом типа, который можно преобразовать в такой тип. Пример:
Единственный способ инициализировать ссылку с помощью временного объекта является инициализация постоянного временного объекта. После инициализации переменная ссылочного типа всегда указывает на один и тот же объект; ее невозможно изменить, чтобы она указывала на другой объект.
Поскольку передача аргумента ссылочного типа в функцию и возврат значения ссылочного типа из функции являются инициализацией, формальные аргументы функции, а также возвращаемые ссылки инициализируются правильно.
Переменные ссылочного типа можно объявлять без инициализаторов только в указанных ниже случаях.
Объявления функций (прототипы). Пример:
Объявления типов значений, возвращаемых функцией. Пример:
Объявления члена класса ссылочного типа. Пример:
При инициализации переменной ссылочного типа компилятор с помощью графа принятия решений, показанного на следующем рисунке, выбирает между созданием ссылки на объект и созданием временного объекта, на который указывает ссылка.

Граф принятия решений для инициализации ссылочных типов
Инициализация внешних переменных
1.4 – Присваивание и инициализация переменных
В предыдущем уроке «1.3 – Знакомство с переменными в C++» мы рассмотрели, как определить переменную, которую мы сможем использовать для хранения значений. В этом уроке мы узнаем, как помещать значения в переменные, и как использовать эти значения.
Присваивание значения переменной
Копирующее присваивание названо так, потому что оно копирует значение с правой стороны оператора = в переменную с левой стороны оператора. Оператор = называется оператором присваивания.
Вот пример, где мы используем присваивание дважды:
Предупреждение
Одна из наиболее распространенных ошибок, которые делают новички, – это путать оператор присваивания ( = ) с оператором равенства ( == ). Присваивание ( = ) используется для присвоения значения переменной. Равенство ( == ) используется для проверки того, равны ли два операнда по значению.
Копирующая и прямая инициализации
Одним из недостатков присваивания является то, что для него требуются как минимум две инструкции: одна для определения переменной, а другая для присвоения значения.
Эти два шага можно совместить. При определении переменной вы также можете одновременно указать для нее начальное значение. Это называется инициализацией.
C++ поддерживает три основных способа инициализации переменных. Во-первых, мы можем выполнить копирующую инициализацию, используя знак равенства:
Подобно копирующему присваиванию, этот код копирует значение с правой стороны знака равно в переменную, создаваемую с левой стороны.
Во-вторых, мы можем выполнить прямую инициализацию с помощью скобок.
Для простых типов данных (например, целых чисел) копирующая и прямая инициализации, по сути, одинаковы. Различия между копирующей инициализацией и прямой инициализацией мы увидим в этой серии статей позже.
В-третьих, инициализация списком, которую мы рассмотрим в следующем разделе.
Инициализации списком
К сожалению, прямая инициализация с круглыми скобками не может использоваться для всех типов инициализации (таких как инициализация объекта списком данных). Чтобы обеспечить более согласованный механизм инициализации, существует инициализация списком (также иногда называемая унифицированной инициализацией или инициализацией с фигурными скобками), в которой используются фигурные скобки.
Инициализация списком бывает двух форм:
Эти две формы функционируют почти одинаково, но обычно предпочтительнее прямая форма.
Инициализация переменной с пустыми фигурными скобками указывает на инициализацию значения. В большинстве случаев инициализация значения инициализирует переменную нулевым значением (или пустым, если это более подходит для данного типа). В таких случаях, когда происходит обнуление, инициализация значения также называется нулевой инициализацией.
Инициализация списком имеет дополнительное преимущество, так как запрещает «сужающие» преобразования. Это означает, что если вы попытаетесь использовать инициализацию списком для инициализации переменной значением, которое она не может безопасно удерживать, компилятор выдаст предупреждение или ошибку. Например:
Лучшая практика
По возможности отдавайте предпочтение прямой инициализации списком.
Вопрос: C++ обеспечивает копирующую, прямую инициализации и инициализацию списком, а также копирующее присваивание. Существует ли прямое присваивание или присваивание списком?
Нет, C++ не поддерживает синтаксис прямого присваивания или присваивания списком.
Используйте явную инициализацию значением, если вы действительно используете это значение.
Используйте инициализацию значения, если значение временное и будет заменено.
Инициализируйте свои переменные
Инициализируйте свои переменные при создании. В конечном итоге вам могут встретиться случаи, когда вы захотите проигнорировать этот совет по определенной причине (например, критический для производительности фрагмент кода, который использует множество переменных), и это нормально, если выбор сделан сознательно.
Для более подробного обсуждения этой темы Бьёрн Страуструп (создатель C++) и Герб Саттер (эксперт по C ++) сами дают рекомендацию здесь.
Мы исследуем, что произойдет, если вы попытаетесь использовать переменную, для которой нет четко определенного значения, в уроке «1.6 – Неинициализированные переменные и неопределенное поведение».
Лучшая практика
Инициализируйте свои переменные при создании.
Инициализация нескольких переменных
В последнем разделе мы отметили, что можно определить несколько переменных одного типа в одной инструкции, разделив имена запятыми:
Мы также отметили, что лучше всего вообще избегать этого синтаксиса. Однако, поскольку вы можете столкнуться с чужим кодом, использующим этот стиль, всё же полезно поговорить об этом немного подробнее хотя бы, чтобы акцентировать внимание на некоторых причинам, по которым вам следует избегать его.
Вы можете инициализировать несколько переменных, определенных в одной строке:
К сожалению, здесь есть распространенная ошибка, которая может возникнуть, когда программист по ошибке пытается инициализировать обе переменные с помощью одной инструкции инициализации:
В верхней инструкции переменная a будет оставлена неинициализированной, и компилятор может пожаловаться, а может и не пожаловаться. Если он не пожалуется, это отличный способ для вашей программы периодически давать сбои и выдавать случайные результаты. В ближайшее время мы подробнее поговорим о том, что произойдет, если вы будете использовать неинициализированные переменные.
Лучший способ запомнить, что это неправильно, – рассмотреть случай прямой инициализации или инициализации списком:
Небольшой тест
Вопрос 1
В чем разница между инициализацией и присваиванием?
Инициализация дает переменной начальное значение в момент ее создания. Присваивание дает переменной значение в какой-то момент после создания.
Вопрос 2
Какую форму инициализации следует использовать?



