Форум программистов, компьютерный форум CyberForum.ru

Функторы, предикаты, функциональные адаптеры, лямбда-функции - C++

Войти
Регистрация
Восстановить пароль
Другие темы раздела
C++ Перевод текста в XML http://www.cyberforum.ru/cpp/thread1086971.html
Всем доброго дня! Как-то внезапно возникло несколько вопросов. 1. Есть текст, обычной исходный код С++ в *.cpp файле. Как мне его перевести в *xml файл? Есть как понимаю, много всяких библиотек (сам гляжу на QtXML), что можете посоветовать? Писать всё это добро планирую на C++ с использованием Qt. Вкратце, зачем мне всё это надо, и какие цели я хочу достичь. Хочу сделать небольшую коллекцию...
C++ Ошибка при сборке проекта в Eclipse, с++ Подскажите пожалуйста, как можно исправить ошибку? Возможно я что-то не то сделал или не все инструкции доделал до конца? Description Resource Path Location Type fatal error: wchar.h: No such file or directory testCpp line 44, external location: c:\mingw\lib\gcc\mingw32\4.8.1\include\c++\cwchar C/C++ Problem По контексту "наверное" я понимаю в чем проблема. В папке cwchar, нет файла... http://www.cyberforum.ru/cpp/thread1086878.html
C++ Работа с твиттер (нужна библиотека)
Уважаемые программисты! Подскажите пожалуйста какой(ими) библиотеками вы пользуетесь для работы с twitter. Хочу создать бота, который бы твител кое-какую информацию.
C++ [OpenCV] Поиск по шаблону
Добрый день, имеется код с robocraft, в котором используется функция cvMatchTemplate, вопрос состоит в том, чтобы узнать нашла ли функция шаблон на данном изображении или нет. #include </usr/include/opencv/cv.h> #include </usr/include/opencv/highgui.h> #include <stdlib.h> #include <stdio.h> #include <iostream> using namespace std;
C++ [Поток] Один вход, два выхода [Как сделать?] http://www.cyberforum.ru/cpp/thread1084796.html
Речь об STL-ных потоках. Нужно делать report в файл и на консоль (а может, только на один). Если я заведу поток rep, как мне сделать, чтобы при rep << "msg"; у меня фактически получилось fs << "msg"; cin << "msg"; Спасибо.
C++ Дедушка, а что это за костыль у тебя? Есть языки программирования с очень длинной и непростой историей, у которых примерно половина их возможностей – это какие-то исторически сложившиеся сложные и ненужные костыли. К таким, например, относится и наш любимый C++. Если вы не разработчик компилятора C++, почти всегда можно найти что-то, чего вы в языке не знаете. http://habrahabr.ru/company/yandex/blog/206234/ А что это за костыли... подробнее

Показать сообщение отдельно
MrGluck
Ворчун
Эксперт CЭксперт С++
6658 / 3849 / 509
Регистрация: 29.11.2010
Сообщений: 10,190

Функторы, предикаты, функциональные адаптеры, лямбда-функции - C++

02.02.2014, 15:37. Просмотров 3376. Ответов 11
Метки (Все метки)

Вступление

Статья ориентирована на программистов С++, поверхностно знающих/желающих узнать STL, в особенности, с использованием его алгоритмов. Это краткий обзор по основным понятиям, в конце будет приведен список литературы для более полного ознакомления с материалом.

Часто, алгоритмы STL имеют перегруженную версию или схожую по функционалу с добавлением в названии _if в конце, реализующуюся с применением функционального объекта или функции.
Пример:
copy - copy_if
find - find_if
equal
includes
sort
accumulate
и т.д.
Это позволяет гибко подстраивать их в рамках определенной задачи.


Функторы

Функторы (их еще называют объект-функциями) - конструкция, которая предоставляет возможность использовать объект как функцию. Это может быть структура или класс, перегружающие оператор(). В языке С используется указатель на функцию. Конечно, подобная вещь оставлена для совместимости, но в реальности теряет смысл в использовании в С++, впрочем, не упомянуть о возможности было бы неверно.

Пример использования функтора:
C++
1
2
3
4
5
6
7
struct Comp 
{ 
    bool operator()(const std::string &s1, const std::string &s2) const 
    { 
        return s1.length() < s2.length(); 
    } 
};
Данный оператор принимает две const строки по ссылке и возвращает истину если длина первой меньше длины второй. Аналогично можно было бы сделать с использованием класса при указании модификатора доступа public для operator().
Часто, функциональные объекты делают шаблонными для лучшей возможности повторного использования кода.
C++
1
2
3
4
5
6
7
8
9
template <typename T>  
class Mult 
{ 
    public: 
        T operator()(const T &t1, const T &t2) const 
        { 
            return t1 * t2; 
        } 
};
Заметьте, что т.к. тип T неизвестен, то указываем передачу на всякий случай по ссылке, чтобы не создавать временных объектов. При использовании шаблонного объекта обязательно явно объявлять тип в угловых скобках. Необходимо указывать в качестве аргумента название класса, выступающего объект-функцией с круглыми скобками. Вызов может происходить вот так:
C++
1
2
3
const std::size_t N = 3; 
int A[N] = {3, 2, 5}; 
std::cout << std::accumulate(A, A + N, 1, Mult<int>());
Надо отметить, что в общем случае, идентификатор типа возвращаемого значения operator() у функтора может быть любой. В данной программе вызывается перегруженная версия алгоритма accumulate (определена в <numeric>), которая требует возврата того же типа данных, что и при передаче в функциональный объект, но в других алгоритмах может потребоваться и другой тип данных.


Предикаты

Предикаты- подмножество функторов, в которых тип возвращаемого значения operator() bool. Предикаты используются в алгоритмах сортировок, поиска, а также во всех остальных, имеющих на конце _if. Смысл в том, что объект-функция в случае использования предиката возвращает истину или ложь в зависимости от выполнения необходимого условия. Это либо удовлетворение объектом неких свойств, либо результат сравнения двух объектов по определенному признаку.

Пример использования предиката:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class DividedByTwo 
{ 
    public: 
        bool operator()(const int x) const 
        { 
            return x % 2 == 0; 
        } 
}; 
 
int main() 
{ 
    const std::size_t N = 3; 
    int A[N] = {3, 2, 5}; 
    std::cout << std::count_if(A, A + N, DividedByTwo()); 
}

Функциональные адаптеры

Основные унарные и бинарные функциональные объекты, необходимые для сравнения, уже включены в STL и используются с добавлением хедера functional. Все они являются шаблонными классами и требуют определения необходимых операторов в типе данных, с которым работают. Примеры: std::greater<>, std::less<>.

Следующий код сортирует элементы по убыванию с применением нашего объекта.
C++
1
2
3
4
5
6
int main() 
{ 
    const std::size_t N = 3; 
    int A[N] = {3, 2, 5}; 
    std::sort(A, A + N, std::greater<int>()); 
}
Принцип работы std::greater схож со следующим кодом:
C++
1
2
3
4
bool operator()(const T &lhs, const T &rhs) const  
{ 
    return lhs > rhs; 
}
Иногда, нам может потребоваться реализовать, например, подсчет элементов исходя из нескольких условий, т.е. необходимо скомбинировать результаты с определенными значениями или другими функциями. В таких случаях применяют функциональные адаптеры. Необходимо отметить, что и сами адаптеры могут служить частью вычислений других адаптеров, за счет чего достигается гибкость вычислений. Основные адаптеры: bind1st, bind2nd, not1, not2.

Пример: подсчитать количество элементов, больших (>), чем два.
C++
1
2
3
4
5
6
int main() 
{ 
    const std::size_t N = 3; 
    int A[N] = {3, 2, 5}; 
    std::cout << std::count_if(A, A + N, std::bind2nd(std::greater<int>(), 2)); 
}
Данный код выведет 2. Все верно, лишь элементы 3 и 5 превосходят 2.

Пример объединения адаптеров: подсчитать количество элементов, не больших !(>), чем два.
C++
1
2
3
4
5
6
int main() 
{ 
    const std::size_t N = 3; 
    int A[N] = {3, 2, 5}; 
    std::cout << std::count_if(A, A + N, std::not1(std::bind2nd(std::greater<int>(), 2)));
}
Этот ужас, как и ожидалось, выдаст результат 1. Почему ужас? Не знаю, как вам, но по мне, так читаемость этого кода оставляет желать лучшего. И С++11 вводит специальный инструментарий, который позволяет всего этого избежать - лямбда функции, о них мы поговорим далее. Стоит отметить, что и некоторые старые адаптеры и функции были заменены новыми конструкциями - std::function, std::mem_fn, std::bind, а такие, как std::bind1st, std::bind2nd были признаны устаревшими.


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

Наверное, самая приятная часть 11 стандарта, которая позволяет создавать очень гибкий код прямо на месте, не расползаясь мыслью по разным специальным классам и структурам, создаваемым для сравнения, а также повышающая читаемость кода в разы, ведь критерий сравнения или необходимая для выполнения функция определяется прямо рядом с местом использования. Тем не менее, это не отменяет использование всего того, что было озвучено выше, функциональные объекты имеют право на существование и должны использоваться там, где это действительно принесет полезный результат. Например, если необходимых инструкций для выполнения в теле функции достаточно много и определение данной функции на месте понизит удобочитаемость, или если у нас есть схожие куски кода и мы хотим вынести общий в одно место. В остальных случаях, данная конструкция будет очень удобна. Она обладает множеством различных тонкостей, мы лишь покажем общее с ней ознакомление, т.к. описание всех возможностей требовало бы отдельной статьи. Я покажу лишь использование конструкций с лямбда-функциями, и вы поймете, почему её так полюбили программисты.

Пример: необходимо подсчитать количество неотрицательных элементов, кратных 7
C++
1
2
3
4
5
6
7
8
int main() 
{ 
    const std::size_t N = 20; 
    int A[N]; 
    std::iota(A, A + N, -10); // A[0] = -10, A[1] = -9, ... A[N-1] = 9 
    std::cout << std::count_if(A, A + N, [](const int x) 
        { return x >=0 && x % 7 != 0; } ); 
}
C++
1
[](const int x) { return x >=0 && x % 7 != 0; }
и есть наша лямбда-функция.

Или же другой: вывести максимальный по модулю элемент
C++
1
2
3
4
5
6
7
8
int main() 
{ 
    const std::size_t N = 20; 
    int A[N]; 
    std::iota(A, A + N, -10); // A[0] = -10, A[1] = -9, ... A[N-1] = 9 
    std::cout << *std::max_element(A, A + N, [](const int x, const int y) 
        { return std::abs(x) < std::abs(y); } ); 
}
Необходимо лишь знать список аргументов, передаваемых функции, и то, что она должна делать (список функций вы можете посмотреть по ссылкам на информационные источники ниже в разделе algorithms library). В круглых скобках - список аргументов, которые она принимает (все как и у функтора). В фигурных идет тело функции, выполнение заканчивается после возврата с помощью return, но его наличие вовсе не обязательно.

Например, следующий код значение каждого элемента увеличивает значение на единицу и выводит на экран.
C++
1
2
3
4
5
6
7
int main() 
{ 
    const std::size_t N = 20; 
    int A[N]; 
    std::iota(A, A + N, -10); // A[0] = -10, A[1] = -9, ... A[N-1] = 9 
    std::for_each(A, A + N, [](int x) {std::cout << ++x << " "; } ); 
}
Передача аргументов в лямбда-функцию может происходить как по значению, так и по ссылке. Но что делать, если нужно, например, посчитать количество элементов, кратных некоему числу k, которое введет пользователь? Для этого используется список захвата, он передается функции в квадратных скобках, аргументы перечисляются через запятую. Передача может идти также как по значению, так и по ссылке.

Ниже приведен пример, в котором подсчитывается количество элементов, кратных k и больших, чем m.
C++
1
2
3
4
5
6
7
8
9
int main() 
{ 
    const std::size_t N = 20; 
    int A[N], k, m; 
    std::iota(A, A + N, -10); // A[0] = -10, A[1] = -9, ... A[N-1] = 9 
    std::cin >> k >> m; 
    std::cout << std::count_if(A, A + N, [k, m](const int x) 
        { return x % k != 0 && x > m; } ); 
}
Все просто и лаконично, а главное, читаемость кода возрастает в разы. Теперь я покажу пример, где используется передача аргумента по ссылке.

Пример: подсчитать количество положительных элементов и отдельно количество четных, результат вывести на экран. Для простоты, используем алгоритм for_each.
C++
1
2
3
4
5
6
7
8
9
10
11
12
int main() 
{ 
    const std::size_t N = 20; 
    int A[N], num_positives = 0, num_evens = 0; 
    std::iota(A, A + N, -10); // A[0] = -10, A[1] = -9, ... A[N-1] = 9 
    std::for_each(A, A + N, [&num_positives, &num_evens](const int x) 
    { 
        if (x >= 0) num_positives++; 
        if (x % 2 == 0) num_evens++; 
    } ); 
    std::cout << num_positives << " " << num_evens << std::endl; 
}

Подведение итогов

Я рассмотрел лишь часть функционала, впрочем вышло и так объемно.
Лямбда-функции являются частью языка и не требует подключения дополнительных хедеров. Перед их использованием необходимо убедиться, что ваш компилятор поддерживает 11 стандарт, и установлен ключ -std=c++11 (или -std=c++0x). Также, лямбда-выражения доступны с использованием семейства библиотек boost. VS поддерживает лямбда-функции лишь с 2010 студии, для пользователей gcc и, соотв. mingw рекомендуется обновиться до/на основе версии 4.7.0 и выше.
Bash
1
2
3
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install gcc-4.8 g++-4.8
Если вы пользуетесь CodeBlocks/Dev-cpp/QtCreator - можете отдельно скачать новый компилятор mingw и прописать пути к нему в IDE. Если наберется много вопросов о подключении - могу создать отдельную статью.
За сим откланиваюсь.
С уважением, MrGluck.

Данная статья была написана специально для сайта онлайн тестов http://www.quizful.net/test
Ссылка на первоисточник: http://www.quizful.net/post/functors...dapters-in-STL


Литература

1) Л.Аммераль - STL для программистов на С++, глава 6 (Функциональные объекты и адаптеры)
2) Дэвид Р.Мюссер, Жилмер Дж.Дердж, Атул Сейни - C++ и STL справочное руководство, 2 изд,
глава 8 (Функциональные объекты), глава 23 (Справочное руководство по функциональным
объектам и адаптерам)
3) http://en.cppreference.com/w/cpp/utility/functional
4) http://cplusplus.com/reference/functional
5) http://en.wikipedia.org/wiki/Anonymous_function#C.2B.2B
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru