C++ = C + 面向对象 + 泛型编程 + STL
26.STL容器
STL(标准模板库),它其中包含了:容器、迭代器、算法、空间配置器、配接器、仿函数六个部分,这里介绍一些容器以及几个简单算法的使用。
1.容器
Vector
一、理论:
- 扩充空间(不论多大)都应该这样做:
(1)配置一块新空间
(2)将旧元素一一搬往新址
(3)把原来的空间释放还给系统 - vector 的数据安排以及操作方式,与array 非常相似。两者的唯一差别在于空间的利用的灵活性。Array 的扩充空间要程序员自己来写。
- vector内部实现基于数组,所以它空间连续,可以通过++偏移,通过[下标]来存取,值得注意的是,系统提供了push_back()函数但是没有push_front()函数,但是,既然选择了使用这种容器,就尽量避免使用push_back()来添加元素,而应该使用下标存取
二、实际应用:
- Vector 的数据成员:
Protected:
iterator start; //表示目前使用空间的头
iterator finish; //表示目前使用空间的尾
iterator end_of_storage; //表示目前可用空间的尾
(注意这几个数据成员在外部不可以使用,他们是protected , 所以只能通过函数得到他们)
Public:
iterator //vector 的迭代器(是普通指针)
- Vector的重要成员函数
Public:
iterator begin(); //返回start
iterator end(); //返回 end
reference front(); //返回首元素的引用
reference back(); //返回尾元素的引用
size_type size(); //返回使用空间的大小
size_type capacity(); //返回容量的大小
void push_back(const T& x); //将元素插入到最尾端
pop_back(); //尾删除
(注意当用push_back 向vector 尾部加元素的时候,如果当前的空间不足,vector 会重新申请空间,这次申请的空间是原来的空间大小的1.5倍,也就是新的可用空间将增加为原来的1.5倍,(vs2005之前是两倍))
void pop_back(); //将最尾端的元素取出
(注意当用pop_back 删除尾部的元素时,vector 的capacity 是不会变化的)
iterator erase(iterator position) //清除某位置上的元素,vector的使用空间会减少
void insert(插入的位置,插入的数值) //在某个位置才插入多少个元素,vector 的使用空间会增加
void clear() //清除所有元素,vector 的使用空间减为零,可用空间不变
注:Iterator find(起始位置,结束位置, 查找的元素值) //找到指定的值的位置,他不是vector 的成员而是一个c++ 提供的函数
List
一 、理论:
STL list 是一个双向链表,迭代器具备前移和后移的能力。list 有一个重要的性质:插入操作和结合操作会造成原有的迭代器失效。应用时应该用<list>
二、实际应用:
- 以下操作同vector
list<int> lst;
lst.push_back(0);
lst.begin();
lst.end();
lst.insert();
lst.erase();
lst.clear();
- 以下为list 的特有操作
void push_front(const T &x); //插入一个结点,作为头结点
void push_back(const T &x); //插入一个结点,作为尾结点
void pop_front(); //移除头结点
void pop_back(); //移除尾结点
void remove(const T&value); //将数值为value的所有元素移除
void unique(); //将“连续而相同的元素”移除只剩一个
void splice(iterator position, list &x); //将x 结合于position所指位置之前。X 必须不同于*this
void splice(iterator position, list&, iterator i); //将x链表中的i所指的元素结合于position所指位置之前。Position 和 i 可指向同一个list
void splice(iterator position, list&, iterator first, iterator lsat); //将[first, last)内的所有元素结合于 position 所指的位置之前,position和[first, last)可指向同一个list,但是position 不能位于[first, last)之内。
void merge(list& x); //将x合并到*this 身上。两个lists 的内容都必须先经过递增排序。
void reverse(); //将*this 的内容逆向重置
void sort(); //将list 的元素进行升序排序
- 反向迭代器
list<int>::reverse_iterator rev_ite =lst.rbegin();
rbegin(); //链表反向的头
rend(); //链表反向的尾
注意:当使用反向迭代器遍历到某一个元素之后如果需要使用之前介绍的函数去删除或者对其进行其他操作,需要将反向迭代器转换为正向迭代器,因为那些函数都是针对正向迭代器设计,传入反向迭代器的时候就会发生错误
rev_ite.base(); //将反向迭代器转换为正向迭代器
注意:反向迭代器的头转换为正向迭代器应该是正向迭代器的尾,所以两者之间使用.base()转换之后会存在一个位置的偏移,这在编写程序的时候应该被注意,否则会出现非预期的结果
Stack
一、理论:
stack只允许栈顶添加,栈顶删除,没有提供迭代器
二、实际应用
push() //压栈
pop() //删除栈顶
top() //获取栈顶元素
empty() //判断栈是否为空
Queue
一、理论
queue只允许队尾添加队头删除,无迭代器
二、实际应用
push() //入队
front() //队头装的东西
back() //队尾装的东西
pop() //出队
empty() //判断队列是否为空
Deque
一、理论:
- deque 是一种双向开口的连续性空间。可以在头尾两端分别做元素的插入和删除操作。Vector 从技术观点也可以在头尾两端进行插入和删除操作,但是其头部操作的效率很低。
- deque 没有容量的观念。它是动态以分段连续空间组合而成,随时可以增加一块更大的空间,然后复制元素。
- deque 的迭代器不是普通的指针,其复杂度比vector 复杂的多。除非必要,我们应该尽量选择使用vector 而非 deque。
- deque 由一段一段的定量连续空间构成。一旦有必要在deque 的前端和尾端增加新空间,便配置一段定量连续空间,串接在整个deque 的头端或尾端。
- 可以使用下标的操作
- 应用时应该加
<deque>
二. 实际应用:
deque 的其他操作基本同vector
void push_back();
void push_front();
void pop_back();
void pop_front();
void clear();
void erase();
void insert();
void resize(); //重新设置deque的长度大小
Map
一、理论:
- map称为映射表,map 的特性是,所有元素都会根据元素的键值自动被排序,map的所有元素都是pair,同时拥有实值(value)和键值(key)。Pair的第一元素被视为键值,第二元素被视为实值。Map 不允许两个元素拥有相同的键值。
- map 的键值关系到map的元素的排列规则,任意改变map元素键值将严重破坏map的组织。所以不可以通过map 的迭代器来改变map 的键值。但是可以通过迭代器来修改元素的实值。
二、实际应用
- 使用map 时首先包括头文件:
#include<map>
- 使用时应该加上宏:
#pragma warning
(disable:4786) 来去除警告 - 构造函数:
map<string,int> simap; //第一个参数是键值、第二个参数是实值
iterator find(键值); //它是map 的成员函数,用来找指定键值map的迭代器
pair<string,int> pairTemp(string(“A”),5); //pair的构造函数
iterator insert(iterator position, pairTemp); //将pairTemp 插入到map 中,迭代器不用传了,因为map会自动按照键值排序
void erase(iterator position); //删除指定位置上的 map 元素
size_type count(键值); //判断该键值的Map 元素是否存在(本来是用来计数的,但是键值不能重复,所以就是判断有无)
size_type size(); //返回map 中的元素的个数
iterator lower_bound(键值); //返回该键值或者大于该键值的map 的迭代器,如果找到有这个键值,就返回该键值的迭代器,如果没找到,就返回该键值的下一个键值的迭代器
iterator upper_bound(键值); //返回大于该键值的map 的迭代器,与第10条相比的区别是,无论有没有该键值,返回的都是下一个键值对应的迭代器
Hash_map
一、理论
内部结构是hash_table,查找效率是o(1)
二、实际使用
与map的使用基本相同,定义一个hash_map的语法如下:
hash_map<char,int> 变量名; //定义一个hash_map
Set
一、理论:
- Set称为集合,Set的特性是,所有元素都会根据元素的键值自动被排序,Set 的元素不像Map那样可以同时拥有实值和键值,Set 元素的键值就是实值,实值就是键值。Set 不允许两个元素有相同的键值。
- 因为Set 元素值就是其键值,关系到 Set 元素的排列规则。如果任意改变Set 的元素值,会严重的破坏Set组织。
三、实际应用:
- 初始化
可以定义一个数组,将数组直接赋值给Set的元素。这时把数组中的重复元素删除。
int a[5]={1,2,3,4,5};
set<int> iset(a,a + 5);
insert(要插入的值);
erase(要删除的值);
find(要查找的值);
count(要数的值); //返回该值的数值
clear();
lower_bound();
upper_bound();
2.算法
这里介绍几个常用的算法:
for_each(first,end,fun)
random_shuffle(first,end) //从first到end随机排列
- 需要加srand((unsigned int)time(0));
sort(first,end,Rule_fun) //从first到end排序,不提供规则的时候,默认按照从小到大排序
reverse(first,end) //翻转
count(first,end,n) //从first到end统计n的个数
在帮助文档里面搜关键字algorithm functons查看已封装的算法
27.文件操作
C语言读写文件:
这一部分继承自C语言的对于文件的读写操作
文件指针
打开文件
写入文件
读取文件
关闭文件
写入位置
C++ 文件和流:
之前我们使用头文件<iostream>
可以通过cout和cin对缓冲区读取流和写入流。
这里介绍如何从文件读取流和向文件写入流。这需要用到 C++ 中另一个标准库 fstream,它定义了三个新的数据类型:
数据类型 | 描述 |
---|---|
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息。 |
fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。 |
要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件 <iostream>
和 <fstream>
。
打开文件
在从文件读取信息或者向文件写入信息之前,必须先打开文件。ofstream 和 fstream 对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream 对象。
下面是 open() 函数的标准语法,open() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。
void open(const char *filename, ios::openmode mode);
参数:
- filename 文件名
- mode 打开模式,可以是以下这些值
模式标志 | 描述 |
---|---|
ios::app | 追加模式。所有写入都追加到文件末尾。 |
ios::ate | 文件打开后定位到文件末尾。 |
ios::in | 打开文件用于读取。 |
ios::out | 打开文件用于写入。 |
ios::trunc | 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。 |
以上两种或两种以上的模式可以结合使用。例如:
ofstream outfile;
outfile.open("file.txt", ios::out | ios::trunc );
关闭文件
当 C++ 程序终止时,它会自动关闭刷新所有流,释放所有分配的内存,并关闭所有打开的文件。但程序员应该养成一个好习惯,在程序终止前关闭所有打开的文件。
下面是 close() 函数的标准语法,close() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。
void close();
写入文件
在 C++ 编程中,我们使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用的是 ofstream 或 fstream 对象,而不是 cout 对象。
读取文件
在 C++ 编程中,我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是 ifstream 或 fstream 对象,而不是 cin 对象。
读取 & 写入实例
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
//写入文件
ofstream outFile;
outFile.open("./file/testfile.txt",ios::out);
if (!outFile.bad())
{
outFile << "这里开始写入内容" << endl;
outFile.close();
}
//读取文件
ifstream inFile;
inFile.open("./file/testfile.txt",ios::in);
if (!inFile.bad())
{
cout << inFile.rdbuf(); //将文件内容输出到控制台上
inFile.close();
}
system("pause");
return 0;
}
文件位置指针
istream 和 ostream 都提供了用于重新定位文件位置指针的成员函数。这些成员函数包括关于 istream 的 seekg("seek get")
和关于 ostream 的 seekp("seek put")
。
seekg 和 seekp 的参数通常是一个长整型。第二个参数可以用于指定查找方向。查找方向可以是 ios::beg
(默认的,从流的开头开始定位),也可以是 ios::cur
(从流的当前位置开始定位),也可以是 ios::end
(从流的末尾开始定位)。
文件位置指针是一个整数值,指定了从文件的起始位置到指针所在位置的字节数。下面是关于定位 "get" 文件位置指针的实例:
// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
28.动态创建对象
什么是动态创建对象:
在程序运行的过程中,根据上面运行的结果不同,下面创建不同类型的对象
动态创建对象的原理:
在这里#include <iostream>
#include <string>
#include <list>
using namespace std;
class AA{};
class BB{};
struct RuntimeObject
{
char* name;
void (*createObject)(); //函数指针
RuntimeObject* pNext;
};
void createBB()
{
BB* p = new BB();
cout << "create BB" << endl;
}
void createAA()
{
AA* p = new AA();
cout << "create AA" << endl;
}
RuntimeObject RuntimeAA = {"AA",&createAA,NULL};
RuntimeObject RuntimeBB = {"BB",&createBB,&RuntimeAA};
void RuntimeCreateObject(const char* className)
{
RuntimeObject* temp = &RuntimeBB;
while(temp)
{
if(strcmp(temp->name,className) == 0)
{
temp->createObject();
return;
}
temp = temp->pNext;
}
}
int main()
{
char className[20] = {0};
cin >> className;
RuntimeCreateObject(className);
system("pause");
return 0;
}
将现有的类每种类对应的一个对象通过链表保存,在程序运行过程中,根据输入的className的不同,遍历链表,使用链表中的函数指针调用相应的函数创建对应类型的对象
在后期添加其他类(比如class C)的时候只需要在链表中添加一个这个类的一个对象,然后编写相应的创建对象的函数即可,而不需要改变RuntimeCreateObject()函数
注意:这只是一种思路,代码的写法有很多,可以封装到类里面,实现高内聚低耦合
动态创建对象什么时候用?
程序在运行过程中需要根据运行结果创建不同的对象且需要创建的对象种类比较多的情况下,选用这种方式可以提高代码复用性。比如MFC的消息映射表就采用了动态创建对象这一设计思路。