Essential C++学习(三)泛型编程风格

3.1 指针的算术运算

函数find:给定一个储存任何类型数字的vector或者array,以及一个值,如果此值存在于vector或者array内,必须返回一个指针指向该值;反之则返回0,表示此值并不在vector或者array内。

任务1:将array的元素传入find(),而不指定array的类型

  • 解法一:增加一个参数,用来表示array的大小
	template <typename elemType>
	elemType* find(const elemType *array, int size, const elemType &value)

实现:从0开始访问每个元素,依次处理至size-1个元素。

问题:传递给find()的array是以第一个元素的指针传入的,如何通过特定位置进行元素访问?

方法一:下标运算符[ ]

array[2];//返回array的第三个元素
	template <typename elemType>
	elemType* find(const elemType *array, int size, const elemType &value)
	{
		if (!array || size < 1)
			return 0;
		for (int ix = 0; ix < size; ++ix)
			//可以对指针使用下标运算符
			if (array[ix] == value)
				return &array[ix];
		return 0;
	}

array是以第一个元素的指针传入find()中,通过下标[]运算符访问array的每个元素是通过array的起始地址加上索引值产生该元素的地址,然后该地址再被dereference以返回元素值实现的。

方法二:指针 *

*(array+2);//array+2产生的地址是array的地址加上两个整数元素的大小(4 byte)
	template <typename elemType>
	elemType* find(const elemType *array, int size, const elemType &value)
	{
		if (!array || size < 1)
			return 0;
		for (int ix = 0; ix < size; ++ix, ++array)//每次array+1指向下一个元素
			//可以对指针使用下标运算符
			if (*array == value)
				return array;
		return 0;
	}
  • 解法二:使用第二个指针来取代参数size
	template <typename elemType>
	elemType* find(const elemType *first, const elemType *last, const elemType &value)
	{
		if (!first || !last)
			return 0;
        //如果first不等于last,就将value拿来和first所指的元素作比较,
        //如果相同就返回first,否则first+1,指向下一个元素
		for (; first != last; ++first)//每次array+1指向下一个元素
			//可以对指针使用下标运算符
			if (*first == value)
				return first;
		return 0;
	}

调用方式

int ia[3] = {1, 1, 2};
int *pi = find(ia, ia+3, ia[2]);

任务2:将vector的元素传入find(),而不指定vector的类型

可使用array的解法二,使用一对用来表示“起始地址/结束地址”的参数传入find()。不同之处在于vector可以是空的,因此调用前先确定vector不为空。

vector<string> svec;
//将“取第一个元素的地址”的操作包装为函数
//begin()返回0或者是vector第一个元素的地址
template <typename elemType> 
inline elemType* begin(const vector<elemType> &vec)
    {return vec.empty() ? 0:&vec[0];}
//end()返回0或者是vector最后一个元素的下一个地址
inline elemType* end(const vector<elemType &vec)
    {return vec.empty() ? 0:&vec.end();}
//调用find()
find(begin(svec), end(svec), search_value);

如何扩展find()的功能,令它也能支持list类别。

指针的算数运算必须首先假设所有元素都储存在连续空间,因此指针的算术运算不适用于list。

解决办法:在底层指针的行为之上提供一层抽象,取代程序原本的“指针直接操作”方式。把底层指针的处理通通放在此抽象层中,让用户无须直接面对指针操作,这样可以处理标准库中的所有容器类。(???完全不同在说什么)

3.2 了解Iterator(泛型指针)

first和last都是iterator class object,可以把first和last当作指针一样,使用“和指针相同的语法”,差别在于*、!=、++运算符都是有iterator class内相关的inline函数提供的。

A. 原生指针 

     最普通的指针,定义: 类型 *变量名;

     与之对比的是使用上有类似指针的功能 实际并不是指针。比如:迭代器

     [一个类重载 *和->操作符 那么可以像指针一样使用  但是这种并不是原生的]

B. 泛型指针

     第一种就是 void *指针 可以指向任意的数据结构 因此可以称为"泛型"。

     第二种就是指具有指针特性的泛型数据结构 如:泛型迭代器(iterator)和接下来要说的智能指针。

C. 智能指针

     C++中没有自动回收内存的机制,因此出现了智能指针。一般我们将一个指针封装到一个智能指针类中,该类中有一个引用计数器。 对指针的复制等操作会使引用计数+1,delete操作会使引用计数-1。计数=0时,指针=NULL。

原生指针 泛型指针 智能指针 都是什么?

  • 获取iterator
  1. begin()函数:返回一个iterator,指向第一个元素
  2. end()函数:返回一个iterator,指向最后一个元素的下一个位置
for(iter = svec.begin(); iter != svec.end(); ++iter)
    {cout << *iter <<' ';
  • 定义和使用标准容器的iterator

定义需要提供

  1. 迭代对象(某个容器)的类型,用来决定如何访问下一个元素
  2. iterator所指的元素类型,用来决定iterator提领操作的返回值
vector<string> svec;
//iter指向一个string类型的vector
//iter一开始指向svec的第一个元素
vector<string>::iterator iter = svec.begin();

::表示这个iterator是位于string vector定义内的嵌套[nested]类型。

面对const vector使用const_iterator实现遍历操作,允许读取vector的元素,但不允许写入操作。

const vector<string> cs_svec;
vector<string>::const_iterator iter = cs_svec.begin();
  • 通过iterator获取元素值

采用一般指针的dereference方式

cout << "string value of element: " << *iter;
  • 通过iterator调用底部元素提供的操作

采用arrow运算符->

cout << iter->size;

使用iterator取代下标运算符后的display()函数

template<typename elemType>
void display(const vector<elemType> &vec, ostream &os)
{
    vector<elemType>::const_iterator iter = vec.begin();
    vector<elemType>::const_iterator end_it = vec.end();
    //如果vec是空的,iter=vec.end(),for循环不执行
    for(;iter != vec.end();++iter)  
        os << *iter << ' ';
    os<<endl;
}

重新实现find(),让它同时支持两种形式,一对指针或者一对指向某种容器的iterator。

template <typename IteratorType, typename elemType>
IteratorType find( IteratorType first, TteratorType last, const elemType &value)
{
    for(; first != last; ++first)
        if(*first == value)
            return first;
    return last;
}
        

处理array、vector、list:

const int asize = 3;
int ia[asize]={1, 1, 2};
vector<int> ivec(ia, ia+asize);
list<int> ilist(ia, ia+asize);

int *pia = find(ia, ia+asize, 1024);
if(pia != ia+asize)
    //...找到了

vector<int>::iterator it;
it = find(ivec.begin(), ivec.end(), 1024);
if(it != ivec.end())
    //...找到了

list<int>::iterator iter;
iter = find(ilist.begin(), ilist.end(), 1024);
if(iter != ilist.end())
    //..找到了

find()使用了底部元素所属类型的equality(相等)运算符,如果底部元素所属类型没提供就GG了。解法一是传入一个函数指针,取代原本固定使用的=,解法二是使用function object。

下一个目标:将现有的find()版本演化为泛型算法

3.3 所有容器的共同操作

  • ==,!=
  • =
  • empty()在容器内无任何元素时返回true,否则返回false
  • size()
  • clear()删除所有元素
  • begin()和end()
  • insert()将单一元素或某个范围内的元素插入容器
  • erase()将容器内的单一元素或某个范围内的元素删除

3.4 使用顺序性容器

顺序性容器用来维护一组排列有序,类型相同的元素。

  • vector
  1. 以一块连续内存来存放元素。
  2. 对vector进行随机访问的效率高(顺序固定),插入或者删除的效率低(要复制每个元素)。
  3. 适合数列。
  • list
  1. 以双向链接来储存内容,前向(forward)指针指向下一个(next)元素,后向(backward)指针指向上一个(preceding)元素,可以执行前进或后退操作,每个元素包含:value、back指针、front指针。
  2. 对list进行随机访问的效率低(每次都要遍历元素),插入或删除的效率高(设定back、front指针即可)。
  3. 排有顺序的表格时。
  • deque(发音deck)
  1. 以一块连续内存来存放元素。
  2. 不同于vector的是deque对最前端元素和最末端元素的插入和删除操作效率更高。
  3. 作为底部储存元素(queue是以deque实现)。
  • 定义方式
1.产生空容器
vector<int> ivec;
list<string> svec;
2.产生特定大小的容器
vector<int> ivec(10);
list<string> svec(16);
3.产生特定大小的容器并为元素赋初值
vector<string> svec(16,"unsigned");
list<int> ivec(10, -1);
4.通过一对ierator产生容器,这对ierator用来标示一组作为初值的元素的范围
string sa[3]={"how", "are", "u"};
vector<string> svec(sa, sa+3);
5.复制原有容器内的元素产生新容器
list<int> ivec1;
//...填充ivec1
list<int> ivec2(ivec1);
  • 几个操作函数

push_back():在容器末尾插入一个元素

pop_back():删除容器末尾最后一个元素

list和deque还提供了push_front()和pop_front(),这些函数并不返回被删除的元素值。

back():返回容器末端元素的值。front():返回容器最前端元素的值。

#include <deque>
deque<int> a_line;
int ival;
while(cin >> ival)
{
    //将ival插入a_line末端
    a_line.push_back(ival)
    //读取最前端的元素
    int curr_value = a_line.front();
    //删除最前端元素
    a_line.pop_front();
}

insert()函数的四种变形

1.可将value插入position之前。会返回一个iterator,指向被插入的元素value
iterator insert(iterator position, elemType value)
2.可在position前插入值都为value的count个元素
void insert(iterator position int count, elemType value)
3.可在position前插入[first,last)所标示的各个元素
void insert(iterator1 position, iterator2 first, iterator2 last)
4.可在position前插入元素,元素初值为其所属类型的默认值
iterator insert(iterator position)

erase()函数的两种变形

1.可删除posit所指的元素
iterator erase(iterator posit)
2.可删除[first,last)范围内的元素
iterator erase(iterator first, iterator last)

list不支持iterator的偏移运算,因此不能写first+num的形式。

3.5 使用泛型算法

首先要包含对应的algorithm头文件:

#include<algorithm>

以vector为例,如果给定的值已经存在于数列之中,is_elem()返回true;否则返回false。

泛型搜索算法选用binary_search():用于有序集合的搜索,如果搜索到目标就返回true,否则返回false。

#include<algorithm>
//前置声明
extern bool grow_vec(vector<int>& ,int);
//binary_search()要求其作用对象必须经过排序,我们可以将容器先复制一份,然后对副本执行排序操作,再调用binary_search()
vector<int> temp(vec.size());
copy(vec.begin(), vec.end(), temp.begin());
sort(temp.begin, temp.end());

bool is_elem(vector<int> &vec, int elem)
{
    int max_value = vec.empty() ? 0 : vec[vec.size()-1];
    if(max_value<elem)
        return grow_vec(vec,elem);
    if(max_value==elem)
        return true;

    return binary_search(temp.begin(), temp.end(), elem);
}

我记得对容器的复制操作应该还可以写成

vector<int> temp(vec);

3.6 如何设计一个泛型算法

这节我看的好累...呜呜呜这知识它不进脑子啊!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

vector<int> less_than_10(const vector<int> &vec)
{
    vector<int> nvec;
    for(int ix = 0; ix < vec.size(); ++ix)
    {
        if(vec[ix] < 10)
            nvec.push_back(vec[ix]);
    }
    return nvec;
}
//改
vector<int> less_than(const vector<int> &vec, int value);
//再改
vector<int> filter(const vector<int> &vec, int filter_value, bool(*pred)(int, int));
bool less_than(int v1, int v2)
{    return v1<v2 ? true : false;    }
bool bigger_than(int v1, int v2)
{    return v1>v2 ? true : false;    }
//调用方法
vector<int> old_vec;
int value;
vector<int> new_vec = filter(old_vec, value, less_than)
//filter()的定义
vector<int> filter(const vector<int> &vec, int filter_value, bool(*pred)(int, int))
{
    vector<int> nvec;
    for(int ix = 0; ix < vec.size(); ++ix)
    {
        if( pred(vec[ix], filter_value))
            nvec.push_back(vec[ix]);
    }
    return nevc;
}

使用泛型算法find_if(),找出等于10的元素。其中泛型算法find()接受三个参数:两个iterator标示出检测范围,第三个参数是我们想要寻找的数值。

int count_occurs(const vector<int> &vec, int val)
{
    vector<int>::const_iterator iter = vec.begin();
    int occurs_count = 0;
    while((iter = find( iter, vec.end, val)) != vec.end())
    {
        ++occurs_count;
        ++iter;
    }
    return occurs_count;
}
  • Function Object

1.定义

function object是某种对function call运算符做了重载操作的一类class的实例对象,可被当成一般函数来使用。(啥玩意儿啊)。

2.作用

function object实现了原本可能以独立函数加以定义的事物——为了效率!这样可以令call运算符成为inline,从而消除“通过函数指针来调用函数时”需付出的额外代价。

3.一组function object

算术运算(六个,plus<type>,minus<type>...)、关系运算(六个,less<type>,greater<type>...)和逻辑运算(三个,对应&&、||、!)三大类。

4.使用

首先要包含头文件

#include <functional>

以sort()为例,sort()在默认情形下会使用底部元素的类型所提供的<运算符将元素按升序排列,如果我们传入greater_than function object,元素就会以降序排列:

sort(vec.begin(), vec.end(), greater<int>());
  • Function Object Adapter

function object adapter(适配器)会对function object进行修改操作。binder adapter(绑定适配器)会将function object的参数绑定至某特定值,使binary(二元)function object转化为unary(一元)function object。

bind1st会将指定值绑定至第一操作数,bind2st会将指定值绑定至第二操作数。

vector<int> filter(const vector<int> &vec, int val, less<int> &lt)
{
    vector<int> nvec;
    vector<int>::iterator iter = vec.begin();
    //bind2nd(less<int>,val)会把val绑在less<int>的第二个参数上
    while((iter = find_if(iter, vec.end(), bind2nd(lt, val))) != vec.end())
    {
        nvec.push_back(*iter);
        iter++;
    }
    return nvec;
}

使用template消除filter()与元素类型的依赖性,使用iterator消除filter()与容器类型的依赖性。

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;
        *at++ = *first++;
    }
    return at;
}

调用filter(),测试其在array和vector上的使用。

方法一:使用insert iterator adapter

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<functional>
using namespace std;
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;
		*at++ = *first++;
	}
	return at;
}
int main()
{
	const int elem_size = 8;
	int ia[elem_size] = {12,8,43,0,6,21,3,7};
	vector<int> ivec(ia, ia+elem_size);
	//下面这个容器用来储存过滤结果
	int ia2[elem_size];
	vector<int> ivec2(elem_size);//elem_size个数个0
	cout << "filtering integer array for values less than 10\n";
	filter(ia, ia + elem_size, ia2, elem_size, less<int>());

	cout << "filtering integer vector for values less than 10\n";
	filter(ivec.begin(),ivec.end(),ivec2.begin(),elem_size,greater<int>());
	system("pause");
	return 0;
}

方法二:使用negator,对function object的真伪值取反

  1. not1对unary function object的值取反
  2. not2对binary function object的值取反

那我把

not1(bind2nd(pred, val))

的less<int>()换成greater<int>()不就行了哈哈。

3.7 使用Map

  • 定义

一对数值,包括key和value,key通常是字符串。任何一个key在map内就一份,多了要用multimap。

  • 实例
#include<map>
#include<string>
map<string,int> words;
//输入方式
words["happy"]=1;
string tword;
while(cin<<tword)
    words[tword]++;

words[key]会取出与key对应的value,如果key不在map内,这个key会被自动加入map中,相应的value被设置为所属类型的默认值。

  • 成员函数
  1. first,对应key
  2. second,对应value
map<string,int>::iterator it = words.begin();
for(; it != words.end(); ++it)
    cout<<"key: "<<it->first<<" value: "<<it->second<<endl;
  • 查询map内是否存在某个key

1.把key当索引用(缺点是会把key加进map里)

int count = 0;
if(!(count=words[key]))
    //...key不在map里

2.把key传入map的find()函数并调用

int count = 0;
map<string,int>::iterator it = words.find("what");
if(it != words.end())
    count = it->second;

3.map的count()函数会返回key在map内的个数

int count = 0; 
if(words.count("what"))//存在
    count=words["what"];

3.8 使用Set

  • 定义

set由一群key组成,可以判断某值是否在某集合中。

  • 实例

在图形遍历算法中可以用set储存每个遍历过的节点,移至下一点前可以先查询set,判断该节点是否已经遍历过。

//定义一个用来记录“排除字眼”的string类型set:
#include<set>
#include<string>
set<string> word_exclusion;
//将某个单字放入map前,先检查是否存在于word_exclusion set中
while(cin>>tword)
{
    if(word_exclusion.count(tword))
        //tword被排除了嘤嘤嘤,跳过这次迭代
        continue;//重新对执行条件求值并开始另一次迭代
    words[tword]++;
}
  • 初始化

默认情形下,set元素依据其所属类型默认的从小到大次序排列。

int ia[6]={1, 5, 8, 2, 9, 1};
vector<int> vec(ia,ia+6);
set<int> iset(vec.begin(), vec.end());

iset的元素将会是{1, 5, 8}。

  • 几个操作函数

1.insert():为set加入单一元素和为set加入某个范围的元素。

iset.insert(ival);
iset.insert(vec.begin(), vec.end());

2.iterator操作

set<int>::iterator it = iset.begin();
for(;it != iset.end(); ++it)
    cout<<*it<<' ';
cout<<endl;

3.9 如何使用Iterator Inserter

3.6节对filter()的实现用到了赋值(assign)操作,将容器中符合要求的元素一一赋值至另一个容器。标准库提供了三个insertion adapter,这些adapter让我们可以避免使用assign运算符,但是不能用于array上。使用前添加头文件

#include<iterator>
  • back_inserter()
  1. 以容器的push_back()函数取代assignment运算符

  2. 适合vector

  3. 传入参数为容器本身

  • inserter()

  1. 以容器的insert()函数取代

  2. 适用于vector

  3. 接受两个参数,一个是容器,另一个是iterator

  • front_inserter()

  1. 以容器的oush_front()函数取代

  2. 适用于list和deque

int main()
{
	int ia2[elem_size];
	vector<int> ivec2;//没有定义容器大小哦

	cout << "filtering integer vector for values less than 10\n";
	filter(ivec.begin(),ivec.end(),back_insert(ivec2),elem_size,greater<int>());
}

3.10 使用iostream Iterator

  • 分类

istream_iterator和ostream_iterator,分别支持单一类型的元素读取和写入。使用前包含头文件

#include<iterator>
  • 定义

//将is定义为一个“绑至标准输入设备”的istream_iterator
istream_iterator<string> is(cin);
//不指定istream对象便代表了last itertator,标示“要读取的最后一个元素的下一个位置”
istream_iterator<string> eof;
//将os定义为一个“绑至标准输出设备”ostream_iterator,输出字符串之间以空格分隔
ostream_iterator<string> os(cout, " ")
  • 调用
copy(is, eof, back_inserter(text));
copy(text.begin(), text.end(), os);

从文件读入,输出到文件:

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<fstream>
#include<iterator>
using namespace std;
int main()
{
	ifstream in_file("input_file.txt");
	ofstream out_file("output_file.txt");
	if (!in_file || !out_file)
	{
		cerr << "FBI warning\n";
		return -1;
	}
	istream_iterator<string> is(in_file);
	istream_iterator<string> eof;
	vector<string> text;
	copy(is, eof, back_inserter(text));
	sort(text.begin(), text.end());
	ostream_iterator<string> os(out_file, " ");
	copy(text.begin(), text.end(), os);
	system("pause");
	return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值