【C++快速上手】十四、C++模板深入学习

举报
ReCclay 发表于 2022/02/22 00:59:21 2022/02/22
【摘要】 一、模板的概念 对重载函数而言,C++的检查机制能通过函数参数的不同及所属类的不同。正确的调用重载函数。例如,为求两个数的最大值,我们定义MAX()函数需要对不同的数据类型分别定义不同重载(Overlo...

一、模板的概念

重载函数而言,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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。