《C++ Templetes》《1、函数模板》

在《 Effective C++》中第一个建议就是,把C++看作是一个语言的联邦,其中Templates在联邦中留下了浓墨重彩的一笔。首先说一下为什么需要使用模板;在C语言中是没有模板这个概念的,C中如果声明一个函数,或者指定类型,在程序中是不可更改的。如果一个类似的问题,需要一段代码进行处理,除了输入类型不同外,其余的代码都是相同的,这个时候就需要模板来解决这个问题。

1、函数模板

1.1 函数模板的简单示例:

函数模板提供了一种函数行为,这个行为可以用多种不同的类型进行调用。问题还是那个问题,但是解决办法更具有通用性,如果你也有同样的需求,你就能拿过来使用,不受限于你是何种类型。
举一个简单的栗子:

//用来返回两个值中较大的那个的函数模板
template <typename T>
inline T const& max_num(T const& a,T const & b){
	return a < b ? b : a;
}

上面就是一个简单的模板,通过参数a,b将两个值传递给函数模板;模板参数用T来代替。
Template的的使用方法:
template <用逗号隔开的参数列表>,在上面的例子中,参数类型是T,你可以使用任意标识符作为类型参数的名称,A,B,C,D都可以,但是使用T已经成为了一种惯例。
举一个使用的栗子:

#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& max_num(T const& a,T const& b){
    return a < b? b:a;
};
int main(){
    int i = 42,j = 40;
    cout << "max(i,j) : "<<max_num(i,j) << endl;
    
    double d1 = 1.23,d2 = 4.56;
    cout << "max(d1,d2) : "<<max_num(d1,d2) << endl;
    
    string s1 = "hello",s2 = "world";
    cout << "max_num(s1,s2) : "<<max_num(s1,s2)<<endl;  

    return 0;
}

//输出内容:

max(i,j) : 42
max(d1,d2) : 4.56
max_num(s1,s2) : world

...Program finished with exit code 0
Press ENTER to exit console.

在上面的栗子中,max_num被调用了3次,3次的参数每次都不同,分别是int,double,string。通常而言,模板函数并不是一个可以处理任何类型的单一实体,而是对于实例化模板参数的每种类型,都从模板产生出一个不同的实体。
在传入int参数时,声明和实现会自动帮你生成出来:

const int& max_num(int const&,int const&);
inline int const& max_um(int const& a,int const& b){
	return a<b?b:a;
}

也就是说,他会帮你将具体类型代替模板参数,这个过程叫做实例化。
如果,函数模板本身出问题,或者调用了不支持模板内部使用的参数,程序编译都会报错。这说明模板被编译了2次;
1、实例化之前:检查模板本身是否有语法错误;
2、实例化期间:产看是否所有调用都有效;

1.2 模板参数

函数模板有2种类型的参数:
(1)模板参数:位于函数模板名称前面,在尖括号内声明

template <typename T>

(2)调用参数:位于函数模板名称之后,在圆括号内声明

max_num(T const& a,T const& b)

在实际中,也有可能出现不同类型的情况,但是模板是死脑筋,如果是int,double,这两种不同的参数类型,模板是不允许进行自动类型转换的。
举个栗子:

max_num(4,5) 		//返回其中较大的数值
max_num(4,4.2) 		//出错

这个问题,有3种常规的解决办法:

  • a.对实参进行强制类型转换
max_num(static_cast<double>(4),4.2)
  • b.显式地指定T的类型
max<double>(4,4.2)
  • c.在声明和实现中,让2个参数具有不同的类型;
//指定返回T1类型
template <typename T1,typename T2>
inline T1  max_num(T1 const& a,T2 const& b){
    return a < b? b:a;
};
...
max_num(4,4.2);

这个与之前,增加了一个参数类型,但是把这2个参数转型为返回类型的过程会创建一个新的局部临时对象,这样就不能返回引用,只能返回T1。

  • d.引入额外的参数类型
template<typename T1,typename T2,typename RT>
inline RT max_num(T1 const& a,T2 const& b);
//明确指定各个参数的类型;
max_num<int,double,double>(4,4.2);
//只指定返回的类型,也可;
max_num<double>(4,4.2)

1.3 实参演绎

在C++中,模板(Templates)是一种强大的泛型编程工具,它允许程序员在编写代码时使用类型参数,从而使代码更加通用和灵活。模板可以分为函数模板和类模板两种类型。而实参演绎(Argument Deduction)是函数模板中一个非常关键的概念,它指的是编译器根据函数模板调用时提供的实际参数(实参)的类型来推断模板参数的类型和值的过程。

实参演绎的概念

实参演绎的字面意思就是:通过实参的类型来推断函数模板的类型参数。在C++中,当函数模板被调用时,编译器会根据传递给模板函数的实参类型自动推断出模板参数的具体类型,从而生成一个特定类型的函数实例。这个过程就是实参演绎。

实参演绎的过程

实参演绎的过程涉及编译器如何根据实参的类型来确定模板参数的类型。具体来说,包括以下几个关键步骤:

  1. 匹配实参和模板参数:编译器首先会尝试将函数模板调用中的实参与模板参数进行匹配。如果实参的类型可以直接与模板参数的类型相对应,则匹配成功。

  2. 类型转换:在某些情况下,如果实参的类型与模板参数的类型不完全匹配,编译器会尝试进行类型转换。例如,从数组类型到指针类型的转换,或从函数类型到函数指针类型的转换。

  3. 推导模板参数类型:根据实参的类型和可能发生的类型转换,编译器会推导出模板参数的具体类型。

  4. 实例化函数模板:一旦模板参数的类型被确定,编译器就会使用这些类型来实例化函数模板,生成一个具体的函数实例。

注意事项

  1. 模板参数的可推导性:并非所有的模板参数都可以通过实参演绎来推导。有些模板参数可能需要显式指定,特别是在模板参数没有出现在函数参数列表中的情况下。

  2. 类型转换的限制:虽然编译器会尝试进行类型转换来匹配实参和模板参数,但这些转换通常受到语言规则的限制。例如,不能将数组直接传递给需要指针参数的函数模板,但可以将数组名隐式转换为指向数组首元素的指针。

  3. 重载解析:当存在多个重载的函数模板时,编译器需要根据实参的类型和模板参数的推导结果来选择最合适的函数模板进行实例化。

示例

以下是一个简单的函数模板示例,展示了实参演绎的过程:

template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    int i = max(1, 2);  // 实参演绎:T被推导为int
    double d = max(1.0, 2.0);  // 实参演绎:T被推导为double
    return 0;
}

在这个示例中,编译器根据传递给max函数的实参类型自动推导出了模板参数T的类型,并生成了相应的函数实例。

综上所述,C++模板中的实参演绎是一个自动且强大的机制,它允许程序员编写更加通用和灵活的代码,同时减少了代码重复和提高了开发效率。

1.4 函数模板的重载

和普通函数一样,函数模板可以被重载,根据传入参数类型的个数,选择不同的实现,将上面的l例子进行延伸:

/******************************************************************************

Welcome to GDB Online.
  GDB online is an online compiler and debugger tool for C, C++, Python, PHP, Ruby, 
  C#, OCaml, VB, Perl, Swift, Prolog, Javascript, Pascal, COBOL, HTML, CSS, JS
  Code, Compile, Run and Debug online from anywhere in world.

*******************************************************************************/
#include <iostream>
#include <string>
using namespace std;
inline int const& max_num(int const& a,int const& b){
    return a < b ? b : a;
}
        
template <typename T>
inline T const& max_num(T const& a,T const& b){
    return a < b ? b : a;
};

template <typename T>
inline T const& max_num(T const& a,T const& b,T const& c){
    return max_num(max_num(a,b),c);
};

int main(){
    cout << "max_num(1,2,3) : "  << max_num(1,2,3)<<endl;  
    cout << "max_num(1.0,2.0) : " << max_num(1.0,2.0) << endl;  
    cout << "max_num('a','b') : " << max_num('a','b') << endl;  
    cout << "max_num(1,2) : " << max_num(1,2) << endl;  
    cout << "max_num<>(1,2) : " << max_num<>(1,2) << endl;  
    cout << "max_num<double>(1,2) : "   <<  max_num<double>(1,2) << endl;  
    cout << "max_num('a',1.2) : "  <<  max_num('a',1.2) << endl;  
    return 0;
}
max_num(1,2,3) : 3         //3个参数的模板
max_num(1.0,2.0) : 2       //max_num<double>
max_num('a','b') : b       //max_num<char>
max_num(1,2) : 2.          //非模板函数(优先调用)
max_num<>(1,2) : 2         //调用模板函数
max_num<double>(1,2) : 2.  //调用max_num<double>
max_num('a',1.2) : 97      //类型不同,只能调用非模板函数
...Program finished with exit code 0
Press ENTER to exit console.

补充说明:
(1)重载函数模板的时候,要把自己的改变限制在:显式地指定模板参数;
(2)一定要让函数模板的所有重载版本的声明都位于它们被调用的位置之前;
再举一个例子,为指针和普通的C字符串重载,求最大值的模板。

#include <iostream>
#include <string>
#include <cstring>
using namespace std;
        
template <typename T>
inline T const& max_num(T const& a,T const& b){
    return a < b ? b : a;
};

//求2个指针所指向值的最大值
template <typename T>
inline T* const& max_num(T* const& a,T* const& b){
    return *a < *b ? b : a;
};

//求2个C字符串的最大值
template <typename T>
inline char const* const& max_num(char const* const& a,char const* const& b){
    return std::strcmp(a,b) < 0 ? b : a;
};


int main(){
    int a = 7;
    int b = 42;
    cout << "max_num(a,b): " << max_num(a,b) << endl;
    
    std::string s = "hey";
    std::string t = "you";
    cout << "max_num(s,t): " << max_num(s,t) << endl;
    
    int* p1 = &b;
    int* p2 = &a;
    cout << "max_num(p1,p2): " << max_num(p1,p2) << endl;
    
    char const* s1 = "aaa";
    char const* s2 = "bbb";
    cout << "max_num(s1,s2): " << max_num(s1,s2) << endl;
    return 0;
}
#include <iostream>
#include <string>
#include <cstring>
using namespace std;

//通过传引用进行调用        
template <typename T>
inline T const& max_num(T const& a,T const& b){
    return a < b ? b : a;
};

//求2个C字符串的最大值(通过传值进行调用)
template <typename T>
inline T const& max_num(char const*  a, char const* b){
    return std::strcmp(a,b) < 0 ? b : a;
};

//求三个任意类型的最大值
template <typename T>
inline T const& max_num(T const& a, T const& b,T const& c){
    return max_num(max_num(a,b),c);
};

int main(){
    int a = 7;
    int b = 42;
    int c = 61;
    cout << "max_num(a,b,c): " << max_num(a,b,c) << endl;  //ok
    
    char const* s1 = "aaa";
    char const* s2 = "bbb";
    char const* s3 = "ccc";
    cout << "max_num(s1,s2,s3): " << max_num(s1,s2,s3) << endl;  //error
    return 0;
}

为什么第二个会出错,是因为对于c-string而言,max(a,b)产生了一个新的临时局部变量,这个值有可能会被外面的max_num函数以传引用的方式返回,这将导致传回无效的引用。
所以这两个例子给我们的启示:所有重载的实现里面,我们都是通过引用来传递每个实参的;通常情况下,在重载函数模板时,最好改变那些需要改变的内容,即改变参数的数目,或者显示地指定模板参数

2、总结

  • 模板函数为不同的模板实参定义了一个函数家族;
  • 当你传递模板实参的时候,可以根据实参的类型来对函数模板进行实例化;
  • 可以显示地指定模板参数;
  • 可以重载函数模板;
  • 当你重载函数模板的时候,应该把改变限制在:显示地指定模板参数;
  • 一定要让函数模板的所有重载版本的声明位于函数的调用位置之前(先声明再调用)

3、参考

3.1 《C++ Templates》

  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值