当前课程知识点:基于Linux的C++ > 第十一讲 泛型编程 > 11.10 泛型编程实践(二) > LinuxCPP1110
我们来看这样一个例子
使用指针作为迭代器
你看它是怎么工作的
我们调用标准库里面的 find() 那个函数
来查找一个数组中的特定的元素
具体程序代码是这个样子
定义一个 size 这样一个常量
设它为 16
然后我开辟这样一个数组 a[size]
就表示 16 个元素的一个整数数组
我 for 循环把这个数组设定个值
然后我想查 key 这个值
在不在我们这个数组里
key 值一开始初始化成 7
find() 这个标准库里边的函数
它需要带三个参数
第一个参数是它的数组的首元素的基地址
第二个参数是这个数组的最后一个元素的
后一个元素的基地址
当然你说后一个元素不妥当
其实是这个数组的最后一个元素的
下一个存储位置的基地址
第三个就是你要查找的那个数据
这就是 find()
当然严格讲起来 find() 这个函数
它的前两个参数其实传的
当然不是数组的元素的基地址
而是两个迭代器
我们这里就使用这个指针作为迭代器
所以 find() 第一个参数传数组的基地址 写a
第二个参数传数组的最后一个元素的
下一个位置的地址编号
那个当然是 a+size
为啥 因为我们数组的
最后一个元素是 size-1 啊
0 号元开始嘛 所以过了 size-1
就是最后一个元素之后
那个存储空间的位置
才是 a 加size 我们传的是这个
这叫过尾元
同学们在使用标准库的时候
要特别注意这一条
它的所有的这样的迭代器
这样的容器元素的存储
都是使用过尾元的机制
这样的话 它的整个循环架构
它是一个半开半闭区间
左边是封闭的 右边是开的
所以有一个过尾的元素
它专门用它来判定这个数据的集
也就是我们容器中的元素是不是结束了的
你说为什么要这么去做呢
我们用结尾的方式不可以吗 可以是可以
但是有的时候有点小麻烦
更重要地 有的时候
我们可能想存储一个指针
我们没有办法用空指针
来作为这个容器的结尾标志
因为有的时候空指针也会作为元素存进去
它本身是有意义的
所以在这个时候
我们要想决定这个容器是不是结尾
要想判定这一点
那么使用过尾元其实是更方便的
除此之外 你就得定义一个无定义指针
来表示它的结尾标志 或者结尾指针
那还不如使用过尾元呢
这个模式操纵起来其实更方便
我们把 find() 这个函数返回值初始化给 ip
就是一个指向整数的指针
当 ip 结果是 a+size 的时候
也就是当 ip 指向这个过尾元的时候
说明我们没有找到对应的元素
因为如果找到了
find() 就会返回它所对应的
那个元素的基地址
所以你注意 find() 这个函数返回值
其实也是一个迭代器
它的返回值就是你要查找的
那个元素的基地址
所以如果 ip==a+size 说明我们没找到
我们输出这个信息就行了
否则 我们就输出我们找到了这个信息
指针作为迭代器
你看到我们调用 find() 函数
就是按照这样的模式进行工作的
接下来我们看向量如何工作
我们现在使用迭代器来操作向量
我们定义一个向量 这个例子里
我这个向量元素个数 我设成 10
vector〈int〉 iv(10)
10 个元素 还是初始设定
一个元素、一个元素给它赋上值
然后我们查找 key
key 值 仍然是 7
然后我们要定义 3 个迭代器变量
那个迭代器的类型叫什么呢
叫 vector〈int〉::iterator
这是向量迭代器的完整的类型的描述
这个类型是定义在 vector 那个类模板里面的
所以你要使用它的时候必须要用名解析
因为我们使用 int
是来体化我们这个向量模板的
所以这个类名是 vector〈int〉
解析“::iterator”
我们得到的就是这个迭代器
这就是完整的迭代器类型
在这个类型上 我定义变量 it
定义变量 head
把它初始化成 iv.begin()
定义变量 tail
把它初始化成 iv.end()
在向量这个类上
它提供了 begin() 和 end() 两个成员函数
begin() 获得这个向量的首元素的迭代器
end() 获得这个向量的过尾元的迭代器
这两个函数返回值都是迭代器
你就可以直接初始化为 head 和 tail
赋值也可以 就按照这个模式
然后我们调用 find()
传 head 传 tail 传 key
传指向零号元的迭代器
传指向过尾元的迭代器
传待查找的键值 key
返回值就是找没找到那个元素的时候
它返回那个迭代器
找到了返回找到元素的迭代器
没找着返回过尾元的对应的迭代器
所以我们把 find() 这个返回值赋值给 it
if( it != tail ) 说明我们找到了
否则我就没找着
做相应的处理就可以了
这个就是我们的向量迭代器
你看它和指针作为迭代器使用的方式
是不是非常非常类似
指针我们需要定义 int * ip 迭代器呢
我们需要定义 vector〈int〉::iterator it
必须按照这个方式来定义
从实现这个角度来讲
除了这两者之间的差别
其实其它(包括原理)都是类似的 对吧
这个就是标准模板库最强大的功能
它能够让你与指针一样的方式
来使用标准模板库里面的标准容器
这个是我们向量迭代器
当你不想通过这个迭代器
修改目标数据对象的值的时候
你定义这个迭代器
应该把它定义成常量
这个就叫常迭代器 或者叫迭代器常量
其实严格讲起来应该叫迭代器常量
你比如说讲 你可以这么定义:
const vector〈int〉::iterator it
那么在这个时候你想 *it 赋值为 10 就是错的
*it 可以引领 没错 这个没问题
但是因为 *it 本身是一个 const vector〈int〉出来的
它是一个 const
它的目标数据对象是个 const 是个常量
所以你可以引领
但是你不能把它赋值为另外一个值
这是不允许的
引领赋值给别人没问题
但是引领 要想赋值它就不成
这个就是常迭代器
迭代器可以和我们输入输出流一起工作
这个时候我们事实上
是将输入输出流作为容器的
听上去有点奇怪
但是输入输出流本身确实可以作为容器
因为它能够容纳数据
凡是能够容纳数据的
我们都可以称之为容器
它是按照这样的一个模式
那么在使用方式上
你需要定义流迭代器对象
你比如讲用 ostream_iterator〈int〉
可以用这个类来定义
一个输出流迭代器 oit(cout, "")
什么意思呢 这个意思就是
我要构造一个输出流迭代器
并且把这个输出流迭代器
和 cout 给挂接在一起
也就是说 我是把 cout 作为我们的输出流
每输出一个数据
中间都要用“”引起来的空格来分隔
这是我们输出流迭代器对象 oit
第二个例子是输入流
我们从 cin 里想获取数据
那么你就可以用 istream_iterator〈int〉
这个类来构造一个对象 iit( cin )
把它和 cin 挂接在一起
这样的话 这个数据流就是从 cin 里面来的
可以使用一个空指针创建流结束迭代器
什么意思呢
就是你可以构造一个不带参数的输入流的对象和输出流的对象
就是你可以构造一个不带参数的输入流的对象和输出流的对象
用它来作为流结束的标志
你比如说 istream_iterator〈int〉 iit
后面没有“()”
它就没有使用带参数的构造函数版本
它就使用无参数的构造版本
来构造一个空对象
它实际上就类似于空指针
我们就可以用它来代表
这个流结束的迭代器
相当于刚才那个向量迭代器的过尾元
需要说明的是
凡是可以出现迭代器参数的标准算法里
都可以使用我们的流迭代器
所以实际上标准算法和
我们输入输出流可以直接挂接的
操作起来肯定很方便
我们先看输出流迭代器的例子
在这里 我定义了两个常量
upper_bound 和 lower_bound
用来表达一个整数的范围
还是我们以前演示过的一个例子
我就随机生成一系列的整数
然后把它保存到向量里
然后我们对它进行输入输出
我们也可能是从输入流里直接获取数据
放到这个向量里 元素尺寸是 8 个
我实现了一个函数 Display()
它需要带两个参数
第一个参数是向量 第二个参数是字符串
我们首先输出这个字符串
然后我们定义一个整型向量的迭代器 head
然后再定义一个尾部迭代器 tail
head、tail 定义两个整型向量的迭代器
然后我们定义一个 oit 变量
让我们的输出流迭代器和 cout 输出流挂接在一起
让我们的输出流迭代器和 cout 输出流挂接在一起
中间用“;”分隔开
然后我们调用标准算法 copy() 将向量里的元素
一个接着一个地拷贝到我们的输出流里
每拷贝一个 插入一个分号
每拷贝一个 插入一个分号
就按照这个方式进行工作
你看 copy() 这个函数 它需要带三个参数
第一个是头指针 也就是头迭代器
第二个是尾迭代器
第三个是输出流迭代器
也就是说 copy() 这个函数
带三个参数全是迭代器
首元素在哪里 过尾元在哪里
这是前两个参数的意义
第三个参数就是这个流的目的地
想把这些元素拷哪去
指向它的原始位置 oit
指向它一开始的位置 就拷贝到这个位置
第二个元素接着继续往下拷贝
就按照这个方式
copy() 一个函数调用
就完成了所有的元素的拷贝
非常快吧 对于我们这道题来讲呢
它尤其特殊
因为我们输出流迭代器实际上
是和 cout 挂接在一起的
所以我们的数据向输出流里面一拷贝
结果就是把这个数据输出到屏幕上
拷贝一个 输出一个
拷贝一个 输出一个
拷贝一个 输出一个
中间是用逗号分隔开的
就按这个方式进行工作 很方便
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口