Списки инициализации (initializer lists) - это мощный механизм в C++ для инициализации объектов и агрегатов. Они появились в C++11 и значительно улучшили способы инициализации переменных.
Основные виды инициализации в C++
Прямая инициализация - T obj(arg);
Копирующая инициализация - T obj = arg;
Uniform инициализация - T obj{arg}; (с использованием фигурных скобок)
Позволяет использовать фигурные скобки {} для инициализации любых объектов.
#include <iostream>
#include <vector>
#include <string>
int main() {
// Инициализация встроенных типов
int x{5}; // вместо int x = 5;
double y{3.14}; // вместо double y = 3.14;
// Инициализация массивов
int arr[]{1, 2, 3, 4, 5}; // вместо int arr[5] = {1, 2, 3, 4, 5};
// Инициализация STL-контейнеров
std::vector<int> vec{1, 2, 3, 4, 5};
std::string str{"Hello"};
// Инициализация динамических объектов
int* ptr = new int[3]{10, 20, 30};
std::cout << "x = " << x << ", y = " << y << std::endl;
for (auto v : vec) std::cout << v << " ";
delete[] ptr;
return 0;
}
Пояснение:
Фигурные скобки {} обеспечивают более унифицированный синтаксис инициализации. Они предотвращают сужающие преобразования (narrowing conversions) - компилятор выдаст ошибку, если произойдет потеря данных.
Класс std::initializer_list
Шаблонный класс std::initializer_list позволяет передавать списки значений в функции и конструкторы.
#include <iostream>
#include <initializer_list>
#include <vector>
// Функция, принимающая список инициализации
void printNumbers(std::initializer_list<int> numbers) {
for (auto num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
}
class MyVector {
std::vector<int> data;
public:
// Конструктор, принимающий список инициализации
MyVector(std::initializer_list<int> init) : data(init) {}
void print() {
for (auto elem : data) {
std::cout << elem << " ";
}
std::cout << std::endl;
}
};
int main() {
// Вызов функции со списком инициализации
printNumbers({1, 2, 3, 4, 5});
// Создание объекта с инициализацией через список
MyVector vec{10, 20, 30, 40};
vec.print();
return 0;
}
Пояснение: std::initializer_list - это lightweight-объект, содержащий константный массив элементов. Он предоставляет методы begin(), end(), size() для доступа к элементам.
Использование в конструкторах классов
Списки инициализации особенно полезны при создании классов, которые должны поддерживать инициализацию списком значений.
#include <iostream>
#include <initializer_list>
#include <string>
class ShoppingList {
std::string* items;
size_t count;
public:
// Конструктор со списком инициализации
ShoppingList(std::initializer_list<std::string> initList)
: count(initList.size()) {
items = new std::string[count];
size_t i = 0;
for (const auto& item : initList) {
items[i++] = item;
}
}
// Деструктор
~ShoppingList() {
delete[] items;
}
void print() const {
std::cout << "Shopping List:" << std::endl;
for (size_t i = 0; i < count; ++i) {
std::cout << " - " << items[i] << std::endl;
}
}
};
int main() {
// Инициализация через список
ShoppingList myList{"Milk", "Eggs", "Bread", "Cheese"};
myList.print();
return 0;
}
Пояснение: Конструктор со списком инициализации имеет приоритет при выборе конструктора, если используется синтаксис с фигурными скобками. Это позволяет создавать удобные интерфейсы для классов.
Инициализация агрегатов
Агрегаты (структуры и массивы) могут быть инициализированы с помощью списков инициализации.
#include <iostream>
#include <string>
// Агрегатная структура (нет пользовательских конструкторов, private-членов и т.д.)
struct Person {
std::string name;
int age;
double height;
};
int main() {
// Инициализация структуры
Person p1{"Alice", 30, 165.5};
// Инициализация массива структур
Person team[]{
{"Bob", 25, 180.0},
{"Charlie", 35, 172.3},
{"Diana", 28, 167.8}
};
std::cout << p1.name << " is " << p1.age << " years old." << std::endl;
std::cout << "Team members:" << std::endl;
for (const auto& member : team) {
std::cout << " - " << member.name << std::endl;
}
return 0;
}
Пояснение: Для агрегатных типов можно использовать списки инициализации для прямого заполнения полей. Порядок элементов в списке должен соответствовать порядку объявления полей в структуре.
Списки инициализации в STL
Контейнеры STL активно используют списки инициализации.
#include <iostream>
#include <vector>
#include <map>
#include <set>
int main() {
// Инициализация вектора
std::vector<int> primes{2, 3, 5, 7, 11, 13};
// Инициализация множества
std::set<std::string> fruits{"apple", "banana", "orange"};
// Инициализация словаря
std::map<std::string, int> ages{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35}
};
std::cout << "Primes: ";
for (auto p : primes) std::cout << p << " ";
std::cout << "\nFruits: ";
for (auto f : fruits) std::cout << f << " ";
std::cout << "\nAges:\n";
for (auto a : ages) {
std::cout << a.first << ": " << a.second << std::endl;
}
return 0;
}
Пояснение: STL-контейнеры имеют конструкторы, принимающие std::initializer_list, что делает их инициализацию очень удобной. Это работает для всех стандартных контейнеров.
Особенности и ограничения
Запрет сужающих преобразований:
int x{5.0}; // Ошибка: сужающее преобразование double -> int
Пустые списки инициализации:
int x{}; // Инициализация значением по умолчанию (0)
std::vector v{}; // Пустой вектор
Приоритет конструкторов:
class Example {
public:
Example(int, int); // #1
Example(std::initializer_list); // #2
};
Example e1(1, 2); // Вызов #1
Example e2{1, 2}; // Вызов #2 (список инициализации имеет приоритет)
Списки инициализации в C++ предоставляют:
Единообразный синтаксис инициализации для всех типов
Безопасность (запрет сужающих преобразований)
Удобство работы с контейнерами и агрегатами
Возможность создания гибких интерфейсов классов
Рекомендуется использовать фигурные скобки {} для инициализации вместо круглых () или знака =, так как это обеспечивает более строгую проверку типов и единообразие кода.