一、模板的概念
模板概念:
- 模板实现了通用类型的代码,不需要关心数据的类型,因此 C++标准模板库STL产生。模板可以分为:类模板和函数模板,其类型推导和具体代码生成都是在编译阶段。
- 没有实例化的模板:编译器只是对模板进行简单的语法检测,并不会生成代码
- 实例化之后:根据用户对模板实例化的类型来生成代码,并且对生成的代码进行语法检测
模板类型的参数:
- 类型模板参数:类型不确定的,实例化后才生成对应代码
- 非类型模板参数:类型已经具体化
- 如果有缺省或者传参时,必须是常数,因为要在编译阶段确定大小
- 浮点数、类对象以及字符串不允许作为非类型模板参数
template <class T, size_t N = 5> // 非类型模板参数缺省时,必须是常数
class arr
{
public:
arr()
:_size(0)
{
}
T& operator[](const int index){
return _arr[index];}
void insert(const T& data){
_arr[_size++] = data;}
private:
T _arr[N];
size_t _size;
};
int main()
{
arr<int, 10> a; // 非类型模板参数传参时必须是常数,否则报错
return 0;
}
模板的编译链接过程:
- 有以下加法模板函数:函数声明:a.h头文件中,函数定义:a.cpp中
a.h头文件:加法模板的声明
#pragma once
template <class T>
T Add(const T& left, const T& right);
a.cpp源文件:加法模板的定义
#include "a.h"
template <class T>
T Add(const T& left, const T& right)
{
return left + right;
}
main.cpp
#include "a.h"
int main()
{
int sum = Add(1, 2);
return 0;
}
注意:这段代码在链接过程中会发生错误,找不到 int 类型的模板函数地址!
- 原因:模板的分离编译,没有生成对应类型的代码,造成链接时找不到函数地址入口
- 编译:对程序进行语法正误检测,检查无误后生成汇编代码。注意:头文件不参加编译,并且编译器对一个工程中的多个源文件是分离编译的。
- 链接:将对个 .obj 目标文件合并成一个,并处理未解决符号表中的地址问题,到其他源文件的导出符号表中寻找对应函数地址。
- 分离编译:同一项目有多个源文件共同实现,而每个源文件都是单独生成目标文件,最后才将所有文件链接起来形成单一的可执行文件。
- a.cpp源文件在编译时,编译器没有看到对 Add 模板函数的实例化,因此不会生成具体的 int 类型代码
- main.obj 目标文件在连接时,去其他目标文件找 Add< int >的函数地址,没有找到因此报连接错误。
解决:
- 在模板定义处显示实例化,不推荐
- 将模板声明和定义放到同一 .hpp 头文件中
1.在模板定义处显示实例化
#include "a.h"
template <class T>
T Add(const T& left, const T& right)
{
return left + right;
}
void Test()
{
Add<int>(1, 2);
Add<double>(1.2, 2.3);
}
2.放在同一头文件中
#pragma once
template <class T>
T Add(const T& left, const T& right);
template <class T>
T Add(const T& left, const T& right)
{
return left + right;
}
二、模板的特化
函数模板特化:
- 下面实现一个通用类型的比较函数,返回两者较大数。
template <class T>
T& Max(T& l, T& r)
{
return l > r ? l : r;
}
int main()
{
int a = 1, b = 2;
cout << Max(a, b) << endl;
return 0;
}
- 对于int、double等类型能正确执行,但是如果传入字符串类型,比较的是地址大小,和预期不同,因此采用函数模板特化方式处理特殊类型
解决:
- 直接将特殊类型的函数重载出来,较为简单,推荐使用
- 进行模板函数特化处理
template <class T>
T& Max(T& l, T& r)
{
return l > r ? l : r;
}
// 进行函数重载
const char* Max(const char* l, const char* r)
{
if (strcmp(l, r) == 1)
return l;
else
return r;
}
// 进行特化处理:重载参数存在 & 的函数模板
template <>
const char*& Max<const char*>(const char*& l, const char*& r)
{
if (strcmp(l, r) > 0)
return l;
else
return r;
}
int main()
{
int a = 1, b = 2;
cout << Max(a, b) << endl;
const char* ptr1 = "bsd";
const char* ptr2 = "acv";
cout << Max(ptr1, ptr2) << endl;
return 0;
}
函数模板特化方法:
- 关键字 template 后加一个空的尖括号<>
- 函数名后加尖括号,尖括号里指针特化的类型
- 函数形参列表:和模板函数的基础参数类型相同
类模板特化:
- 类模板特化分为:全特化和偏特化
- 全特化:将模板参数列表中所有的参数都确定化
- 偏特化:针对默认参数进一步进行条件限制,形成特化版本
#include <iostream>
using namespace std;
template <class T1, class T2>
class Date
{
public:
Date() {
cout << "Date<T1, T2>" << endl; }
T1 _day;
T2 _week;
};
// 全特化版本
template <>
class Date <int, char>
{
public:
Date() {
cout << "Date<int, char>" << endl; }
int _day;
char _week;
};
// 偏特化:1.模板部分参数特化
template <class T1>
class Date <T1, char>
{
public:
Date() {
cout << "Date<T1, char>" << endl; }
T1 _day;
char _week;
};
// 偏特化:2.对参数进行进一步限制
template <classT1, class T2>
class Date <T1*, T2*>
{
public:
Date() {
cout << "Date<T1*, T2*>" << endl; }
T1 _day;
T2 _week;
};
int main()
{
Date<int, char> d1; // 走全特化版本
Date<int, int> d2; // 走基础模板
Date<char, char> d3; // 走特化char版本
Date<int*, int*>d4; // 走特化指针版本
return 0;
}
特化方法:
- 在关键字 template 后的尖括号中进行处理,全特化:空的,偏特化:添加不需要特化的,对参数限制:添加限制参数
- 类名后加尖括号和特化内容
三、类型萃取
- 类模板的特化应用:类型萃取
下面实现一个通用类型的拷贝函数:
template <class T>
void Copy(T& dst, const T& src, size_t size)
{
// 内存拷贝:优点:效率高 缺点:设计内存资源管理是浅拷贝
memcpy(dst, src, size * sizeof(T));
// 深拷贝:优点:一定不会出错 缺点:效率太低
for (int i = 0; i < size; i++)
dst[i] = src[i];
}
- 采用 memcpy 内存拷贝:拷贝效率较高,对于内置类型不会出错。但是对于设计动态内存的数据,则会造成内存泄漏,甚至因为 double free 造成程序崩溃
- 采用深拷贝的方式:拷贝效率太低,但是深拷贝不会造成错误
因此使用类模板特化:类型萃取,将内置类型提取出来,进行内存拷贝
// 类型萃取
struct True_Type
{
static bool Get(){
return true;}
};
struct False_Type
{
static bool Get(){
return false;}
};
// 其它类型
template <class T>
struct TypeTraits
{
typedef False_Type PODTYPE;
};
// 内置类型:特化处理
template<>
struct TypeTraits<int>
{
typedef True_Type PODTYPE;
};
template<>
struct TypeTraits<char>
{
typedef True_Type PODTYPE;
};
template<>
struct TypeTraits<double>
{
typedef True_Type PODTYPE;
};
template<>
struct TypeTraits<short>
{
typedef True_Type PODTYPE;
};
// 对拷贝函数进行重载
template <class T>
void Copy(T* dst, const T* src, size_t size, True_Type)
{
// 内置类型
memcpy(dst, src, size * sizeof(T));
}
template <class T>
void Copy(T* dst, const T* src, size_t size, False_Type)
{
// 其他类型
for (size_t i = 0; i < size; i++)
dst[i] = src[i];
}
template <class T>
void Copy(T* dst, const T* src, size_t size)
{
Copy(dst, src, size, TypeTraits<T>::PODTYPE());
}
int main()
{
int arr1[] = {
1,2,3,4 };
int arr2[4] = {
0 };
Copy(arr2, arr1, 4);
String s[] = {
"111", "222", "333" };
String p[3];
Copy(p, s, 3);
return 0;
}
String类
class String
{
public:
// 构造
String(const char* str = "")
{
if (str == nullptr)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
// 拷贝构造
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
// 析构
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
// 赋值运算符重载:现代法
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
private:
char* _str;
};