4-1 模板概念,函数模板定义、调用、非类型模板参数

 

目录

 

4-1 模板概念,函数模板定义、调用、非类型模板参数

函数模板

非类型模板参数


4-1 模板概念,函数模板定义、调用、非类型模板参数

概述:
1、所谓泛型编程,是以独立于任何特定类型的方式编写代码。使用泛型编程时,我们需要提供具体程序实例所操作的类习惯或者值。

2、模板是泛型编程的基础。模板是创建类型或者函数的蓝图或者公式。我们给这些蓝图或者公式提供足够的信息,让这些蓝图或者公式真正变成具体的类或者函数,这种转变发生在编译时。

3、模板支持将类型作为参数程序设计方式,从而实现了对泛型程序设计的直接支持。也就是说C++模板机制语序在定义类、函数时将类型作为参数。

模板一般分为 函数模板与类模板
函数模板的定义

template<typename T>
T funcadd(T a, T b)
{
    T he = a + b;
    return he;
}

1、模板定义使用template关键开头的,后面跟<>,<>里边 叫 模板参数列表(模板实参), 如果模板参数列表中有多个参数,则用逗号分开。<>里必须至少有一个模板参数。模板参数前面有一个关键字 typename/class(不是用来定义类的)

2、如果模板参数列表中有多个模板参数,那你就要用多个typename/class:<typename T, typename Q>

3、模板参数列表中表示在函数定义中要用到的 “类型” 或者 “值”,也和函数的参数列表类似。我们用的时候,有的时候得指定模板实参给他,指定的时候我们要用<>把模板实参包起来。有的时候又不需要我们指定模板实参,系统能够根据一些信息推断出来。

4、funcadd这个函数声明了一个名字为T的类型参数,这里T实际是类型,这个T到底是啥类型,编译器在编译的时候会根据funcadd的调用来确定。

函数模板的使用
函数模板调用函数调用区别不大,调用的时候,编译器会根据你调用这个函数模板时的实参去推断这个模板参数列表里的参数类型。

模板参数是推断出来的:推断的依据是根据你调用这个函数的时候(有的时候需要你提供),函数的实参来推断的。当然有时候光凭借你提供的函数实参推断不出来模板参数,这个时候就需要用<>来主动提供模板参数了。

int he = funcadd(2, 1); //2,1系统认为是int,所以系统能推断出模板的形参是int,也就是 
                        //说T是int型
 
int he = funcadd(2.1f, 1); //报错,无法推断是double型还是int型
 
int he = funcadd(2.1f, 1.1f); //可以,这里返回仍为double型

非类型模板参数

因为T前边有一个typename/calss ,这表示T代表一个类型,是一个类型参数。

那么在模板参数列表里边,还可以定义非类型参数;非类型参数代表的是一个值。

既然非类型参数代表一个值,那么我们肯定不能用typename/class这种关键字来修饰这个值

我们当然要用以往学习过的传统类型名来指定费类型参数。比如你非类型参数S,如果是个整型,那么就用 int s。

当模板被实例化时,这种非类型模板参数的值,或者是用户提供的,或者是编译器推断的,都有可能。

但是这些值都必须是常量表达式。因为实例化这些模板实在编译器编译时进行的。

template <int a, int b>
int func2()
{
    int he = a + b;
    return he;
}
 
 
int result = func2<2, 3>(); //显示指定模板参数——用<>提供额外信息
 
int i = 12;
int result2 = func2<i, 3>(); //不可以,报错,必须给定编译时就能确定的值这里i是在运行 
                             //时才能确定的值

 实例化模板实在编译时进行的不是运行时

template <typename T, int a, int b>
int func3(T c)
{
    int result = (int)c + a + b;
    return result;
}
 
 
int i = func3<int, 11, 12>(13);
 
int i = func3<double, 11, 12>(13); //这里系统会以<>传递的类型为准,将13转成double型

 

这里 L1与L2的值是编译器推断出来的

template <unsigned L1, unsigned L2>
int charscomp(const char (&p1)[L1], const char (&p2)[L2])
{
    return strcmp(p1, p2);
}
 
int result3 = charscomp("test1","test"); //没有提供费类型模板参数,系统会根据test1 
                                         //的长度6个,test的长度5个,来取代L1,L2

模板函数可以是内联函数

template <typename T, int a, int b>
inline
int func3(T c)
{
    int result = (int)c + a + b;
    return result;
}

模板定义并不会导致编译器生成代码,只有在我们调用这个函数模板时,使用编译器为我们实例化了一个特定版本的函数之后编译器才会生成代码。

编译器生成代码的时候,需要能够找到函数的函数体,所以,函数模板的定义通常都是在 .h 中

 

 

函数模板

https://www.cnblogs.com/lifexy/category/1153105.html

1)初探函数模板

2)深入理解函数模板

3)多参函数模板

4)重载函数模板

当我们想写个Swap()交换函数时,通常这样写:

void Swap(int& a, int& b)
{
    int c = a;
    a = b;
    b = c;
}

但是这个函数仅仅只能支持int类型,如果我们想实现交换double,float,string等等时,就还需要从新去构造Swap()重载函数,这样不但重复劳动,容易出错,而且还带来很大的维护和调试工作量。更糟的是,还会增加可执行文件的大小.

所以C++引入了泛型编程概念

函数模板

  • 一种特殊的函数,可通过不同类型进行调用
  • 函数模板是C++中重要的代码复用方式
  • 通过template关键字来声明使用模板
  • 通过typename关键字来定义模板类型

(1)模板定义是用template 关键字开头的,后边跟<>, <>里面的叫做参数列表(模板参数)
<>里必须至少得有一个模板参数。有typename  或者 class(不是用来定义类的)关键字。参数列表里,如果有多个模板参数,就要用多个typename
(2)模板参数列表里边表示在函数定义中用到的“类型”或者“值”,也和函数参数列表类似,那我们用的时候,有的时候指定 模板参数,指定的时候我们要用<>把模板参数包起来,有的时候又不需要指定。系统会根据一系列的信息,自动推断出来。
(3) 这个函数声明了一个名字为T的类型参数这里注意,T实际是类型,编译器在编译的时候会确定。

template <typename T>       //声明使用模板,并定义T是一个模板类型

void Swap(T& a, T& b)           //紧接着使用T
{
    T c = a;
    a = b;
    b = c;
}
int a=0;
int b=1;
Swap(a,b);                自动调用,编译器根据a和b的类型来推导

float c=0;
float d=1;
Swap<float>(c,d);          显示调用,告诉编译器,调用的参数是float类型

函数模板的使用


函数模板调用 和 函数调用的区别不大,调用的时候,编译器会根据你调用这个函数模板时的实参去,推断模板参数列表里的参数(形参)的类型
模板参数:有时候是推断出来的,有的时候用<>来主动提供模板参数。

#include <iostream>
using namespace std;

//函数模板
template <typename T>
T funcadd( T a , T b)
{
    T add = a + b;
    return add;
}

int main()
{
    int add = funcadd (3 , 1);  编译器能自己推断。一调用就会实例化int版本。
    cout << add <<endl;

    编译器会推断出来这个模板的形参类型,就会为我们实例化一个特定版本的函数。
    double b2 = funadd( 3.1f , 3.2f) ; //实例化double的版本
    //double b3 = funadd(3.1f , 2);   //出错
    return 0;
}

深入理解函数模板

为什么函数模板能够执行不同的类型参数?

答:

  • 其实编译器对函数模板进行了两次编译
  • 第一次编译时,首先去检查函数模板本身有没有语法错误
  • 第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数
  • 所以函数模板,其实只是一个模具,当我们调用它时,编译器就会给我们生成真正的函数.

试验函数模板是否生成真正的函数

通过两个不同类型的函数指针指向函数模板,然后打印指针地址是否一致,代码如下:

#include <iostream>

using namespace std;

template <typename T>       
void Swap(T& a, T& b)        
{
    T c = a;
    a = b;
    b = c;
}   

int main()
{
    void (*FPii)(int&,int&);  

    FPii = Swap ;                   //函数指针FPii

    void (*FPff)(float&,float&);

    FPff = Swap ;                  //函数指针FPff

    cout<<reinterpret_cast<void *>(FPii)<<endl;
    cout<<reinterpret_cast<void *>(FPff)<<endl;
   //cout<<reinterpret_cast<void *>(Swap)<<endl;
              //编译该行会出错,因为Swap()只是个模板,并不是一个真正函数
    return 0;
}

运行打印:

0x41ba98

0x41ba70

可以发现两个不同类型的函数指针,指向同一个函数模板,打印的地址却都不一样,显然编译器默默帮我们生成了两个不同的真正函数

多参数函数模板

在我们之前小节学的函数模板都是单参数的, 其实函数模板可以定义任意多个不同的类型参数,例如:

template <typename T1,typename T2,typename T3>       
T1 Add(T2 a,T3 b)
{
    return static_cast<T1>(a+b);      
}

注意:

  • 工程中一般都将返回值参数作为第一个模板类型
  • 如果返回值参数作为了模板类型,则必须需要指定返回值模板类型.因为编译器无法推导出返回值类型
  • 可以从左向右部分指定类型参数 

非类型模板参数

#include <iostream>
using namespace std;
#include <cstring>
//非类型模板参数
//因为T前边有一个typename/class ,这表示T代表一个类型,是一个类型参数。
//这如果是 int a ,就是非类型参数。
template <int a, int b>
int funcadd ()
{
    int add = a + b;
    return add;
}

template <typename T , int a, int b>
int funcadd2 (T c)
{
   int ret;
   ret = (int)c + a + b;
   return ret;
}

int main()
{
    int add = funcadd <12 , 13>( );//显式的指定模板参数--在尖括号中提供额外的信息。
    cout << add <<endl;

    int a = 12;
    int result = funcadd <a,13>;//这个不可以:非类型模板参数,值必须在编译的时候就能确定。注意编译

    int result = funcadd2 <int ,11,12> (13);
    cout << result <<endl;


    int result2 = charscomp("test2" ,"test");//没有提供非类型模板参数,系统会根据test2和
    //test的长度,为6 和 5
    cout << result2 <<endl;
    return 0;
}
#include<iostream>
using namespace std;
#include <cstring>

template <unsigned L1 , unsigned L2>
inline //inline放在模板参数列表之后
int charscomp(const char(&p1)[L1] , const char (&p2)[L2])
{
  return  strcmp(p1,p2);
}

int main()
{

    int result = charscomp("test2" ,"test");//没有提供非类型模板参数,系统会根据test2和
                                              //test的长度,为6 和 5
    cout << result2 <<endl;
    return 0;
}

重载函数模板

  • 函数模板可以像普通函数一样被重载
  • 函数模板不接受隐式转换
  • 当有函数模板,以及普通重载函数时,编译器会优先考虑普通函数
  • 如果普通函数的参数无法匹配,编译器会尝试进行隐式转换,若转换成功,便调用普通函数
  • 若转换失败,编译器便调用函数模板
  • 可以通过空模板实参列表来限定编译器只匹配函数模板

#include <iostream>
  
using namespace std; 

template <typename T>        
T Max(T a,T b)
{
    cout<<"T Max(T a,T b)"<<endl;   
    return a > b ? a : b;
} 

template <typename T>        
T Max(T* a,T* b)                    //重载函数模板 
{
    cout<<"T Max(T* a,T* b)"<<endl;    
    return *a > *b ? *a : *b;
} 


int Max(int a,int b)                //重载普通函数 
{
    cout<<"int Max(int a,int b)"<<endl;   
    return a > b ? a : b;
}  
 
int main()
{  
    int a=0;
    int b=1;
    
    cout<<"a:b="<<Max(a,b) <<endl ;        //调用普通函数 Max(int,int)
    
    cout<<"a:b="<<Max<>(a,b)<<endl;        //通过模板参数表 调用 函数模板 Max(int,int)
    
    cout<<"1.5:2.0="<<Max(1.5,2.0)<<endl;   
     //由于两个参数默认都是double,所以无法隐式转换,则调用函数模板 Max(double,double)
    
    int *p1 = new int(1);
    int *p2 = new int(2); 
 
    cout<<"*p1:*p2="<<Max(p1,p2)<<endl;  // 调用重载函数模板 Max(int* ,int* )    
    
    cout<<"'a',100="<< Max('a',100)<<endl;        
    //将char类型进行隐式转换,从而调用普通函数 Max(int,int)
    
    
    delete p1;
    delete p2; 
    
    return 0;
}

结果

int Max(int a,int b)
a:b=1

T Max(T a,T b)
a:b=1
 
T Max(T a,T b)
1.5:2.0=2

T Max(T* a,T* b)
*p1:*p2=2

int Max(int a,int b)
'a',100=100

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值