《Essential C++》读书笔记 之 泛型编程风格

2014-07-07

3.1 指针的算术运算(The Arithmetic of Pointer)

新需求1

新需求2

新需求3

3.2 了解 Iterators(泛性指针)

3.3 所有容器的共通操作

3.6 如何设计一个泛性算法

Function Objects

Function Object Adapters

Standard Template Library(STL)主要由两种组件构成:

  • 一是容器(container),包括vector,list,set,map等类;
  • 另一种是用以操作这些容器类的所谓泛型算法(generic algorithm),包括find(),sort(),replace(),merge()等等。

容器概述

vector和list这两个容器是所谓的序列式容器(sequential container)。序列式容器会依次维护第一个元素、第二个元素......直到最后一个元素。我们在序列式容器上主要进行所谓的迭代(iterate)操作。

map和set这两种容器属于关联式容器(associative container)。关联式容器可以让我们快速寻找容器中的元素。

所谓map乃是一对对的key/value组合。key用于搜索,value要存储或取出数据。

所谓set,其中仅含有key。我们对它进行查询操作,为的是要判断某值是否存在其中。例如:我们想要建立一组索引表,用来记录新闻、故事中出现的字,我们希望一些中性字眼,如the,an,but排除掉。我们可以把这些中性字放在exclude_word的一个set中,每次放入某个字到索引表之前,查询一下exclude_word。若有,忽略;反之,加入。

泛型算法概述

所谓泛型算法,提供了许多可施行于容器及数组型别上的操作行为。这些算法之所以称为泛型(generic),因为它们和它们所要操作的元素型别无关。

泛型算法是通过function template技术,达成“与操作对象之型别相互独立”的目的。

3.1 指针的算术运算(The Arithmetic of Pointer)


返回

假设我们需要一个函数find()要完成以下任务。给定一个存储整数的vector,以及一个整数值。如果此值存在于vector内,返回一个指针指向该值;反之,返回0。

1 int *find(const vector<int>&vec, int value)
2 {
3     for(int ix=0;ix<vec.size();++ix)
4         if(vec[ix]==value)
5             retrun &vec[ix];
6     return 0;
7 }

View Code

新需求1:要求这个函数不仅可以处理整数,也可以处理任何型别。其实,这个任务其实要求我们将泛型算法find()以fuction template的形式呈现:

1 template <typename elemType>
2 elemType *find(const vector<elemType>&vec, const elemType &value)
3 {
4     for(int ix=0;ix<vec.size();++ix)
5         if(vec[ix]==value)
6             retrun &vec[ix];
7     return 0;
8 }

View Code

新需求2:要求这个函数可以处理vector和array内任意型别的元素。

这个任务难度较大,我们将它分割一下:

(1)将array的元素传入find(),而不指明该array;

(2)将将vector的元素传入find(),而不指明该vector。理想情况下,这两个问题的解法会包含我们最初问题的共同解法。

我们先看任务(1):首先要了解array是如何传入函数的,看如下代码:

int min(int *arrary){};

当数组被传递给函数,或者由函数返回时,仅有第一个元素的地址会被传递。

由于函数find()需要知道何时停止对array的读取。

解法之一是:增加一个参数,用来表示array的容量,声明如下:

template <typename elemType>
elemType *find(const elemType *arr, int size, const elemType &value);

解法之二是:传入另一个地址,指示array读取操作的终点。我们将此值成为“哨兵”。

template <typename elemType>
elemType *find(const elemType *arr, const elemType *sentinel, const elemType &value);

解法二最让人感兴趣的地方是:array从参数表中彻底消失了---这形同解决了小问题(1)

解法一实现:

 1 template <typename elemType>
 2 elemType *find(const elemType *arr, int size, const elemType &value)
 3 {
 4     if(!array||size<1)
 5         return 0;
 6     for(int ix=0;ix<size;++ix)
 7         if(arr[ix]==value)
 8             retrun &arr[ix];
 9     return 0;
10 }

View Code

上述代码中,虽然array是以第一个元素的指针传入find()中,但我们可以看到,仍然可以通过subscript(下标)运算符存取array的每个元素,就如同此arr是个对象(而非指针形式)一般。why?

因为事实上所谓的下标操作就是将array的起始值加上索引值,产生出某个元素的地址,然后该地址在被提领以返回元素值。例如:

arr[2];

会返回array的第三个元素。

*(arr+2)

也返回第三个元素的值。 这里的2,在指针运算中,要把“指针所指之型别”的容量大小考虑进去。若arr第一个元素为1000,那么arr+2的地址为1000+2*4。

解法二实现:

 1 template <typename elemType>
 2 elemType *find(const elemType *first, const elemType *last, const elemType &value)
 3 {
 4     if(!first||!last)
 5         return 0;
 6     for(;first!=last;++first)
 7         if(*first==value)
 8             retrun first;
 9     return 0;
10 }

View Code

调用解法二函数find()

    string sa[4]={"when","where","who","what"};string *ps=find(sa,sa+3,sa[3]);

再看任务(2):这个任务是说,无论vector元素类型为何,都能一一存取vector内的每个元素。因为vector和array相同,都是一一块连续内存存储所有元素,所以它们的处理方式相同。但是,切记:vector可以为空。

如果定义一个空的vector,执行是就会抛错,见如下代码:

    vector<string> vec;find(&vec[0],&vec[vec.size()-1],search_value);

我们可以封装一下“取用第一个元素的地址”,“取用最后一个元素的地址”

 1 template <typename elemType>
 2 inline elemType * begin(const vector<elemType> &vec)
 3 {
 4     return vec.empty()?0:&vec[0];
 5 }
 6 template <typename elemType>
 7 inline elemType * end(const vector<elemType> &vec)
 8 {
 9     return vec.empty()?0:&vec[vec.size()-1];
10 }
11
12 find(begin(vec),end(vec),search_value);

View Code

新需求3:令它也支持list类。

这又是一个难题。list也是一个容器。不同的是,list元素以一组指针相互链接(linked):向前(forward)指针用来寻址下一个(next)元素,回向(backward)指针用来寻址上一个(preceding)元素。

因此,指针的算术运算并不适用于list,因为指针的算术运算必须首先假设所有元素都存储在连续的空间里,然后才能根据当前指针,加上元素大小之后,指向下一个元素。

首先浮起的念头是,多写一份find()函数,使其有能力处理list对象。我们的理由是:array、vector、list的指针行为大相径庭,以至于无法以一个通用的语法来取得其下一个元素。

这样的说法错综复杂。对的部分是,他们的地层指针运行方式,就标准语法而言的确大不相同。错的部分是,我们不需要提供另一个find()函数来支持list。事实上,除了参数表之外,find()的实现内容一点也不需要改变。

解决这个问题的方法是,在底层指针的行为处理上提供一层抽象化机制,取代原来的“指针直接操作的“方式 。

3.2 了解Iterators(泛型指针)


返回

那么,如何对底层指针的操作实现抽象化呢?

  • 第一:需要一组对象,提供有如内建运算符(++, *, ==, !=)一般运算符,并允许我们只为这些运算符提供一份代码实现。我们可以利用c++的类机制来完成。
  • 第二:要设计一组iterator classes,让我们得以使用“和指针相同的语法”进行程序的撰写。

实现上述问题后,我们就可以得到方法find()代码:

 1 template <typename IteratorType, typename elemType>
 2 IteratorType find(IteratorType first, IteratorType last, const elemType &value)
 3 {
 4     if(!first||!last)
 5         return 0;
 6     for(;first!=last;++first)
 7         if(*first==value)
 8             retrun first;
 9     return 0;
10 }

View Code

这样,array、vector和list就都能调用它了:

 1     const int asize=8;
 2     int ia[asize]={1,1,2,3,5,8,13,21};
 3     vector<int> ivec(ia,ia+asize);
 4     list<int> ilist(ia,ia+asize);
 5
 6     int *pia=find(ia,ia+asize,1024);
 7
 8     vector<int>::iterator it;
 9     it=find(ivec.begin(),ivec.end(),1024);
10
11     list<int>::iterator iter;
12     iter=find(ilist.begin(),ilist.end(),1024);

View Code

3.3 所有容器的共通操作


返回

以下为容器类(以及string类)的共同操作:

  • equality(==)和inequality(!=)运算符,返回true或false。
  • assignment(=)运算符,将某个容器复制给另一个容器。
  • empty()会在容器无任何元素是返回true,否则返回false。
  • szie()传用容器内当前含有的元素数目。
  • clear()删除所有元素。

每个容器还提供如下函数:

  • begin()返回一个iterator,指向容器第一个元素。
  • end()返回一个iterator,指向容器的最后一个元素的下一个位置。
  • insert()将一个或某个范围内的元素安插到容器内
  • erase()将容器内的单一元素或某个范围内的元素删除。

3.6 如何设计一个泛性算法


返回

现有个函数,用户给一个整数vector,我们必须返回一个新的vector,其中内含原vector之中小于10的所有数值。它的代码如下:

1 vector<int> less_than(const vector<int> &vec,int less_tan_val)
2 {
3     vector<int> nvec;
4     for(int ix=0;ix<vec.size();++ix)
5         if(vec[ix]<less_tan_val)
6             nvec.push_back(vec[ix]);
7     return nvec;
8 }

View Code

新需求:这个函数允许用户指定不同的比较操作,如大于、小于等等。如何can能将“比较操作”参数化呢?

解法:以函数调用取代less-than运算符,加入第三个参数pred,用它来指定一个函数指针,下面是这个函数声明:

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 greater_than(int v1,int v2){return v1>v2?true:false;)

函数filter第一个版本的定义:

1 vector<int> filter_ver1(const vector<int> &vec, int filter_value, bool (*pred)(int,int))
2 {
3     vector<int> nvec;
4     for(int ix=0;ix<vec.size();++ix)
5         //调用pred所指函数比较vec[ix]和filter_value
6         if(pred(vec[ix],filter_value))
7             nvec.push_back(vec[ix]);
8     return nvec;
9 }

View Code

调用filter_ver1:

1     vector<int> big_vec;
2     int value;
3     //...填充big_vec和value
4     vector<int> lt_10=filter_ver1(big_vec,value,less_than);

View Code

Function Objects


返回

所谓function object,是某种class的实体对象,这类classes对function call运算符进行了重载操作,如此一来,可是function object被当成一般函数来使用。

function object实现出我们原本可能以独立函数加以定义的事物。但又何必如此呢?主要是为了效率。我们可以令call 运算符成为inline,因而消除“通过函数指针来调用函数”时需付出的额外代价。

标准程序库事先定义了一组function objects,分为算术运算(arithmetic)、关系(relational)、逻辑运算(logical)三大类。以下列表中的type会被替换为内建型别或class 型别:

算术运算:plus<type>,minus<type>,...

关系:less<type>,greater<type>,...

逻辑运算:分别对应与&&,||,!运算符:logical_and<type>,logical_or<type>,logical_not<type>

看如何使用function object:

1 //欲使用事先定义的function objects,首先得含入相关头文件
2 #include <functional>
3
4     //sort()会使用底部元素型别所供应的greater_than运算符,将匀速递减排序
5     sort(vec.begin(),vec.end(),greater<int>);

View Code

其中的: greate<int>{}会产生一个匿名的class template object,传给sort()。

Function Object Adapters


返回

fuction object less<type>期望外界传入两个值,如果第一个值小于第二个值就返回true。但在本例中,每个元素都必须和用户指定的数值进行比较。理想情况下,我们需要做的就是将less<type>转化为一个一元运算符。这可以通过“将其第二个参数绑定(bind)至用户指定的数值”而达成。这么一来,less<type>便会将每个元素拿出来一一与用户指定的数值比较。

标准程序库提供的adapter(配接器)便应此而生。

见使用了bind2nd adapter的函数filter_ver2:

 1 vector<int> filter_ver2(const vector<int> &vec, int filter_value, less<int> &lt)
 2 {
 3     vector<int> nvec;
 4     vector<int>::const_iterator iter=vec.begin();
 5
 6     //bind2nd(lt,val)会把val绑定于less<int>的第二个参数上,这样,less<int>会将每个iter和val比较。
 7     while((iter=find_if(iter,vec.end(),bind2nd(lt,val))))!=vec.end())
 8     {
 9             nvec.push_back(vec[ix]);
10             iter++;
11     }
12     return nvec;
13 }

View Code

接下来如何消除filter()与vector元素型别的相互馆来,以及filter()与vector容器类型的相依关联,意识filter更加泛型化呢?

为了消除它和容器类型间的相依性,我们传入一对iterator[first,last],并将在参数表中增加另一个iterator,用以指定从何处开始复制元素。见如下代码:

 1 template <typename InputIterator,typename OutputIterator, typename ElemType, typename Com>
 2 OutputIterator filter_ver1(InputIterator first, InputIterator last, OutputIterator at, const ElemType &val, Com pred)
 3 {
 4     while((first=find_if(first,last,bind2nd(pred,val))))!=last)
 5     {
 6             cout<<"found value: "<<*first;
 7             *at++=*first++;
 8     }
 9     return at;
10 }

View Code

转载于:https://www.cnblogs.com/Ming8006/p/3830078.html

《Essential C++》读书笔记 之 泛型编程风格相关推荐

  1. Essential C++读书笔记

    Essential C++读书笔记 一.C++编程基础 1.对象初始化(两种不同的初试化语法) int num_tries = 0, num_right = 0 int num_tries(0)//构 ...

  2. ESSENTIAL C++ 读书笔记

    title: ESSENTIAL C++ 读书笔记 date: 2016-05-27 12:46:36 categories: C++ tags: - C++ 1.条件语句 if条件语句,如果括号内的 ...

  3. 《EMCAScript6入门》读书笔记——24.编程风格

    转载于:https://www.cnblogs.com/zhengxj1991/p/9068808.html

  4. shader入门精要读书笔记40 素描风格的渲染

    总结下头文件: HLSLSupport.cginc - (自动包含) 为跨平台的着色器编译宏和定义提供帮助. UnityShaderVariables.cginc - (自动包含)常用的全局变量. U ...

  5. 《Essential C++》读书笔记 之 基于对象编程风格

    <Essential C++>读书笔记 之 基于对象编程风格 2014-07-13 4.1 如何实现一个class 4.2 什么是Constructors(构造函数)和Destructor ...

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

    文章目录 前言 一.泛型编程风格: 二.指针VS泛型指针 2.1.指针的使用及其局限 2.2. 泛型指针的定义和使用 2.3.泛型函数=模板函数+泛型指针 三.容器的共通操作(**非常重要**) 四. ...

  7. 《Essential C++ 中文版》 读书笔记及习题解答

    目录 前言 preface 第一章 C++编程基础 Basic C++ Programming 简介 读书笔记 1.1 如何撰写C++程序 1.2 对象的定义与初始化 1.3 撰写表达式 1.4 条件 ...

  8. 读书笔记 ——《系统程序员成长计划》篇1:代码风格

    说明:   本文章旨在总结备份.方便以后查询,由于是个人总结,如有不对,欢迎指正:另外,内容大部分来自网络.书籍.和各类手册,如若侵权请告知,马上删帖致歉.   QQ 群 号:513683159 [相 ...

  9. 《Effective C++》读书笔记(第一部分)

    有人说C++程序员可以分为两类,读过Effective C++的和没读过的.世界顶级C++大师Scott Meyers 成名之作的第三版的确当得起这样的评价. 本书并没有你告诉什么是C++语言,怎样使 ...

最新文章

  1. Asp.net高级程序设计之服务器控件(4)
  2. 基于 vue2 导航栏透明渐变
  3. Linux下,C++编程论坛题目抽取
  4. 乱码 转ios_王者荣耀:转区数量提升25倍,教你解决检测异常,IOS也有艾琳了
  5. 缓存设计方案 你了解吗 SpringBoot 快速集成实现一级缓存Redis和二级缓存Caffeine 可自定义扩展
  6. 配置数据库连接池的时候。
  7. layui upload.render上传组件js动态添加html后再次渲染
  8. sql server 中DateName()函数及DatePart()函数
  9. 10.11 noip模拟试题
  10. Spring Cloud Ribbon客户端负载均衡(学习总结)
  11. 计算机硬件及组成原理pdf百度云,计算机组成原理整理版本.pdf
  12. 超强进程结束命令NTSD
  13. 电脑突然重启解决方案
  14. C措辞教程第四章: 数组(8)
  15. Linux执行命令常见的英语语句
  16. Android开发,实现摇一摇功能
  17. [ESP][驱动]ST7701S RGB屏幕驱动
  18. Android美化插件,Android控件美化Shape
  19. c++实训 选猴子当大王
  20. 培养青少年学习少儿编程

热门文章

  1. 智能实验室-杀马(Defendio) 4.16.0.840
  2. 字符串charAt()
  3. Android开发:关于WebView
  4. android 开发 获取各种intent (图片、apk文件、excel、pdf等文件)
  5. 类型参数的约束(C# 编程指南)
  6. JavaMail的体系结构及发送复杂邮件
  7. fork/join 并行编程
  8. 【166期推荐】医院中电脑耗材采购该不该归信息科负责?
  9. Exchange动态同步中的INTERNET_29错误代码
  10. D3.js 教程: 使用 JavaScript 创建可交互的柱状图