<C++>10 模板
本文最后更新于:2022年6月22日 下午
10 模板
C++ 另一种编程思想称为:泛型编程。主要利用的技术就是模板
C++ 提供两种模板:函数模板、类模板
10.1 函数模板
函数模板:建立一个通用函数,其返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表。
语法:
template<typename T> // <———— 声明模板
T met(T obj) { // <———— 这里是一处函数的声明或定义
return obj;
}
template:声明创建模板
typename:其后面的符号是一种数据类型。也能写成 class
T:通用数据类型。名字随便起
template<class HOMU> HOMU mosiMita(HOMU homu) { cout << "HomuHomu MiniDomu!" << endl; return homu; }
注意事项:
-
自动类型推导,必须推导出一致的泛型才能使用
-
模板必须确定泛型的数据类型才能使用。
不能推导的场合,应该显式地指定泛型类型
template<typename T> void biat() { // <———— 不能进行类型推导 cout << "Ye dada!" << endl; } void heli() { biat<int>(); // <———— 因不能进行类型推导,必须显式指定那个泛型类型 }
普通函数与函数模板的区别:
-
函数模板在编译时不生成任何目标代码。仅当通过模板生成具体的函数实例时才生成目标代码。
-
函数指针只能指向模板的实例,而不能指向模板本身
-
被多个源文件引用的函数模板,应当连同函数体一起放在头文件中
-
普通函数调用时能发生自动类型转换
函数模板调用时,如果利用自动类型推导,则不发生自动类型转换。
如果显式指定泛型类型,则仍会发生自动类型转换
普通函数和函数模板的调用规则:
-
函数模板可以发生重载
-
如果普通函数和函数模板都能实现,则优先调用普通函数
可以通过空模板参数列表来强制调用函数模板
-
如果函数模板可以产生更好的匹配,则优先调用函数模板
template<typename T> void act(T t) { cout << "模板函数" << endl; } void act(int n) { cout << "普通函数" << endl; } int main() { act(1); // <—————— 调用普通函数 act<>(1); // <—————— 调用函数模板(空模板参数) act('1'); // <—————— 调用函数模板(更好的匹配) }
模板的局限性:
-
模板的通用性不是万能的。C++ 提供模板的重载,可以为特定类型实现更好的操作
template<typename T> void act(T t) { // <—————— 函数模板 cout << "T" << endl; } template<> void act(string s) { // <—————— 函数模板的重载 cout << s << endl; } int main() { string s = "Heruin"; act(s); // <—————— 此处调用重载模板 }
10.2 类模板
声明模板 template 后加一个类即构成类模板
语法:
template<class T, class Y> // <—————— class 也可以写成 typename,一样的
class Lumine { // <—————— 类的声明或定义
public:
T lum_bro;
Y lum_wep;
Lumine(T t, Y y) { // <—————— 构造器
this->lum_bro = t;
this->lum_wep = y;
}
void introduce() { // <—————— 一个函数。模板类型变化时也可能出问题
cout << lum_bro << endl;
cout << lum_wep << endl;
}
};
int main() {
Lumine<int,int> lumine(1, 1); // <—————— 要这样去实例化
}
与函数模板的区别:
-
类模板不能自动类型推导,所以必须显式指定类型
-
类模板在模板列表中可以有默认参数
template<class T = string, class Y> // <———— 添加了默认参数。这样,不显示指定就用默认类型 class Lumine;
类模板中成员函数的创建时机:
-
普通类的成员函数在一开始就能创建
-
类模板的成员函数在调用时才能创建
因为数据类型不确定,该类在模板被调用前不会被创建
类模板继承问题:
-
子类继承的父类是一个模板的场合,子类声明时要指明那个父类的泛型类型
不指定的场合,编译器不能给子类分配内存
-
想要灵活指定父类泛型类型的场合,子类也要称为模板
template<class TAR> class DF17 {}; template<class TAR> class HX8 : private DF17<TAR> {}; // <—————— [优质答案:“我不知道。”]
类模板成员函数的类外实现:
template<class ELE>
class Cryo {
public:
void act(ELE t);
Cryo();
};
template<class R>
Cryo<R>::Cryo() {} // <—————— 类模板构造器类外实现
Cryo<char>::Cryo() {}
template<typename E>
void Cryo<E>::act(E t) {} // <—————— 类模板成员函数类外实现
10.2.1 类模板对象做函数参数
(下例中的)模板类如下:
template<class T>
class Gro{}; // <—————— 随便写的一个空类
三种方式:
-
指定传入类型:
void act1(Gro<int>& g) { // <———— “都说了是 Gro<int> 啦,你怎么听的呀(怒)” cout << "指定传入类型" << endl; }
-
参数模板化:
template<typename T> void act2(Gro<T>& g) { // <———— “回去告诉你们那个参数,他是个模板朕也是个模板” cout << "参数模板化" << endl; }
-
整个类模板化:
template<class T> void act3(T& t) { // <———— “啊?什么模板类,没看到啊” cout << "整个类模板化" << endl; }
10.2.2 类模板分文件编写
类模板分文件编写的场合,在运行时可能报错
这是因为,分文件编写的场合,包含的是头文件。而类模板成员函数是在调用时创建,这会导致编译器不能读取源文件中的实现。
头文件 hydro.h
:
template<class ELE>
class Hydro { // <———— 随便写的,意思一下
public:
void act(ELE t);
Hydro();
};
源文件 hydro.cpp
:
template<class R>
Hydro<R>::Hydro() {} // <———— 随便写的,意思一下
template<typename E>
void Hydro<E>::act(E t) {} // <———— 随便写的,意思一下
解决方法:
-
不包含头文件,而是包含源文件(一般不这样):
#include<iostream> using namespace std; #include "hydro.cpp" // <———— 包含的不是头文件而是源文件 int main() { Hydro h; }
-
将头文件和源文件合并为
hydro.hpp
:头文件
hydro.hpp
:template<class ELE> class Hydro { public: void act(ELE t); Hydro(); }; template<class R> Hydro<R>::Hydro(){} template<typename E> void Hydro<E>::act(E t) {}
之后,包含该 hpp 文件即可
#include<iostream> using namespace std; #include "hydro.hpp" int main() { Hydro h; }
后缀名 hpp 是约定俗成的名称。非要变也可以
10.2.3 类模板做友元
template<class L>
class Anemo; // <———— [5]
template<typename L>
void act2(Anemo<L> a); // <———— [4]
template<class L>
class Anemo {
friend void act1(Anemo<L> a) {} // <———— [1]
friend void act2<>(Anemo<L> a); // <———— [3]
private:
L val = 1;
};
template<typename L>
void act2(Anemo<L> a) {} // <———— [2]
-
全局函数类内实现的场合,可以直接包含友元
[1]
friend void act1(Anemo<L> a) {}
-
全局函数的类外实现,需要添加模板声明
[2]
template<typename L>
void act2(Anemo<L> a) {} -
全局函数类外实现的场合,包含友元时要加入空模板参数列表,以示其为模板函数而非普通函数
[3]
friend void act2<>(Anemo<L> a);
-
由于类内使用了此前未声明过的全局函数,所以还要将全局函数在类实现前进行声明,以向编译器提示该函数的存在
[4]
template<typename L>
void act2(Anemo<L> a); -
由于该全局函数参数是一个此前未声明过的类,故该类也要在全局函数声明前声明,以向编译器提示该类的存在
[5]
template<class L>
class Anemo;
附录
可变数组类
template<class C>
class OArray {
public:
C* cs = NULL;
OArray() {
m_size = 5;
cs = new C[m_size];
}
OArray(int n) {
if (n <= 0) n = 5;
m_size = n;
cs = new C[m_size];
}
void add(const C& obj) {
if (cs == NULL) cs = new C[m_size];
if (m_length >= m_size) {
C* temp = new C[m_size * 2];
for (int i = 0; i < m_size; i++) {
temp[i] = cs[i];
}
delete[] cs;
cs = NULL;
cs = temp;
m_size *= 2;
}
cs[m_length++] = obj;
}
void deleteLast() {
if (m_length <= 0) return;
m_length--;
cs[m_length] = NULL;
}
C& operator[](int n) {
return cs[n];
}
void operator=(OArray<C>& o) {
this->m_size = o.m_size;
if (this->cs != NULL) delete[] this->cs;
this->cs = NULL;
this->m_length = 0;
this->cs = new C[o.getSize()];
for (int i = 0; i < o.getLength(); i++) {
add(o.cs[i]);
}
}
int getLength() {
return m_length;
}
int getSize() {
return m_size;
}
void print() {
for (int n = 0; n < m_length; n++) {
cout << cs[n] << '\t';
}
cout << endl;
}
~OArray() {
if (this->cs != NULL) delete[] this->cs;
this->cs = NULL;
}
private:
int m_size = 0;
int m_length = 0;
};