STL
一、为什么要使用模板
1、普通的静态类型语言需要针对个体类型实现
C/C++是静态的编程语言(编译后才能执行),优点是安全(编译时会检查语法、数据类型),运行速度快(源代码会被释放成纯二进制执行文件),但这也成员编写代码的桎梏,它迫使我们要为每一种数据类型都实现一遍数据类型不同但算法几乎完全相同的算法(从抽象层面是一致)。
2、宏常量、宏函数解决类型不确定
宏常量:在编译时也必须确定一种类型,只是把类型的不确定推迟到预处理。
宏函数:不会进行类型检查安全性差,每一次的使用都会在代码段再次展开(效率可能会提高,但也会使用可执行文件增大、造成冗余),复杂的函数也不适合实现成宏函数。
函数、宏函数、外部函数、内部函数、成员函数、静态成员函数、友元函数。
3、泛型编程(自动类型推算)
不管什么类型的数据都可以调用、执行的代码。
C++管这种技术叫模板。
二、函数模板
声明模板代码:
template <typename 实参类型1,typename 实参类型2>
实参类型1 func(实参类型2 参数1)
{
实参类型2 变量;
}
在声明类模板时 typename <=> class
1、实例化
模板不是一个可以处理任何类型的单一的实体(函数、类),而是对任何类型都生成一个不同的实体。
模板根据参数的类型生成实体的过程叫实例化。
模板只有在使用时才会进行实例化,只需要使用模板就可以启动实例化过程(程序员不需要做额外的工作)。
在使用模板时所需要类型可以根据实参自动推导,可以在函数名与小括号之间添加对<类型>显示的提供类型。
2、二次编译
每次使用模板时都进行二次编译(检查代码本身、语法是否正确)。
注意只有在二次编译时才产生二进制指令,第一次的编译结果(对模板的编译)仅仅是在编译器内部形成一个用于描述函数模板的数据结构。
3、模板的隐式推断
1、可以根据实参自动推导类型。
2、不可以进行类型提升(除非显式的设置类型)。
4、普通函数与模板函数之间可以构成重载,在调用时优先选择普通函数,除非显示使用<>指定类型。
三、类模板
声明模板代码:
template <typename 实参类型1,typename 实参类型2>
class Test : public 实参类型1;
{
public:
Test(实参类型1 t);
实参类型1 a;
实参类型2 b;
};
1、类模板的使用
从模板到对象会经历两个过程:
编译期:编译器将类模板实例化为类并生成对象创建指令。
运行期:处理器执行对象创建指令,将类实例化为对象
类在设计完成后就是一种数据类型的,而类模板本身并不代码一个确定的类型,即不能用于创建对象,也不能用于定义指针或引用,只有经过实例化后才具有类型的语义。
模板类谁使用谁实例化,模板参数不支持隐匿推断。
2、静态成员
类模板的静态成员,既不是对象的一份也不是模板的一份,而是在该模板的每个实例化中都有一份,各自有一个独立的拷贝,且为该类的实例化对象所共有。
3、递归实例化
类模板的实例化参数可以是任意类型,只要提供的类型能够提供模板所需要的功能(是一种类型),类模板可以使用自己的实例化类来实例化自己(递归实例化),这种结构在逻辑上有一种二维表的特性。
4、类模板的特殊实例化
模板类一般情况都能雨露均粘,但也有一些特殊的运算符或运算过程有些类型不支持,比如:float、double 不能用在switch也不能使用%运算符,再比如char*类型就不能直接使用 一些运算符,还有一此自定义类(没重载运算符)。
类模板的特化就是对特殊类型实现一个特殊版本,有两种特化:
类的全特化:针一种特殊类型,所有类的成员函数全部重写。
template<typename T>
class Test
{
...
};
// 类的全特化
template<> class Test<char*>
{
...
}
类的成员特化:针对一种特殊类型,只重写某些成员。
template <typename T>
class Test
{
public:
void func(void)
{
....;
}
};
// 类的成员特化
template<> void Test<char*>::func(void)
{
...
};
5、对类模板的参数自行指定。
类型板在特化时可以指定特殊的参数,特化的越准确,优先级别就超高。
类的特化版本之间可能会实例化出同等的类,然后导致二义性错误。
当产生二义性时可以选择修改参数类型,也可以特化出一个更准确的版本。
6、类模板的参数也可以缺省,规则与函数的缺省参数一致。
7、类模拟的参数不可以进行隐式转换。
Test<10> t; //错误
func(100); //可以
8、类模板的参数也可以是一个数据(但只能是字面值常量)。
迭代器:是一种帮助数据结构遍历的一种内部指针(对象)。
容器<类型参数>::iterator it; // 定义代器对象
注意:使用迭代器虽然能够文件数据结构的遍历,但任何导致数据结构发生变化的行为都可能导致之前获取的迭偌器失效,重新开始初始化才是最安全的。
四种迭代器类型:
iterator 正向迭代器
const_iterator 常正向迭代器
reverse_iterator 反向迭代器
const_reverse_iterator 常反向迭代器
八个获取迭代器位置的函数:
begin() 正向迭代器的起始位置
begin()const 常正向迭代器的起始位置
end() 正向迭代器的结束位置(最后元素的下一个位置)
end()const 常正向迭代器的结束位置
rbegin() 反向迭代器的起始位置
rbegin()const 常反向迭代器的起始位置
rend() 反向迭代器的结束位置(最后元素的下一个位置)
rend()const 常反向迭代器的结束位置
容器:
以常见的数据结构为架构,以模板技术为主实现的能够存储任意类型的数据结构模板。
由于是以类模板的形式实现的,使用前需要先实例化。
向量(数组):
在C++当数组来使用,在使用过程中它会自动调整自己的长度,完全不用关心越界问题。
使用前导入vector头文件。
实例化:
vector<类型> 向量对象;
向量不包含任何元素,也不占用任何存储空间,但是向量本身还是要占用存储空间的。
vector<类型> 向量对象(初始化大小n);
向量中会存储n个类型的对象,基本类型会初始化为0,类型会自动调用无参构造。
vector<类型> 向量对象(初始化大小n,初始化值);
基本类型可以设置初始值,类对象可以调用有参构造。
vector<类型> 向量对象(起始地址,结束地址);
增
insert 在指定位置添加一个元素
insert 在指定位置添加N个元素
insert 在指定位置添加一个范围的元素
push_back 在尾部添加一个元素
删
erase 删除指定的位置元素
erase 删除指定的范围
pop_back 删除尾部元素
查
find 全局函数,使用时要添加头文件 algorithm
被查找的对象要实现 == 运算符
find(statr,end,T val)
返回值是所有在位置的迭代器。
排
sort 全局函数,使用时要添加头文件 algorithm
find(statr,end) 被排对象实现<运算符
find(statr,end,bool (cmp)(a,b)) 提供回调函数。
容器的一些功能需要依赖对象的无参构造、拷贝构造、==、<。
四、双端队列
是队列的一种增强版本,具备向量的所有功能。
它从头部插入跟尾部插入的效率一样,没有反转功能。
与向量的内部结构不同,向量的内存是连续的,而双端队列是链式的。
双端队列的内存开销比较大,对元素的下标访问效率比较低,为了保持存储空间首尾都能使用,内存管理会比向量复杂的多。
增加和删除的效率与链表不相上下,不是最优也不是最差的一种容器。
五、堆栈、队列
堆从数据结构上来说是二叉树,也是种内存的名字。
栈<=>堆栈
堆栈stack、队列queue是一种在其它容器的基础上改编的一种容器,功能加以限制,数据只能从指定端口进出。
不能使用迭代器。
六、优先队列
优先队列最队列最大区别就会根据无素的值进行"排序",最大的元素会放在队首。
基本类型会根据<进行比较,选择最大的一个元素放在队首,类类型的元素必须要重载<运算符,否不能使用。
七、映射(字典)
是一种关联型的容器,里面存储的是一对数据(key,value)存储的。
根据现在世界中数据之间的对应关系,组合成一系列的key-value形成集合,可以根据key来快速获取对应的value,底层采用红黑树结构进行存储,因此查找速度非常快(类似二分查找)。
身份证,个人信息
学号,考试成绩
姓名,个人简历
主键,数据库记录
map<key,value> m;
m[key] = val; key不存在则添加,key存在则覆盖之前的value。
key不会重复。
八、多重映射
与映射的功能基本一致,但key值可以重得,因此使用方法与映射有所区别。
添加数据需要使用pair(key,value)合成一组,使用inster函数进行插入。
遍历时返回的也是pair对象:
pair.first <=> key
pair.second <=> value
根据key获取多个value,会返一个迭代器的范围pair(it,it)
typedef multimap<key,value>::iterator IT;
paif<IT,IT> pr = mm.equal_range(key);
for(IT it=pr.first; it!=pr.second; it++)
{
cout << it->first << it->second << endl;
}
九、集合与多元集合
集合的特点是元素不允重复,并为会对元素进行排序。
多元集合中的元素可以重复,也会对元素进行排序。
查找一个元素在集合存在的个数:
typedef multiset<value>::iterator IT;
paif<IT,IT> pr = ss.equal_range(key);
for(IT it=pr.first; it!=pr.second; it++)
{
cout << *it<< endl;
}
上一篇: STL空间配置器
下一篇: 跑酷类游戏实现背景无限循环