规则:
const作用:
const优点:
1:编译器可以对const进行类型安全检查(所谓的类型安全检查,能将程序集间彼此隔离开来,这种隔离能确保程序集彼此间不会产生负面影响,提高程序的可读性);
2:有些集成化的调试工具可以对const常量进行调试,使编译器对处理内容有了更多的了解,消除了一些隐患。
eg:void hanshu(const int i){.......} 编译器就会知道i是一个不允许被修改的常量
3:可以节省空间,避免不必要的内存分配,因为编译器通常不为const常量分配内存空间,而是将它保存在符号表中,这样就没有了存储于读内存的操作,使效率也得以提高;
4:可以很方便的进行参数的修改和调整,同时避免意义模糊的数字出现;
分类如下:
常变量: const 类型说明符 变量名
常引用: const 类型说明符 &引用名
常对象: 类名 const 对象名
常成员函数: 类名::fun(形参) const
常数组: 类型说明符 const 数组名[大小]
常指针: const 类型说明符* 指针名 ,类型说明符* const 指针名
首先提示的是:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小]), const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。如:
const int a=5; 与 int const a=5; 等同
类名 const 对象名 与 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关键字使得程序每次直接去内存中读取变量值而不是读寄存器值,这个作用在解决一些不是程序而是由于别的原因修改了变量值时非常有用。
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():"<<a<<ends<<b<<endl;}
void Example::print() const {cout<<"print() const:"<<a<<ends<<b<<endl;}
const修饰参数是为了防止函数体内可能会修改参数原始对象。因此,有三种情况可讨论:
void Fun( const A *in); //修饰指针型传入参数
void Fun(const A &in); //修饰引用型传入参数
void func (const int& n)
{
n = 10; // 编译错误
}
令函数返回一个常量,可以有效防止因用户错误造成的意外。
if (a*b = c)
如果a,b,c都是如同int的内置类型,编译器会直接报错
因为对于内置类型的*操作返回的不是一个左值,因此不能放在=的左边。为什么会出现这种情况呢?可能用户只是想比较是否相等,却打字打漏了一个等号(ORZ)。因此,对于很多自定义类型的函数,应该尽量与内置类型兼容,在应该返回右值的函数返回那里应该加上const。
if (a*b == c)
const修饰函数返回值的含义和用const修饰普通变量以及指针的含义基本相同。这样可以防止外部对 object 的内部成员进行修改。
const int* func() // 返回的指针所指向的内容不能修改
{
// return p;
}
类的常成员函数
由于C++会保护const对象不被更新,为了防止类的对象出现意外更新,禁止const对象调用类的非常成员函数。因此,常成员函数为常对象的唯一对外接口。
常成员函数的声明方式:类型说明符 函数名(参数表) const
class A
{
public:
int& getValue() const
{
// a = 10; // 错误
return a;
}
private:
int a; // 非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():"<<a<<ends<<b<<endl;}
void Example::print() const {cout<<"print() const:"<<a<<ends<<b<<endl;}
如果为静态常数据成员,由于不属于具体对象,所以不能在构造函数里赋值,仍然应该在类外赋值。特别地,如果静态常量为整数或枚举类型,C++允许在类内定义时指定常量值。
比如以下两种方式均合法:
class Example{
public:
Example(int x, int y):a(x),b(y){}
void print();
void print() const;
private:
const int a,b;
static const int c = 10; //静态常量
};
class Example{
public:
Example(int x, int y):a(x),b(y){}
void print();
void print() const;
private:
const int a,b;
static const int c; //静态常量
};
const int Example::c = 10;
用const修饰的类对象,该对象内的任何成员变量都不能被修改。
因此不能调用该对象的任何非const成员函数,因为对非const成员函数的调用会有修改成员变量的企图。
class A
{
public:
void funcA() {}
void funcB() const {}
};
int main
{
const A a;
a.funcB(); // 可以
a.funcA(); // 错误
const A* b = new A();
b->funcB(); // 可以
b->funcA(); // 错误
}
在类内重载成员函数
class A
{
public:
void func() {}
void func() const {} // 重载
};
const_cast运算符用来修改类型的const或volatile属性。
简单来说,const_cast可以将常指针或常引用的const属性去除,注意:const_cast不用来将常对象转换为普通对象。只是为调用他的对象提供接口:
举个例子:
const int a = 4;
const int * p = &a;
int * m = const_cast<int*>(p);
* m = 5;
cout<<" a:"<<a<<ends<<&a<<endl;
cout<<"*p:"<<*p<<ends<<p<<endl;
cout<<"*m:"<<*m<<ends<<m<<endl;
输出结果:
a:4 0x61ff04
*p:5 0x61ff04
*m:5 0x61ff04
可见,const_cast并没用真正修改a中的值,只是修改了m和p指针中的值,并且它们指向的是同一块空间。
const_cast的一个安全用法是,在类中的一个非常成员函数中,调用重载的常成员函数,以此来实现代码重用。
再举个例子:
const int & get_element(int n) const{
if(n>=0 && n <len){
throw "invalid memory visit";
}
return element[n];
}
int & get_element(int n){
return const_cast<int &>(
static_cast<const Array *>(this)->get_element(n)
);
}
这是一个类中的两个同名函数,第一个函数仅供const对象调用,非const的函数为了重用const函数的代码,需要先将当前对象的指针转化为const类型,以此调用const函数,然后再将返回的结果用const_cast去除const属性,返回的引用就是可修改的了。
(1) 编译器处理方式不同
define宏是在预处理阶段展开。
const常量是编译运行阶段使用。
(2) 类型和安全检查不同
define宏没有类型,不做任何类型检查,仅仅是展开。
const常量有具体的类型,在编译阶段会执行类型检查。
(3) 存储方式不同
define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
const常量会在内存中分配(可以是堆中也可以是栈中)。
(4)const 可以节省空间,避免不必要的内存分配。 例如:
#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......
double i=Pi; //此时为Pi分配内存,以后不再分配!
double I=PI; //编译期间进行宏替换,分配内存
double j=Pi; //没有内存分配
double J=PI; //再进行宏替换,又一次分配内存!
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。
static
1、static局部变量 将一个变量声明为函数的局部变量,那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中
2、static 全局变量 表示一个变量在当前文件的全局内可访问
3、static 函数 表示一个函数只能在当前文件中被访问
4、static 类成员变量 表示这个成员为全类所共有
5、static 类成员函数 表示这个函数为全类所共有,而且只能访问静态成员变量
static关键字的作用:
const关键字的作用
1、https://blog.csdn.net/u011333734/article/details/81294043
2、https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777416.html
3、https://www.cnblogs.com/jiabei521/p/3335676.html
4、https://www.jianshu.com/p/a346cb1ca104
5、https://www.jianshu.com/p/5c35cc218bb4