C++模板初阶
- 泛型编程
- 函数模板
- 概念
- 函数模板格式
- 函数模板原理
- 函数模板的实例化
- 模板参数的匹配原则
- 类模板
- 类模板的定义格式
- 类模板的实例化
泛型编程
我们前面学习了C++的函数重载功能,那么我们如何实现一个通用的交换函数呢,比如:我传入int就是交换int,传入double就进行double类型的交换……
void Swap( int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
void Swap(double& a, double& b)
{
double temp = a;
a = b;
b = temp;
}
void Swap(char& a, char& b)
{
char temp = a;
a = b;
b = temp;
}
void Swap(int*& a, int*& b)
{
int* temp = a;
a = b;
b = temp;
}
我们可以发现尽管有了函数重载的技术,但是我们还是避免不了所有问题;我们很难写出一个通用的Swap函数,如果我们每增加一个新类型,我们就得重新写一个Swap函数,很麻烦!那么有没有什么办法让我只写一份通用的代码,所有类型都可以用呢?当然有!对于C语言的这种不足,C++在此基础上提出了泛型编程的概念:就是对于一些所有类型都可通用的代码,比如Swap这种我们在写具体实现的时候可以假装我们的数据类型是通用的,然后在对其进行处理,当我们在调用该函数的时候,编译器会自动推演出数据的具体类型,这就好比我们在写方程一样,我们先假设未知数,将未知数先当成已知,最后在解出未知数!这里自动推演数据类型的过程就相当于在解方程!
这个编写通用代码的过程就是在造模子通过给这个模子传递不同的参数类型,来确定具体的数据类型,从而获得具体类型对应的代码,生成具体类型代码的过程是由编译器来帮助我们完成的,我们无需在自己手动写了;
在C++中模板是泛型编程的基础!
模板又分为:函数模板、类模板;
函数模板
概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用参数时,由编译器根据所传参数类型自动推演函数参数的具体类型,由此产生特定类型的函数版本,这个过程,被称为实例化;
函数模板格式
template<class 类型参数名1,class 类型参数名2,…… >
返回类型 函数名(参数列表)
或者
template<typename 类型参数名1,typename 类型参数名2……>;
返回类型 函数名(参数列表)
注意:typename是用来定义模板参数的关键字,class也可以,但是struct不可以;
实例:写一个通用的交换函数:
template <typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
函数模板原理
么如何解决上面的问题呢?大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。机器生产淘汰掉了很多手工产品。本质是什么,重复的工作交给了机器去完成。有人给出了论调:懒人创造世界。
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器;
函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化;
1、隐式实例化
上述Add的调用,就是隐式实例化,编译器会根据我们传递给Add的参数自动推演出类型参数的具体类型,以此来生成具体类型的交换函数来给我们用;
当然我们不能乱传递参数类型,比如:Add(a,x);
编译器在调用该Add函数的时候,根据左参数类型先推演出类型参数T为int类型,然后又根据有参数类型推演出类型参数T为double类型,那么参数类型到底具体为那个类型?这就会让编译器陷入歧义,编译器也就会报错,Add函数也就无法正确实例化!类型参数,会根据以第一次推演出的结果作为默认类型,与auto在一排的用法差不多,但是模板无法完成参数的隐式转换!
那如果我们就是要调用Add(a,x);函数应该怎么办?
有两种解决办法:
1、强转a,或强转x,就是将a,x的类型转换成一致的;
2、显示实例化;
2、显示实例化:
就是明确指定类型参数的具体类型,不需要编译器再根据参数类型自动推演了;明确指定参数的具体类型过后,编译器就会自动生成具体函数,来供调用处使用!
比如上述的Add(a,x)
我们像这样做:Add<int>(a,x);显示的指定类型参数为int,编译器就会自动生成参数类型为int的Add函数:
如果类型不匹配编译器会自动尝试隐式转换!无法完成完成隐式类型转换的编译器直接报错;
这里的隐式类型转换与上面隐式调用的类型转换不一样,这里使明确规定了类型参数的具体类型,也就是先生成了具体函数,才有的隐式类型转换;上面是根据参数类型推演类型参数的具体类型,然后再生成匹配的函数,参数不匹配,无法生成具体函数,怎么能进行隐式转换呢?
模板参数的匹配原则
1、非模板函数与函数模板同名,同时函数模板还可以实例化成非模板函数,编译器会优先调用非模板函数;
为什么会出现这种情况呢?
编译器是很聪明的,如果我们去调用函数模板时,编译器会首先根据参数类型推演出类型参数的具体类型,然后实例化出具体的Add函数来调用,你看啊,现在我明明有现成的Add函数可以用,那么我们为什么还要去实例化,再调用呢?这不是脱了裤子放屁,多此一举嘛!
那如果我们非要调用模板实例化出来的Add函数,该怎么办呢?
当然是显示调用:
2、对于非模板函数与同名函数模板,其他条件都相同,但是非模板函数的参数类型与调用处的类型不一样,编译器会根据函数模板实例化出最匹配的函数来调用:
3、对于隐式实例化函数模板,不会发生隐式类型转换;对于显示实例化函数模板,可以发生隐式类型转换;
类模板
类模板的定义格式
template<class T1,class T2,class T3,……>
class 类模板名
{
//类成员定义;
};
比如现在我们写一个栈类模板:
template <class T>
class Stack
{
Stack()
{
int capacity = 4;
_a = new T[capacity];
_top = 0;
_capacity = capacity;
}
~Stack()
{
delete[] _a;
_top = _capacity = 0;
}
private:
T* _a;
int _top;
int _capacity;
};
类模板中的成员函数全是模板函数
然后注意此时的Stack不是具体的类,而是编译器根据被实例化的类型生成具体类的模具;
同时类模板的成员函数也可以实现在类内声明,类外定义;
只不过与我们平时写的具体的类的定义方式不太一样:
比如现在我在类内声明构造函数,类外定义构造函数:
这样我们既能定义基本数据类型为double的栈,也可以定义基本数据类型为int的栈了:
这时候或许会有读者说,我用typedef也能做到类型替换啊,可是typedef不能做到Stack<int>与Stack<double>两种栈同时存在啊,如果想要这两个栈同时存在,我们就只能再重新定义一个栈,这样很是麻烦!
同时不建议类模板的声明与函数定义分离!
类模板的实例化
类模板的实例化与函数模板的实例化不同,类模板实例化只能通过显示实例化,不能隐式实例化: