const与#define相比, 有何优点?
const
常量具有数据类型, 而宏常量没有数据类型. 编译器可以对前者进行类型安全检查, 而对后者只会进行字符替换, 没有类型安全检查, 容易发生意想不到的错误.- 从编译的角度来看,
const
定义的常量在程序运行过程中只有一份拷贝(因为对应的是内存地址, 全局的只读变量, 在静态区), 而#define
相当于字符替换, 每一次替换相当于打了一次拷贝. - 有些集成化的调试工具可以对const常量进行调试, 但是不能对宏常量进行调试.
const常用形式及代表含义
含义 | 使用形式 及 作用 |
---|---|
const常量 | const int Max = 100; int const Max =100 const与int的位置可互换,二者等价 |
修饰后的变量在程序的任意位置将不能再被修改,就如同常数一样,任何修改该变量的尝试都会导致编译错误(由于常量在定义以后就不能再被修改,所以定义时必须初始化)。 对于类中的const成员变量必须通过初始化列表进行初始化 | |
const引用 | const int i = 1024; const int &ref_i = i; int const &ref2_i = i 后两句等价int &const ref3_i = i 这种方式未定义,会报错:‘const’ qualifiers cannot be applied to ‘int&’ |
cosnt引用是指向const对象或者普通对象的引用,该引用ref_i 可以读取对象i 的值,但是,不能修改 i 的值,任何对ref_i 的赋值都是非法的。 并且 不能用普通引用指向const常量(int &ref4_i = i; // 非法 )。(注意,一旦引用被定义,它就不能再指向其它对象,这是引用本身的性质,这也是为什么引用必须进行初始化的原因)。 总结来说就是const引用只是表明:保证不会通过此引用间接的改变被引用的对象! |
|
const指针 | 1)int age = 39; const int *ptr = &age; 2) const int age = 98; const int *ptr = &age; 3) const int age = 32; int *ptr = &age; 4) int age = 90; int * const ptr = &age; |
对于1),const 修饰的是int ,表明ptr 指向一个const int ,但是ptr 和age 本身都不是const ,不能使用ptr 来修改age 的值,但是age 自身可以修改自己的值,同时,ptr 也可以转而指向其它变量;对于2), const 修饰的是int ,表明ptr 指向一个const int ,同时age 本身就是const,说明既不能通过ptr ,也不能通过age 来修改变量age的值,但是ptr 本身仍然不是const ,因此可以转而指向其它变量;对于3),C++禁止将 const 变量的地址赋给非const 指针(除非使用强制类型转换);对于4), const 修饰的是* ,表明ptr 本身是一个常量指针,这使得ptr 自身的值不能改变,也就是只能指向age ,不能指向其它变量 |
|
const函数参数 | void fun(const int * i); void fun(const int & i); |
不能在函数体内修改指针或引用指向的值,但是这里指针可以转而指向其他值(其实没多大用,因为指针本身就是值传递,即使改变指向,也不会影响实参的指向,除非用二级指针) | |
const函数返回值 | const int fun( int i); |
阻止用户修改返回值,返回值必须要相应的赋给一个具有const属性的变量 | |
const成员函数 | <类型说明符> <函数名> ( <参数表> ) const; |
不会修改类的成员数据,也不能调用其它非const成员函数。 可以利用const限定符实现函数重载。 const对象默认会调用const成员函数 |
const限定符和static的区别
- 静态变量的值虽然只能进行一次初始化,但是它的值是可变的。而const的值是不可变的。
- const定义的变量在超出其作用域后空间就会被释放,而static定义的静态变量不会释放空间,而是一直存在,知道程序结束
- static表示静态,类的静态成员函数、静态成员变量都是和类相关的,而不是和具体的对象相关,即使没有具体对象,也能调用静态成员函数和静态成员变量。而类则是和具体的对象相关的,不同的对象独占一个const变量
- static静态成员变量不能在类的内部进行初始化,智能在类的外部进行初始化,并且初始化时不能加
static
关键字。之后,无需创建对象也能通过类使用该静态变量,同时 - 由于const变量只是针对于某个类的对象而言的,类可以创建多个不同的对象,每个对象都有自己的const变量,又因为const变量值只能进行一次初始化而不仅再次赋值,因此, const变量也不能在类的内部初始化,而只能在类构造函数的初始化列表中进行 。
- 如果想要创建整个类的恒定变量,那么应用使用
static const
来声明(const枚举量也可以)
关于const的其它注意事项
将非const指针赋给const指针(两级间接关系)
p222
const引用与不可寻址值
const引用可以用不同类型的对象初始化(主要能从一种类型转换到另一种类型即可),也可以用字面常量初始化,如下所示:
1 | double dval = 3.14159; |
首先要知道,上面的初始化对于非cosnt引用是不合法的,将导致编译错误!
原因:引用在内部存放的是一个对象的地址,它是该对象的别名。对于不可寻址的值,如文字常量,以及不同类型的对象,编译器为了实现引用,必须生成一个临时对象,将该对象的值置入临时对象中,引用实际上指向该对象(对该引用的操作就是对该临时对象的操作),但用户不能访问它。因此,C++为了不让用户通过引用修改该临时变量,强制要求必须使用const引用。
const引用与临时变量
由于上面提到的原因, C++引用类型在面对会产生临时变量的语句时,必须使用const引用来指向!!切记!!
1 | int i = 100; |
const引用与非const引用在内存中的对比
内存地址都是一样的,网上有的写不一样,是错误的!
1 | cosnt int t = 9; |
不允许非const引用指向需要临时对象的对象或值,即,编译器产生临时变量的时候引用必须为const!!!!切记!!
1 | int iv = 100; |
const变量的生存期、作用域和链接性
临时变量一般会在语句块的末尾自动释放,但是有两个例外:
- 将临时对象作为初始化因子,例如
string s = string("hello world");
- 将一个常量引用变量绑定到这个临时对象上。
作用域
在全局作用域中声明的const变量,只能在当前文件中访问,如果要在其他文件中使用,则必须用extern
显式的声明const变量全局变量。(在C中,可以不用extern?)
用const修饰函数参数
是否应将void Func(int x) 改写为void Func(const int &x),以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
问题是如此的缠绵,我只好将“const &”修饰输入参数的用法总结一下。
对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。
对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)
。
用const修饰函数的返回值
- 修饰内置类型的返回值时, 加
const
没有意义 - 修饰自定义类型的返回值时, 此时的返回值不能作为左值使用, 既不能被赋值, 也不能被修改
- 修饰指针或引用的返回值时, 取决于我们想让用户干什么
用const修饰类的成员函数
表明该函数 不能修改类对象的数据成员, 并且不能调用非 const 函数 (因为非const函数有可能会修改数据成员)
1 | int GetCount(void) const; // const 成员函数 |
在修饰成员函数时, const 关键字不能与 static 关键字同时使用, 因为 static 关键字修饰的是静态成员函数, 其不含有this
指针, 因此不能实例化, 而 const 成员函数必须具体到某一实例.
类中的const常量
有时我们希望某些常量只在类中有效, 由于#define
定义的宏常量是全局的, 因此不能达到目的, 但是直接用const
修饰数据成员并不是我们想要的含义, const数据成员只在某个对象(注意是对象, 不是类)生存期内是常量, 而对于整个类而言却是可变的, 因为类可以创建多个对象, 不同的对象其const
数据成员的值可以不同.
从上面的概念我们可以得出, 不能在类声明中初始化const
数据成员, 而应该在类的构造函数的初始化表中进行.
要建立整个类中的常量, 则应该使用枚举常量, 枚举常量的缺点是, 它的隐含数据类型是整数, 其最大值有限, 且不能表示浮点数.