前言
"打牢基础,万事不愁" .C++的基础语法的学习
引入
序列容器的学习.以<C++ Prime Plus> 6th Edition(以下称"本书")内容理解
本书中容器内容不多只有几页.最好是有数据结构方面的知识积累,如果没有在学的同时补上.
序列容器回顾
C++基础语法:STL之容器(1)--容器概述和序列概述-CSDN博客
序列容器内元素按严格线性顺序排列,至少是正向迭代器(含以上).序列容器包括deque(双端队列),forward_list(单链表),list(双向链表),queue(队列),priority_queue(优先队列),stack(栈),vector(动态数组),array(替代数组的容器)
容器的内容非常重要,但不在api如何调用,而在于对数据结构的认识.程序本质上就做了两件事,处理数据和数据交互(CPU和内存,内存和硬件之间).数据结构除了表示某个类型的数据集合,也是在计算机中的存储方式.对软件性能有较大的影响.不过也不用担心,STL容器提供了方便的使用,即使理解不那么深,也一样可以写程序
数据结构概览
数据结构加上算法等于数据集合.算法操作包括基本的增加集合元素,删除集合元素,查找集合元素(可选),加上其他想实现的算法.
学习数据结构,最好在脑子里形成一个画面,关于数据添加,数据删除,和查询,从图中反映出来
分析容器时,如果没有源码做参照,理解起来很吃力.但又没有现成代码,所以下列代码完全是揣测.
注意:下列代码为了练手,试图重现逻辑,不保证准确.
vector容器
特点:物理容器是数组,逻辑上是动态变化的数组
本书中vector描述
第1部分:前面介绍了多个使用vector模板的例子,该模板是在vector头文件中声明的。简单地说,vector是数组的一种类表示,它提供了自动内存管理功能,可以动态地改变vector对象的长度,并随着元素的添加和删除而增大和缩小。它提供了对元素的随机访问。在尾部添加和删除元素的时间是固定的,但在头部或中间插入和删除元素的复杂度为线性时间.(本段黑体字是本书原话)
部分解读和代码:
----在vector头文件中声明,自动内存管理
1.建立vector容器,假设数据类型为T
template<class T> //声明容器类
class vector{
int n; //n表示vector内元素数目
T* head; //指针指向动态数组
public:
vector(); //构造函数
void push_back(T val); //尾部添加元素
}
template<class T>
vector::vector(){
n=1; //初始化时内含1个元素
/*这里是硬编码,编程要尽力避免这种情况*/
head=new T[1]; //申请1个元素的内存空间,n=0也不会报错
}
初始个数为1,但里面不放数据,和链表里的"头结点"类似
2.尾部添加元素
void push_back(T val){
n++; //索引先加1;
T *newVector=new T[n]; //再次申请一段内存空间
/*数组首个元素始终被空置*/
while(n>2){ //除了插入第一个元素外的其他情况
for(int i=1;i<n-1;i++){
newVector[i]=head[i]; //复制旧数组head到新数组newVector当中
}
break; //for循环完毕后跳出while
}
newVector[n-1]=val; //新元素插入到数组末尾,插入第一个元素时,n=2
T* tmp=head; //标识原来的数组
head=newVector; //把添加完数据的数组作为新的vector容器
delete[] tmp; //删除原来数组head
}
代码思路:
1>插入元素:
申请一块内存空间,把索引值加1.新数组末尾的索引值是n-1;其他数据(索引从1到<n-1)的部分对原数组数据进行复制. 当第一次插入的时候发现for循环不符,单独用while写一段.数组首个元素--索引为0的那个始终空置,没有初始化,也从不访问.
2>删除原来的物理容器:
和链表一样,先用tmp做标识,指向他所在的数据块,然后将新的容器建立,最后删除tmp.
3.删除元素(单个)
序列提供的api中有个erase(p)的算法,以传入迭代器为参数
思路:生成一个新容器,删掉迭代器指向的数据,并返回新数组.
如果以索引为参数容易写.可以参照C语言的"动态数组",以迭代器为参数也差不多,因为没有迭代器转化索引的api,就不展开了.
4.随机访问
运算符重载operator[](int order){}; 这个比较基础.本书P380第11章"使用类"
5.在尾部添加和删除元素的时间是固定的,但在头部或中间插入和删除元素的复杂度为线性时间
表16.7序列的要求里有insert函数和erase函数,也是以迭代器为参数的,在头部或中间插入和删除,要访问vector容器中所有元素,所以复杂度为线性时间.
第2部分:除序列外,vector还是可反转容器(reversible container)概念的模型。这增加了两个类方法:rbegin( )和rend( ),前者返回一个指向反转序列的第一个元素的迭代器,后者返回反转序列的超尾迭代器
部分解读和代码:
这里又有个可反转容器的概念,他和迭代器一样属于概念上的设计.
回顾迭代器,有了迭代器的概念后,设计接口api
/*伪代码,object是容器类对象,it为迭代器iterator对象*/
/*用途:遍历容器对象的数据*/
for(it=object.begin();it!=object.end();it++){
cout<<*it<<endl;
}
以此为依据,设计迭代器类和迭代器对象函数it++,*it,及容器类函数begin()和end()
迭代器的设计流程:概念→接口设计→内容设计.
可反转容器的接口在本书P698页
可反转容器的目的是反向遍历容器对象,可以在迭代器类的基础上做.例如:rbegin()函数返回超尾迭代器-1;rend()函数返回begin()迭代器.运算符重载operator--(),修改迭代器属性(指向容器类对象的指针).
注意:可反转容器需要--的操作,查表本书P690表16.4"迭代器的性能",推导出只有实现双向迭代器以上的容器,才能做可反转容器. 从这里理解在迭代器设计时,根据功能不同,划分迭代器的层次.
第3部分:vector模板类是最简单的序列类型,除非其他类型的特殊优点能够更好地满足程序的要求,否则应默认使用这种类型
关键词:非常好用的数据结构,默认使用类型,首选的数据结构类型.
小结
STL容器本身不难使用,关键是在学习使用过程中,理解设计者的设计理念