《Essential C++》学习笔记 第三章:泛型编程风格(一)


前言

一、泛型编程风格:

   学习过程中,我认为这里的"泛"指的是更"广泛",更“一般”的意思,具有更强的普适性,对于不同的数据类型的处理方式都可以用同一个函数解决,而不需要针对不同数据类型编写功能相同的函数,这依赖于第二章的template和泛型指针。
  这一章比较长,分两部分写:(一)对应的是3.1节到3.5节(二)对应的剩余的3.6节到3.10节。

二、指针VS泛型指针

2.1、指针的使用及其局限

  指针在对array进行处理时性能极佳。
  对于array:我们可以用一个指向这些容器的指针(point+i)表示第i+1个元素的地址。比如以下程序:

int main()
{ 
	int ia[7] = { 1,1,421,8,789,421,1 },*point_array=ia;
	cout << "数组:\n" << "第3个元素:*(point_array+2):" << *(point_array + 2)
		<< ",第二、第三个元素地址上的联系:" << point_array + 1
		<< ',' << point_array + 2;
}

  接下来会通过指针对vector的才做说明其局限性。虽然vector和array一样在地址上连续存放(如下图)
地址上连续
在不使用指针的情况下,显示第1、5个元素的方法如下:(当然还可以vec[0],vec[4],但我目的在于引出指针操作vector时的局限性)

int main()
{ 
	int ia[7] = { 1,1,421,8,789,421,1 };
	vector<int> vec(ia, ia + 7);
	cout << *vec.begin()<<endl<<*(vec.begin()+4);
}

运行后发现输出了1和789,由此推测vec.begin()返回了vec第一个元素的地址,然而用指针指向一个vector元素中的一个地址,很难获得预期的值。
在这里插入图片描述
如图所示,直观上,定义一个指针竟然无法指向该地址,更不用说指针的算术运算了。
  指针的局限性便在于此,对于vector、list、deque这样的容器操作起来很不方便,甚至会出错。

2.2、 泛型指针的定义和使用

泛型指针的定义
定义方法如上图:2.1节的"*“改为”::iterator"。下面测试泛型指针是否有与指针一样操作方法:
在这里插入图片描述

从上图可见,对于vector容器来说,可以用"+i"让泛型指针偏移到第i-1个元素;对于list容器来说,不能用"+i"实现偏移,但是可以用"++"来偏移到下一个元素。
  总结:目前学到的进度来说,泛型指针是针对容器的指针,这里所说的容器是指vector、list、deque等。之所以说是“泛型”,是因为它适用于多个容器,而不指定某一个;且对于不同容器,其操作方式较统一。

2.3、泛型函数=模板函数+泛型指针

  array需要使用指针,vector、list和deque使用泛型指针,这太不“泛型”了,那就整得更“泛”一点吧。
想象以下场景:有一个数组或者某一容器,需要进行一定的处理,比如排序或增删改查。数据类型是未知的,但实现的功能却是一样的。以查元素为例:

template<typename IteratorType, typename elemType> IteratorType
find_elem(IteratorType first, IteratorType last, const elemType& value)
{//first 和last是指向相同类型数据的指针,可能是array,list或vector
	//而value可能只是当中一个数据,所以类型不同,
	//find函数返回的是指向容器中有value值的地址
	for (; first != last; first++)
		if (*first == value)
			return first;

	cout << "Not elem equivalent" << value;
	return last;
}

这是一个泛用性很强的函数,不论是数组还是容器,数据类型是整型、浮点、字符、字符串都可以用这个函数,返回的是所需数值的地址。调用方法如下

int main()
{
	int array_1[6] = { 15,34,53,72,91,121 };
	vector<int> ivec(array_1, array_1 + 6);
	list<int> ilist(array_1, array_1 + 6);
	//数组*************************************
	int* pia;
	pia = find1(array_1, array_1 + 6, 15);
	cout << *pia<<endl;
	//vector************************************
	vector<int>::iterator p_vec;
	p_vec = find1(ivec.begin(), ivec.end(), 1024);
	if(p_vec!=ivec.end()) cout << *p_vec<<endl;
	else cout << "404 not found\n" ;
	//list**************************************
	list<int>::iterator p_list;
	p_list = find1(ilist.begin(), ilist.end(), 121);
	if (p_list != ilist.end())
		cout << *p_list << endl;
	else cout << "404 not found\n";
}

泛型函数就是这样一类泛用于各类容器的函数,C++自带这样的函数,总共超过60个。功能包括搜索、排序、复制、删除、替换等。书中附录B有使用例子。

三、容器的共通操作(非常重要)

各个容器都可以通过’.'运算法调出共同操作:最常用的是**.end()、.size()、.empty()、.find()**和.begin()。另外一个谨慎使用的.clear()。这些操作使用起来非常方便。
刚开始觉得没什么,写的比较简洁,其功能看名字也很明了,但后来发现这个太有用啦!

四、使用顺序性容器

顺序性容器存放的是排列有序。类型相同的元素
1.vector:以一块连续内存来存储元素。对于增删改查四种操作:“查、改”的时候由于地址是连续的,在初始地址加上一个数就找到了,实现起来非常高效;“增、删”在中间插入n个元素需要先把后面的所有元素都后移/前移n个位置再插入,效率不高。
2.list:一种双向链表,记录着前后元素的地址。“查、改”的时候需要从初始地址一个一个地循着下一节点找到对应位置,效率不高。“增、删”操作相对来说很方便。
3.deque:队列。和vector很像,删除最前和最后的元素效率比vector高。
  插入操作:
特殊的插入

  • 容器.push_back(值),加入一个值作为最后的元素
  • 容器.push_front(值),加入一个值作为第一个元素

一般的插入

  • insert(地址,值),在指定地址上插入值,返回插入的值的地址。
  • insert(地址,个数,值) ,以指定地址为起始地址,插入n个相同的值,无返回。
  • insert(地址,起始地址,结束地址),以指定地址为起始地址,插入【起始地址,结束地址】内的值,无返回。
  • inser(地址),在指定地址前一个地址上加入一个默认值。返回插入的值的地址。
    为了得到这个未知,一般配合find()函数得到对应地址。
      删除操作:
    特殊的删除
  • 容器.pop_back(值),删除最后一个元素
  • 容器.pop_front(值),删除第一个元素
    一般化的删除
  • erase(地址),删除指定地址上的值。
  • erase(首地址,尾地址),删除范围内的元素。

特别注意的是,list不能进行偏移运算,因此若容器为list,不能进行以下操作:

 ilist.erase(it1,it1+6);//删除
 //插入同理

而应该通过6次对泛型指针的’++'运算得到尾地址,再执行删除。

五、 使用泛型算法

需要#include 。具体不说了,就是调包,在附录B有使用例子。
后续补充:
泛型算法中有一个算法是find(),而容器共通操作中有一个.find,以下是两者的区别:
共同点:1.功能相同:都是"返回一个[First,Last]范围内值为value的地址,如果没有该值,则返回Last的地址"
2.不论是容器还是数组,都可以使用泛型算法find()
不同点:1.作用对象不同,如果能确定对象是一个容器(vector,deque,map,set),则可以直接用.find(),只有容器才能用.find(),数组不能用.find()。

这是一个血的教训:这里简单提一下,map是一个容器,可以存储一个“key”,一个“value”,并且可以通过“key”找到“value”,类似于成绩单。
我想找到map里面一个特定的值,倒腾了半天想用find(),结果出现下面的问题
在这里插入图片描述
好嘛,转过去一看,是泛型算法的源码,关键我还看不懂,不知道怎么对应修改参数。
然后我使用了容器的共通操作.find()
在这里插入图片描述
OK,通了。
那总结一下吧:如果确定是个容器,那首选用容器共通操作。
什么情况下用泛型算法呢?我认为在编写function的时候用最好,原因在于:我们并不知道用户会传来一个数组还是容器,如果是数组则无法用容器共通操作,只能用泛型算法,那如果写function的时候遇到我上面的情况怎么办呢?先再学习学习吧,我也没搞清楚。如果有大佬指点下就好了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值