赞
踩
泛型编程关注算法,旨在编写独立于数据类型的代码,即任何类型都用一套代码,C++实现泛型编程的工具就是模板,可以用模板来定义函数或者类。但是模板的设计中,迭代器非常重要。理解迭代器是打开STL世界大门的钥匙,因为迭代器使得算法独立于容器类型。
模板和迭代器是STL通用方法的重要组成部分。
模板使得算法独立于存储的数据类型。
迭代器使得算法独立于使用的容器类型。
面向对象编程关注的是数据。
double * find_ar(double * ar, int n, const double & val)
{
for (int i=0;i<n;++i)
{
if (ar[i] == val)
return &ar[i];
}
return nullptr;
}
这里用的时间复杂度最差的顺序查找,只是为了展示哈。
这种写法可以适合于所有数据类型的数组,不只是double,所以可以写为模板函数,就可以推广到各种类型。
但是!如果数据使用链表存储呢?傻了,由于上面代码使用数字下标进行索引,而链表不支持随机访问,所以无法推广到链表。所以重用代码的程度不强,通用性不够好。
链表需要先定义一个结点的结构
struct Node{
double item;
Node * next;
};
Node * find_list(Node * head, const double & val)
{
Node * start;
for (start=head;start != nullptr;start=start->next)
if (start->item == val)
return start;
return nullptr;
}
同样的,这个代码可以推广到所有用链表存储的数据类型,定义成模板函数,但是它仍然是一个和链表容器关联在一起的模板函数,通用性有限。
可不可以把数组和链表这两个模板函数压缩为一个呢?只用一个函数,实现数组表示法和链表表示法的遍历。毕竟这两个函数的算法实际上是一样的,只不过前者用了下标索引,后者用了p=p->next
方法而已。
泛型编程就是基于这种需求,它想实现真正的大面积代码重用,抽象程度更高,用同一个find函数处理数组,链表,甚至其他的容器类型。即函数独立于容器类型,也独立于数据类型。
在刚才的这个例子中,我们清楚的看到,函数独立于数据类型是通过模板实现的,但是只用模板不能让函数独立于容器类型,因为不同的容器的底层需要的操作不一样。而让函数实现独立于容器类型的目标的工具,正是迭代器。
迭代器是STL算法的接口
可以看到,迭代器需要的特征很少,根本不复杂的。
而且常规指针完全已经符合这三个特征了哦。所以可以这么写代码:
typedef double * iterator;
iterator find_ar(iterator ar, int n, double & val)
{
for (int i=0;i<n;++i)
if (*ar == val)
return ar;
return nullptr;
}
挺难想到的诶。还以为iterator是个保留字不能自己定义呢。
但是,具有这三个特征的只是初级的简单的迭代器,STL按照功能强弱还定义了多种级别的迭代器。
再把上面的函数继续修改,改参数,改成像迭代器的那么个样子:
typedef double * iterator;
iterator find_ar(iterator begin, iterator end, const double & val)
{
iterator ar;
for (ar=begin;ar!=end;++ar)
if (*ar == val)
return ar;
return end;//返回end就表示没找到
}
class iterator{ Node * pt;//私有数据是一个Node指针 public: iterator():pt(0){} iterator(Node * pn):pt(pn){} //重载*运算符函数,实现迭代器的解引用功能 double operator*(){return pt->item;} //重载++运算符 iterator operator++(int) // it++,后缀递增运算符 { iterator tmp = *this;//先存住当前迭代器 pt = pt->next; return tmp; } //注意后缀版本的int参数只是摆设,用不到的,只是C++编译器要区分一下,总不能前后缀的函数头一样吧 iterator operator++() //++it,前缀递增运算符 { pt = pt->next; return *this; } //重载== bool operator==(const iterator & it) { if (it->item == pt->item) return true; return false; } //重载!= bool operator!=(const iterator & it) { if (it->item == pt->item) return false; return true; } };
有了迭代器类之后,链表的find函数就可以写为:
iterator find_list(iterator head, const double & val)
{
iterator start;
for (start=head;start!=nullptr;++start)
if(*start == val)
return start;
return nullptr;
}
现在,链表和数组的代码基本一样了,唯一的差别是数组那边是end,链表这边是空指针判断链表尾,所以为了实现大一统,我们还有一步工作要做,即让链表也有超尾。当然啦,这并不是对迭代器的要求,这是对容器类的要求,毕竟是要求链表要有超尾元素得嘛。
STL就马上行动,先给所有容器类定义了对应的迭代器类型,对于有的类比如vector,迭代器实际就是指针,就像咱们上面的代码一样,底层就是咱们这么写的;而对于链表这种类型,迭代器就是一个类的对象。不管是指针还是对象,总之迭代器的功能就是解引用,递增,==,!=这几个。
vector<int>
类的迭代器是vector<int>::iterator
list<int>
类的迭代器是list<int>::iterator
然后STL赶紧给所有容器类定义了超尾标记,且每个容器类都有begin(),end()方法,分别返回指向容器第一个元素的迭代器,和指向超尾位置的迭代器。
超尾,超 不是 超级 的意思,而是 超过 的意思。哈哈
注意所有容器类的迭代器的类型也许是不一样的哦。有的类的迭代器是指针类型,有的类的迭代器是类对象哦。但是我们使用者看来,看不到任何区别,底层的实现我们都是看不见的,因为C++采用了同一的风格设计迭代器的类。
现在,不管对数组还是链表进行查找,都是直接使用迭代器:
vector<double> scores;
for(auto p = scores.begin();p!=scores.end();++p)
cout << *p << endl;
list<double> scores;
for(auto p = scores.begin();p!=scores.end();++p)
cout << *p << endl;
首先,泛型编程的理念是通用,抽象,重用代码。它强调算法,关键思想是要让算法独立于数据类型和容器类型。独立于数据类型可以用模板工具达到目的,但是用了模板以后,发现不同容器的具体情况有所差别,于是给容器类加了需求,即让所有容器类都要用超尾位置标记,然后让所有容器类都设计一个适合自己的迭代器类型,并且不同容器类的迭代器的类设计要一致统一。
我觉得这两句话特别精辟有力。
模板使得算法独立于存储的数据类型。
迭代器使得算法独立于使用的容器类型。
迭代器在有的容器类中是指针,因为指针就可以满足需求;在有的容器类中则必须是类对象,因为常规指针满足不了这些容器类的需求,只能专门定义一个类来实现所有需要的方法。
为什么要对迭代器进行分类呢????
因为不同算法对迭代器的要求不同,比如:
感觉STL像一个忠实的服务提供者,算法是客户,客户需要什么,STL钻研地透透的,然后完美地开发出多样的完美的工具供用户自行挑选使用。
但是这五种迭代器的共同点是:
都定义了解引用,==,!=, ++(包括前缀和后缀)这四个最基本操作。
“输入”是指把容器的信息当做程序的输入,即只可以读取容器内信息,不可以输出到容器内。
--
运算符。所以基于输入迭代器的算法都是单通行single-pass的,即本次遍历的顺序和上一次遍历的顺序完全无关,和本次遍历前面的迭代器也完全无关(即每次遍历时,遍历顺序都不一定一样哦)。“输出”是指程序把信息输出到容器内,即可以修改容器的值。
其实cout也是如此啊,只可以修改发送到显示器的字符流,不能读取屏幕上的内容。
常规指针就是可以读可以写的,const指针是只读的。
由于正向迭代器不支持递减操作,所以不可以用常规指针实现,一般会用一个类来实现。
list类,双向链表,使用双向迭代器,所以不可以用于基于随机访问的算法。
由于双向迭代器支持正向迭代器的所有操作,还支持递减,所以可以用常规指针来实现。用常规指针实现的双向迭代器是C++的内置类型,不是类。但是也可以用类来实现。
排序和二分查找等算法,需要从一个位置直接跳到任何一个位置,即随机访问。
vector类的迭代器是随机访问迭代器,可以用于任何算法。
其实可以看到,指针满足所有迭代器的要求。
5个迭代器是有层次结构的,很像继承关系(但是并不是继承机制!因为正向迭代器可以实现为一个类,但是双向迭代器可以实现为常规指针!即这个层次里的5类迭代器,有的用类,有的用指针,并没有用C++的继承机制。):
看这个表,能对5种迭代器支持的操作更加清楚
在选择迭代器到自己的算法中时,尽量选择要求最低的最简单的可满足需求的迭代器,即级别最低的迭代器。比如,虽然find函数只需要输入迭代器,但是正向,双向,随机访问迭代器都是可以用的,但是我们并不会选择这些复杂的,而是选择简单的输入迭代器。
指针满足所有迭代器的要求
迭代器是广义指针,是STL算法的接口
指针满足所有迭代器的要求,指针就是迭代器
所以,STL算法也可以用于基于指针的非STL容器,比如数组。
迭代器是广义指针,但是指针就是迭代器。迭代器是一个比指针更宽大的概念,它包含了指针,指针确确实实一定的,百分百,是迭代器。而迭代器不一定是指针,因为它可能是用类来实现的,所以还可能是类的对象。
指针一定是迭代器;迭代器不一定是指针。
以快排算法的实现sort()为例
const int SIZE = 5;
double receipts[SIZE];
sort(receipts, receipts + SIZE);//或者sort(&receipts[0], &receipts[SIZE]);
sort函数第一个参数是指向第一个元素的迭代器,第二个元素是指向超尾的迭代器
C++把超尾的概念也用到了非STL容器中。
能够把STL算法用到常规容器,就是因为指针是迭代器,且常规容器也有超尾的概念。
只有容器类自己的成员方法,比如vector类的push_back()方法,有权限该容器的大小。
int casts[5] = {5, 6, 7, 3, 4};
vector<int> dice[5];
copy(casts, casts+5, dice.begin());//把数组copy给vector
都是STL提供的
可以用来把信息复制到显示器上。有了ostream_iterator迭代器,就可以使用copy()函数了。
ostream_iterator模板是输出迭代器概念的一种模型,是一个适配器adapter(即一个类,或一个函数,可把其他接口转换为STL使用的接口)。
这里涉及到好几个STL领域内的术语,比如概念concept(STL用来描述一系列要求),适配器等。都是很抽象的很难理解的。
//创建迭代器
#include <iterator>
ostream_iterator<int, char> out_iter(cout, " ");//有两个模板参数哦
这时候out_iter迭代器就是一个接口,是一个ostream_iterator<int, char>
类的对象,这个接口使用cout显示信息。
所以用法是:
*out_iter++ = 15;//相当于cout << 15 << " ";
先把15给out_iter指向的位置,再给out_iter迭代器递增。其实就是先把15和分隔符发给输出流,再递增迭代器。
太高级了吧!!!!!太会玩了。。。。牛逼,还是C++牛逼。
如果用copy(),会更简单,可以一次输出多个数据:
#include <iostream>
#include <iterator>
int main(){
using namespace std;
ostream_iterator<double, char> out_iter(cout, " ");
double prices[4]= {1.2, 3.5, 9.8, 5.2};
copy(prices, prices+4, out_iter);//注意常规数组没有begin()和end()方法,要用指针做迭代器
return 0;
}
1.2 3.5 9.8 5.2
还可以直接用匿名迭代器:
#include <iostream>
#include <iterator>
int main(){
using namespace std;
double prices[4]= {1.2, 3.5, 9.8, 5.2};
copy(prices, prices+4, ostream_iterator<double, char> (cout, " "));
return 0;
}
是输入迭代器概念的一个模型
也有俩模板参数,第一个指出读取的数据类型,第二个指出输入流的字符类型。构造函数第一个参数cin表示用cin管理的输入流,如果不写构造函数的参数则表示输入失败。
示例:
copy(istream_iterator<int, char> (cin), istream_iterator<int, char>(), dice.begin());
#include <iostream>
#include <iterator>
#include <vector>.
int main(){
using namespace std;
vector<int> dice(5);
copy(istream_iterator<int, char> (cin), istream_iterator<int, char>(), dice.begin());//直到输入失败
copy(dice.begin(), dice.end(), ostream_iterator<int, char>(cout, " "));
return 0;
}
输入字母,不是int,输入失败,所以结束
1 2 3 4 a
1 2 3 4 0
输入浮点数,不是int,输入失败,所以结束,但是浮点数的点之前的数据会被读取哦
2 5 8
9
2.3
2 5 8 9 2
vector类有一个成员函数叫做rbegin(),rend()。分别返回指向超尾的反向迭代器和指向第一个元素的反向迭代器。
rbegin()和end()返回的迭代器指向的位置都是超尾, 但是类型不同,前者是反向迭代器,后者是普通迭代器。
主要是用来反向显示容器的内容,或者其他反向的操作。它可以用来简化已有函数的使用。
不需要声明反向迭代器。。。。
emmmm,难道反向迭代器没有构造函数??不需要传入模板参数,它就只能通过rbegin(),rend()函数来访问???
有一个严重问题:如果对dice.rbegin()
解引用会怎么样???毕竟这返回的是超尾位置,解引用肯定出错。
所以,反向指针不可以像正向迭代器那样,先解引用再递增迭代器。
而是要先递减,再解引用,这样,就不会对dice.rbegin()
解引用了,且到了dice.rend()
,也是先递减为第一个元素前面的那个元素位置,再对第一个元素解引用。
#include <iostream>
#include <iterator>
#include <vector>.
int main(){
using namespace std;
vector<int> dice(5);//这里必须分配空间啊!
copy(istream_iterator<int, char> (cin), istream_iterator<int, char>(), dice.begin());
copy(dice.rbegin(), dice.rend(), ostream_iterator<int, char>(cout, " "));
return 0;
}
4 5 6 7 8 a
8 7 6 5 4
#include <iostream> #include <iterator> #include <vector>. int main(){ using namespace std; int casts[10] = {6, 7, 8, 5, 6, 3, 71, 12, 56, 45}; vector<int> dice(10); copy(casts, casts+10, dice.begin()); cout << "Let the dice be cast!\n"; ostream_iterator<int, char> out_iter(cout, " "); copy(dice.begin(), dice.end(), out_iter); cout << endl; cout << "Implicit use of reverse iterator.\n"; copy(dice.rbegin(), dice.rend(), out_iter); cout << endl; cout << "Explicit use of reverse_iterator.\n"; vector<int>::reverse_iterator ri; for (ri=dice.rbegin();ri!=dice.rend();++ri) cout << *ri << ' '; cout << endl; return 0; }
Let the dice be cast!
6 7 8 5 6 3 71 12 56 45
Implicit use of reverse iterator.
45 56 12 71 3 6 5 8 7 6
Explicit use of reverse_iterator.
45 56 12 71 3 6 5 8 7 6
vector<int>
,这是因为迭代器需要使用这个容器类自己的成员方法以动态调整容器的大小。非成员函数不可能有权限去修改容器的大小的,那还有什么隐私什么访问控制的天理?虽然是泛型编程的主场了,OOP的规则和思想还是要尊重的。
back_insert_iterator<vector<int>> back_iter(dice);
back_insert_iterator
一样。front_insert_iterator<vector<int>> front_iter(dice);
insert_iterator<vector<int>> iter(dice, dice.begin());
#include <iostream> #include <string> #include <iterator> #include <vector> #include <algorithm> void output(const std::string & s){std::cout << s << " ";} int main() { using namespace std; string s1[4] = {"fine", "fish", "fashion", "fate"}; string s2[2] = {"busy", "bats"}; string s3[2] = {"silly", "singers"}; vector<string> words(4); copy(s1, s1+4, words.begin()); for_each(words.begin(), words.end(), output); cout << endl; //构建匿名末尾插入迭代器对象 copy(s2, s2+2, back_insert_iterator<vector<string>>(words)); for_each(words.begin(), words.end(), output); cout << endl; //构建匿名插入迭代器 copy(s3, s3+2, insert_iterator<vector<string>>(words, words.begin()));//插入在words前端,比front_insert_iterator慢 for_each(words.begin(), words.end(), output); cout << endl; return 0; }
fine fish fashion fate
fine fish fashion fate busy bats
silly singers fine fish fashion fate busy bats
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。