• 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文件略有不同
  • 相关阅读:
    c语言,动态数组
    利用Word来发布博客到博客园(onenote类似)
    c语言,volatile
    c语言,变长数组
    C语言,sprintf与sscanf函数[总结]
    c语言,数组和指针
    Linux的notifier机制的应用
    Linux进程上下文切换过程context_switch详解--Linux进程的管理与调度(二十一)
    内核线程的进程描述符task_struct中的mm和active_mm
    Linux用户抢占和内核抢占详解(概念, 实现和触发时机)--Linux进程的管理与调度(二十)
  • 原文地址:https://www.cnblogs.com/luomgf/p/5004562.html
Copyright © 2020-2023  润新知