Стандарты языка С++

История стандартов языка C++

Важную роль в развитии языка C++ играют стандарты языка. Хотя язык C++ разрабатывался с 1980-х годов, первый стандарт языка C++98 был окончательно утвержден только в 1998 году.

В 2003 году был издан стандарт С++03, являющийся уточнением стандарта C++98.

Наиболее существенные изменения языка произошли в стандарте C++11, разработка которого завершилась в 2011 году. Далее будут изложен ряд нововведений стандарта C++11.

В 2014 году был издан стандарт С++14, не содержащий существенных изменений, а только устраняющий ряд дефектов стандарта С++11.

Разработка следующего стандарта C++17 будет завершена в 2017 году. Этот стандарт также содержит ряд интересных нововведений, однако их не столь много, как в стандарте С++11.

Ссылки:

Статья в википедии про стандарт С++11

Таблица поддержки нововведений стандартов С++11, С++14, С++17 в разных компиляторах

mingw-w64-install.exe - установщик свежей версии компилятора mingw-w64 для Windows

Auto-тип переменной

При работе с контейнерами STL иногда приходится писать конструкции вида

map<int, pair<string, double>>::reverse_iterator it;

То есть описание типа может быть очень длинной строкой. Вместо этого в C++11 можно объявить имя типа, как auto, что означает, что компилятор должен сам определить тип переменной. В этом случае переменная должна быть явно инициализирована при объявлении, например:

auto it = a.begin();

В этом случае компилятор знает тип значения a, поэтому он может определить тип, который возвращает метод begin(), и тем самым будет определен тип переменной it.

Самый простой вариант (можно использовать для определения того, включена ли поддержка C++11):

auto a = 0;

В этом случае переменная a будет иметь тип int. Если написать:

auto a = 0.0;

то переменная a будет иметь тип double.

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

range-based циклы

В C++11 появились range-based циклы: циклы, в которых переменная пробегает по всем значением контейнера. Контейнер должен поддерживать методы begin() и end() -- это может быть vector, list, set, map.

Пример такого цикла для вывода элементов вектора:

vector<int> a;
for (int elem: a)
    cout << elem << endl;

Очень часто в качестве типа переменной используется слово auto.

Таким образом можно модифицировать элементы вектора, если сделать цикл по переменной-ссылке, а не по переменной-значению:

vector<int> a;
for (auto & elem: a)
    ++elem;

Если цикл пробегает по элементам контейнера map, то переменная будет парой из двух элементов: ключ и значение. То есть:

map<int, int> a;
for (auto & elem: a)
{
    // a.first - ключ элемента словаря, a.second - его значение
}

Универсальная инициализация

В языке C элементы массива можно инициализировать списком значений, например, так:

int a[3] = {1, 2, 3};

В С++11 такая универсальная инициализация распространена на структуры и классы. В списке инициализации в фигурных скобках необходимо указать значения полей структуры в том порядке, в котором они объявлены. Например,

struct point {
    int x, y;
};
point P;
P = {1, 2};

В этом случае P.x будет равно 1, P.y будет 2.

Еще один способ использования универсальной инициализации:

point P{1, 2};

Поскольку pair является структурой, то этот способ можно использовать для инициализации pair. Например, если функция возвращает значение типа pair, то можно написать:

return {a, b};

вместо

return make_pair{a, b};

Шаблоны с переменным числом аргументов и std::tuple

В C++11 появилась возможность создания шаблонов с переменным числом аргументов. Один из примеров такого шаблона - tuple, определенный в одноименном заголовочном файле. Это кортеж, то есть переменная, которая содержит несколько полей различных типов.

Например:

tuple<string, string, int> person;

Доступ к составным полям tuple осуществляется при помощи функции-шаблона get с одним числовым параметром -- номер поля, к которому производится доступ. Например:

get<0>(person) = "Peter";
get<1>(person) = "Ivanov";
get<2>(person) = 16;

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

for (int i = 0; i < 3; ++i) {cout << get<i>(person) << " "};

Объекты класса tuple сравниваются в лексикографическом порядке, поэтому их удобно использовать при сортировке векторов вместо pair, если нужно сортировать по нескольким параметрам.

Структура tie

tie является структурой, определенной в заголовочном файле tuple. Структура tie похожа на tuple, только членами структуры являются ссылки на какие-либо переменные (или прочие lvalue, например, элементы массива и т.д.). Это позволяет модифицировать переменные, переданные в качестве параметров при создании tie.

Пример. Пусть функция f возвращает пару значений, то есть структуру pair или tuple. Хочется записать эти значения в две переменные. Раньше мы писали так:

auto res = f();
a = res.first;
b = res.second;

С использованием tie это можно сделать так:

tie(a, b) = f();

Лямбда-функции

Лямбда-функции - это безымянные функции, которые не получают собственного имени, а используются только в месте объявления.

Распространенное использование лямбда-функций - это параметр-компаратор при сортировке. Вместо создания специальной функции-компаратора, можно описать функцию прямо в вызове функции sort.

Пример - отсортируем vector<int> a по последней цифре числа:

sort(a.begin(), a.end(), [](int x, int y) -> bool {return x % 10 < y % 10;});

nullptr

В языке C для обозначения нулевого указателя использовался макрос NULL, который на самом деле был объявлен так:

#define NULL 0

То есть NULL является значением типа int, что может привести к проблемам сравнения указателя с целым числом. В стандарте C++11 для обозначения нулевого указателя появилось новое специальное значение nullptr.

Угловые скобки во вложенных шаблонах

При определении вложенных шаблонов, например:

vector <vector<int>> a;

в C++11 можно не ставить пробел между "<<", что было обязательным в ранних версиях C++.

Типы данных

С С++11 стандартизирован тип long long int, который раньше был расширением GNU C++.

Кроме того, появились типы данных фиксированного размера, например, "32-битное целое число", они определены в заголовочном файле cstdint.

Стандартная библиотека

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