Посмотрите на строку номер 5. Вы не можете объявить что-либо без инициализатора. Строка 5 не сообщает компилятору, каким может быть тип данных.
Изначально auto было несколько ограничено. Затем, в более поздних версиях языка, у него появилось больше возможностей.
В строках 7 и 8 была использована инициализация в скобках. Эта функция также была добавлена в 11 версии C++.
Не забывайте, что в случае использования auto у компилятора должен быть способ определить ваш тип.
Теперь встаёт хороший вопрос, что произойдёт, если мы напишем auto a = {1, 2, 3}? Это ошибка компиляции? Это вектор?
На самом деле, в 11 версии C++ был представлен std::initializer_list<type>. Инициализированный список в скобках будет считаться легковесным контейнером, если объявлен как auto.
И как упоминалось ранее, определять типы объектов компилятором полезно, когда у вас есть сложные структуры данных:
void populate(auto &data) { // видите!
data.insert({"a", {1, 4}});
data.insert({"b", {3, 1}});
data.insert({"c", {2, 3}});
}
auto merge(auto data, auto upcoming_data) { // и не надо писать длинный идентификатор снова
auto result = data;
for (auto it: upcoming_data) {
result.insert(it);
}
return result;
}
int main() {
std::map<std::string, std::pair<int, int>> data;
populate(data);
std::map<std::string, std::pait<int, int>> upcoming_data;
upcoming_data.insert({"d", {5, 3}});
auto final_data = merge(data, upcoming_data);
for (auto itr: final_data) {
auto [v1, v2] = itr.second; // про структурное связывание будет ниже
std::cout << itr.first << " " << v1 << " " << v2 << std::endl;
}
return 0;
}
Не забудьте проверить строку 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
по ссылке.
Так что, если внутри лямбда-функции вы хотите преобразовать данные в какой-то другой формат, вы можете применить её, воспользовавшись преимуществами области видимости. Например:
std::vector<int> data = {2, 4, 4, 1, 1, 3, 9};
int factor = 7;
for_each(begin(data), end(data), [&factor](int &val) { // захват factor по ссылке
val = val * factor;
factor--; // это будет работать, потому что переменная находится в области видимости лямбды
});
for(int val: data) {
std::cout << val << ' '; // 14 24 20 4 3 6 9
}
В приведённом выше примере, если вы захватили локальные переменные по значению ([factor]
) в лямбда-выражении, то вы не можете изменить factor
в 5 строке. Вы просто не имеете права делать это. Не злоупотребляйте своими правами!
Наконец, обратите внимание, что мы берём переменную val
в качестве ссылки. Это гарантирует, что любое изменение внутри лямбда-функции фактически изменяет vector
.
Инициализатор в if и switch
Вам точно понравится эта возможность в С++ 17.
std::set<int> input = {1, 5, 3, 6};
if(auto it = input.find(7); it == input.end()) { // первая часть - инициализация, вторая - условие
std::cout << 7 << " not found!" << std::endl;
}
else {
// it тоже попадает в область видимости else!
std::cout << 7 << " is there!" << std::endl;
}
Очевидно, теперь вы можете выполнять инициализацию переменных и проверять условие сразу внутри блоков if
или switch
. Это поможет сделать код лаконичным и чистым. Общая форма:
if (init-statement(x); condition(x)) {
// какой-то код
}
else { // else тоже имеет переменную x в области видимости
// какой-то другой код
}
Компиляция и constexpr
Скажем, у вас есть какое-то выражение для оценки, и его значение не изменится после инициализации. Вы можете предварительно рассчитать значение, а затем использовать его в качестве макроса. Или, как предложил C++ 11, можно использовать constexpr
.
Программисты стремятся максимально сократить время выполнения программ. Поэтому если некоторые операции можно отдать на выполнение компилятору, это стоит сделать.
constexpr double fib(int n) { // функция объявлена с помощью constexpr
if(n == 1) return 1;
return fib(n-1) * n;
}
int main()
{
const long long bigval = fib(20);
std::cout << bigval << std::endl;
return 0;
}
Приведённый выше код — распространённый пример использования 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