模版
模版在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文件略有不同