C++模板总结

C++模板

模板定义:模板是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现了真正的代码可重用性。

模板分类:函数模板和类模板。函数模板针对参数类型不同的函数;类模板仅针对数据成员和成员函数类型不同的类。

使用模板目的:让程序员编写与类型无关的代码。

注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,如不能在main函数中声明或定义一个模板。

函数模板

函数模板形式:

template< class 形参名, class 形参名, ……> 返回类型 函数名(参数列表){ 函数体 }

其中template和class是关键字,class可用typename关键字替代,此处typename和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很像,模板形参不能为空一旦声明了模板函数就可以使用模板函数的形参名声明类中的成员变量和成员函数,即可以在函数中使用内置类型的地方都可以使用模板形参名。模板形参需要使用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称它实例化了函数模板的一个实例。

举例:template < class T> void swap(T &a, T &b) {};,当调用此模板函数时,类型T会被调用时的类型所替代,如swap(a, b),a和b为int时,模板函数swap中的形参T被int替换,模板函数为swap(int &a, int &b);而a和b为double时,模板函数swap中的形参T被double替换,模板函数为swap(double &a, double &b),这样便实现函数与类型无关的代码。

注意:对函数模板而言,不存在h(int, int)这样的调用不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即h(2, 3)或int a, b;h(a, b)可以。

类模板

类模板格式如下:

template< class 形参名, class 形参名, ……> class 类名 {…};

类模板也是以template开始,后接模板参数列表,模板参数不能为空,一旦声明了类模板,就可以使用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。

举例:template< class T> class A { public: T a; T b; T add(T c, T &d);};。

类模板对象的创建:如模板类A,使用模板类创建对象的方法:A< int> m;。在类A后面跟上<>尖括号并在里面填上相应类型,这样类A中凡是用到模板形参的地方,都会被int所代替。当类模板有两个模板形参时,创建对象方法为A< int, double> m,类型中间用逗号隔开。

对于类模板,模板形参类型必须在类型后的尖括号中明确指定。使用A<2>错误,原因是类模板形参不存在实参推演的问题

在类模板外部定义成员函数方法:template < 模板形参参数列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体}。

举例:假如有两个模板形参T1,T2的类A,含有void add()函数,则定义该函数方法:template< class T1, class T2> void A< T1, T2>::add(){}

注意:在类外定义类的成员时,template后面的模板形参应与要定义的类的模板形参一致。模板的声明或定义只能在全局,命名空间或类范围内进行。不能在局部范围,函数内进行。

模板形参

类型:类型形参,非类型形参,模板形参。

类型形参

1)类型形参由关键字class或typename后接说明符构成,如template void h(T a){};,其中T就是类型形参,类型形参名可有用户自己确定。不能为同一个模板类型形参指定两种不同的类型。对函数,直接报错,对类中函数,则可能计算出错。

非类型形参

1)非类型模板形参:模板的非类型形参也就是内置类型形参,如template< class T, int a> class B{};,其中int a就是非类型的模板形参。

2)非类型形参在模板定义的内部是常量值,即说非类型形参在模板的内部是常量。

3)非类型模板的形参只能是整型,指针和引用,如double,String,String *等都不允许。但double ,double &,对象引用或指针可以。

4)调用非类型模板形参的实参必须是一个常量表达式,即必须在编译时计算出结果。

5)注意:任何局部对象,局部变量,局部对象地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针,全局变量,全局对象也不是常量表达式,不能做非类型模板形参的实参。

6)全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。

7)sizeof表达式结果为常量表达式,可做非类型模板形参的实参。

8)当模板形参时整型时,调用该模板时的实参必须为整型,且编译期间是常量,如对template< class T, int a>class A{},若有int b,A< int, b> m出错,因为b不是常量,当为const int b,则正确。

9)非类型形参一般不应用与函数模板中,如template< class T, int a> void h(T b){},使用h(2)错误,解决方法,使用显示模板实参,用h< int, 3>(2)能实现把非类型形参a设置为整数3。

10)非类型模板形参的形参与实参之间允许转换情况:

1、允许从数组到指针,从函数到指针的转换。举例:template< int *a > class A{}; int b[1]; A< b > m,数组到指针转换。

2、const修饰符的转换。举例:template< const int a> class A{}; int b; A<&b> m,int 到const int *转换。

3、提升转换。举例:template< int a> class A{}; const short b = 2; A< b > m,从short到int提升转换。

4、数值转换。举例:template< unsinged int a> class A{}; A< 2 > m,从int到unsigned int转换。

5)常规转换。

模板形参

模板形参表示一个未知的类型。模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用完全相同,即可以用于指定返回类型,变量声明等。

typename

typename是一个C++程序设计语言中的关键字。当用于泛型编程时是另一术语“class”的同义词。这个关键字用于指出模板声明(或定义)中的非独立名称的类型,而非变量名。

C++规定:无论何时,如果使用一个依赖于模板参数的类型时,而且你想要使用这个类型的成员函数本身就是一个类型,就必须在整个名字前加上typename。举例:template< class T > typename Vec< T >::iterator Vec< T >::erase(iterator it){}。无论什么时候,如果你使用一个依赖于模板参数的类型时,如vector< T >,而且你想要使用这个类型的成员时,如size_type,他本身也是一个类型时,必须在整个名字之前加上typename,以便让系统知道要把这个名字当成类型来对待。举例:typedef typename vector< T >::size_type vec_sz;。

typedef

typedef常用来定义一个标识符及关键字的别名,它是语言编译过程的一部分,但它并不实际分配内存空间。

1)定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。#define只是简单的字符串替换而typedef则为一个类型起新名字。

举例:char *pa, pb; 注意不是两个指针变量;而是用typedef char *PCHAR; PCHAR pa, pb;则为两个指针变量。

2)在旧的C代码中,帮助struct。以前代码中struct新对象时,必须带上struct,形式为struct 结构名 对象名。

举例:struct tP{int x; int y}; 定义对象为struct tP tt1;而在C++中直接结构名 对象名,如tP tt1;即可。当结构体如下定义:typedef struct tP{int x, int y}P;时,则可以是用P pp1定义对象,方便。此功能在C++中无太大作用。

3)用typedef定义与平台无关的类型。

举例:定义一个REAL的浮点类型,使其在目标平台上表示最高精度类型,则typedef long double REAL,在不支持long double平台,更改为typedef double REAL即可。跨平台时,只要改typedef本身,不用对其他源码做修改。因为typedef是定义一种类型的新别名,不是简单的字符串替换,故比宏更稳健。此优点在我们写代码的过程中可以减少不少代码量。

4)为复杂的声明定义一个新的简单的别名。方法:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量的部分留到最后替换,得到的即为原声明的最简化版。

举例:void (*b[10])(void(*)());,变量名为b,先替换右边括号里的,pFunParam为别名一:typedef void (*pFunParam)();,在替换左边的变量b,pFunx为别名二:typedef void (*pFunx)(pFunParam);,原声明的最简化版为:pFun b[10];。

复杂的声明可以使用“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号就跳转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。

define

1)用于定义变量:如#define PI 3.1415,编译器在处理这个代码之前会对PI进行处理,替换为3.1415,它与const有区别,它的实质是简单的文本替换,并不是作为一个量来使用。

2)用于对函数进行定义:如#define MAX(a, b) ((a) > (b) ? (a) : (b)),这个定义是比较两个数大小,此函数没有类型检查功能,它与模板相像,但没函数模板安全。在使用宏函数时,一定要养成良好的习惯和良好的代码编写风格,建议所有层次都加上括号。

3)define用于单行定义:如#define A(x) ##x或#define B(x) #@x或#define C(x) #x,如果假设x = 1,则A(1)就是1,B(1)就是1,C(1)就是1。

4)define用于多行定义:如下,切记每行都要加\。

#define MACRO(arg1, arg2) do { \
    test1; \
    test2; \
}while (0);

5)定义宏和取消宏定义的方法:定义一个宏使用#define,取消一个宏定义使用#undef。

6)使用宏进行条件编译:格式:#ifdef … (#else) … #endif。如下例子:

#ifdef HEL 
#define WR 1
#else
#define WR 0
#endif

7)用define来处理头文件被头文件或者原文件包含的情况:因头文件包含可以嵌套,那么c文件有可能包含多次同一个文件,就会出现重复定义问题,可以使用条件编译开关来避免重复包含。如下:

#ifndef _TRY_H_
#define _TRY_H_
...
// 文件内容
#endif

8)在大规模的开发过程中,特别是跨平台和系统的软件里,define重要功能是条件编译。如

#ifdef WINDOWS
......
#endif
#ifdef LINU
......
#endif

typedef和#define区别:typedef只是为了增加可读性而为标识符另起的新名称,而#define原本在C中是为了定义常量,到了C++,const,enum,inline出现使它也渐渐成为了起别名的工具。主张用typedef。遵循#define定义可读的常量以及一些宏语句的任务,而typedef则常用来定义关键字、冗长的类型的别名。宏定义只是简单的字符串代替(原地扩展),而typedef则不是原地扩展,它的新名字具有一定的封装性,以致于新命名的标识符具有更易定义变量的功能。

inline

在C和C++中,inline关键字用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达形式的宏定义。

表达式形式的宏定义举例:

#define Exp(var1, var)((var1)+(var2))*((var1)-(var2))

取代上述形式宏定义原因:

1)C中使用define这种形式宏定义是因为C语言是一个高效的语言,这种定义在形式及使用上像一个函数,但它使用预处理器实现,没有参数压栈,代码生成等一系列的操作,效率高。

2)这种宏定义在形式上类似于一个函数,使用它,仅仅只是做预处理器符号表中的简单替换,故不能进行参数有效性检测,不能享受C++编译器严格检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,故使用它存在一系列的隐患和局限性。

3)在C++中引入类及类的访问控制,这样如果一个操作或一个表达式涉及到类的保护成员或私有成员,就不可使用这种宏定义实现(因为无法将this指针放在合适的位置)。

4)inline推出目的:取代宏定义,消除宏定义缺点,同时很好继承宏定义优点。

inline定义的类的内联函数,函数代码放入符号表中,在使用时直接进行替换,像宏一样展开,没有调用开销,效率高。类的内敛函数是真正的函数,编译器在调用时会检查它的参数类型,保证调用正确。消除宏定义隐患和局限性。inline可以作为某个类成员,故可以使用其类中的保护成员和私有成员。inline可以完全取代表达式形式的宏定义;但注意,内联函数一般只会用在函数内容简单的时候,原因是内联函数的代码会在任何调用它的地方展开,如果太复杂,代码膨胀带来的恶果可能大于效率提高带来的益处。如果函数体内出现循环或者其它复杂的控制结构时,处理这些复杂控制结构所花费的时间远大于函数调用所花的时间,此时将函数定义为内联,则意义不大。内联函数重要使用地方是类的存取函数。

指定内联函数方法:只需要在定义函数时,增加inline,注意是定义,不是函数声明。函数声明加inline,没有任何效果。对函数inline声明,只是程序员对编译系统提出的一个建议,即它是建议性的,而不是指令性的,并非一经指定为inline,编译系统就必须这样做。编译系统会根据具体情况决定是否这样做。

enum

enum(枚举类型)是C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。

定义格式为:enum <类型名> {<枚举常量表>}

enum:指明其后的标识符是一个枚举类型的名字。

枚举常量表:由枚举常量构成。枚举常量或枚举成员,是以标识符形式表示的整型量,表示枚举类型的取值。枚举常量表列出枚举类型的所有取值,各枚举常量之间以”,”间隔,且必须各不相同。取值类型与条件表达式相同。

举例:enum week {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

1)枚举常量代表该枚举类型的变量可能取的值,编译系统为每个枚举常量指定一个整数值,缺省状态下,这个整数就是所列举元素的序号,序号从0开始。

2)可以在定义枚举类型时为部分或全部枚举常量指定整数值,在指定值之前的枚举常量扔按缺省方式取值,而指定值之后的枚举常量按次加1的原则取值。

3)各枚举常量的值可以重复。

举例:enum fruit {apple, orange, banana = 1, peach};,则对应常量值为apple = 0, orange = 1, banan = 1, peach = 2。如enum week {Sun = 7, Mon = 1, Tue,…};,则Sun = 7,Mon = 1, Tue = 2…

4)枚举常量只能以标识符形式表示,而不能是整型、字符型等文字常量。

使用:定义枚举类型的主要目的是:增加程序的可读性。枚举类型最常见也是最有意义的用处之一就是用来描述状态量。

定义格式:定义枚举类型之后,就可以定义该枚举类型的变量,如week weekday1,weekday2;也可以类型与变量同时定义(甚至类型名可省),如下格式:enum {Sun, Mon, Tue,…}weekday1,weekday2;

相关操作:

1)枚举变量的值智能取枚举常量表中所列的值,就是整型数的一个子集;

2)枚举变量占用内存的大小与整型数相同;

3)枚举变量只能参与赋值和关系运算以及输出操作,参与运算时用其本身的整数值;举例:

enum color_set1{RED,BLUD,WHITE,BLACK}color1,color2;
enum color_set2{GREEN,RED,YELLOW,WHITE}color3,color4;
//则允许的赋值操作如下:
color3=RED;   //将枚举常量给枚举变量
color4=color3; //相同类型的枚举变量幅值,color4的值为RED
int i=color3;  // 将枚举变量赋给整型变量,i值为1
int j=GREEN;   // 将枚举常量赋值给整型变量,j值为0

4)允许的关系运算符有:==,<,>,<=,>=,!=等。

5)枚举变量可以直接输出,输出的是变量的整数值。

cout << color3; //输出的是color3的整数值,为RED的整数值1

6)枚举类型是预处理指令#define的替代。但enum用来定义一系列宏定义常量区别用,相当于一系列的#define * *,当然它后面的标识符也可当做一个类型标识符,typedef enum则是用来定义一个数据类型,那么该类型的变量值只能在enum定义的范围内去。

注意:枚举变量可以直接输出,但不能直接输入;不能直接将常量赋给枚举变量;不同类型的枚举变量之间不能相互赋值;枚举变量的输入输出一般都采用switch语句将其转换为字符或字符串,枚举类型数据的其他处理也往往应用switch语句,以保证程序的合法法和可读性。同一个程序中不能定义同名的枚举类型,不同的枚举类型中也不能存在同名的命名常量。

static

在C中,1)static修饰函数中的变量(栈变量):改变变量的生存期,作用域不变仍为所在函数。只能初始化一次。2)static修饰全局变量:限制全局变量智能被模块内访问,不可以在别的模块中用extern声明调用。3)static修饰函数:作用于修饰全局变量类似,也是限制该函数只能在模块内访问,不能在别的模块中用extern声明调用。

在C++中,1)static静态数据成员属于整个类所有,类的所有对象共同维护;2)static静态函数成员也属于整个类,一般用于调用静态数据成员,不能直接访问非static成员(要指定类才行)。

static作用:1)扩展生存期;2)限制作用域;3)唯一性。

全局静态变量:

1)不会被其它文件所访问,修改;

2)其他文件中可以使用相同名字的变量,不会发生冲突。

局部静态变量:

1)内存中的位置:静态存储区;

2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化);

3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。

4)当static用来修饰局部变量时,它就改变了局部变量的存储位置,从原来的占中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍驻留在内存当中,直到程序结束,只不过不能再对其进行访问。

静态函数优点:

1)其他文件中可以定义相同名字的函数,不会发生冲突;

2)静态函数不能被其他文件所用。

存储说明符auto和register对应自动存储。具有自动存储期的变量在进入声明的程序块时被建立,他在该程序块活动时存在,退出改程序块时撤销。

存储说明符extern和static用来说明具有静态存储器的变量和函数。用static声明的局部变量具有静态存储持续期,或静态范围。显然它的值在函数调用之间保持有效,但其名子的可视性扔限制在其局部域内。静态局部对象在程序执行到该对象的声明处时,被首次初始化。

参考文献:
http://www.cnblogs.com/gw811/archive/2012/10/25/2738929.html
http://www.cnblogs.com/stli/archive/2010/12/02/1894890.html
http://blog.chinaunix.net/uid-29067889-id-3819834.html
http://baike.baidu.com/item/inline
http://c.biancheng.net/cpp/biancheng/view/134.html
http://www.cnblogs.com/ForFreeDom/archive/2012/03/22/2412055.html

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值