Было время, когда С++ не хватало динамизма, и увлечься этим языком было трудно. Но всё изменилось, когда было принято решение развить стандарт C++. С 2011 года язык стал более динамичным и постоянно развивается. В статье мы рассмотрим некоторые интересные функциональные возможности языка.
Когда в 11 версии C++ только появилось auto
, жизнь стала намного легче.
Идея auto
состояла в том, чтобы заставить компилятор C++ определять тип ваших данных во время компиляции, вместо того чтобы заставлять вас каждый раз объявлять тип. Это было удобно, если у вас были типы данных вроде map<string, vector <pair <int, int>>>
?
auto an_int = 26; // при компиляции тип выводится в int
auto a_bool = false; // в bool
auto a_float = 26.04f; // в float
auto ptr = &a_float; // и даже в указатель
auto data; // а можно ли так? Вообще-то нельзя.
Посмотрите на строку номер 5. Вы не можете объявить что-либо без инициализатора. Строка 5 не сообщает компилятору, каким может быть тип данных.
Изначально auto было несколько ограничено. Затем, в более поздних версиях языка, у него появилось больше возможностей.
В строках 7 и 8 была использована инициализация в скобках. Эта функция также была добавлена в 11 версии C++.
Не забывайте, что в случае использования auto у компилятора должен быть способ определить ваш тип.
Теперь встаёт хороший вопрос, что произойдёт, если мы напишем auto a = {1, 2, 3}? Это ошибка компиляции? Это вектор?
На самом деле, в 11 версии C++ был представлен std::initializer_list<type>. Инициализированный список в скобках будет считаться легковесным контейнером, если объявлен как auto.
И как упоминалось ранее, определять типы объектов компилятором полезно, когда у вас есть сложные структуры данных:
Не забудьте проверить строку 25! Выражение auto [v1, v2] = itr.second
— новая функция в 17 версии C++. Это называется структурным связыванием. В предыдущих версиях приходилось извлекать каждую переменную отдельно. Но структурное связывание сделало этот процесс более удобным.
Более того, если вы хотите получить данные, используя ссылку, то просто добавьте символ — auto &[v1, v2] = itr.second
.
В 11 версии C++ появились лямбда-выражения. Это что-то вроде анонимных функций в JavaScript. Они являются безымянными функциональными объектами и захватывают переменные в различных областях на основе некоторого краткого синтаксиса. Они также могут быть присвоены переменным.
Лямбды будут полезны, если вам нужно сделать в коде быстрое и небольшое изменение, и вы не хотите писать для этого отдельную функцию. Другое довольно распространённое использование функции — сравнение.
std::vector<std::pair::<int, int>> data = {{1, 3}, {7, 6}, {12, 4}}; // обратите внимание на скобочную инициализацию
std::sort(begin(data), end(data), [ ](auto a, auto b) { // auto!
return a.second < b.second;
});
Приведённый выше пример может многое сказать.
Во-первых, обратите внимание, как фигурные скобки упрощают вам жизнь. Затем следуют универсальные begin()
, end()
, которые тоже были добавлены в 11 версии. После идёт лямбда-выражение в качестве компаратора ваших данных. Параметры лямбда-выражения объявлены с помощью auto
, что было добавлено в 14 версии С++. До этого auto
нельзя было использовать в качестве параметров функции.
Обратите внимание, мы начинаем лямбда-выражение с квадратных скобок [ ]
. Они определяют область действия лямбды — сколько у неё полномочий над локальными переменными и объектами.
Как определено в этом потрясающем репозитории по современному C++:
[ ]
— ничего не захватывает. Таким образом, вы не можете использовать любую локальную переменную внешней области видимости в лямбда-выражении. Вы можете использовать только параметры.[=]
— захватывает локальные объекты (локальные переменные, параметры) в области видимости по значению. Вы можете использовать, но не изменять их.[&]
— захватывает локальные объекты (локальные переменные, параметры) в области видимости по ссылке. Вы можете изменить их, как в примере, приведённом ниже.[this]
— захватывает этот указатель по значению.[a, &b]
— захватывает объект a
по значению, объект b
по ссылке.Так что, если внутри лямбда-функции вы хотите преобразовать данные в какой-то другой формат, вы можете применить её, воспользовавшись преимуществами области видимости. Например:
В приведённом выше примере, если вы захватили локальные переменные по значению ([factor]
) в лямбда-выражении, то вы не можете изменить factor
в 5 строке. Вы просто не имеете права делать это. Не злоупотребляйте своими правами!
Наконец, обратите внимание, что мы берём переменную val
в качестве ссылки. Это гарантирует, что любое изменение внутри лямбда-функции фактически изменяет vector
.
Вам точно понравится эта возможность в С++ 17.
if
или switch
. Это поможет сделать код лаконичным и чистым. Общая форма:Скажем, у вас есть какое-то выражение для оценки, и его значение не изменится после инициализации. Вы можете предварительно рассчитать значение, а затем использовать его в качестве макроса. Или, как предложил C++ 11, можно использовать constexpr
.
Программисты стремятся максимально сократить время выполнения программ. Поэтому если некоторые операции можно отдать на выполнение компилятору, это стоит сделать.
Приведённый выше код — распространённый пример использования constexpr
.
Поскольку мы объявили функцию вычисления Фибоначчи как constexpr
, компилятор может предварительно вычислить fib(20)
во время компиляции. Так что после неё он может заменить строку с
const long long bigval = fib (20);
на
const long long bigval = 2432902008176640000;
Обратите внимание, что переданный аргумент является константным значением. Важный момент: в функциях, объявленных constexpr
, передаваемые аргументы также должны быть constexpr
или const
. В противном случае они будут вести себя как обычные функции, и во время компиляции предварительный расчёт выполняться не будет.
Переменные также могут быть constexpr
. В этом случае, как вы можете догадаться, эти переменные должны вычисляться во время компиляции. Иначе вы получите ошибку компиляции.
Интересно, что позже в C++ 17 были представлены constexpr-if
и constexpr-lambda
.
Как и пара, кортеж представляет собой набор значений фиксированного размера для различных типов данных.
auto user_info = std::make_tuple("M", "Chowdhury", 25); // используем auto, чтобы уменьшить описание типов
// чтобы получить доступ к данным
std::get<0>(user_info);
std::get<1>(user_info);
std::get<2>(user_info);
// в 11 версии С++ мы использовали tie, чтобы сделать связывание
std::string first_name, last_name, age;
std::tie(first_name, last_name, age) = user_info;
// но в 17 версии стало гораздо удобнее
auto [first_name, last_name, age] = user_info;
Иногда удобнее использовать std::array
вместо кортежа. Такой массив подобен обычному массиву в C вместе с несколькими функциями стандартной библиотеки C++. Эта структура данных была добавлена в 11 версии C++.
Очень подробное название для функции. Идея состоит в том, что с 17 версии типы шаблонных параметров будут выводиться и для стандартных шаблонных классов. Ранее это поддерживалось только для функций.
std::pair<std::string, int> user = {"M", 25}; // раньше
std::pair user = {"M", 25}; // C++ 17
В этом примере для первого элемента кортежа будет выведен тип const char *
, а не std::string
.
Выводимый тип задаётся неявно. Это становится ещё удобнее для кортежей.
// раньше
std::tuple<std::string, std::string, int> user ("M", "Chy", 25);
// C++ 17
std::tuple user2("M", "Chy", 25);
Эта функция не имеет никакого смысла, если вы слабо знакомы с шаблонами в C++.
Указатели могут быть адскими.
Из-за свободы, которую предоставляют такие языки, как C++, иногда становится очень легко выстрелить себе в ногу. И во многих случаях именно указатели ответственны за вред, нанесённый компьютеру.
К счастью, в C++11 появились умные указатели, которые намного удобнее, чем простые. Они помогают программистам предотвращать утечки памяти, освобождая её, когда это возможно. Они также обеспечивают исключительную безопасность.
Источник: tproger.ru