泛型编程:通用 + 灵活 编写与类型无关的逻辑代码,是代码复用的一种手段。 与类型无关。
分为 函数模板 和 类模板
| |
| 实例化 |实例化
V V
模板函数 模板类
|
v
对象
一个方法有问题,所有的方法都有问题,这样维护起来就不方便。将代码交给编译器去写。
利用模板机制可以显著减少冗余信息,能大幅度节约程序代码,进一步提高面向兑现程序的可重用性和可维护性,
模板是实现代码重用机制的一种工具,它可以实现类型参数化,即就是把类型定义为参数,从而实现代码重用。
缺陷:
增长了代码编译的时间;
一旦出错,编译器报错,非常诡异。
函数模板
格式:
template
返回值类型 函数名(参数列表/模板形参表)
{
函数体
}
注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替typename)建议尽量使用typename
typename就是类型名。 以前是没有typename的,C++11后来加入了typename,它与class没什么区别。
每个类型的前面都必须加上typename或者class。
C++中class与struct的区别
1.类中的默认访问权限不同;
2.继承体系中的默认访问形式不同;
3.模板的参数列表中不能出现struct。
注意:inline关键字必须放在模板形参列表之后,函数返回值之前,千万不能放在template之前。
eg:
template
T Add(T left, T right)
{
cout<
}
模板是一个蓝图,它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,
产生模板特定类型的过程称为函数模板实例化。
函数模板并不是真正的函数,是编译器生成代码的规则。
注意:模板函数的编译分两个阶段
1.实例化之前,检查模板代码本身,是否出现简单语法错误,如:模板参数列表少了typename
2.在实例化后,检查模板生成的代码,是否所有的调用都有效,如:实例化类型不支持某些函数调用(两个复数相加)
自定义类型的对象可能会遇到编译器报错,这是因为+未重载的缘故。
从函数实参确定模板形参类型和值的过程称为模板实参推演,多个类型形参的实参必须完全匹配。
eg: cout<
不知道int转换成double,还是double转换成int,所以当类型不完全匹配时,编译器直接报错)。
在编译期间,根据传递实参的类型来推演函数模板中模板参数列中T的类型从而生成具体类型的代码。 (静态多态)
cout
显示实例化:直接指定模板参数列表中T的类型,可能会发生隐式类型转换。
若转换成功,则生成代码,若转换失败则出错。
cout<
模板函数的匹配原则
一个模板只在当前定义的函数模板或者类模板中有效。
如果自己写的函数不能处理,那么就会调用模板来生成相应类型的代码。如果自己写的函数可以处理,并且有模板函数存在,
那么会优先调用自己写的函数去处理。
重载
直接将模板不能处理的类型函数直接给出,特化:书写比较复杂。
cout<
注意:
1.函数的所有重载版本的声明都应该位于该函数被调用位置之前。
2.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
3.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。
如果模板可以产生一个具有更佳匹配的函数,那么将选择模板。
4.显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能匹配这个调用, 而且所有的模板参数都应该根据实参推演出来。
5.模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
一般不会转换实参以匹配已有的实例化,相反会产生新的实例,编译器只会执行两种类型转换:
const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用。
数组或函数的转换:数组实参将转换为指向其第一个元素的指针函数实参将转换为当做指向函数类型的指针。
类模板
template
vector
memcpy:内存拷贝,实际上是浅拷贝,会造成释放资源时,找不到对象。
bool IsPODType() plain old data(内置类型) 但是每次都需要运行,效率不高。
如果是内置类型,则用memcpy拷贝,如果不是,则用for循环中的operator=拷贝(一个一个地拷贝)。
实际上也没有提高效率。
使用模板特化来解决。
struct TrueType
{
static bool Get()
{
return true;
}
};
struct FalseType
{
static bool Get()
{
return false;
}
};
template
struct TypeTraits
{
typedef FalseType PODType;
};
// 特化int
template<>
struct TypeTraits
{
typedef TrueType PODType;
};
if (TypeTraits
{
memcpy();
}
实际上,FalseType 与 TrueType中的成员函数可以不给出,相当于一个空类,但是由于类名不同,也可以区分开。
而是通过模板类将TrueType 与 FalseType分别传入Copy中。
类型萃取:类模板特化的应用。
分离编译:
模板编译
1.对模板函数进行简单的语法检测;
2.根据实例化来生成代码。
模板不支持分离编译。
1.在模板头文件 xxx.h 里面显示实例化->模板类的定义后面添加 template class SeqList
译器可能不支持,另一方面实例化依赖调用者(不推荐)。
2.将声明和定义放到一个文件 "xxx.hpp" 里面,推荐使用这种方法。
List(T* first, T* last); // [first, end)
STL:标准模板库 (通用 + 效率)
通俗来说,将常见的数据结构封装--线性结构&特殊线性结构&二叉搜索树&哈希
通用算法:与类型无关(模板)、与数据结构无关、用户可以定制(灵活性)的算法
eg:
void sort(first, last);
void remove(first, last, op);
六大组件
仿函数
greater less
空间配置器
allocater
算法
find sort swap reverse merge
容器
string vector list deque map set multimap multiset
迭代器
iterator const_iterator reverse_iterator const_reverse_iterator
适配器
stack queue priorty_queue
容器(存放或者管理数据)
线性结构:
序列式容器
vector(动态的顺序表)
扩容用的是伙伴算法。
迭代器:当做指针来处理,原生态的指针,比如vector迭代器(vector底层是一段连续空间)。
list的迭代器是通过封装原生态的指针实现的。 实际是个类,根据其底层数据结构实现类似指针的操作。
一种设计模式,按照某种方式遍历容器。
功能:让算法与数据结构无关。
迭代器失效
迭代器无法使用-->指针指向空间非法-->空间被释放
vector
插入操作->增容->开辟新空间,拷贝元素,释放旧空间->没能及时更新迭代器
list
删除时没有重新赋值给迭代器,那么迭代器就会失效。
map
删除时没有重新赋值给迭代器,那么迭代器就会失效。
正向迭代器: begin() end() ++朝后走 --朝前走
反向迭代器: rbegin() rend() ++朝前走 --朝后走
* -> ++ -- != ==
提供者:容器的设计者,比较清楚容器所对应的数据结构。
list(带有头结点的双向循环链表)
list迭代器需要list的实现者自己提供。(封装结点的指针)
1. typedef ListIterator
2. Iterator begin()
{
return _pHead->pNext;
}
Iterator end()
{
return _pHead;
}
给出头结点的原因
1.为了方便操作;
2.为了方便找到end。
Find:没找到返回last.
vector
while (it != v.end())
{
cout<<*it<<" ";
++it;
}
cout<
注意:迭代器的访问权限是public。
array(静态的顺序表) (size_t N 非类型的模板参数) C++11添加的
array
at 与 operator[] 功能相同
为什么需要特化?
模板类对于某种类型无法提供编译方式,或者说无法处理。
全特化
template<>
class Seqlist
{};
偏特化(局部特化某个形参列表,局部特化形参列表为指针类型或者引用类型)
template
class Data
{};
注意:偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
注意:模板的全特化和偏特化都是在已定义的模板基础之上,不能单独存在。
forward_list(带有头结点的单向循环链表) C++11添加的 SGI版本 slist
无法进行++ --操作
string(动态的字符顺序表)
deque(双向的队列) 假想的连续空间 类似于动态的二维数组
头插可以在当前队列前面插入一段空间,然后在前面这段空间采取尾插。
中控满了之后重新开辟更大的空间,然后依次拷贝到新空间的中间部分,方便头插尾插。
实现迭代器必须知道分段空间的位置,是否处于空间的边缘位置,要知道下一个位置在哪。
适配器(封装了别的数据结构) 配接器
stack
适配器(container adapters) :依赖于其他数据结构的接口 库里面搭载的是双端队列
template
增容时用vector效率低,选用deque
queue
库里面搭载的是双端队列
template
template
栈和队列为什么没有迭代器?
特殊线性结构,不需要遍历。
priority_queue(优先级队列)
堆算法 + vector (实际上堆相当于完全二叉树,因此可以通过数组下标去访问)
#include
priority_queue
for (int i = 0; i <= 9; i++)
{
q.push();
}
cout<
默认是大堆,通过改变less为greater就可以改变为小堆。
eg: priority_queue
自定义类型:用仿函数 operator()()
关联式容器
存储键值对
底层为红黑树 近似平衡的二叉搜索树
通过迭代器可以得到一个有序序列
插入红色结点
map
key必须是唯一的
按照中序遍历的方式。
map
m.insert(pair
m.insert(make_pair("史进", "九纹龙"));
m["林冲"] = "豹子头";
find用map提供的,因为查找效率为O(logN)。
查询的时候,先插入。
若插入成功,则表示之前没有这个结点,该节点的位置在叶子结点的左侧或者右侧。 返回pair
<插入新节点的迭代器, true> 不用find的原因是挡结点不存在时无法返回一个值。
若插入失败,则表示之前就存在这个结点,那么返回
实例化pair类 insert_unique()
插入结点的颜色是红色。(待插入结点的双亲的颜色是红色,就需要调整红黑树中结点的位置)
T& operator[](const key_type& k)
{
return ((*(key_type(k, T())).first).second);
}
multimap
存放的是
insert_equal()
key存在,则返回新节点的迭代器,
key不存在,则返回新节点的迭代器。
set
data唯一
先去重(如果有重复元素)再排序。
multiset
存放的是data
data可以重复
底层为哈希桶(C++11)
unordered_map
没有rbegin,因为是单链表链接的。
unordered_multimap
unordered_set
unordered_multiset
map 与 unordered_map
1.底层结构不同(红黑树、哈希桶)
2.有序序列 无序序列
3.查找效率O(logN) O(1)
4.二叉搜索树查找 哈希函数直接定位位置
5.不需要增容 需要增容
6.不会冲突 有可能会发生冲突
7.迭代器 ++ -- 不可以--
8.使用场景 有序但是查找效率低, 无序但是查找的效率高
9.前者比后者更加节省空间 (哈希桶会在数组中存放头结点的地址,并且有的数组中是没有元素的)
强制类型转换
static_cast
隐式类型的转换
用于非多态类型的转换(静态转换),任何标准转换都可以用它,但是它不能用于两个不相关类型的转换。
eg:
int a = 10;
double d = 12.34; // 隐式转换---相关类型 (表示的都是数字,无非是数据格式不同)
a = static_cast
string s = i; // 这样就会编译报错,因为不是相关类型
int *p1 = &i;
double *p2 = p1;// 编译报错 强制类型的转换--不相关类型 (虽然都是地址,但是解引用后访问的地址空间大小不同)
reinterpret_cast
不相关类型的转换(类似于C中的强制类型转换)
eg:
int *pa = reinterpret_cast
int *p1 = &a;
double *p2 = reinterpret_cast
有BUG,比如强转某个函数为无参无返回值,这时候调用可以不传参,就访问。
const_cast
改变const属性
删除变量的const属性,方便赋值。
eg:
const int a = 10; // 监视窗口看到的是100,打印出来的是10,这里的10是从寄存器中读取的,实际上a的值被改了
// 这里可以加上volatile保证去内存中读取数据
int *pa = const_cast
*pa = 100;
RTTI
运行时类型识别
dynamic_cast typeid
dynamic_cast
动态类型转换 只能用于包含虚函数的类。
用于将一个父类对象的指针或引用转换为子类对象的指针或引用。
void Func(A* p)
{
p->f(); // 这里并不确定调用的是哪一个函数(多态实现f()) 到底是父类指针还是子类指针是不确定的
}
向上转型:子类对象指针->父类对象指针(不需要转换)
向下转型:父类对象指针->子类对象指针(用这种方式安全)
识别指针是指向子类对象还是父类对象。
先检查是否能够转换成功,能转换成功则转换,反之直接返回NULL。
pa->f();
B* pb = dynamic_cast(pa);
if (pb) // 转换成功
{
pb->_a = 1;
pb->_b = 2;
}
else
{
pa->_a = 10;
}
C++中单参数的构造函数的类都支持隐式类型转换
C c1(10); // 调用了C构造函数
C c2 = 20; // 隐式类型的转换 单个参数可以
//1.用20构造一个匿名对象;
//2.拷贝构造20;
//3.编译器优化成一个构造函数来处理。
explicit(可显的)
清除单个参数的构造函数的隐式类型转换。
通过构造的方式转换成一个无名对象来赋值。
对于单个参数构造函数,可以将其接收参数转化成类类型对象。用explicit修饰构造函数,抑制由构造函数定义的隐式转换,explicit
关键字类内部的构建声明上,在类的定义体外部的定义上不再重复。
注意:
智能指针是不允许单个参数赋值的,必须通过构造函数传参的方式。 防止自己释放了指针导致多次释放(有explicit关键字)
auto_ptr
auto_ptr