itmo.ru aco.ifmo.ru c-visionlab.ru
вернуться в оглавление предыдущая глава предыдущий параграф следующий параграф следующая глава


6.4. Объекты-функции. Предикаты

6.4.1. Объекты-функции. Пример 6.5 (использование объектов-функций)

Объекты-функции – это объекты, у которых перегружен оператор вызова функций operator(). В библиотеке STL уже определено несколько полезных арифметических и других объектов-функций (описание в файле <functional>):

  • plus сложение
  • minus вычитание
  • multipies умножение
  • divides деление
  • modulus деление по модулю
  • negate отрицание

Рассмотрим пример с отрицанием всех элементов вектора. Можно выполнить этот пример с помощью цикла, а можно сделать намного проще с использованием алгоритма transform и стандартной функции negate.

vector<int> v;
vector<int>::iterator it=v.begin();
while(it != v.end())
{
    *it = -(*it);
    it++;
}
// то же самое с использованием объекта-функции negate
transform(v.begin(),v.end(), v.begin(), negate<int>());
// вывод контейнера на экран
copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));

Алгоритмы (описание в файле < algorithm>) позволяют выполнять некоторые типовые действия надо контейнерами с использованием объектов-функций стандартной библиотеки или своих объектов-функций. Подробно алгоритмы, функции, и другие возможности библиотеки STL приводятся в Приложении 5.

Стандартные алгоритмы можно использовать и для ввода и вывод контейнера на экран. При чтении ввод происходит до ввода первого не числового символа.

Программисты могут определить свои объекты-функции, которые могут быть наследниками от стандартных. В объектах-функциях обязательно должен быть перегружен оператор вызова функции (), в конструкторе могут задаваться необходимые параметры, или он может быть пустым (см.пример 6.5).

Функции могут быть двух типов:

  • Унарная функция – это функция, в которой участвует один операнд (например, x=-y - унарный минуc)
  • Бинарная функция – это функция, в которой участвуют два операнда (например, x=y+z - сложение, умножение, и т.д.)

Для унарных функций перегруженный оператор вызова функции должен содержать один параметр, в бинарных – два параметра.

Иногда нужно преобразовать бинарную функцию в унарную, например умножение – бинарная функция, нужны два элемента, а мы хотим умножить все элементы контейнера на одно и то же число. Для этого можно использовать функцию bind2nd.

Функция binder2nd – преобразует бинарную функцию в унарную, и принимает второй аргумент как параметр бинарной функции (описание в файле <functional>)

// умножение каждого элемента на 2
transform (v.begin(), v.end(), v.begin(), bind2nd(multiplies<int>(), 2)); 

Пример 6.5. Использование объектов-функций

/////////////////////////////////////////////////////////////////////////////
// Программирование на языке высокого уровня. Основы языка С++
// Пример 6.5. Использование объектов-функций
// 
// http://aco.ifmo.ru/el_books/programming
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////
#include <functional> 
#include <algorithm> 
#include <vector> 
#include <iostream> 
#include <iterator>
using namespace std;

/////////////////////////////////////////////////////////////////////////////
// создание объекта-функции для заполнения случайными числами 
// функция Rand - шаблон, наследник от стандартной функции unary_function
// параметры шаблона: PAR - тип данных, void - возвращаемое значение оператора ()
template <class PAR>
class Rand : public unary_function<PAR, void>
{
    // диапазон случайных чисел
    PAR m_min, m_max;
public:
    // конструктор, в котором задается диапазон случайных чисел
    Rand(PAR min, PAR max) 
    : m_min(min), m_max(max)
    { }

    // перегруженный оператор вызова функции, в котором число value заполняется случайным числом
    void operator() (PAR& value) 
    { 
        value=(PAR)(rand()*(m_max-m_min))/RAND_MAX+m_min; 
    }
};
/////////////////////////////////////////////////////////////////////////////
// тестирование объектов-функций STL - negate, ввод-вывод
void main()
{
    vector<int> v;

    // чтение контейнера из потока ввода (до первого не числового символа)
    copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(v)); 
    // вывод контейнера на экран
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
    cout<<endl;

    // использование объекта-функции negate
    transform(v.begin(),v.end(), v.begin(), negate<int>());
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
    cout<<endl;

    // заполнение контейнера случайными числами при помощи объекта-функции Rand
    for_each(v.begin(), v.end(), Rand<int>(-10, 10)); 
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
    cout<<endl;

    // умножение каждого элемента на 2, 
    // использование стандартной бинарной функции multiplies, 
    // преобразованной в унарную при помощи функции bind2nd 
    transform (v.begin(), v.end(), v.begin(), bind2nd(multiplies<int>(), 2));
    copy (v.begin(), v.end(),ostream_iterator<int>(cout, " "));
    cout<<endl;
}
/////////////////////////////////////////////////////////////////////////////

6.4.2. Предикаты. Пример 6.6 (использование предикатов)

Предикаты позволяют без изменения шаблона изменять критерии сравнения элементов контейнера и другие подобные действия. У предикатов объект-функция возвращает значение bool.

В файле <functional> уже определено несколько полезных предикатов:

  • equal_to бинарный предикат равенства
  • not_equal_to бинарный предикат неравенства
  • greater бинарный предикат >
  • less бинарный предикат < (используется по умолчанию)
  • greater_equal бинарный предикат >=
  • less_equal бинарный предикат <=
  • logical_and бинарный предикат И
  • logical_or бинарный предикат ИЛИ
  • logical_not унарный предикат НЕ

Например, стандартный алгоритм сортировки сортирует по возрастанию, если мы хотим сделать сортировку по убыванию – можно использовать предикат greater:

// сортировка в порядке убывания
sort(v.begin(), v.end(), greater<int>());

Можно определять свои предикаты, как наследники от стандартных объектов-функций.

Пример 6.6. Использование предикатов

/////////////////////////////////////////////////////////////////////////////
// Программирование на языке высокого уровня. Основы языка С++
// Пример 6.6. Использование предикатов
// 
// http://aco.ifmo.ru/el_books/programming
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////

#include <functional> 
#include <numeric>
#include <algorithm> 
#include <vector> 
#include <iostream> 
#include <iterator>
using namespace std;

/////////////////////////////////////////////////////////////////////////////
// создание объекта-функции для заполнения случайными числами 
// функция Rand - шаблон, наследник от стандартной функции unary_function
// параметры шаблона: PAR - тип данных, void - возвращаемое значение оператора ()
template <class PAR>
class Rand : public unary_function<PAR, void>
{
    // диапазон случайных чисел
    PAR m_min, m_max;
public:
    // конструктор, в котором задается диапазон случайных чисел
    Rand(PAR min, PAR max) 
    : m_min(min), m_max(max)
    { }

    // перегруженный оператор вызова функции, в котором число value заполняется случайным числом
    void operator() (PAR& value) 
    { 
        value=(PAR)(rand()*(m_max-m_min))/RAND_MAX+m_min; 
    }
};
/////////////////////////////////////////////////////////////////////////////
// создание объекта-функции определения попадания числа в диапазон
// функция InRange - шаблон, наследник от стандартной функции unary_function
// параметры шаблона: int - тип данных, bool - возвращаемое значение оператора ()
class InRange : public unary_function<int, bool>
{
    // диапазон чисел
    int m_left, m_right;
public:
    // конструктор, в котором задается диапазон
    InRange(int left, int right) 
    : m_left(left), m_right(right) 
    {}

    // перегруженный оператор вызова функции, в котором определяется 
    // попадание числа value в диапазон от m_left до m_right
    bool operator() (const int& value) 
    { 
        return (value>m_left && value<m_right); 
    }
};
/////////////////////////////////////////////////////////////////////////////
// тестирование предикатов
void main()
{
    vector<int> v(10);

    // заполнение контейнера случайными числами при помощи объекта-функции Rand
    for_each(v.begin(), v.end(), Rand<int>(-10, 10)); 
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
    cout<<endl;

    // сортировка с использованием предиката greater
    sort(v.begin(), v.end(), greater<int>());
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
    cout<<endl;

    // использование InRange для подсчета количества элементов в диапазоне от 0 до 10 
    cout << count_if(v.begin(), v.end(), InRange(0, 10));
    cout<<endl;
}
/////////////////////////////////////////////////////////////////////////////