const
定义常量
限定符声明变量只能被读
|
|
必须初始化
|
|
在另一连接文件中引用const常量
|
|
便于进行类型检查
用const方法可以使编译器对处理内容有更多了解。
|
|
可以避免不必要的内存分配
|
|
由于const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
可以通过函数对常量进行初始化
|
|
假定对ROM编写程序时,由于目标代码的不可改写,本语句将会无效,不过可以变通一下:
|
|
只要令i的地址处于ROM之外,即可实现:i通过函数初始化,而其值有不会被修改。
const的常量值可以被修改
观察以下一段代码:
|
|
通过强制类型转换,将地址赋给变量,再作修改即可以改变const常量值。
const与指针
const char *p 表示 指向的内容不能改变。
char * const p,就是将P声明为常指针,它的地址不能改变,是固定的,但是它的内容可以改变。
|
|
修饰函数参数
const只能修饰输入参数:
如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const修饰,否则该参数将失去输出功能。
如果输入参数采用“指针传递”,那么加const修饰可以防止意外地改动该指针,起到保护作用
例如StringCopy函数:
|
|
其中strSource是输入参数,strDestination是输出参数。给strSource加上const修饰后,如果函数体内的语句试图改动strSource的内容,编译器将指出错误。
“值传递”无需const修饰
如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const修饰。
例如不要将函数voidFunc1(int x)
写成voidFunc1(const int x)
。同理不要将函数voidFunc2(A a)
写成voidFunc2(const A a)
。其中A为用户自定义的数据类型。
非内部类型输入参数,采取const引用传递
对于非内部数据类型的参数而言,像voidFunc(A a)
这样声明的函数注定效率比较底。因为函数体内将产生A类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。
为了提高效率,可以将函数声明改为voidFunc(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数voidFunc(A &a) 存在一个缺点:
“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可,因此函数最终成为voidFunc(const A &a)
。
内部类型输入参数,不必采取const引用传递
以此类推,是否应将voidFunc(int x)
改写为voidFunc(const int&x)
,以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
因此,
对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。例如将voidFunc(A a)
改为voidFunc(const A &a)
。
对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如voidFunc(int x)
不应该改为voidFunc(const int &x)
。
修饰函数返回值
“指针传递”方式返回值
如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。
|
|
“值传递”方式返回值
如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值。
例如不要把函数int GetInt(void)
写成const int GetInt(void)
。
同理不要把函数A GetA(void)
写成const A GetA(void)
,其中A为用户自定义的数据类型。
“引用传递”方式返回
如果返回值不是内部数据类型,将函数A GetA(void)
改写为const A &GetA(void)
的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。若返回对象的“拷贝”就应该采用“值传递”,仅返回“别名”可以用“引用传递”。
函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
|
|
如果将赋值函数的返回值加const修饰,即
const A &operate = (const A &other);
那么该返回值的内容不允许被改动。上例中,语句a = b = c
仍然正确,但是语句(a = b) = c
则是非法的。
修饰类的数据成员
不能在类声明中初始化const数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道SIZE的值是什么。
|
|
正确的使用const实现方法为:const数据成员的初始化只能在类构造函数的初始化表中进行。
|
|
static和const也以同时修饰数据成员。 然而,它们不能同时修饰成员函数,这两点要注意区别。
|
|
修饰类的成员函数
任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。
const放在函数末尾修饰const成员函数,因为const关键字是左结合。
以下程序中,类stack的成员函数GetCount仅用于计数,从逻辑上讲GetCount应当为const函数。编译器将指出GetCount函数中的错误。
|
|
如果有个成员函数想修改对象中的某一个成员怎么办?这时我们可以使用mutable
关键字修饰这个成员,mutable
的意思也是易变的,容易改变的意思,被mutable关键字修饰的成员可以处于不断变化中,例如:
|
|
注意: 不可以同时用const和static修饰成员函数。
C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。
我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。
static
局部变量
在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。
- 内存中的位置:静态存储区
- 初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
- 作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。
- 生命周期:直到程序结束。
注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置(从原来的栈中存放改为静态存储区)及其生命周期(局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问),但未改变其作用域。
全局变量
在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。
- 内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)
- 初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
- 作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。
- 生命周期:直到程序结束。
注:static修饰全局变量,并未改变其存储位置及生命周期,而是改变了其作用域,使当前文件外的源文件无法访问该变量,好处如下:(1)不会被其他文件所访问,修改。(2)其他文件中可以使用相同名字的变量,不会发生冲突。对全局函数也是有隐藏作用。
类中的成员变量
用static修饰类的数据成员实际使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象。因此,static成员必须在类外进行初始化(初始化格式: int base::var=10;
),而不能在构造函数内进行初始化,不过也可以用const修饰static数据成员在类内初始化。但是定义必须在类的外部,见上文。在类的内部const static类型即使赋字面值常量也只能算是声明,定义必须在外部进行。
不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif
或者#pragma once
也不行。
静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。
静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为所属类类型的指针或引用。
类中的成员函数
用static修饰成员函数,使这个类只存在这一份函数,所有对象共享该函数,不含this指针。
静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。base::func(5,3);
当static成员函数在类外定义时不需要加static修饰符。
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。因为静态成员函数不含this指针。
struct / union
定义
结构体struct:把不同类型的数据组合成一个整体,自定义类型。
共同体union:使几个不同类型的变量共同占用一段内存
相同点
struct和union都有内存对齐,结构体的内存布局依赖于CPU、操作系统、编译器及编译时的对齐选项。
关于内存对齐,先让我们看四个重要的基本概念:
- 数据类型自身的对齐值:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
- 结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
- 指定对齐值:#pragma pack(n),n=1,2,4,8,16改变系统的对齐系数
- 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
首先根据结构体内部成员的自身对齐值得到结构体的自身对齐值(内部成员最大的长度),如果没有修改系统设定的默认补齐长度4的话,取较小的进行内存补齐。
不同点
结构体struct:不同之处,stuct里每个成员都有自己独立的地址。sizeof(struct)是内存对齐后所有成员长度的加和。
共同体union:当共同体中存入新的数据后,原有的成员就失去了作用,新的数据被写到union的地址中。sizeof(union)是最长的数据成员的长度。
总结
struct和union都是由多个不同的数据类型成员组成, 但在任何同一时刻, union中只存放了一个被选中的成员, 而struct的所有成员都存在。在struct中,各成员都占有自己的内存空间,它们是同时存在的。一个struct变量的总长度等于所有成员长度之和。在Union中,所有成员不能同时占用它的内存空间,它们不能同时存在。Union变量的长度等于最长的成员的长度。对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于struct的不同成员赋值是互不影响的。
inline
inline用来向编译器请求声明为内联函数,编译器有权拒绝。
与宏函数的对比
- 内联函数在运行时可调试,而宏定义不可以;
- 编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会;
- 内联函数可以访问类的成员变量,宏定义则不能;
- 在类中声明同时定义的成员函数,自动转化为内联函数
- 宏只是预定义的函数,在编译阶段不进行类型安全性检查,在编译的时候将对应函数用宏命令替换。对程序性能无影响。
不能声明为inline的函数
- 包含了递归、循环等结构的函数一般不会被内联。
- 虚拟函数一般不会内联,但是如果编译器能在编译时确定具体的调用函数,那么仍然会就地展开该函数。
- 如果通过函数指针调用内联函数,那么该函数将不会内联而是通过call进行调用。
- 构造和析构函数一般会生成大量代码,因此一般也不适合内联。
- 如果内联函数调用了其他函数也不会被内联。
typedef / using
二者功能都是定义新类型,using 为c++11新特性。下面语句功能一致:
|
|
explicit
explicit禁止了隐式转换类型,用来修饰构造函数。原则上应该在所有的构造函数前加explicit关键字,当你有心利用隐式转换的时候再去解除explicit,这样可以大大减少错误的发生。如果一个构造函数
Foo(int)
;则下面的语句是合法的:
|
|
如果给构造函数加了explicit,即 explicit Foo(int)
;就只能进行显示转换,无法进行隐式转换了:
|
|
指针 / 引用
本质上的区别是,指针是一个新的变量,只是这个变量存储的是另一个变量的地址,我们通过访问这个地址来修改变量。
而引用只是一个别名,还是变量本身。对引用进行的任何操作就是对变量本身进行操作,因此以达到修改变量的目的。
区别如下:
指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:
12int a=1; int *p=&a;int a=1; int &b=a;上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。
而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。
可以有const指针,但是没有const引用(一般说的const引用其实是指向const对象的引用);
- 指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
- 指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
- 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
- “sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;
- 指针和引用的自增(++)运算意义不一样。
- 指针传参的时候,还是值传递,试图修改传进来的指针的值是不可以的。只能修改地址所保存变量的值。引用传参的时候,传进来的就是变量本身,因此可以被修改。
注意:const引用只是表明,保证不会通过此引用间接的改变被引用的对象! 详见:C++引用与const引用