三、泛型编程风格

  • Standard Template Library(STL)主要由两种组件构成:容器(container)如vectorlistsetmap等类,和用来操作这些容器的泛型算法(generic algorithm)如find()sort()replace()merge()等。
  • 顺序性容器(sequential container)依次维护元素,主要进行迭代(iterate)操作;关联容器(associative container)可以快速查找容器中的元素值。
  • 泛型算法通过function template技术,与操作对象的类型相互独立且容器类型无关。不直接在容器上操作,而是借由一对iteratorfirstlast)标示需要迭代的元素范围。
  • 当数组被传给函数或从函数中返回时,仅有第一个元素的地址会被传递。
  • 下标操作[]就是将起始地址加上索引值,产生出某个元素的地址,然后再提领该地址以返回元素值。
int array[24];
//下列语句都会返回第三个元素值
array[2];
*(array + 2);
  • vectorarray相同,都是以一块连续内存储存所有元素,但vector可以为空,array却不可以。所以操作vector要先确定不为空。
  • list也是一个容器,元素以一组指针相互链接:前向指针指向下一个元素,后向指针指向上一个元素。
  • 指针的算术运算必须首先假设所有元素都储存在连续空间里,然后才能根据当前指针加上元素大小后指向下一个元素。故并不适用于list
  • 每个标准容器都有一个begin()操作函数,可返回一个泛型指针iterator,指向第一个元素。end()则指向最后一个元素的下一位置。
  • 无论如何定义iterator对象,以下都是对iterator进行赋值、比较、递增和提领操作:
for(iter = svec.begin(); iter != svec.end(); ++iter)
	cout << *iter << ' ';
  • 下句中iter被定义为一个iterator,指向一个string类型的vector,其初值指向svec的第一个元素:
vector<string>::iterator iter = svec.begin();
//双冒号[::]表示此iterator是位于string vector定义内的嵌套(nested)类型。

//const类型的vector用const_iterator指向
const vector<string> cs_vec;
vector<string>::const_iterator iter = cs_vec.begin();
  • const_iterator允许我们读取vector元素,但不允许任何写入操作。
  • 常见的泛型算法:
  • 搜索(search):find(), count(), adjacent_find()(搜寻第一组相邻且值重复的元素)find_if(), count_if(), binary_search(), find_first_of()(搜索某些元素首次出现的位置)
  • 排序(sort)和次序整理(ordering):merge(), partial_sort(), partition()(切割)random_shuffle(), reverse(), rotate(), sort()
  • 复制(copy)、删除(deletion)、替换(substitution):copy(), remove(), remove_if(), replace(), replace_if(), swap(), unique()(去除重复)
  • 关系(relational):equal(), includes(), mismatch()(比较两个序列是否匹配)
  • 生成(generation)与质变(mutation):fill(), for_each(), generate(), transform()
  • 数值(numeric):accmulate(), adjacent_difference()(相邻差)partial_sum(), inner_product()(内积)
    集合(set):set_union()(并集),set_difference()(差集)
  • 内积是数量积、点乘,得到一个标量;外积是叉乘、向量积,得到平面的法向量。
  • 所有容器的共通操作:
  • equality(==)和inequality(!=)运算符,返回truefalse
  • assignment(=)运算符,将某个容器复制给另一个容器
  • empty()会在容器无任何元素时返回true,否则返回false
  • size()返回容器内目前持有的元素个数
  • clear()删除所有元素
  • begin()返回一个iterator,指向容器的第一个元素
  • end()返回一个iterator,指向容器的最后一个元素的下一个位置
  • insert()将单一或某个范围内的元素插入容器内
  • erase()将容器内的单一元素或某个范围内的元素删除
  • insert()erase()视容器本身为顺序性或关联容器而有所不同。
  • 顺序性容器用来维护一组排列有序、类型相同的元素(如vectorlistdeque)。
  • vector以一块连续内存来存放元素,其中每一个元素都被储存在距离起始点固定的偏移位置上,适合随机访问(random access),插入或删除除末端元素以外的位置效率极低。
  • list以双向链接(double-linked)储存,每个元素都包含value、back指针和front指针。在list的任意位置进行插入删除都颇具效率,但随机访问操作效率极低。
  • deque(double-ended queue,读作“带克”)以连续内存储存元素,对于最前端和末端元素的插入或删除效率颇高。标准库的queue便是以deque实现的。
  • 使用顺序性容器必须包含相关头文件:
#include <vector>
#include <list>
#include <deque>
  • 定义顺序性容器对象的方式有5种:
//产生空的容器
vector<int> ivec;

//产生特定大小的容器
//每个元素都以其默认值作为初值
vector<int> ivec(32);//默认值为0

//产生特定大小的容器并为每个元素指定初值
vector<int> ivec(10, -1);

//通过一堆iterator产生容器。
//这对iterator用来标示作为初值的元素范围
int ia[8] = {1,1,2,3,5,8,13,21};
vector<int> fib(ia, ia+8);

//根据某个容器产生新的容器
//复制原容器内的元素作为新容器的初值
vector<int> fib2(fib);
  • push_back()会在末端插入一个元素,pop_back()会删除最后一个元素。
  • listdeque还提供了push_front()pop_front()
  • pop_back()pop_front()并不会返回被删除的元素值,因此,读取最前端和末端元素应该采用front()back()
  • 下述两个erase()函数返回的iterator,皆指向被删除的最后一个元素的下一个位置:
//删除posit所指的元素
iterator erase(iterator poist);
//删除[first, last)范围内的元素
iterator erase(iterator first, iterator last);
  • list支持iterator的偏移运算。
  • 使用泛型算法必须包含头文件#include <algorithm>
  • find()用于搜索无序集合中是否存在某值,搜索范围由iterator[first,last)标出。若找到就返回一个iterator指向该值,否则返回一个iterator指向last,属于linear search。
  • binary_search()用于搜索有序集合,属于binary search,效率比find()高。
  • count()返回数值相符的元素数目。
  • search()比对某个容器内是否存在某个子序列,存在则返回一个iterator指向子序列起始处,否则指向容器末尾,
  • 泛型算法max_element()可以返回一对iterator所表示的数列范围内最大的元素值。
  • copy()接受两个iterator标识出复制范围,第三个iterator指向复制行为的目的地(也是个容器)的第一个元素,后续元素会被依次填入。

确保目标容器拥有足够空间以放置每个即将到来的元素是程序员的责任。

  • function object是某种类的实例对象,这类class对function call运算符做了重载操作,可使function object被当作一般函数来使用。
  • 标准库定义的function object分为算术运算(arithmetic)、关系运算(relation)和逻辑运算(logical)。以下所示的type在实际使用时会被替换为内置类型或class类型:
  • 算数运算:plus<type>, minus<type>, negate<type>, multiplies<type>, divides<type>, modules<type>
  • 关系运算:less<type>, less_equal<type>, greater<type>, greater_equal<type>, equal_to<type>, not_equal_to<type>
  • 逻辑运算:logical_ans<type>, logical_or<type>, logical_not<type>
  • 使用预先定义的function object要包含头文件#include <functional>
  • 默认情况下sort()使用less_than将元素升序排列,如下语句用greater<int>()产生一个未命名的class template object传给sort()
sort( vec.begin(), vec.end(), greater<int>() );

标准库提供的adapter(适配器)可以将less<type>转化为一个一元运算符,即将其第二参数绑定至用户指定的数值,这样一来less<type>便会将每个元素与用户指定的数值进行比较。

  • function object adapter会对function object进行修改操作。绑定适配器(binder adapter)会将function object的参数绑定到某个特定值,使二元函数对象转化为一元。bind1st将指定值绑定到第一操作数,bind2nd则绑定在第二操作数上。
less<int> lt;
iter  = find_if(vec.begin(), vec.end(), bind2nd(lt, val));
  • 为了消除函数与元素类型之间的依赖性,将其改为function template,并将元素类型加入template声明中;为了消除函数与容器类型之间的依赖性,传入一对iterator[first,last),并在参数列表中增加另一个iterator,用来指定从何处开始复制元素。
template <typename InputIterator, typename OutputIterator, 
			typename ElemType, typename Comp>

OutputIterator filter(InputIterator first, InputIterator last, 
						OutputIterator at, const ElemType &val, Comp pred)
{
	while((first = find_if(first, last, bind2nd(pred, val))) != last)
	{
		//观察
		cout << "found value: " << *first <<endl;
		
		//执行赋值操作,然后让两个iterator前进
		//*at++ = *first++; //与下面三句效果相同
		*at = *first;
		at++;
		first++;	
		//但是++优先级在*前,可能因为语句没结束所以++不进行
	}
	return at;
}
  • 还有一种适配器叫negator(取反器),对函数对象的真伪值取反。not1可对unary function object(一元函数对象)取反,not2对binary function object取反。例如,寻找大于或等于10的元素时,可以这么写:
iter  = find_if(vec.begin(), vec.end(), not1(bind2nd(less<int>, 10)));

思路整理:

  • 一开始写了一个函数,可以找出vector内小于10的所有元素。然而函数过于死板,没有弹性。
  • 为函数加上了一个数值参数,让用户得以指定某个数值来和vector中的元素做比较。
  • 加入新的参数:函数指针,让用户可以指定比较方式。
  • 引入function object的概念,得以将某组行为传给函数,比函数指针效率更高。(如何编写自己的function object 见第四章
  • 以function template的方式重新实现函数。(传入一对iterator支持多种容器,将元素类型参数化来支持多种元素类型,将“比较操作”参数化以同时支持函数指针和function object两种方式)
  • 至此,函数与元素类型、比较操作以及容器类型都无关,成为了一个泛型算法!
  • map(#include <map>)被定义为一对(pair)数值,key通常是一个字符串,扮演索引角色,另一个数值是value

  • 表达式words[tword]words是一个map)会取出与tword相应的value,如果tword不在map内,它会因此被放到map内,并获得默认值0

  • map对象有一个名为first的member,对应于key,另有一个名为second的member对应于value。

  • 查询map内是否存在某个key,有三种方法:

    • 把key当作索引
      • 如果某个key并不存在于map中,使用此方法会将该key加入map,对应的value设为默认值。
map<string, int> M;
	M["hello"]++;
	if(M["hi"])
		cout<<"oh"<<endl;
	if(M["hi"])
		cout<<"oh"<<endl;//无 
	int countt = 0;
	if(!(countt = M["hi"]))
		cout<<"oh"<<endl;
	if(M["hi"])
		cout<<"oh"<<endl;//有 
	//所以其实用if判断一下没关系,
	//一般人也不会写什么countt = M["hi"]吧
    • 利用map的find()函数
      • 注意不要和泛型算法的find()搞混。
      • M.find("hi");
      • 如果key已经在map中,则返回一个iterator指向 <key,value> 形成的一个pair,否则返回end()
    • 利用map的count()函数
      • count()会返回某特定项在map内的个数。
      • num = M.count("hello");
  • 任何一个key值在map内最多只会有一份。
  • set(#include <set>)由一群key组合而成。如果想知道某值是否存在于某个集合内,可以用set。任何一个key值在set内也最多只会有一份。
  • 默认情形下,set元素皆依据其所属类型默认的less_than运算符进行排列。
int ia[10] = {1,3,5,8,5,3,1,5,8,1};

vector<int> vec(ia, ia+10);
set<int> iset(vec.begin(), vec.end());//{1,3,5,8} 
  • 为set加入单一元素可使用单一参数的insert(),加入某个范围的元素使用双参数的insert()
  • 泛型算法中与set相关的算法:交集set_intersection(),并集 set_union(), 差集set_difference(), 对称差集set_symmetric_difference()
  • (当函数是将元素赋值到某个已存在的容器上时,程序员要考虑目的端容器不够大的问题,所以)标准库提供了insertion adapter以避免使用容器的赋值运算符#include <iterator>
  • back_inserter()会以容器的push_back()函数取代赋值运算符。
    • unique_copy(ivec.begin(), ivec.end(), back_inserter( result_vec));
    • //传入back_inserter的参数是容器本身
  • inserter()以容器的insert()函数取代赋值操作。
    • inserter()接受两个参数:容器和指向容器内插入操作起点的iterator。
  • front_inserter()以容器的push_front()函数取代赋值运算符。只适用于listdeque
  • 这些adapter并不能用在array上,array不支持元素插入。
  • 标准库定义了供输入输出使用的iostream iterator类,称为istream_iteratorostream_iterator,分别支持单一类型的元素读取和写入(#include <iterator>)。
  • 从标准输入设备读取字符串,定义一对用来标示元素范围的iterator:
//first
istream_iterator<string> is(cin);//绑至标准输入设备

//last标示要读取的最后一个元素的下一个位置
istream_iterator<string> eof;
//只要定义istream_iterator时不为其指定istream对象,它便代表了end-of-file

  • ostream_iterator标示字符串元素的输出位置:
//绑至标准输入设备
ostream_iterator<string> os(cout, " ");
//第二个参数可以是C-style字符串,也可以是字符串常量,
//用来表示各个元素被输出时的分隔符,
//默认情况下无分隔符

  • 完整程序如下
#include <iostream>
#include <iterator>
#include <algorithm>
#include <vector>
#include <string>

using namespace std;

int main()
{
	istream_iterator<string> is(cin);
	istream_iterator<string> eof;
	
	vector<string> text;
	copy(is, eof, back_inserter(text));
	
	sort(text.begin(), text.end());
	
	ostream_iterator<string> os(cout, " ");
	copy(text.begin(), text.end(), os);
}
  • 不从标准输入输出读写而是从文件中读写:
...
#include <fstream>
int main()
{
	ifstream in_file("input_file.txt");
	ofstream out_file("output_file.txt");
	
	if(!in_file || !out_file)
	{
		cerr << "!!unable to open the necessary files.\n";
		return -1;
	}
	
	//将 istream_iterator 绑定到 ifstream 对象 
	istream_iterator<string> is(in_file);
	istream_iterator<string> eof;
	//将 ostream_iterator 绑定到 ofstream 对象 
	ostream_iterator<string> os(out_file, " ");
	//...
}
  • stable_sort()会在符合排序条件的原则下维护元素间原本的相对顺序。
  • typedef机制(见第四章)让我们得以为任何一个类型提供另一个等价名称,通常用来简化那些声明起来十分麻烦的类型。
typedef vector<string> vstring;
  • getline()是标准库提供的函数,从文件(第一参数)中读取一行内容,其第三参数用来指定行末字符,默认为换行符。读入的文字行被放在第二参数中。

getline无法通过Visual C++和Borland C++ Builder,只有GCC可接受
但确实符合C++ Standard

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值