c++模板与泛型编程完整笔记 | STL前置基础
c++模板与泛型编程
前言
1.泛型编程:思想
不针对int/double/string写死代码,写一套通用逻辑,适配所有类型。
2.模板:c++唯一实现泛型的语法工具之一
c++没有"泛型关键字",模板=c++泛型编程的唯一落地方式。
3.STL容器=官方写好的大量模板类
vector,map,set,string 全部都是类模板,如果你不懂模板,只会调用STL,永远看不懂底层。
一、为什么必要要有模板&泛型编程
1.传统代码的巨大缺陷(重复冗余)
如果你要写一个“取最大值”函数:
-
写一份int版
-
写一份double版
-
写一份float版
-
未来自定义结构体,类还要再写
代码几乎一模一样,只有类型不同,极度冗余,难维护。
2.泛型编程的核心宗旨
算法逻辑不变,数据类型可变。编辑只写一次,自动适配任意类型,这就是泛型
3.模板的作用
给类型填占位符,编译时自动生成对应代码。
二、模板两大核心分类
模板只有两种,没有第三种:
1.函数模板 -> 通用函数
作用:写一份函数逻辑,给int、double、string都能用。
语法格式:
template<typename T>
返回值 函数名(参数)
{
//里面可以用T当作类型
}
示例代码:
#include<iostream>
using namespace std;
//模板头, T是类型占位符
template<typename T>
T getMax(T a,T b)
{
return a > b ? a : b;
}
int main()
{
//编译器自动看你传入什么值,自己推导出T是什么类型
cout<<getMax(10,20)<<endl;//T变成int
cout<<getMax(1.5,3.2)<<endl;//T变成double
//也可以手动强制写类型
cout<<getMax<int>(5,9)<<endl;
return 0;
}
函数模板重点
1.template
2.可以自动推导类型,调用的时候很多时候不用写
3.模板本身不是真正函数,编译的时候编译器生成一份真实的函数代码。
2.类模板 ->通用类(STL容器全是这个)
作用:写一个通用的类,可以存int,可以存string,可以存自定义结构体。vector
语法格式
template<typename T>
class 类名
{
//成员变量,成员函数,T就代表将来的类型
};
示例代码
#include<iostream>
#include<string>
using namespace std;
template<typename T>
class MyBox
{
private:
T data;//这个变量是什么类型,等到创建对象才知道
public:
void set(T val)
{
data = val;
}
T get()
{
return data;
}
};
int main()
{
//类模板:不能自动推导!必须写<类型>
MyBox<int> box1;
box1.set(100);
cout<<box1.get()<<endl;
MyBox<string> box2;
box2.set("hello");
cout<<box2.get()<<endl;
return 0;
}
剥离分配器、调试宏之后的可读简化版vector容器源码
// vector 是类模板!T是存储的元素类型,Alloc是内存分配器(模板默认参数)
template<typename T, typename Alloc = std::allocator<T>>
class vector
{
public:
// 类型别名,STL到处在用
typedef T value_type;
typedef T* iterator; //迭代器本质就是裸指针!
typedef const T* const_iterator;
private:
iterator _Start; // 起始
iterator _Finish; //有效数据结尾
iterator _EndOfStorage; //整块内存的结尾
//分配器对象,负责申请释放堆内存
Alloc _alloc;
public:
//构造函数
vector()
{
_Start = nullptr;
_Finish = nullptr;
_EndOfStorage = nullptr;
}
//析构:销毁元素,释放堆内存
~vector()
{
clear();
//释放整块堆内存
if(_Start != nullptr)
{
_alloc.deallocate(_Start, capacity());
}
}
// size:有效元素数量
size_t size() const
{
return static_cast<size_t>(_Finish - _Start);
}
// capacity总容量
size_t capacity() const
{
return static_cast<size_t>(_EndOfStorage - _Start);
}
bool empty() const
{
return _Start == _Finish;
}
// operator[] 下标访问
value_type& operator[](size_t n)
{
return *(_Start + n);
}
// begin迭代器
iterator begin()
{
return _Start;
}
iterator end()
{
return _Finish;
}
// clear:调用对象析构,不释放内存!capacity不变
void clear()
{
while(_Finish != _Start)
{
--_Finish;
_alloc.destroy(_Finish);
}
}
// push_back 核心!
void push_back(const T& val)
{
// 判断内存满了:有效结尾 == 内存总结尾 → 需要扩容
if(_Finish != _EndOfStorage)
{
//还有剩余空间,直接构造对象
_alloc.construct(_Finish, val);
++_Finish;
}
else
{
//扩容!开辟新空间,拷贝旧元素,释放旧空间
reallocate();
_alloc.construct(_Finish, val);
++_Finish;
}
}
private:
//扩容函数,原版vector一般扩大2倍
void reallocate()
{
size_t old_cap = capacity();
// 扩容策略:空的就给1;否则翻倍
size_t new_cap = old_cap == 0 ? 1 : old_cap * 2;
//申请一块更大堆内存
iterator new_start = _alloc.allocate(new_cap);
//把旧内存全部拷贝到新内存
iterator p_old = _Start;
iterator p_new = new_start;
for(; p_old != _Finish; ++p_old,++p_new)
{
_alloc.construct(p_new, *p_old);
_alloc.destroy(p_old);
}
//释放旧内存
_alloc.deallocate(_Start, old_cap);
//更新三个指针指向新内存
_Start = new_start;
_Finish = p_new;
_EndOfStorage = new_start + new_cap;
}
};
类模板重点
1.template
2.创建对象必须手动写<类型>,编译器不会自动推导
3.如果成员函数写在类外面,外面也要重新写一遍template
4.STL的vector
简单对比,一眼分清
1.函数模板
-
产出:通用函数
-
使用:大多自动推导类型,不用写<>
-
STL对应:sort、max、swap这些算法
2.类模板
- 产出:通用类、通用数据结构
- 使用:创建对象必须写<类型>
- STL对应:vector、map、set容器
三、模板关键概念
1.模板参数 = 类型参数
普通函数参数:传数值
模板参数:传类型
2.模板实例化
-
隐式实例化:编译器自动推导,写代码时不用手动指定类型
-
函数模板可以隐式实例化;类模板不能完全靠隐式实例化 template<typename T> T max(T a, T b) { return a > b ? a : b; } max(1,2);//隐式实例化,编译器推导出T = int -
显式实例化:手动写<类型>,强制告诉编译器生成该类型的代码
-
函数模板:max<int>(10,20); 类模板:vector<int> v;,STL所有容器使用过程,全部是[类模板实例化] -
补充:显式实例化声明(extern)
-
//强制编译器生成int版本的函数模板案例 template int max<int>(int a,int b); //extern显式实例化声明:告诉编译器别处已经生成实例,本文件不要重复生成 extern template int max<int>(int a,int b);
3.模板不能分离编译
模板代码必须写在头文件.h中
原因:
编译时需要看到完整模板源码才能生成对应类型代码,如果拆分cpp,编译找不到模板实现,直接报错
四、泛型编程的真正定义
1.不聚焦数据类型,只聚焦算法逻辑
2.用模板实现与类型无关的通用代码
3.一套代码,复用所有数据类型
对比:
面向对象:聚焦[对象、继承、多态]
泛型编程:聚焦[通用、复用、类型无关]
STL = 泛型编程思想的工业级1成品库
- 容器:通用数据结构(类模板)
- 算法:通用算法(函数模板)
- 迭代器:通用指针适配
五、模板特化
1.什么是特化?
通用模板对某些特殊类型不适用,单独写一份定制版
2.全特化
//通用<typename T>
void show(T a)
{
cout<<"通用模板"<<endl;
}
//专门为string<>
<string>(string a)
{
cout<"字符串特化版本"<endl;
}
优先级:特化版本 > 通用模板
STL大量用特化优化不同类型的存储结构
六、模板默认参数
可以给模板参数设置默认类型
template<typename T>
class Test
{
public:
T val;
};
//不写类型默认int
Test<> t1;
Test<double> t2;
vector第二参数内存分配器就是模板默认参数
这就是我看不懂<T,Alloc>的来源
浙公网安备 33010602011771号