Инициализаторы
Инициализатор определяет начальное значение переменной. Можно инициализировать переменные в этих контекстах:
В определении переменной:
int i = 3; Point p1{ 1, 2 };
В качестве одного из параметров функции:
set_point(Point{ 5, 6 });
В качестве возвращаемого типа функции:
Point get_new_point(int x, int y) { return { x, y }; } Point get_new_point(int x, int y) { return Point{ x, y }; }
Инициализаторы могут принимать эти формы:
Выражение (или разделенный запятыми список выражений) в скобках:
Point p1(1, 2);
Знак равенства с последующим выражением:
string s = "hello";
Список инициализации в фигурных скобках. Список может быть пустым или может состоять из набора списков:
struct Point{ int x; int y; }; class PointConsumer{ public: void set_point(Point p){}; void set_points(initializer_list<Point> my_list){}; }; int main() { PointConsumer pc{}; pc.set_point({}); pc.set_point({ 3, 4 }); pc.set_points({ { 3, 4 }, { 5, 6 } }); }
Типы инициализации
Существует несколько типов инициализации, которые могут встречаться на различных этапах выполнения программы. Различные типы инициализации не является взаимоисключающими, например, инициализация списка может активировать инициализацию значений, а в других условиях она может активировать агрегатную инициализацию.
Нулевая инициализация
Нулевая инициализация — задание для переменной нулевого значения, неявно преобразованного в тип:
Числовые переменные инициализируются значением 0 (или 0,0; 0,0000000000 и т.п.).
Переменные char инициализируются значением «\0».
Указатели инициализируются значением nullptr.
Члены массивов, классов POD, структур и объединений инициализируются нулевым значением.
Нулевая инициализация выполняется в разное время:
При запуске программы — для всех именованных переменных, имеющих статическую длительность. Далее эти переменные могут быть инициализированы повторно.
Во время инициализации значений — для скалярных типов и типов класса POD, которые инициализируются с помощью пустых фигурных скобок.
Для массивов, у которых инициализировано только подмножество членов.
Ниже приведены некоторые примеры нулевой инициализации:
struct my_struct{
int i;
char c;
};
int i0; // zero-initialized to 0
int main() {
static float f1; // zero-initialized to 0.000000000
double d{}; // zero-initialized to 0.00000000000000000
int* ptr{}; // initialized to nullptr
char s_array[3]{'a', 'b'}; // the third char is initialized to '\0'
int int_array[5] = { 8, 9, 10 }; // the fourth and fifth ints are initialized to 0
my_struct a_struct{}; // i = 0, c = '\0'
}
Инициализация по умолчанию
Инициализация по умолчанию для классов, структур и объединений — инициализация, использующая конструктор по умолчанию. Конструктор по умолчанию может быть вызван путем опущения выражения инициализации или путем использования ключевого слова new:
MyClass mc1;
MyClass* mc3 = new MyClass;
Если класс, структура или объединение не имеет конструктор по умолчанию, компилятор выдает ошибку.
Скалярные переменные по умолчанию инициализируются при их определении без выражения инициализации. Они имеют неопределенные значения.
int i1;
float f;
char c;
Массивы по умолчанию инициализируются при их определении без выражения инициализации. Если массив инициализируется по умолчанию, его члены инициализируются по умолчанию и приобретают неопределенные значения:
int int_arr[3];
Если члены массива не имеют конструктор по умолчанию, компилятор выдает ошибку.
Инициализация по умолчанию переменных-констант
Константные переменные необходимо объявлять вместе с инициализатором. Если они относятся к скалярным типам, они вызывают ошибку компилятора, а если они относятся к типам классов, имеющих конструктор по умолчанию, они вызывают предупреждение:
class MyClass{};
int main() {
//const int i2; // compiler error C2734: const object must be initialized if not extern
//const char c2; // same error
const MyClass mc1; // compiler error C4269: 'const automatic data initialized with compiler generated default constructor produces unreliable results
}
Инициализация по умолчанию статических переменных
Статические переменные, объявленные без инициализатора, инициализируются значением 0 (с неявным преобразованием в тип).
class MyClass {
private:
int m_int;
char m_char;
};
int main() {
static int int1; // 0
static char char1; // '\0'
static bool bool1; // false
static MyClass mc1; // {0, '\0'}
}
Дополнительные сведения об инициализации глобальных статических объектов см. в разделе Дополнительные сведения о запуске.
Инициализация значения
Инициализация значения происходит в следующих случаях:
Именованное значение инициализируется путем инициализации с пустыми фигурными скобками.
Анонимный временный объект инициализируется с помощью пустых скобок или фигурных скобок.
Объект инициализируется с помощью ключевого слова new и пустых скобок или фигурных скобок.
Инициализация значения делает следующее:
Для классов, имеющих хотя бы один открытый конструктор, вызывается конструктор по умолчанию.
Для классов, не относящихся к объединениям, у которых нет объявленных конструкторов, объект инициализируется нулевым значением, и вызывается конструктор по умолчанию.
Для массивов каждый элемент инициализируется по значению.
Во всех остальных случаях переменная инициализируется нулевым значением.
class BaseClass {
private:
int m_int;
};
int main() {
BaseClass bc{}; // class is initialized
BaseClass* bc2 = new BaseClass(); // class is initialized, m_int value is 0
int int_arr[3]{}; // value of all members is 0
int a{}; // value of a is 0
double b{}; // value of b is 0.00000000000000000
}
Копируемая инициализация
Копируемая инициализация — инициализация одного объекта с помощью другого объекта. Это происходит в следующих случаях:
Переменная инициализируется с помощью знака равенства.
Аргумент передается функции.
Объект возвращается из функции.
Возникает или перехватывается исключение.
Нестатический данные-член инициализируется с помощью знака равенства.
Класс, структура и члены объединения инициализируются с помощью копируемой инициализацией во время агрегатной инициализации. Примеры см. в разделе Агрегатная инициализация.
Этот код содержит примеры копируемой инициализации:
#include <iostream>
using namespace std;
class MyClass{
public:
MyClass(int myInt) {}
void set_int(int myInt) { m_int = myInt; }
int get_int() const { return m_int; }
private:
int m_int = 7; // copy initialization of m_int
};
class MyException : public exception{};
int main() {
int i = 5; // copy initialization of i
MyClass mc1{ i };
MyClass mc2 = mc1; // copy initialization of mc2 from mc1
MyClass mc1.set_int(i); // copy initialization of parameter from i
int i2 = mc2.get_int(); // copy initialization of i2 from return value of get_int()
try{
throw MyException();
}
catch (MyException ex){ // copy initialization of ex
cout << ex.what();
}
}
Копируемая инициализация не может вызывать явные конструкторы:
vector<int> v = 10; // the constructor is explicit; compiler error C2440: cannot convert from 'int' to 'std::vector<int,std::allocator<_Ty>>'
regex r = "a.*b"; // the constructor is explicit; same error
shared_ptr<int> sp = new int(1729); // the constructor is explicit; same error
В некоторых случаях, если конструктор копии класса удален или недоступен, копируемая инициализация вызывает ошибку компилятора. Дополнительные сведения см. в разделе Явная инициализация.
Прямая инициализация
Прямая инициализация использует (не пустые) круглые или фигурные скобки. В отличие от копируемой инициализации она может вызывать явные конструкторы. Это происходит в следующих случаях:
Переменная инициализируется путем использования не пустых скобок или фигурных скобок.
Переменная инициализируется с помощью ключевого слова new и не пустых скобок или фигурных скобок.
Переменная инициализируется с помощью static_cast.
В конструкторе базовые классы и нестатические члены инициализируются с помощью списка инициализации.
В копии захваченной переменной в лямбда-выражении.
Ниже приведены примеры прямой инициализации:
class BaseClass{
public:
BaseClass(int n) :m_int(n){} // m_int is direct initialized
private:
int m_int;
};
class DerivedClass : public BaseClass{
public:
// BaseClass and m_char are direct initialized
DerivedClass(int n, char c) : BaseClass(n), m_char(c) {}
private:
char m_char;
};
int main(){
BaseClass bc1(5);
DerivedClass dc1{ 1, 'c' };
BaseClass* bc2 = new BaseClass(7);
BaseClass bc3 = static_cast<BaseClass>(dc1);
int a = 1;
function<int()> func = [a](){ return a + 1; }; // a is direct initialized
int n = func();
}
Инициализация списка
Инициализация списка выполняется, если переменная инициализируется с помощью списка инициализации в фигурных скобках. Списки инициализации в фигурных скобках можно использовать в следующих случаях:
Инициализируется переменная.
Класс инициализируется с помощью ключевого слова new.
Объект возвращается из функции.
Аргумент передается функции.
Один из аргументов в прямой инициализации.
В инициализаторе нестатических данных-членов.
В списке инициализаторов конструктора.
Примеры инициализации списка:
class MyClass {
public:
MyClass(int myInt, char myChar) {}
private:
int m_int[]{ 3 };
char m_char;
};
class MyClassConsumer{
public:
void set_class(MyClass c) {}
MyClass get_class() { return MyClass{ 0, '\0' }; }
};
struct MyStruct{
int my_int;
char my_char;
MyClass my_class;
};
int main() {
MyClass mc1{ 1, 'a' };
MyClass* mc2 = new MyClass{ 2, 'b' };
MyClass mc3 = { 3, 'c' };
MyClassConsumer mcc;
mcc.set_class(MyClass{ 3, 'c' });
mcc.set_class({ 4, 'd' });
MyStruct ms1{ 1, 'a', { 2, 'b' } };
}
Агрегатная инициализация
Агрегатная инициализация — форма инициализации списка для массивов и типов классов (часто структур и объединений), со следующими характеристиками:
Отсутствие закрытых или защищенных членов.
Отсутствие заданных пользователем конструкторов, за исключением явно заданных по умолчанию или удаленных конструкторов.
Отсутствие базовых классов.
Отсутствие виртуальных функций-членов.
Отсутствие инициализаторов с фигурными скобками и знаками равенства для нестатических членов.
Агрегатные инициализаторы состоят из списка инициализации в фигурных скобках с знаком равенства или без него:
#include <iostream>
using namespace std;
struct MyAggregate{
int myInt;
char myChar;
};
int main() {
MyAggregate agg1{ 1, 'c' };
cout << "agg1: " << agg1.myChar << ": " << agg1.myInt << endl;
cout << "agg2: " << agg2.myChar << ": " << agg2.myInt << endl;
int myArr1[]{ 1, 2, 3, 4 };
int myArr2[3] = { 5, 6, 7 };
int myArr3[5] = { 8, 9, 10 };
cout << "myArr1: ";
for (int i : myArr1){
cout << i << " ";
}
cout << endl;
cout << "myArr3: ";
for (auto const &i : myArr3) {
cout << i << " ";
}
cout << endl;
}
Выходные данные этого кода:
agg1: c: 1
agg2: d: 2
myArr1: 1 2 3 4
myArr3: 8 9 10 0 0
Важно!
Члены массива, объявленные, но не инициализированные явно во время агрегатной инициализации, инициализируются нулевым значением, как в myArr3.
Инициализация соединений и структур
Если соединение не имеет конструктор, можно инициализировать его с помощью значения (или с помощью другого экземпляра соединения). Значение используется для инициализации первого нестатического поля. Это отличается от инициализации структур, где первое значение в инициализаторе используется для инициализации первого поля, второе — для инициализации второго поля и т. д. Сравните инициализацию соединений и структур в следующем примере:
struct MyStruct {
int myInt;
char myChar;
};
union MyUnion {
int my_int;
char my_char;
bool my_bool;
MyStruct my_struct;
};
int main() {
MyUnion mu1{ 'a' }; // my_int = 97, my_char = 'a', my_bool = true, {myInt = 97, myChar = '\0'}
MyUnion mu2{ 1 }; // my_int = 1, my_char = 'x1', my_bool = true, {myInt = 1, myChar = '\0'}
MyUnion mu3{}; // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
MyUnion mu4 = mu3; // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
//MyUnion mu5{ 1, 'a', true }; // compiler error: C2078: too many initializers
//MyUnion mu6 = 'a'; // compiler error: C2440: cannot convert from 'char' to 'MyUnion'
//MyUnion mu7 = 1; // compiler error: C2440: cannot convert from 'int' to 'MyUnion'
MyStruct ms1{ 'a' }; // myInt = 97, myChar = '\0'
MyStruct ms2{ 1 }; // myInt = 1, myChar = '\0'
MyStruct ms3{}; // myInt = 0, myChar = '\0'
MyStruct ms4{1, 'a'}; // myInt = 1, myChar = 'a'
MyStruct ms5 = { 2, 'b' }; // myInt = 2, myChar = 'b'
}
Инициализация статистических выражений, содержащих статистические выражения
Агрегатные типы могут содержать другие агрегатные типы, например массивы массивов, массивы структур и т. п. Эти типы инициализируются с помощью вложенных наборов фигурных скобок.
struct MyStruct {
int myInt;
char myChar;
};
int main() {
int intArr1[2][2]{{ 1, 2 }, { 3, 4 }};
int intArr3[2][2] = {1, 2, 3, 4};
MyStruct structArr[]{ { 1, 'a' }, { 2, 'b' }, {3, 'c'} };
}
Инициализация ссылок
Дополнительные сведения об инициализации ссылок см. в разделе Инициализация ссылок.
Инициализация внешних переменных
Объявления автоматических, регистровых, статических и внешних переменных могут содержать инициализаторы. Однако объявления внешних переменных могут содержать инициализаторы, только если эти переменные не объявлены как extern. Дополнительные сведения см. в разделе Внешние.