C++模板初阶 -- 讲解超详细
目录
1.泛型编程
2. 函数模板
2.1 函数模板的概念
2.2 函数模板格式
2.3 函数模板原理
2.4 函数模板的实例化
2.5 模板参数的匹配原则
3. 类模板
3.1 类模板的格式
3.2 类模板实例化
3.3 模板分离
1.泛型编程
我们在理解泛型编程之前,先来看一个例子:
int add(int x, int y)
{return x + y;
}double add(double x, double y)
{return x + y;
}int main()
{cout << add(1, 2) << endl;cout << add(6.6, 2.5) << endl;return 0;
}
这个时候就很烦,如果每次实现两个数相加,不同于以上类型那我岂不是每次都要写一个重载函数?有没有一个办法能解决这个问题?能不能让编译器自动识别类型并计算结果?
这个时候C++就提出了泛型编程,什么是泛型编程?
简单理解:就相当一个模具,我们需要什么类型的物品就使用什么模具,在程序中就是当我们要传不同的参数,编译器可以套模板自动完成加减。(官方解释:泛型编程 - 编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。)
在C++中模板分为两种,一种是函数模板,一类是类模板!!!
2. 函数模板
刚刚了解了泛型编程,在此基础上又知晓了模板,那如何去使用模板?
2.1 函数模板的概念
函数模板:自动识别参数的类型,生成对应版本的函数,并计算对应的结果。
2.2 函数模板格式
template<typename T1, typename T2,......,typename Tn>模板 函数类型
template<class T1, class T2,......,class Tn>
注意:class和typename是关键字,二者都是可以用;但是不能用struct代替class(语法规定)
struct在C语言是结构体,在C++中升级为类(既可以是结构体,也可以是类);为了避免搞混,模板使用typename和class。(而typename和class也是有区别的,如果类模板实例化函数使用过程如果对模板的依赖性比较强,必须是用typename;其他情况二者都可以使用)(这里不是很好解释,后面写完几个容器时再解释)
template<class T>
const T& add(const T& x, const T& y)
{return x + y;
}int main()
{cout << add(1, 2) << endl;cout << add(6.6, 2.5) << endl;return 0;
}
2.3 函数模板原理
那这时如果两个参数不一样咋办?欸,编译器就会报错,还能咋办?!
只有一个模板参数T,传int和double,那我该是int还是double?编译器cpu干爆了都想不出来,那直接报错。
那这个咋解决?很简单啊,1个不行,那传2个模板T不就解决了!
template<class T1, class T2>
const T2& add(const T1& x, const T2& y)
{return x + y;
}int main()
{cout << add(1, 2) << endl;cout << add(6.6, 2.5) << endl;cout << add(6, 1.1) << endl;return 0;
}
总结:在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
2.4 函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化 和显式实例化。
(1)隐式实例化:编译器自己根据数据推导。
template<class T>
const T& add(const T& x, const T& y)
{return x + y;
}int main()
{cout << add(1, 2) << endl;//编译器将1和2推导为intcout << add(6.6, 2.5) << endl;//编译器将6.6和2.5推导为doublereturn 0;
}
同时像刚才那样两个参数类型不同怎么办?
方法:(1)用户强制转化类型;(2)增加模板参数;(3)显示实例化
int main()
{cout << add(6, (int)1.1) << endl;cout << add((double)6, 1.1) << endl;return 0;
}
(2)显示实例化:在函数名后的<>中指定模板参数的实际类型
int main()
{cout << add<double>(6, 1.1) << endl; //7.1cout << add<int>(6, 1.1) << endl; //7return 0;
}
2.5 模板参数的匹配原则
(1)当非模板函数和模板函数同时存在时,函数模板也可以实例化为该函数。
//现成函数
void swap(int x, int y)
{int tmp = x;x = y;y = tmp;
}template<class T>
void swap(T x,T y)
{T tmp = x;x = y;y = tmp;
}int main()
{int a = 10;int b = 50;swap(a, b);//调用第一个swap<int>(a,b);//调用模板函数return 0;
}
(2)对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板(简而言之:有现成吃现成)
// 现成函数
int add(int x, int y)
{return x + y;
}template<class T1,class T2>
T2 add(T1 x, T2 y)
{return x + y;
}int main()
{int a = 10;int b = 100;double c = 50;add(a, b);//吃现成,调用第一个函数add(a, c);//没有现成,套模板return 0;
}
3. 类模板
3.1 类模板的格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};
在类模板里面我们常用关键字class,那类模板是如何使用的呢?请看下面:
template <class T>
class stack
{
public:stack(size_t capacity = 4):_size(0),_capacity(capacity)//,_arr(new T[_capacity]) //注意这里是按照声明顺序初始化{_arr = new T[_capacity];}
private:T* _arr;size_t _size;size_t _capacity;
};
这里只是对类模板的一些初步认识,在后面的容器会不断的使用类模板。
那这时类模板的调用是否是和普通函数一样? -- 答案是否定的
那类模板为什么不能根据数据自动推到类型实例化成合适的函数?
这个是由语法规定的,类模板实例化对T的依赖性比较高。
3.2 类模板实例化
类模板实例化和函数模板实例化是有区别的:
(1)函数模板实例化,会根据类型自动推导类型实例化;
(2)类模板实例化,由语法规定:类名+<T>才是类型。
在类里面,类名可以作为类型;
在类模板里面,类名+<T>才是类型。(必须+<T>,因为传参过程中形参不涉及T,但是实现过程用到了T)
int main()
{date<int> d1(2024, 9, 1);stack<int> s1(20);return 0;
}
3.3 模板分离
这里提一下:普通函数声明定义分离是可以的,但是模板函数和类是不支持分别定义在.h和.cpp文件的,会出现链接错误,至于为什么?就涉及到预处理,编译,汇编和链接这几个阶段了。
我们先简单了解一下;
预处理阶段 | 展开头文件,宏替换,条件编译和去注释 |
编译阶段 | 检查语法错误,生成汇编代码 |
汇编阶段 | 生成机器可以读懂的二进制汇编指令 |
链接阶段 | 合并生成可执行文件,链接函数地址 |
在类和对象中的时候我们已经了解了call指令
基于此函数距离编译还差一个实例化<T>,这个T只有.cpp知道,但是每个文件又是单向操作,我在编译的时候是不知道的,但是编译时头文件:就是告诉编译器,你不用管待会儿给你,你先运行,但是链接的时候我去找编译器要,欸没有,这不是欺诈吗?所以就会出现链接错误。
举个通俗易懂的例子:
类模板这里只是初步的了解,后面会更加深入的学习。