Позвольте мне сказать, что я хочу изменить последовательность элементов. Сначала я должен определить функцию.
Одно определение:
int main (void) { // sequence void reverse (vector<int> &arr) { int i = 0, j = arr.size() — 1; while (i < j) { int temp = arr[i]; arr[i++] = arr[j]; arr[j--] = temp; } } // calls reverse return 0; }
Но вы не можете сделать это в C++. Ваш компилятор жалуется на это.
file.cpp:9:35: error: function definition is not allowed here void reverse(vector<int> & arr) { ^ file.cpp:21:3: error: no matching function for call to ‘reverse’ reverse(arr); ^~~~~~~
Вы не можете определить функцию внутри другой функции.
Таким образом, единственный подход будет заключаться в том, чтобы определить его в другом месте.
void reverse (vector<int> &arr) { int i = 0, j = arr.size() — 1; while (i < j) { int temp = arr[i]; arr[i++] = arr[j]; arr[j--] = temp; } } int main (void) { // sequence // calls reverse return 0; }
Это просто работает.
[monster@monster tmp]$ clang++ file.cpp && ./a.out 9 8 7 6 5 4 3 2 1 [monster@monster tmp]$
Было бы лучше быть модульным и написать функцию подкачки. Обратите внимание, что мы не можем написать функцию swap
внутри обратной функции, так как это не разрешено. Стандарт говорит, что предварительное объявление является обязательным, но в современных компиляторах оно работает и без него.
void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } void reverse (vector<int> & arr) { int i = 0, j = arr.size() — 1; while (i < j) { swap(arr[i++], arr[j--]); } } // main here
Разве мы не можем просто иметь причудливое правило объявлять функцию где угодно, как во многих других динамических и статических языках?
Короче говоря, мы можем. У нас есть лямбда-выражения C++.
int main(void) { // sequence auto swap = [](int *a, int *b) { int temp = *a; *a = *b; *b = temp; }; auto reverse = [](vector<int> & arr) { int i = 0, j = arr.size() — 1; while (i < j) swap(arr[i++], arr[j--]); }; // calls reverse }
Мы тоже можем это сделать.
auto reverse = [](vector<int> &arr) { auto swap = [](int *a, int *b) { int temp = *a; *a = *b; *b = temp; }; int i = 0, j = arr.size() — 1; while (i < j) swap(arr[i++], arr[j--]); };
Переменная reverse
получает тип данных function<void(vector<int> &)>
, а переменная swap
получает тип данных function<void(int *, int *)>
.
Что насчет рекурсии?
Рекурсия немного сложна. Следующий код не работает.
auto factorial = [](int n) { if (n <= 1) return 1; return n * factorial(n — 1); };
Не работает по разным причинам.
Одна из причин заключается в том, что тип возвращаемого значения вообще неизвестен. auto
не может определить тип. мы знаем, что делает оператор if, возвращает 1
, но мы не уверены, что сделает оператор else
. По крайней мере, наш компилятор не знает.
file.cpp:9:19: error: variable ‘factorial’ declared with deduced type ‘auto’ cannot appear in its own initializer return n * factorial(n — 1); ^ 1 error generated.
Другая причина в том, что лямбда-функция factorial
вообще не захвачена. Чтобы захватить его, нам просто нужно передать то же самое в предложении захвата, [&]
. &
говорит передавать все как ссылку.
file.cpp:9:19: error: variable ‘factorial’ cannot be implicitly captured in a lambda with no capture-default specified return n * factorial(n — 1); ^ file.cpp:7:22: note: ‘factorial’ declared here function<int(int)> factorial = [](int n) { ^ file.cpp:7:34: note: lambda expression begins here function<int(int)> factorial = [](int n) {
Наконец, это будет наше определение.
function<int(int)> factorial = [&](int n) { if (n <= 1) return 1; return n * factorial(n — 1); };
Единственное, что мы выиграли от лямбда-выражений, это то, что их можно определить где угодно.
Хотя это неправда. C++14
добавляет к этому гораздо больше.
В C++14 даже параметры можно определить с помощью auto
.
auto lambda = [](auto x, auto y) { return x + y; };
Приведенный выше код будет эквивалентен
struct unnamed_lambda { template<typename T, typename U> auto operator()(T x, U y) const { return x + y; } }; }; auto lambda = unnamed_lambda{};
Фактическая проблема с лямбда-выражениями - это рекурсии. Как было сказано ранее, мы можем определить тип function<>
и создать лямбда-функцию, но проблема с использованием типа function<>
заключается в том, что std::function
имеет проблемы с производительностью, поскольку он выполняет выделение кучи.
Таким образом, рекурсии в лямбда-выражениях применимы только тогда, когда вы определяете тип function<>
. Так было до C++11
. Но поскольку C++14
позволяет параметрам иметь объявление auto
, мы можем передать саму лямбда-функцию в качестве аргумента.
auto factorial = [](auto &&self, auto n) { if (n <= 1) return 1; return n * self(self, n — 1); }; // usage: factorial(factorial, 5);
&&
является ссылкой RValue. Мы можем игнорировать его, но поскольку мы его не меняем, мы можем просто передать его как ссылку. И смотрите, нам не нужно захватывать саму функцию для рекурсии, так как функция захватывается как аргумент.