下一篇
模板
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。 源码
原理
当用户将函数模板实例化之后,编译器会根据实例化的结果来推演实参的类型,然后根据推演的结果生成处理具体类型的函数
原则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
- . 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
template<class T>
T Add(const T& left, const T& right)
{
cout << typeid(T).name() << endl;
return left + right;
}
int Add(int left, int right)
{
return left + right;
}
int main()
{
Add(1, 2);
Add<>(1, 2);
Add<int>(1, 2);
return 0;
}
template<class T, class U>
T Add(const T& left, const U& right)
{
return left + right;
}
int Add(int left, int right)
{
return left + right;
}
int main()
{
Add(1, 2);
Add(1, 2.0);
return 0;
}
顺序表模板源码
模板参数
注意; 1.浮点数、类对象以及字符串是不允许作为非类型模板参数的。 ⒉非类型的模板参数必须在编译期就能确认结果。
#include<iostream>
using namespace std;
template<typename T,int N=10>
class Array{
public:
T _a[N];
int _size;
Array():
_size(0){}
void pushBack(const T& d){
this->_a[this->_size++]=d;
}
T& operator[](int index){
return this->_a[index];
}
};
int main(){
Array<int, 10>a;
a.pushBack(2);
cout<<a[0];
return 0;
}
模板的特化
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,
注意:函数模板一般不需要对其特化,如果模板那种类型处理是错误,直接将该类型对应普通函数直接写出来
函数特化步骤
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
#include<iostream>
#include <cstring>
using namespace std;
template<class T>
T Max( T left, T right)
{
if (left > right)
return left;
return right;
}
template<>
char* Max<char*>(char* left, char* right)
{
if (strcmp(left,right)>0)
return left;
return right;
}
int main()
{
char s2[] = "hello";
char s1[] = "world";
cout << Max(s2, s1) << endl;
return 0;
}
建议:尽量不特化,容易出错
类模板特化
全特化源码 偏特化源码 作用(了解): 类模板特化用途:类型萃取—类模板特化的应用
分离编译
一个程序(项目)由若干个源文件共同实现,每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
模板编译: 1.编译器只会对模板进行简单的语法检测,并不会深入的检测语法问题 2.当用户对模板进行实例化之后,编译器会根据用户的实例化的类型借助模板生成处理具体类型的代码
扩展
解决方法
#pragma once
template<typename T>
T sub(T a,T b){
return a-b;
};
#include<iostream>
#include "source.hpp"
using namespace std;
int main()
{
cout<<sub(2,3);
return 0;
}
总结
【优点】
- 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
- 增强了代码的灵活性
【缺陷】 - 模板会导致代码膨胀问题,也会导致编译时间变长
- 出现模板编译错误时,错误信息非常凌乱,不易定位错误
STL
- STL: standard template library—C++中的标准模板库
- 通俗来说: STL就是将常见的数据结构以模板的方式进行封装,包含常见通用(模板方式)的泛型算法
STL中有六大组件:
a. 容器:放数据—组织数据—本质:就是对常见的数据结构进行封装
序列式容器—线性数据结构 string:动态类型的顺序表—只能存储字符—字符串 vector :动态类型的顺序表 array:静态类型的顺序表C++11 list:带头结点双向循环链表 forward_list:带头结点单向循环链表c++11 deque:双端队列
关联式容器
b.算法c.迭代器d.适配器e.仿函数 f.空间配置器–封装内存池(高效空间申请和释放以及对空间进行管理)
string
基本使用
元素访问
vs2013下, string是按照1.5倍方式扩容 上述代码放到linux中验证: linux中string是按照2倍扩容的
字符串接受和整行接受
void TestString15()
{
string s1, s2;
while (cin >> s1 >> s2)
cout << s1 << "----" << s2 << endl;
while (getline(cin, s1))
cout << s1 << endl;
}
写时拷贝
vector
-
string类是管理字符的动态顺序表 vector是管理任意类型元素的动态顺序表 -
问题:vector< char>不就是一个字符数组吗?为什么STL.还要单独提供一个string类出来? 1.字符数组不一定是字符串—因为C/C++规定,字符串最后一个有效字符之后必须是\0 2.string类有和字符串相关的一些特殊操作 -
在vs2013中,vector是按照1.5倍方式扩容,,g++是按2倍增长的。 如果提供预估vector中可以存储的元素个数,提前将空间预留足 -
方法分为:构造析构,迭代器,容量,访问元素,修改,其他部分
vector基本使用
vector内的变量
resize注意
const修饰vector
void TestVector2() {
const vector<int>v2{1,5,31,36,36};
return;
}
迭代器失效(重要)
vs2013中进行的
- 什么是迭代器
类似一个指针,迭代器失效实际上是指针失效 - 失效原因
也就是说:迭代器对应的指针如果指向的是一块非法的空间,则指针就失效了,即迭代器就失效了 - 哪些措施导致迭代器失效
所有有可能会导致扩容抄作的方法都可能会导致迭代器失效
正确删除元素
void TestErase1()
{
vector<int> v{ 1, 2, 3, 4, 5 };
auto it = v.begin();
while (it != v.end())
{
it = v.erase(it);
}
}
- 如何解决
迭代器失效如何解决:在使用迭代器之前,最好给迭代器重新赋值
模拟vector
参考
list
list的底层结构:带头结点双向循环链表
基本使用
基本使用
vuector和list中find有什么不同
vector和list实现中有find的方法吗?—没有 为什么vector和list没有提供find的算法呢? 因为: vector和list都是线性的结构,他们的find元素无非就是将区间中所有的元素遍历一遍,如果vector和list都实现find方法,find实现原理都是一样的–整体遍历—代码重复:获取下一个元素方式不通
迭代器失效
场景:
- erase(pos):删除成功之后,pos迭代器指向的节点已经被销毁了,pos迭代器就失效了
- resize(newsize, value),迭代器指向某一元素时,将有效元素变小,迭代器失效。
解决:
list的底层结构
带头结点的双向循环链表 注意:带头节点双向循环链表中不存在空指针域
链表的迭代器类型:不能将其直接设置为原生态的指针 因为:对于链表来说:原生态的指针++之后无法拿到下一个节点,将原生态的指针重新包装一下重新封装一个类 模拟list
typedef typename Iterator::Reference Reference;
在类和对象的位置,知道静态成员变量也可以通过类名::静态成员变量访问,类中包含的其他类型也可以通过类名:类内部定义类型使用 此时编译器就无法知道Reference到底是lterator中的静态成员变量呢?还是一个数据类型? 明确告诉编译器,Reference是lterator内部的一个类型
vector vs list(重)
|