【C++快速上手】十四、C++模板深入学习
一、模板的概念
对重载函数而言,C++的检查机制能通过函数参数的不同及所属类的不同。正确的调用重载函数。例如,为求两个数的最大值,我们定义MAX()函数需要对不同的数据类型分别定义不同重载(Overload)版本。
//函数1.
int max(int x,int y);
{return(x>y)?x:y ;}
//函数2.
float max( float x,float y){
return (x>y)? x:y ;}
//函数3.
double max(double x,double y)
{return (c>y)? x:y ;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
如果在主函数中,我们分别定义了 char a,b; 那么在执行max(a,b);时 程序就会出错,因为我们没有定义char类型的重载版本。
现在,我们再重新审视上述的max()函数,它们都具有同样的功能,即求两个数的最大值,能否只写一套代码解决这个问题呢?这样就会避免因重载函数定义不 全面而带来的调用错误。为解决上述问题C++引入模板机制,模板定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。
二、函数模板
2.1、函数模板基础
函数模板的定义:一种特殊的函数,可以使用不同的类型进行调用,对于功能相同的函数,不需要重复编写代码,并且函数模板与普通函数看起来很类似,区别就是类型可以被参数化。
函数模板通过template与typename两个关键字来定义,如下:
在使用函数模板时有两种方式
- 自动推导调用:
Swap(a, b)
,a是int类型,故对应的T被推导为int类型。 - 显式指定调用:
Swap<int>(a, b)
,指定T是int类型。
下边以代码来体验一下函数模板:
#include <iostream>
using namespace std;
template <typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
void main()
{
int a = 10;
int b = 20;
Swap(a, b); //自动推导调用
//Swap<int>(a, b);//显式指定调用
cout << "a = " << a << endl;
cout << "b = " << b << endl;
float c = 12.3;
float d = 23.4;
//Swap(c, d); //自动推导调用
Swap<float>(c, d); //显示指定调用
cout << "c = " << c << endl;
cout << "d = " << d << endl;
system("pause");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
2.2、函数模板深入
下边来深入理解下函数模板:
- 1、对于函数模板中使用的类型不同,编译器会产生不同的函数
- 2、编译器会对函数模板进行两次编译
- 第一次是对函数模板本身进行编译,包括语法检查等
- 第二次是对参数替换后的代码进行编译,这就相当于编译普通函数一样,进行类型规则检查等。
需要注意的是:
- 3、函数模板是不允许隐式类型转换的,调用时类型必须严格匹配。
- 4、函数模板还可以定义任意多个不同的类型参数,但是对于多参数函数模板:
- 编译器是无法自动推导返回值类型的
- 可以从左向右部分指定类型参数
#include <iostream>
using namespace std;
template <typename T1, typename T2, typename T3>
T1 add(T2 a, T3 b)
{
T1 ret;
ret = static_cast<T1>(a + b);
return ret;
}
int main()
{
int c = 12;
float d = 23.4;
//cout << add(c, d) << endl; //error,无法自动推导函数返回值
cout << add<float>(c, d) << endl; //返回值在第一个类型参数中指定
cout << add<int, int, float>(c, d) << endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
编译执行:
在上边的代码中,我们定义了多类型参数的函数模板,调用时需要注意的是函数返回值需要在第一个参数类型中显示指定,后边的类型可自动推导或显示指定。
函数模板跟普通函数一样,也可以被“重载”!
- C++编译器优先考虑普通函数
- 如果函数模板可以产生一个更好的匹配,那么就选择函数模板
- 也可以通过空模板实参列表
<>
限定编译器只匹配函数模板
#include <iostream>
using namespace std;
template <typename T>
void fun(T a)
{
cout << "void fun(T1 a)" << endl;
}
template <typename T1, typename T2>
void fun(T1 a, T2 b)
{
cout << "void fun(T1 a, T2 b)" << endl;
}
void fun(int a, float b)
{
cout << "void fun(int a, float b)" << endl;
}
int main()
{
int a = 0;
float b = 0.0;
fun(a);
fun(a, b); //普通函数void fun(int a, float b)已经能完美匹配,于是调用普通函数
fun(b, a); //这个调用,函数模板有更好的匹配,于是调用函数模板
fun<>(a, b); //限定只使用函数模板
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
编译输出:
从输出可以得到,编译器会优先去调用普通函数,但是当函数模板有更好的匹配时或使用限定符<>
时,编译器就会去匹配函数模板。
总结
- 函数模板是泛型编程在C++中的应用方式之一
- 函数模板能够根据实参对参数类型进行推导
- 函数模板支持显示的指定参数类型
- 函数模板是C++中重要的代码复用方式
- 函数模板通过具体类型产生不同的函数
- 函数模板可以定义任意多个不同的类型参数
- 函数模板中的返回值类型必须显示指定
- 函数模板可以像普通函数一样“重载”
小知识点:表示类型的模板参数关键字除了有
typename
还有class
,两者通常不做区别。
三、类模板
将模板的思想应用于类,我们就可以只关注类的功能实现,不需要关注具体数据元素的类型,这种思想非常适用于编写数据结构相关的代码,比如数组类、线性表、栈和堆等,只需要实现他们的逻辑功能,不必关注具体的数据类型。
C++中的类模板与函数模板一样,都是使用template <typename T>
这样的格式来声明使用的是类模板,像如下:
template <typename T>
class test
{
private:
T m_value;
public:
test(T value)
{
m_value = value;
}
T get()
{
return m_value;
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
这样我们就定义了一个简单的类模板,其中的T代表任意的类型,可以出现在类模板中的任意地方,与函数模板不同的是,使用类模板时必须显示的指定数据类型,编译器无法自动推导,例如test<int> t
;需要显示的指定数据类型。
编译器对类模板的处理与对函数模板的处理相同:
- 类模板通过具体类型产生不同的类
- 在声明的地方对类模板代码本身进行编译
- 在使用的地方对参数替换后的代码进行编译
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class test
{
private:
T m_value;
public:
test(T value)
{
m_value = value;
}
T get()
{
return m_value;
}
};
int main()
{
//test t(12.3); //error, 不能自动推导类型
test<int> t1(12); //显示指定类型为int
test<string> t2("hello C++"); //显示指定类型为string
cout << t1.get() << endl;
cout << t2.get() << endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
编译输出:
我们在定义类模板时,一般的规则是:
- 将类模板定义在头文件中
- 类模板里边的成员函数,必须要同一个文件中实现,不可分开实现在不同的文件中
- 类模板外部定义的成员函数需要加上
template <typename T>
类似的声明
如下,我们将上边的test类模板实现在test.h头文件中,并将成员函数在外边实现:
#ifndef __TEST_H__
#define __TEST_H__
template <typename T>
class test
{
private:
T m_value;
public:
test(T value)
{
m_value = value;
}
T get();
};
template <typename T> //类外实现的成员函数需要加上类似的模板声明
T test<T>::get()
{
return m_value;
}
#endif //__TEST_H__
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
与函数模板一样,类模板也可以定义多个参数,例如:
template <typename T1, typename T2>
class test
{
private:
T1 m_value1;
T2 m_value2;
public:
test(T1 value1, T2 value2)
{
m_value1 = value1;
m_value2 = value2;
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
总结
- C++类模板与函数模板一样,以相同的方式处理不同的类型,达到代码复用
- 类模板非常适用于编写数据结构相关的代码
- C++类模板在使用时只能显示指定类型
四、模板补充
看下面一段代码:
template <typename T, int N>
...
- 1
- 2
问:这里的N是什么意思呢?说实话,第一次看到它,我也确实懵了…
答:用一个int数做模板参数。
再问:也就是说在函数或者类里面要用这个N,那为什么不直接里面定义它?
再答:因为要作为模板参数啊,由使用模板的人来给这个N的具体值。 比如标准库的std::array,可以std::array<int,10>,就是长度为10的数组了。
再来看一段代码也许就会更明白了:
#include <iostream>
using namespace std;
template<class T, int N> // 这里除了含有class声明的类型之外,还拥有一个int类型
class mysequence {
T memblock [N];
public:
void setmember(int x, T value);
T getmember(int x);
};
//类外实现的成员函数
template<class T, int N>
void mysequence<T,N>::setmember(int x, T value) {
memblock[x]=value;
}
//类外实现的成员函数
template<class T, int N>
T mysequence<T,N>::getmember(int x) {
return memblock[x];
}
int main(){
mysequence<int,5> myints;
mysequence<double,5> myfloats;
myints.setmember(0,100);
myfloats.setmember(3,3.1416);
cout << myints.getmember(0) << '\n';
cout << myfloats.getmember(3) << '\n';
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
参考
文章来源: recclay.blog.csdn.net,作者:ReCclay,版权归原作者所有,如需转载,请联系作者。
原文链接:recclay.blog.csdn.net/article/details/114081728
- 点赞
- 收藏
- 关注作者
评论(0)