Этот пост является частью моего Руководства по C ++ для разработчиков EOS.
- Основы
- Вызов по значению / ссылке и указателям
- Классы и структуры
- Шаблоны
- Итераторы и лямбда-выражения
- Мультииндекс
- Заголовочные файлы
Итераторы
Давайте поговорим об итераторах, действительно полезном инструменте, который активно используется во всей кодовой базе EOS. Если вы работаете с JavaScript, возможно, вы уже знакомы с итераторами, как будто они используются в for of
циклах. Ключевая концепция итераторов - предоставить более удобный способ итерации по коллекции элементов. Дополнительным преимуществом является то, что вы можете реализовать интерфейс итератора для любых пользовательских классов, что делает итераторы универсальным способом просмотра данных.
// @url: https://repl.it/@MrToph/CPPBasics-Iterators
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v{2, 3, 5, 8};
// old way to iterate
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << "\n";
}
// using Iterators
// begin() returns an iterator that points to the beginning of the vector
// end() points to the end, can be compared using != operator
// iterators are incremented by using the + operator thanks to operator-overloading
for (vector<int>::iterator i = v.begin(); i != v.end(); i++)
{
// iterators are dereferenced by * like pointers
// returns the element the iterator is currently pointing to
cout << *i << "\n";
}
// auto keyword allows you to not write the type yourself
// instead C++ infers it from the return type of v.begin
for (auto i = v.begin(); i != v.end(); i++)
{
cout << *i << "\n";
}
// can use arithmetic to "jump" to certain elements
int thirdElement = *(v.begin() + 2);
cout << "Third: " << thirdElement << "\n";
// end is the iterator that points to the "past-the-end" element
// The past-the-end element is the theoretical element that would follow the last element in the vector.
// It does not point to any element, and thus shall not be dereferenced.
int lastElement = *(v.end() - 1);
cout << "Last: " << lastElement << "\n";
// do not go out of bounds by iterating past the end() iterator
// the behavior is undefined
// BAD: v.end() + 1, v.begin() + 10
}
В современном C ++ итераторы являются предпочтительным способом перебора коллекций элементов (vector
s, list
s, map
s). Кроме того, ключевое слово auto
избавляет вас от набора многословных типов, но может привести к менее выразительному коду.
Лямбда-выражения
Вооружившись итераторами, мы можем приступить к изучению концепций функционального программирования современного C ++. Многие функции из стандартной библиотеки принимают в качестве параметров диапазон элементов, представленных двумя итераторами (начало и конец) и анонимной функцией (лямбда-функцией). Затем эта анонимная функция применяется к каждому элементу в диапазоне. Они называются анонимными функциями, поскольку они не привязаны к переменной, а представляют собой короткие логические блоки, переданные как встроенный аргумент функции более высокого порядка. Обычно они уникальны для функции, которой они передаются, и поэтому не требуют дополнительных затрат на имя (анонимно).
С его помощью мы можем создавать конструкции, похожие на сортировку, сопоставление, фильтрацию и т. Д., Которые легко реализовать на таких языках, как JavaScript:
[1,2,3,4].map(x => x*x).filter(x => x % 2 === 1).sort((a,b) => b - a)
Код на C ++ не такой лаконичный, но, тем не менее, имеет ту же структуру. Многие помощники по функциональному программированию из библиотеки std
работают с полуоткрытыми интервалами, что означает, что нижний диапазон включен, а верхний диапазон исключен.
// @url: https://repl.it/@MrToph/CPPBasics-Lambdas
#include <iostream>
#include <vector>
// for sort, map, etc.
#include <algorithm>
using namespace std;
int main()
{
vector<int> v{2, 1, 4, 3, 6, 5};
// first two arguments are the range
// v.begin() is included up until v.end() (excluded)
// sorts ascending
sort(v.begin(), v.end());
// in C++, functions like sort mutate the container (in contrast to immutability and returning new arrays in other languages)
for (auto i = v.begin(); i != v.end(); i++)
{
cout << *i << "\n";
}
// sort it again in descending order
// third argument is a lambda function which is used as the comparison for the sort
sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
// functional for_each, can also use auto for type
for_each(v.begin(), v.end(), [](int a) { cout << a << "\n"; });
vector<string> names{"Alice", "Bob", "Eve"};
vector<string> greetings(names.size());
// transform is like a map in JavaScript
// it applies a function to each element of a container
// and writes the result to (possibly the same) container
// first two arguments are range to iterate over
// third argument is the beginning of where to write to
transform(names.begin(), names.end(), greetings.begin(), [](const string &name) {
return "Hello " + name + "\n";
});
// filter greetings by length of greeting
auto new_end = std::remove_if(greetings.begin(), greetings.end(), [](const string &g) {
return g.size() > 10;
});
// iterate up to the new filtered length
for_each(greetings.begin(), new_end, [](const string &g) { cout << g; });
// alternatively, really erase the filtered out elements from vector
// so greetings.end() is the same as new_end
// greetings.erase(new_end, greetings.end());
// let's find Bob
string search_name = "Bob";
// we can use the search_name variable defined outside of the lambda scope
// notice the [&] instead of [] which means that we want to do "variable capturing"
// i.e. make all local variables available to use in the lambda function
auto bob = find_if(names.begin(), names.end(), [&](const string &name) {
return name == search_name;
});
// find_if returns an iterator referncing the found object or the past-the-end iterator if nothing was found
if (bob != names.end())
cout << "Found name " << *bob << "\n";
}
Синтаксис анонимных функций - это то, к чему нужно привыкнуть в C ++. Они указываются в скобках и сопровождаются списком параметров, например [](int a, int b) -> bool {return a > b; }
. Обратите внимание, что -> bool
определяет возвращаемое логическое значение. Часто можно не указывать тип возвращаемого значения, поскольку он может быть выведен из типа возвращаемого значения в теле функции.
Если вы хотите использовать переменные, определенные в области видимости вне вашей лямбда-функции, вам необходимо выполнить захват переменных. Снова появилась возможность передавать аргументы по ссылке или значению в вашу функцию.
- Чтобы передать по ссылке, вам нужно начать лямбду с символа
&
(например, при использовании ссылок в функции):[&]
- Для передачи по значению используйте символ
=
:[=]
Также существует возможность смешивать и сопоставлять захват по значению и ссылке.
Например, [=, &foo]
создаст копии для всех переменных, кроме foo
, который фиксируется по ссылке.
Это помогает понять, что происходит за кулисами при использовании лямбда-выражений:
Оказывается, лямбды реализуются путем создания небольшого класса; этот класс перегружает оператор (), так что он действует как функция. Лямбда-функция является экземпляром этого класса; при создании класса любые переменные в окружающей среде передаются в конструктор класса лямбда-функции и сохраняются как переменные-члены. Фактически, это очень похоже на идею функтора, которая уже возможна. Преимущество C ++ 11 заключается в том, что это становится почти тривиально простым - так что вы можете использовать его все время, а не только в очень редких случаях, когда имеет смысл написать целый новый класс. Программирование лямбда-функций
Лямбда-функции широко используются в смарт-контрактах EOS, поскольку они предоставляют действительно удобный способ изменения данных с помощью небольшого количества кода. В стандартной библиотеке есть другие функции, которые работают аналогично тому, что мы уже видели с sort, transform, remove_if
и find_if
. Все они экспортируются через заголовок <algorithm>
.
Первоначально опубликовано на cmichel.io
Получайте лучшие предложения по программному обеспечению прямо в свой почтовый ящик