时间:202205232202
- 常量及其定义
- `const`常量和宏常量的区别
- `const`修饰引用
- `const`修饰指针
- `const`变量在内存中位置
- `const volatile`
- `const `在类里面的使用
constexpr 使用总结 常量及其定义
常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值。常量就像是常规的变量,只不过常量的值在定义后不能进行修改。
int a = 5; // 5就是常量 cout << "Hello World " // Hello World 字符串就是一个常量
定义常量的两种方式
//宏常量 #define MAX 100 // const关键字 const int max = 100;const常量和宏常量的区别
-
const使用时有类型安全检查,而宏常量在使用时会直接替换
-
const更加节省内存空间
#define PI 3.14159 const doulbe Pi=3.14159; //此时并未将Pi放入ROM(Read-Only Memory),只读寄存器中 double i=Pi; //此时为Pi分配内存,以后不再分配! double I=PI; //编译期间进行宏替换,分配内存 double j=Pi; //没有内存分配 double J=PI; //再进行宏替换,又一次分配内存!
- 宏常量可以取消
#define PI 3.14159 #undef PI 3.14159 double I=PI; // 错误 未定义标识符 PIconst修饰引用
const 是修饰一个数据为常量,而“引用”是给变量取一个别名,起到指针的作用,但是比指针容易操作,在C++开发中,为了达到“形参的改变,引起实参的变化”,可以把参数定义为一个引用。但是,有时候,我们为了在函数中,形参不能够被修改,起到保护的作用,就可以把这个形参引用定义为const类型。
const int& rx = x;const修饰指针
使用代码示例如下
const int* a1 = new int(10); int const* a2 = new int(10); a1 = new int(20); // 不修改内容 直接赋予新值 *a1 = 20; // 报错 表达式必须是可修改的左值 // 及原式对指向的内容做了限定 不能修改 a2 = new int(30);// 不修改内容 直接赋予新值 *a2 = 20;// 报错 同上 实际上 a1 和 a2 的写法效果是一样的 int* const a3 = new int(10); const int* const a4 = new int(10); a3 = new int(30); // 报错 同上 *a3 = 30; // 直接赋予新值 a4 = new int(30); // 报错 同上 *a4 = 40;// 报错 不能修改值
从上面的示例可以看到,有些值能修改,有些值不能修改,那在实际使用中怎么去判断呢?Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:把一个声明从右向左读。 ( * 读成 pointer to )
// 读成 a1 is pointer to int const 表示int这个值是const 不变 const int* a1 // 读成 a3 const is pointer to int 表示a3这个指针是const不变 int* const a3
总结就是const离谁近,谁就不能变
const变量在内存中位置经过const修饰引用和指针的简单使用,肯定会有疑问,最开始讲的是const不是将变量定义为常量的么?那为什么还能去改变它呢?
其实const修饰的量不是常量,仅仅是个只读量。在编译的时候全部替换const变量被赋予的值,在运行的时候该const变量可通过内存进行修改。做如下两点解释
(1)通过内存(指针)可以修改位于栈区的const变量,语法合乎规定,编译运行不会报错,但是在编译的时候所有用到该常量的地方全部被替换成了定义时所赋予的值,然后在运行的时候无法使用通过指针修改后的值。
(2)通过内存(指针)修改位于静态存储区的的const变量,语法上没有报错,编译不会出错,一旦运行就会报告异常。
const只在编译期间保证常量被使用时的不变性,无法保证运行期间的行为。程序员直接修改常量会得到一个编译错误,但是使用间接指针修改内存,只要符合语法则不会得到任何错误和警告。因为编译器无法得知你是有意还是无意的修改,但是既然定义成const,那么程序员就不应当修改它,不然直接使用变量定义好了,示例代码如下
const int a = 1;
void test()
{
const int c = 3;
int* p = (int*)& c;
*p = 4;
//在编译的时候c被替换成了3,无法使用通过内存修改后的值
//*p表示c被修改后的值
cout << c << " " << *p << endl; // 输出 3 4 但是你debug调试时 c 是显示的4
p = (int*)& a;
//在编译的时候a被替换成了1,p指向a的地址
cout << c << " " << *p << endl; // 输出 3 1
//修改位于静态存储区的const变量,编译无错,运行就会报异常
*p = 4;
}
总结:const修饰的变量是”只读”的,无法通过变量自身去修改自己的值,但是可通过指针引用等间接修改
const volatileconst volatile修饰的变量,可以在编译时不用全部替换被const volatile变量被赋予的值,因此可以在运行时使用通过内存修改后的值:
(1)通过内存(指针)可以修改位于栈区的const volatile变量
(2)通过内存(指针)修改位于静态存储区的的const volatile变量,语法和编译没有报错,运行报错异常。
示例代码如下
const int b = 1;
void test2()
{
const volatile int c = 3;
//const volatile被修饰的变量只有在运行的时候才使用其在初始化时被赋予的值
//编译的时候,const volatile修饰的变量不替换
cout << c << endl; // 输出3
void* p = (void*)&c;
(*(int*)p) = 4;
//修改c的值为4
cout << c << endl; // 输出 4 就是说 c 的值被改变了
p = (void*)& b;
//在运行的时候修改静态存储区的const volatile变量编译不出错,运行报错
(*(int*)p) = 5;
cout << b << endl;
}
const 在类里面的使用
初始化常成员变量:在某个对象生存期内是常量,const常量只能通过初始化列表的方式进行初始化。
class Test
{
public:
const int a;
Test(int i):a(i){}
}
修饰函数参数:在传入的参数前,加上const,表明传入的参数不可修改
void fun0(const Test* p_test); void fun1(const Test& test); void fun1(Test test);
修饰成员函数:成员函数名后加上const,即表明该函数为常成员函数。常成员函数的const隐式修饰this指针,即对象不可修改,所以常成员函数只能访问成员变量,而不能修改。只能调用其他常成员函数。
int g_a;
void g_fun(){}
class Test
{
public:
void fun0(){}
void fun1() const{}
void fun2() const
{
a = 10;//报错,“必须是可修改的左值”
fun0();//报错,对象包含与成员函数不兼容的类型限定符
fun1();//正常,可调用其他常成员函数
g_a = 10;//正常
g_fun();//正常
m_b = 10; // 特殊 可以修改
}
private:
int a;
mutable int m_b; // mutalbe 跟const是反义词,加上关键字就可以对其进行修改
}
修饰函数返回值:当使用const Test* fun1();返回指针时,该返回值只能赋给const修饰的同类型指针。
class Test
{
public:
void fun0(){}
const Test fun1(){
return *this;
}
const Test* fun2(){
return this;
}
};
int main()
{
Test test;
Test r = test.fun1();
const Test* rp = test.fun2(); // 返回指针时需特别注意
Test* rp = test.fun2(); // 报错 const Test* 类型不能用于初始化Test*类型的实体
}



