C++模版完全解析

模版

模版在C++中是一个很重要的概练,生活中的模版也是随处可见,比如制造工程师会
哪一个模子去构造出一个一个的工件,C++程序员能够用模版去实例化y有相同操作
不同类型的函数或者数据结构。简单的理解模版就是为省去重复,同时又比C中的宏容易调试
因为C++编译器会做类型参数语法检查。

C中的模版模拟

想当年C++还没有出现模版的时候,C程序员可能为了写比较两个数的最大值写出如下几种类型

int max_int(int x,int y){return  x>y?x:y}
int max_double(double x,double y){return  x>y?x:y}
int max_string(string x,string y){return x>y?x:y}//C++中

这样的写法实在太繁杂,后来由发现用宏来写可能会舒服点儿,比如

//macro.cpp 
#include <iostream>
using namespace std;
#define MAX(T) 
T max_##T(T x,T y)
{
return  x>y?x:y;
}


MAX(int)
MAX(string)
MAX(double)

#define max(T)  max_##T


int
main(int argc, char ** argv){

        cout<<"int:"<<max(int) (2,3)<<endl;
        cout<<"double:"<<max(double) (3.3,1.2)<<endl;
        cout<<"string:"<<max(string) (string("welcome"),string("guiyang"))<<endl;

        cout<<endl;

        return  0 ;
}

这样看是比写三个方便了,在这个地方有几个东西需要说明

  • 1.我们最终任然是写了三个函数,只不过是通过 预处理器 的方式来为我们创建了
    因为宏只在预处理的时候有效
  • 2.一旦预处理完成我们的宏就不见了不能在符号表找到,不能加-g调试
  • 3.为了确定我们的函数生成在预处理阶段我们可以看一下预处理信息
# 2 "macro.cpp" 2
using namespace std;

int max_int(int x,int y){return x>y?x:y;}
string max_string(string x,string y){return x>y?x:y;}
double max_double(double x,double y){return x>y?x:y;}

int
main(int argc, char ** argv){

 cout<<"int:"<<max_int (2,3)<<endl;
 cout<<"double:"<<max_double (3.3,1.2)<<endl;
 cout<<"string:"<<max_string (string("welcome"),string("guiyang"))<<endl;

 cout<<endl;

 return 0 ;
}
[lmg@localhost cpp]$ 

C++中的模版

C++中的模版也采用了相似的思想,不过将其变为了函数模版
即定义出一个通用的函数,用类型参数形参表示具体的函数模版的类型

  • 语法:template <[typename|class] argsname1,[typename|class] argsname1,...> argsname funcname(argsname1 x,argsname2 y,...)
  • 调用:funcname<argsname1 ,argsname2,..>(x,y,...)
  • 注意:
    • 1.模版形参表和模版实参表要对应,可以有多个
    • 2.模版的声明和使用一般通常的编写手法是放在一个头文件中,需要用到的包含文件即可
    • 3.通常我们写C文件的时候会将声明放在头文件中,而将定义实现放在.c文件中,这是因为
      我们的C/C++通常的处理是声明可以重复但是定义是不能重复的。
    • 4.类型形参表是由调用的类型实参进行实例化,此过程完成在编译阶段,和上面的C实现的区别主要是
      C实现发生在预处理阶段,但是最终都会生成多份儿符号儿表

代码实现

#include <iostream>

template <typename T>  //定义模版名称
T max(T x, T y){
        return x>y?x:y;
}


int
main(int argc, char ** argv){

        std::cout<<"int:"<<max<int>(3,2)<<std::endl;
        std::cout<<"doule:"<<max<double>(2.4,3.4)<<std::endl;
        std::cout<<"string:"<<max<std::string>("welcome","beijing")<<std::endl;
}

为了进一步分析模版实现的特点我们观察如下两个方面

  • 预处理模版的代码
# 2 "template.cpp" 2

template <typename T>
T max(T x, T y){
 return x>y?x:y;
}


int
main(int argc, char ** argv){

 std::cout<<"int:"<<max<int>(3,2)<<std::endl;
 std::cout<<"doule:"<<max<double>(2.4,3.4)<<std::endl;
 std::cout<<"string:"<<max<std::string>("welcome","beijing")<<std::endl;
}

从预处理结果可以看出预处理结果中模版代码并没有改变

  • 用nm命令查看连接结果符合表
0000000000601248 d _DYNAMIC
0000000000601410 d _GLOBAL_OFFSET_TABLE_
0000000000400dd7 t _GLOBAL__I_main
0000000000400fa8 R _IO_stdin_used
                 w _Jv_RegisterClasses
                 U _Unwind_Resume@@GCC_3.0
0000000000400e3c W _Z3maxISsET_S0_S0_
0000000000400e08 W _Z3maxIdET_S0_S0_
0000000000400dec W _Z3maxIiET_S0_S0_
0000000000400d97 t _Z41__static_initialization_and_destruction_0ii
                 U _ZNKSs7compareERKSs@@GLIBCXX_3.4
                 U _ZNSaIcEC1Ev@@GLIBCXX_3.4
                 U _ZNSaIcED1Ev@@GLIBCXX_3.4
                 U _ZNSolsEPFRSoS_E@@GLIBCXX_3.4
                 U _ZNSolsEd@@GLIBCXX_3.4
                 U _ZNSolsEi@@GLIBCXX_3.4
                 U _ZNSsC1EPKcRKSaIcE@@GLIBCXX_3.4
                 U _ZNSsC1ERKSs@@GLIBCXX_3.4
                 U _ZNSsD1Ev@@GLIBCXX_3.4
                 U _ZNSt8ios_base4InitC1Ev@@GLIBCXX_3.4
                 U _ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4
00000000006014c0 B _ZSt4cout@@GLIBCXX_3.4
                 U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@@GLIBCXX_3.4
00000000006015e0 b _ZStL8__ioinit
0000000000400e90 W _ZStgtIcSt11char_traitsIcESaIcEEbRKSbIT_T0_T1_ES8_
                 U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@@GLIBCXX_3.4
                 U _ZStlsIcSt11char_traitsIcESaIcEERSt13basic_ostreamIT_T0_ES7_RKSbIS4_S5_T1_E@@GLIBCXX_3.4
0000000000400fe0 r _ZZL18__gthread_active_pvE20__gthread_active_ptr
0000000000601228 d __CTOR_END__
0000000000601218 d __CTOR_LIST__
0000000000601238 D __DTOR_END__
0000000000601230 d __DTOR_LIST__
00000000004011d8 r __FRAME_END__
0000000000601240 d __JCR_END__
0000000000601240 d __JCR_LIST__
00000000006014bc A __bss_start
                 U __cxa_atexit@@GLIBC_2.2.5
00000000006014b8 D __data_start
0000000000400f60 t __do_global_ctors_aux
0000000000400b20 t __do_global_dtors_aux
0000000000400fb0 R __dso_handle
                 w __gmon_start__
                 U __gxx_personality_v0@@CXXABI_1.3
0000000000601213 d __init_array_end
0000000000601213 d __init_array_start
0000000000400ec0 T __libc_csu_fini
0000000000400ed0 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
00000000006014bc A _edata
00000000006015e8 A _end
0000000000400f98 T _fini
0000000000400988 T _init
0000000000400ad0 T _start
0000000000400afc t call_gmon_start
00000000006015d0 b completed.6349
00000000006014b8 W data_start
00000000006015d8 b dtor_idx.6351
0000000000400b90 t frame_dummy
0000000000400bb4 T main
                 w pthread_cancel

从下面的符合表

0000000000400e3c W _Z3maxISsET_S0_S0_
0000000000400e08 W _Z3maxIdET_S0_S0_
0000000000400dec W _Z3maxIiET_S0_S0_

可以看出生成了三分儿模版函数实例

必知知识点

为了便于理解模版我觉得如下概练需要进一步强度

  • 1、宏替换发生在预处理阶段 -E参数可以看到处理结果
  • 2、模版函数实例化函数模版发生在编译阶段,可以nm命令通过分析二进制文件符合表发现
  • 3、函数模版和模版函数的形参表的顺序必须一致,因为简单理解其实现的是对应替换的功能,再加一点儿编译器做的类型检查
  • 4、模版形参必须是能支持函数模版的操作,比如一个用“>”符合的比较函数,其模版类型T必须能支持“>”符号或者重载了该运算符
  • 5、模版声明定义一般直接放入头文件中,和通常的声明放在头文件实现放在.C|.CPP文件略有不同