const关键字
const关键字1、什么是const2、使用原理2.1、const全局/局部变量2.2、cosnt修饰指针和引用2.3、const修饰函数参数2.4、const修饰函数返回值2.5、const成员函数和数据成员2.6、const修饰类对象
3、const_cast的知识4、const与宏定义的区别5、Static与Const的区别
参考
const关键字
1、什么是const
const是一个C++语言的限定符,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的安全性和可靠性。只要一个变量前用const来修饰,就意味着该变量里的数据只能被访问,而不能被修改,也就是意味着const“只读”(readonly)const 是 C++ 中的关键字,它会在编译期间(时机很重要),告诉编译器这个对象是不能被修改的。
规则:
const离谁近,谁就不能被修改;const修饰一个变量时,一定要给这个变量初始化,若不初始化,在后面也不能初始化。
const作用:
1:可以用来定义常量,修饰函数参数,修饰函数返回值,且被const修饰的东西,都受到强制保护,可以预防其它代码无意识的进行修改,从而提高了程序的健壮性(是指系统对于规范要求以外的输入能够判断这个输入不符合规范要求,并能有合理的处理方式。ps:即所谓高手写的程序不容易死);2:使编译器保护那些不希望被修改的参数,防止无意代码的修改,减少bug;3:给读代码的人传递有用的信息,声明一个参数,是为了告诉用户这个参数的应用目的;
const优点:
1:编译器可以对const进行类型安全检查(所谓的类型安全检查,能将程序集间彼此隔离开来,这种隔离能确保程序集彼此间不会产生负面影响,提高程序的可读性);
2:有些集成化的调试工具可以对const常量进行调试,使编译器对处理内容有了更多的了解,消除了一些隐患。
eg:void hanshu(const int i){.......} 编译器就会知道i是一个不允许被修改的常量
3:可以节省空间,避免不必要的内存分配,因为编译器通常不为const常量分配内存空间,而是将它保存在符号表中,这样就没有了存储于读内存的操作,使效率也得以提高;
4:可以很方便的进行参数的修改和调整,同时避免意义模糊的数字出现;
2、使用原理
分类如下:
常变量: const 类型说明符 变量名
常引用: const 类型说明符 &引用名
常对象: 类名 const 对象名
常成员函数: 类名::fun(形参) const
常数组: 类型说明符 const 数组名[大小]
常指针: const 类型说明符* 指针名 ,类型说明符* const 指针名
首先提示的是:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小]), const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。如:
const int a=5; 与 int const a=5; 等同
类名 const 对象名 与 const 类名 对象名 等同
2.1、const全局/局部变量
const全局变量 在文件a.cpp中定义了一个全局变量a
int a = 1;
在文件test.cpp中使用全局变量a
#include
using namespace std;
extern int a;
int main()
{
//const volatile int a = 7;
int *p = (int *)(&a);
*p = 8;
cout << "a=" << a << endl;
cout << "*p=" << *p;
system("pause");
return 0;
}
结果为:
a = 8
*p = 8
如果将全局变量a定义为const
const int a = 1;
#include
using namespace std;
extern const int a;
int main()
{
//const volatile int a = 7;
int *p = (int *)(&a);
*p = 8;
cout << "a=" << a << endl;
cout << "*p=" << *p;
system("pause");
return 0;
}
这里可以看出const在修饰全局变量时第一个作用,会限定全局变量的作用范围到其定义时所在的编译单元。
const全局变量使得我们指定了一个语义约束,即被修饰的全局变量不允许被修改,而编译器会强制实施这个约束。
#include
using namespace std;
const int a = 7;
int main()
{
//const volatile int a = 7;
int *p = (int *)(&a);
*p = 8;
cout << "a=" << a << endl;
cout << "*p=" << *p;
system("pause");
return 0;
}
运行这段代码,会发现编译器报异常。编译器不允许对const全局变量的改动。
const局部变量
#include
using namespace std;
int main()
{
//const volatile int a = 7;
const int a = 7;
int *p = (int *)(&a);
*p = 8;
cout << "a=" << a << endl;
cout << "*p=" << *p;
system("pause");
return 0;
}
a = 7
*p = 8
运行结果显示const局部变量被修改了,但是在使用变量名输出时,编译器会出现一种类似宏定义的功能一样的行为,将变量名替换为初始值。可见,const局部变量并不能做到真正的不变,而是编译器对其进行了一些优化行为,这导致了const局部变量与真实值产生了不一致。(常量折叠现象)
那么,如果想获取修改后的const局部变量真实值,该怎么办呢?答案是使用volatile关键字。
#include
using namespace std;
int main()
{
const volatile int a = 7;
//const int a = 7;
int *p = (int *)(&a);
*p = 8;
cout << "a=" << a << endl;
cout << "*p=" << *p;
system("pause");
return 0;
}
a = 8
*p = 8
volatile关键字使得程序每次直接去内存中读取变量值而不是读寄存器值,这个作用在解决一些不是程序而是由于别的原因修改了变量值时非常有用。
2.2、cosnt修饰指针和引用
cosnt修饰指针 const修饰指针,涉及到两个很重要的概念,顶层const和底层cosnt
指针自身是一个对象,它的值为一个整数,表明指向对象的内存地址。因此指针长度所指向对象类型无关,在32位系统下为4字节,64位系统下为8字节。进而,指针本身是否是常量以及所指向的对象是否是常量就是两个独立的问题。
从 const 指针开始说起。const int* pInt; 和 int *const pInt = &someInt;,前者是 *pInt 不能改变,而后者是 pInt 不能改变。因此指针本身是不是常量和指针所指向的对象是不是常量就是两个互相独立的问题。用顶层表示指针本身是个常量,底层表示指针所指向的对象是个常量。
更一般的,顶层 const 可以表示任意的对象是常量,这一点对任何数据类型都适用;底层 const 则与指针和引用等复合类型有关,比较特殊的是,指针类型既可以是顶层 const 也可以是底层 const 或者二者兼备。
int a = 1;
int b = 2;
const int* p1 = &a;
int* const p2 = &a;
1.指针常量(指针不可改,指针指向的对象可改)
int a = 10;
int b = 5;
int * const p1 = &a;
p1 = &b; //指针不可改,不合法
*p1 = b; //指针指向的对象可改,合法
2.常量指针(指针可改,指针指向的对象不可改)
int a = 10;
int b = 5;
const int* p2 = &a;
p2 = &b; //指针可改, 合法
*p2 = b; //不合法
拷贝与顶层和底层 const
int i = 0;
int *const p1 = &i; // 不能改变 p1 的值,这是一个顶层
const int ci = 42; // 不能改变 ci 的值,这是一个顶层
const int *p2 = &ci; // 允许改变 p2 的值,这是一个底层
const int *const p3 = p2; // 靠右的 const 是顶层 const,靠左的是底层 const
const int &r = ci; // 所有的引用本身都是顶层 const,因为引用一旦初始化就不能再改为其他对象的引用,这里用于声明引用的 const 都是底层 const
当执行对象的拷贝操作时,常量是顶层const还是底层const的区别明显。其中,顶层 const 不受什么影响。
i = ci; // 正确:拷贝 ci 的值给 i,ci 是一个顶层 const,对此操作无影响。
p2 = p3; // 正确:p2 和 p3 指向的对象相同,p3 顶层 const 的部分不影响。
与此相对的,底层 const 的限制却不能被忽视。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层 const 资格,或者两个对象的数据类型必须能够转换,一般来说,非常量可以转化为常量,反之不行。
int *p = p3; // 错误:p3 包含底层 const 的定义,而p没有。假设成功,p 就可以改变 p3 指向的对象的值。
p2 = p3; // 正确:p2 和 p3 都是底层 const
p2 = &i; // 正确:int* 能够转化为 const int*,这也是形参是底层const的函数形参传递外部非 const 指针的基础。
int &r = ci; // 错误:普通 int& 不能绑定到 int 常量中。
const int &r2 = i; // 正确:const int& 可以绑定到一个普通 int 上。
cosnt修饰引用
常引用所引用的对象不能更新,使用方法为:const 类型说明符 &引用名。
非const引用只能绑定非const对象,const引用可以绑定任意对象,并且都当做常对象。
常引用经常用作形参,防止函数内对象被意外修改。对于在函数中不会修改其值的参数,最好都声明为常引用。复制构造函数的参数一般均为常引用。
仍然是上面那个例子:
class Example{
public:
Example(int x, int y):a(x),b(y){}
Example(const Example &e):a(e.a),b(e.b){} //复制构造函数
void print();
void print() const;
private:
const int a,b;
static const int c = 10;
};
void Example::print() {cout<<"print():"<
void Example::print() const {cout<<"print() const:"<
2.3、const修饰函数参数 const修饰参数是为了防止函数体内可能会修改参数原始对象。因此,有三种情况可讨论: 1、函数参数为值传递:值传递(pass-by-value)是传递一份参数的拷贝给函数,因此不论函数体代码如何运行,也只会修改拷贝而无法修改原始对象,这种情况不需要将参数声明为const。2、函数参数为指针:指针传递(pass-by-pointer)只会进行浅拷贝,拷贝一份指针给函数,而不会拷贝一份原始对象。因此,给指针参数加上顶层const可以防止指针指向被篡改,加上底层const可以防止指向对象被篡改。3、函数参数为引用:引用传递(pass-by-reference)有一个很重要的作用,由于引用就是对象的一个别名,因此不需要拷贝对象,减小了开销。这同时也导致可以通过修改引用直接修改原始对象(毕竟引用和原始对象其实是同一个东西),因此,大多数时候,推荐函数参数设置为pass-by-reference-to-const。给引用加上底层const,既可以减小拷贝开销,又可以防止修改底层所引用的对象。 void Fun( const A *in); //修饰指针型传入参数 void Fun(const A &in); //修饰引用型传入参数 void func (const int& n) { n = 10; // 编译错误 } 2.4、const修饰函数返回值 令函数返回一个常量,可以有效防止因用户错误造成的意外。 if (a*b = c) 如果a,b,c都是如同int的内置类型,编译器会直接报错 因为对于内置类型的*操作返回的不是一个左值,因此不能放在=的左边。为什么会出现这种情况呢?可能用户只是想比较是否相等,却打字打漏了一个等号(ORZ)。因此,对于很多自定义类型的函数,应该尽量与内置类型兼容,在应该返回右值的函数返回那里应该加上const。 if (a*b == c) const修饰函数返回值的含义和用const修饰普通变量以及指针的含义基本相同。这样可以防止外部对 object 的内部成员进行修改。 const int* func() // 返回的指针所指向的内容不能修改 { // return p; } 2.5、const成员函数和数据成员 类的常成员函数 由于C++会保护const对象不被更新,为了防止类的对象出现意外更新,禁止const对象调用类的非常成员函数。因此,常成员函数为常对象的唯一对外接口。 常成员函数的声明方式:类型说明符 函数名(参数表) const const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的;const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。 class A { public: int& getValue() const { // a = 10; // 错误 return a; } private: int a; // 非const成员变量 }; 有如下几个要点: 常成员函数的定义和声明都要含有const关键字;一个函数是否含有const关键字可以作为重载函数,const对象默认调用const函数,非const对象默认调用非const函数,如果没有非const函数,也可以调用const函数;const函数中不能更新目的对象的任何成员(mutable修饰的变量除外,这里不展开阐述),以此方法来保证const对象不被修改。如果const成员函数想修改成员变量值,可以用mutable修饰目标成员变量。 类的常数据成员 类的数据成员不能在任何函数中被赋值或修改,但必须在构造函数中使用初始化列表的方式赋初值。 举个例子,刚才的类如果a, b为常数据成员,则应该改写为如下形式: class Example{ public: Example(int x, int y):a(x),b(y){} //初始化列表方式赋初值 void print(); void print() const; private: const int a,b; }; void Example::print() {cout<<"print():"<