C++编程语言:标准库:第 33 章——STL迭代器(Bjarne Stroustrup)

C++ STL迭代器及相关实用程序介绍

第 33 章  STL 迭代器(STL Iterators)

(译注:迭代即采用循环方式遍历元素,一般都有循环语语句参与,即所谓迭代。)

目录

第 33 章  STL 迭代器(STL Iterators)

33.1  引言(Introduction)

33.1.1  迭代器模型(Iterator Model)

33.1.2  迭代器分类(Iterator Categories)

33.1.3  迭代器特征(Iterator Traits)

33.1.4  迭代器运算(Iterator Operations)

33.2  迭代器适配器(Iterator Adaptors)

33.2.1  反向迭代器(Reverse Iterator)

33.2.2  插入迭代器(Insert Iterator)

33.2.3  移动迭代器(Move Iterator)

33.3  范围访问函数(Range Access Functions)

33.4  函数对象(Function Objects)

33.5  函数适配器(Function Adaptors)

33.5.1  bind()

33.5.2  mem_fun()

33.5.3  function()

33.6  建议(Advice)


33.1  引言(Introduction)

本章介绍 STL 的迭代器和实用程序,特别是标准库函数对象。STL 由标准库的迭代器、容器、算法和函数对象部分组成。STL 的其余部分将在第 31 章和第 32 章中介绍。

迭代器是将标准库算法与其数据连接起来的粘合剂(glue)。反过来,你可以说,迭代器是一种机制,用于最大限度地减少算法对其所操作数据结构的依赖

33.1.1  迭代器模型(Iterator Model)

迭代器类似于指针,因为它提供间接访问操作(例如, 用于解引用)和指向新元素的操作(例如,++ 用于移动到下一个元素)。序列由一对迭代器定义,它们定义了一个半开范围 [begin:end)

也就是说,begin 指向序列的第一个元素,end 指向序列最后一个元素的下一个位置。切勿从 end 读取或写入。注意,空序列的 begin==end;也就是说,对于任何迭代器 p[p:p) 都是空序列。

为了读取序列,算法通常采用一对迭代器 (b,e) 并使用 ++ 进行迭代,直到到达末尾:

while (b!=e) { // != 而非 <

// do something

++b; // go to next element

}

使用 != 而不是 < 来测试我们是否已经到达末尾,部分原因是!= 更准确地表达了我们所测试的内容,部分原因是只有随机访问迭代器支持 <

在序列中搜索某些内容的算法通常会返回序列的末尾以指示“未找到”;例如:

auto p = find(v.begin(),v.end(),x); // look for x in v

if (p!=v.end()) {

// x found at p

}

else {

// x not found in [v.begin():v.end())

}

写入序列的算法通常只提供指向其第一个元素的迭代器。在这种情况下,程序员有责任避免写入超出序列末尾的内容。例如:

template<typename Iter>

void forward(Iter p, int n)

{

while (n>0)

p++ = −−n;

}

void user()

{

vector<int> v(10);

forward(v.begin(),v.size()); // OK

forward(v.begin(),1000); // 大麻烦

}

一些标准库实现会进行范围检查——也就是说,在最后一次调用 forward() 时抛出异常——但你不能依赖这种方法来实现可移植的代码:许多实现并不进行范围检查。一个简单安全的替代方案是使用插入迭代器 (§33.2.2)。

33.1.2  迭代器分类(Iterator Categories)

标准库提供了五种迭代器(五大迭代器类别):

输入迭代器:我们可以使用 ++ 向前迭代,并使用 (重复)读取每一个元素。我们可以使用 == != 比较输入迭代器。这是 istream 提供的迭代器类型;参见 §38.5。

输出迭代器:我们可以使用 ++ 向前迭代,并使用 仅写入一次元素。这是 ostream 提供的迭代器类型;参见 §38.5。

前向迭代器:我们可以使用 ++ 反复向前迭代,并使用 反复读取和写入元素(除非元素是 const)。如果前向迭代器指向类对象,我们可以使用 −> 引用其成员。我们可以使用 ==!= 比较前向迭代器。这是 forward_list 提供的迭代器类型(§31.4)。

双向迭代器:我们可以使用 ++−− 进行正向迭代,并使用 重复读写元素(除非元素是 const)。如果双向迭代器指向一个类对象,我们可以使用 −> 引用其成员。我们可以使用 == != 比较双向迭代器。list, mapset 提供的迭代器就是这种类型的迭代器(§31.4)。

随机访问迭代器:我们可以使用 ++ += 进行正向迭代,并使用 −= 进行反向迭代,并使用 [] 重复读写元素(除非元素是 const)。如果随机访问迭代器指向一个类对象,我们可以使用 −> 引用其成员。我们可以使用 [] 为随机访问迭代器添加下标,使用 + 进行整数加法运算,使用 进行整数减法运算。我们可以通过将一个迭代器减去另一个迭代器来计算两个随机访问迭代器到同一序列的距离。我们可以使用 ==!=<<=>>= 来比较随机访问迭代器。这是 vector 提供的迭代器类型(§31.4)。

从逻辑上讲,这些迭代器按层次结构组织(§iso.24.2):

迭代器类别是概念(§24.3),而非类,因此此层次结构并非通过派生实现的类层次结构。如果您需要使用迭代器类别执行一些高级操作,请使用 iterator_traits(直接或间接)。

33.1.3  迭代器特征(Iterator Traits)

    在 <iterator> 中,标准库提供了一组类型函数,允许我们为迭代器的特定属性编写特化的代码:

迭代器特征 (§iso.24.4.1)

iterator_traits<Iter>

非指针 Iter 的特征类型

iterator_traits<T>

指针 T 的特征类型

iterator<Cat,T,Dist,Ptr,Re>

定义基本迭代器成员类型的简单类

input_iterator_tag

输入迭代器的类别

output_iterator_tag

输出迭代器的类别

forward_iterator_tag

前向迭代器类别;

源自 input_iterator_tag

适用于 forward_listunordered_setunordered_multiset, unordered_map unordered_multimap

bidirectional_iterator_tag

双向迭代器类别;源自 forward_iterator_tag;适用于list,set,multiset,mapmultimap

random_access_iterator_tag

随机访问迭代器的类别;源自 bidirectional_iterator_tag;适用于list,deque,array,内置数组和string

迭代器标签是用于根据迭代器的类型在算法中进行选择的类型。例如,随机访问迭代器可以直接定位到元素:

template<typename Iter>

void advance_helper(Iter p, int n, random_access_iterator_tag)

{

p+=n;

}

在另一方面,前向迭代器必须通过一次移动一步来到达第 n 个元素(例如,跟随列表中的链接):

template<typename Iter>

void advance_helper(Iter p, int n, forward_iterator_tag)

{

if (0<n)

while (n−−) ++p;

else if (n<0)

while (n++) −−p;

}

有了这些辅助函数,advance() 可以始终使用最佳算法:

template<typename Iter>

void advance(Iter p, int n) //使用最佳算法

{

advance_helper(p,n,typename iterator_traits<Iter>::iterator_category{});

}

       通常,advance() 和/或 advance_helper() 会被内联,以确保此标签调度技术不会引入运行时开销。此技术的变体在 STL 中随处可见。

    迭代器的关键属性由 iterator_traits 中的别名描述:

template<typename Iter>

struct iterator_traits {

using value_type = typename Iter::value_type;

using difference_type = typename Iter::difference_type;

using pointer = typename Iter::pointer; // pointer type

using reference = typename Iter::reference; // reference type

using iterator_category = typename Iter::iterator_category; // (tag)

};

对于没有这些成员类型(例如 int)的迭代器,我们提供了 iterator_traits 的特化:

template<typename T>

struct iterator_traits<T>{ // pointer 特化

using difference_type = ptrdiff_t;

using value_type = T;

using pointer = T;

using reference = T& reference;

using iterator_category = random_access_iterator_tag;

};

我们不能笼统地说:

template<typename Iter>

typename Iter::value_type read(Iter p, int n) // 不通用

{

// ... do some checking ...

return p[n];

}

这是一个即将发生的错误。使用指针参数调用 read() 函数将会导致错误。编译器会捕获它,但错误信息可能冗长且难以理解。我们可以这样写:

template<typename Iter>

typename iterator_traits<Iter>::value_type read(Iter p, int n) // 更通用

{

// ... do some checking ...

return p[n];

}

其思路是,要查找迭代器的属性,需要查看其 iterator_traits (§28.2.4),而不是迭代器本身。为了避免直接引用 iterator_traits(毕竟这只是一个实现细节),我们可以定义一个别名。例如:

template<typename Iter>

using Category<Iter> = typename std::iterator_traits<Iter>::iterator_category;

template<typename Iter>

using Difference_type<Iter> = typename std::iterator_traits<Iter>::difference_type;

因此,如果我们想知道两个迭代器(指向同一个序列)之间的差异类型,我们可以选择以下几种方法:

tempate<typename Iter>

void f(Iter p, Iter q)

{

Iter::difference_type d1 = distance(p,q); // 语法错误: ‘‘typename’’ 缺失

typename Iter::difference_type d2 = distance(p,q); // pointer无效,.

typename iterator_traits<Iter>::distance_type d3 = distance(p,q); // OK, 但不雅

Distance_type<Iter> d4 = distance(p,q); // OK, 甚好

auto d5 = distance(p,q); // OK,如果你无需显式提及type

// ...

}

我推荐最后两种选择。

迭代器模板只是将迭代器的关键属性捆绑到一个结构体中,以方便迭代器实现者使用,并提供了一些默认值

template<typename Cat, typename T, typename Dist = ptrdiff_t, typename Ptr = T, typename Ref = T&>

struct iterator {

using value_type = T;

using difference_type = Dist ; // type used by distance()

using pointer = Ptr; // pointer type

using reference = Ref; // reference type

using iterator_category = Cat; // category (tag)

};

33.1.4  迭代器运算(Iterator Operations)

根据其类别(§33.1.2),迭代器提供以下部分或全部操作:

迭代器运算(§iso.24.2.2)

++p

前增(前进一个元素):使 p 指向下一个元素或最后一个元素的下一个位置;结果值是增加后的值

p++

后增(前进一个元素):使 p 指向下一个元素或最后一个元素的下一个位置;结果值是 p 在增值之前的值

p

访问(取消引用):p 引用 p 指向的元素

−−p

前减(后退一个元素):使 p 指向前一个元素;结果值为减少后的值

p−−

后减(后退一个元素):使 p 指向前一个元素;结果值是 p 在减之前的值

p[n]

访问(下标):p[n] 指向 p+n 指向的元素;等价于 (p+n)

p>n

访问(成员访问):等同于 (p).m

p==q

相等:p q 指向同一个元素,还是都指向最后一个元素的下一个位置?

p!=q

不等:!(p==q)

p<q

p 是否指向 q 所指向元素之前的元素?

p<=q

p<q || p==q

p>q

p 是否指向 q 所指向元素之后的元素?

p>=q

p>q || p==q

p+=n

前进 n使 p 指向其指向元素之后的第 n 个元素

p=n

前进 −n:使 p 指向它所指向元素之前的第 n 个元素

q=p+n

q 指向 p 指向的元素之后的第 n 个元素

q=pn

q 指向 p 指向的前 n 个元素

++p 返回 p 的引用,而 p++ 必须返回 p 的副本,其中包含旧值。因此,对于更复杂的迭代器,++p 可能比 p++ 更高效。

以下操作适用于每一个可以实现它们的迭代器,但它们对于随机访问迭代器可能更有效(参见§33.1.2):

迭代器运算(§iso.24.4.4)

advance(p)

p+=np 必须至少是一个输入迭代器

x=distance(p,q)

x=q−pp 必须至少是一个输入迭代器

q=next(p,n)

q=p+np 至少必须是一个前向迭代器

q=next(p)

q=next(p,1)

q=prev(p,n)

q=p−np 必须至少是一个双向迭代器

q=prev(p)

q=prev(p,1)

在每一种情况下,如果 p 不是随机访问迭代器,则算法将执行 n 步。

33.2  迭代器适配器(Iterator Adaptors)

<iterator> 中,标准库提供了适配器,可以从给定的迭代器类型生成有用的相关迭代器类型:

迭代器适配器

reverse_iterator

反向迭代(§33.2.1)

back_inser t_iterator

在末尾插入(§33.2.2)

front_inser t_iterator

在开头插入(§33.2.2)

inser t_iterator

任意位置插入(§33.2.2)

move_iterator

移动而不是复制(§33.2.3)

raw_storage_iterator

写入未初始化的存储(§34.6.2)

iostream 的迭代器在§38.5 中描述。

33.2.1  反向迭代器(Reverse Iterator)

使用迭代器,我们可以从 be遍历序列 [b:e)。如果序列允许双向访问,我们也可以反向遍历序列,从 e b执行此操作的迭代器称为反向迭代器 (reverse_iterator)。反向迭代器从其底层迭代器定义的序列的末尾迭代到该序列的开头。要获得半开序列,我们必须将 b-1 视为序列末尾之后的一个元素,将 e-1 视为序列的开头:[e-1,b-1)。因此,反向迭代器与其对应迭代器之间的基本关系是 &(reverse_iterator(p))==&(p-1)。具体而言,如果 v 是向量,则 v.rbegin() 指向其最后一个元素 v[v.siz e()-1]。考虑:

可以使用 reverse_iterator 像这样查看该序列:

reverse_iterator 的定义如下所示:

template<typename Iter>

class reverse_iterator

: public iterator<Iterator_category<Iter>,

Value_type<Iter>,

Difference_type<Iter>,

Pointer<Iter>,

Reference<Iter>> {

public:

using iterator_type = Iter;

reverse_iterator(): current{} { }

explicit reverse_iterator(Iter p): current{p} { }

template<typename Iter2>

reverse_iterator(const reverse_iterator<Iter2>& p) :current(p.base()) { }

Iter base() const { return current; } // current iterator value

reference operator() const { tmp = current; return ∗−−tmp; }

pointer operator>() const;

reference operator[](difference_type n) const;

reverse_iterator& operator++() { −−current; return this; } // note: not ++

reverse_iterator operator++(int) { reverse_iterator t = current; −−current; return t; }

reverse_iterator& operator−−() { ++current; return this; } // note: not −−

reverse_iterator operator−−(int) { reverse_iterator t = current; ++current; return t; }

reverse_iterator operator+(difference_type n) const;

reverse_iterator& operator+=(difference_type n);

reverse_iterator operator(difference_type n) const;

reverse_iterator& operator=(difference_type n);

// ...

protected:

Iterator current; // current 指向 *this 所指元素之后的元素

private:

// ...

iterator tmp; // 对于需要超出函数范围的临时变量

};

reverse_iterator<Iter> 具有与 Iter 相同的成员类型和操作。具体而言,如果 Iter 是随机访问运算符,则其 reverse_iterator<Iter> 具有 []+< 。例如:

void f(vector<int>& v, list<char>& lst)

{

v.rbegin()[3] = 7; // OK: 随机访问迭代器

lst.rbegin()[3] = '4'; // : 双向迭代器不支持[]

(next(lst.rbegin(),3)) = '4'; // OK!

}

我使用 next() 来移动迭代器,因为 (类[]) + 不适用于双向迭代器,例如 list<char>::iterator

    反向迭代器允许我们以相反的顺序查看序列的方式使用算法。例如,要查找序列中元素的最后一次出现,我们可以将 find() 应用于其反向序列:

auto ri = find(v.rbegin(),v.rend(),val); // 最后出现

请注意,C::reverse_iteratorC::iterator 的类型不同。因此,如果我想编写一个使用反向序列的 find_last() 算法,我必须决定返回哪种类型的迭代器:

template<typename C, typename Val>

auto find_last(C& c, Val v) −> decltype(c.begin()) // 在接口中使用 C的迭代器

{

auto ri = find(c.rbegin(),c.rend(),v);

if (ri == c.rend()) return c.end(); // 使用 c.end() 来表示 "未找到"

return prev(ri.base());

}

(译注:注意函数后加  −> decltype 这种语法是与auto结合使用,由输入参数来指定返回类型,下同。)

对于反向迭代器 (reverse_iterator),ri.base() 返回一个指向 ri 指向位置后一个元素的迭代器。因此,为了获取指向与反向迭代器 ri 相同元素的迭代器,我必须返回 ri.base()-1。但是,我的容器可能是一个不支持 - 迭代器的列表,所以我改用 prev()

反向迭代器是一个非常普通的迭代器,所以我可以明确地编写循环:

template<typename C, Val v>

auto find_last(C& c, Val v) > decltype(c.begin())

{

for (auto p = c.rbegin(); p!=c.rend(); ++p) // 按相反次序浏览序列

if (p==v) return −−p.base();

return c.end(); // c.end() 表示 "未找到"

}

使用(前向)迭代器向后搜索的等效代码是:

template<typename C>

auto find_last(C& c, Val v) > decltype(c.begin())

{

for (auto p = c.end(); p!=c.begin(); ) // 从末尾向前搜索

if (∗−−p==v) return p;

return c.end(); // c.end() 表示 "未找到"

}

find_last() 的早期定义一样,此版本至少需要一个双向迭代器。

33.2.2  插入迭代器(Insert Iterator)

    通过迭代器将输出结果写入容器意味着迭代器指向的元素之后的元素可能会被覆盖。这意味着存在溢出的可能性,并可能导致内存损坏。例如:

void f(vector<int>& vi)

{

fill_n(vi.begin(),200,7); // 7 赋予 vi[0]..[199]

}

如果 vi 中的元素少于 200 个,我们就有麻烦了。

<iterator> 中,标准库以插入器(inserter)的形式提供了一种解决方案:当写入时,插入器会将新元素插入序列中,而不是覆盖现有元素。例如:

void g(vector<int>& vi)

{

fill_n(back_inserter(vi),200,7); // 2007加入vi

}

当通过插入迭代器写入元素时,迭代器会插入其值,而不是覆盖指向的元素。因此,每次通过插入迭代器向容器写入值时,容器都会增加一个元素。插入器既简单又高效,而且实用。

有3种插入迭代器:

insert_iterator 使用 insert() 在指向的元素之前插入。

front_insert_iterator 使用 push_front() 在序列的第一个元素之前插入。

back_insert_iterator 使用 push_back() 在序列的最后一个元素之后插入。

插入器通常通过调用辅助函数来构造:

插入器构造函数(§iso.24.5.2)

ii=inserter(c,p)

ii 是指向容器 c 中的 pinsert_iterator

ii=back_inserter(c)

ii 是指向容器 c 中的 back() back_insert_iterator

ii=front_inserter(c)

ii 是指向容器 c 中的 front() front _insert_iterator

传递给 inserter() 的迭代器必须是容器内的迭代器。对于序列容器,这意味着它必须是双向迭代器(以便可以在其前面插入)。例如,你不能使用 inserter() 创建迭代器来插入到 forward_list 中。对于关联容器,如果迭代器仅用于提示插入位置,则可以使用前向迭代器(例如,unordered_set 提供的迭代器)。

    一个插入器就是一个输出迭代器:

insert_iterator<C>操作(§iso.24.5.2)

insert_iterator p {c,q};

容器 c 的插入器指向 qq 必须指向 c

insert_iterator p {q};

复制构造函数:p q 的副本

p=q

ii 是指向容器 c 中的 front() front _insert_iterator

p=move(q)

复制赋值:p q 的副本

p=move(q)

移动赋值:p 指向 q 指向的内容

++p

使 p 指向下一个元素;该值是 p 的新值

p++

使 p 指向下一个元素;该值为 p 的旧值

p=x

p 之前插入 x

p++=x

p 之前插入 x,然后增加 p

front_insert_iteratorback_insert_iterator 的区别在于,它们的构造函数不需要迭代器。例如:

vector<string> v;

back_insert_iterator<v> p;

你无法通过插入器读取。

33.2.3  移动迭代器(Move Iterator)

    移动迭代器是指从指向的元素读取时进行移动而不是复制的迭代器。我们通常使用辅助函数从另一个迭代器创建一个移动迭代器:

移动迭代器构造函数

mp=make_move_iterator(p)

mp 是指向与 p 相同元素的 move_iteratorp 必须是输入迭代器

移动迭代器的操作与其所基于的迭代器相同。例如,如果移动迭代器 p 是由双向迭代器生成的,我们可以执行 −−p 操作。移动迭代器的 operator () 仅返回指向其所指向元素的右值引用(§7.7.2):std::move(q)。例如:

vector<string> read_strings(istream&);

auto vs = read_strings(cin); // get some strings

vector<string> vs2;

copy(vs,back_inser ter(vs2)); // copy strings from vs into vs2

vector<string> vs3;

copy(vs2,make_move_iterator(back_inser ter(vs3))); // move strings from vs2 into vs3

这假设已经定义了 std::copy() 的容器版本。

33.3  范围访问函数(Range Access Functions)

<iterator> 中,标准库为容器提供了非成员函数 begin() end()

移动迭代器构造函数

p=begin(c)

p c 第一个元素的迭代器;c 是内置数组或具有 c.begin()

p=end(c)

p c 末尾的下一个迭代器;c 是内置数组或具有 c.end()

这些函数非常简单:

template<typename C>

auto begin(C& c) > decltype(c.begin());

template<typename C>

auto begin(const C& c) > decltype(c.begin());

template<typename C>

auto end(C& c) > decltype(c.end());

template<typename C>

auto end(const C& c) > decltype(c.end());

template<typename T, siz e_tN> //对于内置数组

auto begin(T (&array)[N]) > T;

template<typename T, siz e_t N>

auto end(T (&array)[N]) > T;

这些函数供范围for 语句(§9.5.1)使用,当然也可以由用户直接使用。例如:

template<typename Cont>

void print(Cont& c)

{

for(auto p=begin(c); p!=end(c); ++p)

cout << p << '\n';

}

void f()

{

vector<int> v {1,2,3,4,5};

print(v);

int a[] {1,2,3,4,5};

print(a);

}

如果我说的是 c.begin()c.end(),那么调用 print(a) 就会失败。

    当 <iterator> #include 时,带有成员 begin()end() 的用户定义容器会自动获取非成员版本。要为没有非成员版本的容器 My_container 提供 begin() end(),我必须编写如下代码:

template<typename T>

Iterator<My_container<T>> begin(My_container<T>& c)

{

return Iterator<My_container<T>>{&c[0]}; //第一个元素的迭代器

}

template<typename T>

Iterator<My_container<T>> end(My_container<T>& c)

{

return Iterator<My_container<T>>{&c[0]+c.size()}; // 最后一个元素的迭代器

}

在这里,我假设传递第一个元素的地址是创建指向 My_container 第一个元素的迭代器的一种方法,并且 My_container 具有 size()

33.4  函数对象(Function Objects)

    许多标准算法都接受函数对象(或函数)作为参数来控制其工作方式。常见用途包括比较条件、谓词(返回布尔值的函数,即判断函数)和算术运算。在 <function> 中,标准库提供了一些常见的函数对象:

谓词(函数) (§iso.20.8.5, §iso.20.8.6, §iso.20.8.7)

p=equal_to<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x==y

p=not_equal_to<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x!=y

p=greater<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x>y

p=less<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x<y

p=greater_equal<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x=>y

p=less_equal<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x<=y

p=logical_and<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x&&y

p=logical_or<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x||y

p=logical_not<T>(x)

p(x) 意味着当 x T 类型时 !x

p=bit_and<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x&y

p=bit_or<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x|y

p=bit_xor<T>(x,y)

p(x,y) 意味着当 x y T 类型时 x^y

例如:

vector<int> v;

// ...

sort(v.begin(),v.end(),greater<int>{}); //  v按降序排序

这类谓词大致相当于简单的 lambda 表达式。例如:

vector<int> v;

// ...

sort(v.begin(),v.end(),[](int a, int b) { return a>b; }); // v按降序排序

请注意,logical_and logical_or 总是计算它们的两个参数(&& || 则不然)。

算术操作(§iso.20.8.4)

f=plus<T>(x,y)

f(x,y) 意味着当 x y T 类型时 x+y

f=minus<T>(x,y)

f(x,y) 意味着当 x y T 类型时 x-y

f=multiplies<T>(x,y)

f(x,y) 意味着当 x y T 类型时 x*y

f=divides<T>(x,y)

f(x,y) 意味着当 x y T 类型时 x/y

f=modulus<T>(x,y)

f(x,y) 意味着当 x y T 类型时 x%y

f=negate<T>(x)

f(x) 意味着当 x T 类型时 -x

33.5  函数适配器(Function Adaptors)

    函数适配器以函数作为参数并返回可用于调用原函数的函数对象

函数适配器(§iso.20.8.9, §iso.20.8.10, §iso.20.8.8)

g=bind(f,args)

g(args2) 等价于 f(args3),其中 args3 是通过将 args 中的占位符替换为 args2 中的参数而得到的,例如 _1_2 _3

g=mem_fn(f)

如果 p 是指针,则 g(p,args) 表示 p−>f(args),如果 p 不是指针,则表示 p.mf(args)args 是一个(可能为空的)参数列表

g=not1(f)

g(x) 意味着 !f(x)

g=not2(f)

g(x,y) 意味着 !f(x,y)

bind() mem_fn() 适配器执行参数绑定,也称为柯里化(Currying)或部分求值(partial evaluation)。这些绑定器及其已弃用的前身(例如 bind1st()mem_fun()mem_fun_ref())在过去被广泛使用,但大多数用途似乎更适合使用 lambda 表达式(§11.4)。

33.5.1  bind()

    已知一个函数和一组参数,bind() 会生成一个函数对象,该对象可以使用函数剩余的参数(如果有)来调用。例如:

double cube(double);

auto cube2 = bind(cube,2);

调用 cube2() 会使用参数 2 调用 cube,即 cube(2)。我们不必绑定函数的每一个参数。例如:

using namespace placeholders;

void f(int,const string&);

auto g = bind(f,2,_1); // bind f()的第一个参数到2

f(2,"hello");

g("hello"); //同样调用 f(2,"hello");

绑定器中那个奇怪的 _1 参数是一个占位符,它告诉 bind() 函数返回的函数对象的参数应该放在哪里。在本例中,g() 的第一个参数被用作 f() 的第二个参数(译注:即指定返回后的函数的参数就是占位符的位置,这个有别于被绑定的函数)。

    占位符位于 < functional > 的(子)命名空间 std::placeholders 中。占位符机制非常灵活。请考虑:

f(2,"hello");

bind(f)(2,"hello"); // also calls f(2,"hello");

bind(f,_1,_2)(2,"hello"); // 同样调用 f(2,"hello");

bind(f,_2,_1)("hello",2); // 反转参数顺序:同样调用 f(2,"hello");

auto g = [](const string& s, int i) { f(i,s); } // 反转参数顺序

g("hello",2); //同样调用 f(2,"hello");

要为重载函数绑定参数,我们必须明确声明要绑定哪个版本的函数:

int pow(int,int);

double pow(double ,double); // pow()是重载

auto pow2 = bind(pow,_1,2); // : 哪一个pow()?

auto pow2 = bind((double()(double ,double))pow,_1,2); // OK (但丑陋)

注意,bind() 接受普通表达式作为参数。这意味着在 bind() 看到它们之前,引用会解引用。例如:

void incr(int& i)

{

++i;

}

void user()

{

int i = 1;

incr(i); //i 成为2

auto inc = bind(incr,_1);

inc(i); //i 仍为2; inc(i) 增加 i 的本地副本

}

为了解决这个问题,标准库提供了另一对适配器:

reference_wrapper<T>(§iso.20.8.3)

r=ref(t)

r T&t的引用包装器;noexcept

r=cref(t)

r const T& t的引用包装器;noexcept

这解决了 bind() 的“引用问题”:

void user()

{

int i = 1;

incr(i); //i 成为2

auto inc = bind(incr,_1);

inc(ref(i)); //i 成为3

}

由于线程构造函数是可变参数模板(§42.2.2),因此需要使用 ref() 将引用作为参数传递给线程。

到目前为止,我要么立即使用 bind() 的结果,要么将其赋值给使用 car 声明的变量。这样就省去了指定 bind() 调用返回类型的麻烦。这很有用,因为 bind() 的返回类型会随着被调用函数的类型和存储的参数值而变化。特别是,当返回的函数对象必须保存绑定参数的值时,它会更大。但是,有时我们希望具体说明所需参数的类型和返回结果的类型。如果是这样,我们可以为函数指定它们(§33.5.3)。

33.5.2  mem_fun()

    函数适配器 mem_fn(mf) 生成一个可以作为非成员函数调用的函数对象。例如:

void user(Shape p)

{

p−>draw();

auto draw = mem_fn(&Shape::draw);

draw(p);

}

mem_fn() 的主要用途是当算法需要将操作作为非成员函数调用时用于转换函数。例如:

void draw_all(vector<Shape>& v)

{

for_each(v.begin(),v.end(),mem_fn(&Shape::draw));

}

因此,mem_fn() 可以看作是从面向对象调用风格到函数式调用风格的映射。

    通常,lambda 表达式提供了一种简单且通用的 Binder 替代方案。例如:

void draw_all(vector<Shape>& v)

{

for_each(v.begin(),v.end(),[](Shape p) { p−>draw(); });

}

33.5.3  function()

    bind() 可以直接使用,也可以用于初始化自动变量。在这方面,bind() 类似于 lambda。

    如果要将 bind() 的结果赋值给特定类型的变量,可以使用标准库中的 type 函数。函数需要指定特定的返回类型和参数类型。

function<R(Argtypes...)> (§iso.20.8.11.2)

function f {};

f 是空函数;noexcept

function f {nullptr};

f 是空函数;noexcept

function f {g};

f 是一个保存 g 的函数;g 可以是任何

可以用 f 的参数类型调用的东西

function f {allocator_arg_t,a};

f 是一个空function;使用分配器 anoexcept

function f {allocator_arg_t,a,nullptr_t};

f 是一个空函数;使用分配器 anoexcept

function f {allocator_arg_t,a,g};

f 是持有 g 的函数;使用分配器 anoexcept

f2=f

f2f的副本

f=nullptr

f成为空指针

f.swap(f2)

交换 f f2 的内容;f f2 必须是相同的函数类型noexcept

f.assign(f2,a)

f 获得 f2 的副本和分配器 a

bool b {f};

f 转换为 bool;如果 f 非空,则 b 为真;显式;noexcept

r=f(args)

使用参数调用包含的函数;参数类型必须与 f 的参数类型匹配

ti=f.target_type()

ti f type_info如果 f 不包含可调用函数,则 ti==typid(void); noexcept

p=f.target<F>()

如果 f.target_type()==typeid(F) p 指向所包含的对象;​​否则,p==nullptrnoexcept

f==nullptr

f 是否为空?

nullptr==f

f==nullptr

f!=nullptr

!(f==nullptr)

nullptr!=f

!(f==nullptr)

swap(f,f2)

f.swap(f2)

例如:

int f(double);

function<int(double)> fct {f}; // 初始化为f

int g(int);

void user()

{

fct = [](double d) { return round(d); }; // lambda 赋预 fct

fct = f; // 将函数赋矛 fct

fct = g; // : 不正确的返回类型

}

目标函数是为那些想要检查某个函数的罕见情况而提供的,而不是像通常那样简单地调用它。

标准库function类型可以容纳任何可以使用调用运算符 () 调用的对象(见 2.2.1 节,3.4.3 节,11.4 节和 19.2.2 节)。也就是说,函数类型的对象就是函数对象。例如:

int round(double x) { return static_cast<double>(floor(x+0.5)); } // 常规 4/5 舍入

function<int(double)> f; // f 可以保存任何可以用 double 调用并返回 int 的东西

enum class Round_style { truncate, round };

struct Round { // 携带状态的函数对象

Round_style s;

Round(Round_style ss) :s(ss) { }

int operator()(double x) const { return (s==Round_style::round) ? (x+0.5) : x; };

};

void t1()

{

f = round;

cout << f(7.6) << '\n'; // 通过 f 调用函数 round

f = Round(Round_style::truncate);

cout << f(7.6) << '\n'; // call the function object

Round_style style = Round_style::round;

f = [style] (double x){ return (style==Round_style::round) ? x+0.5 : x; };

cout << f(7.6) << '\n'; // call the lambda

vector<double> v {7.6};

f = Round(Round_style::round);

std::transform(v.begin(),v.end(),v.begin(),f); // 传递算法

cout << v[0] << '\n'; //通过lambda传递

}

我们得到 8878

    显然,function对于回调、将操作作为参数传递等很有用。

33.6  建议(Advice)

[1] 输入序列由一对迭代器定义;§33.1.1。

[2] 输出序列由单个迭代器定义;避免溢出;§33.1.1。

[3] 对于任何迭代器 p[p:p] 为空序列;§33.1.1。

[4] 使用序列的末尾表示“未找到”;§33.1.1。

[5] 将迭代器视为更通用且通常行为更好的指针;§33.1.1。

[6] 使用迭代器类型(例如 list<char>::iterator)而不是指针来引用容器的元素;§33.1.1。

[7] 使用 iterator_traits 获取有关迭代器的信息;§33.1.3。

[8] 您可以使用 iterator_traits 进行编译时调度;§33.1.3。

[9] 使用 iterator_traits 根据迭代器的类别选择最佳算法;§33.1.3。

[10] iterator_traits 是实现细节;建议隐式使用;§33.1.3。

[11] 使用 base() reverse_iterator 中提取迭代器;§33.2.1。

[12] 可以使用插入迭代器向容器中添加元素;§33.2.2。

[13] move_iterator 可用于将复制操作转换为移动操作;§33.2.3。

[14] 确保容器可以使用范围for 遍历;§33.3。

[15] 使用 bind() 创建函数和函数对象的变体;§33.5.1。

[16] 注意,bind() 会提前解除引用;如果要延迟解除引用,请使用 ref();§33.5.1.

[17] 可以使用 mem_fn() lambda p->f(a) 调用约定转换为 f(p,a);§33.5.2.

[18] 当你需要一个可以容纳各种可调用对象的变量时,请使用函数;§33.5.3.

内容来源:

<<The C++ Programming Language >> 第4版,作者 Bjarne Stroustrup

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值