const 那些事
const 那些事
强烈推荐看:《C++编程思想-8章常量》。
1. const 含义
const=constant,
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。
举个例子,就是 const int test_a = 12, 你是没法给 test_a 赋值的。
const 关键字指定变量的值是常量,并通知编译器防止程序员对其进行修改。
经典一问:const 变量是否可以被修改?
答1:可以。const 修饰的始终是变量,如果const 变量位于栈区,那么是可以通过取地址修改。
但是,如果const 变量位于全局,那么是不可以修改的。
1 | // const 变量可以修改的示例 |
答2:Linux上 全局常量是放在 .rdata段的,该段只读,所以尝试修改就会报错。
为什么C++无法修改全局静态常量?(Linux平台示例-知乎)
const作用
- 定义常量
const int a=100;
- 类型检查
const常量有类型,编译器会进行安全检查;#define宏定义只是字符串替换,不进行安全检查;
#define 也是有类型的,详细见:讨论#define和const的类型问题
- const 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。
- 其他情况下它只是一个 const 限定的变量,不要将与常量混淆。
- 防止修改,起保护作用,增加程序健壮性
修饰函数参数等,如果参数被修改会保存。
1
2
3void f(const int i){
i++; // error!
} - 节省空间,避免不必要的内存分配
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数。
const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
理解常量和常量表达式
const的存储位置探究(Todo)
- const修饰的量不是常量,仅仅是个只读量。在编译的时候全部替换赋值的const常量,类似#define替换;
在运行的时候该const变量可通过内存进行修改。总结来说就是,const修饰的数据,编译的时候会进行替换,这样被赋值的数据运行的时候数据是编译期定好的。
即使你修改了 const 修饰的变量,被赋值的数据不会因为运行时的修改而变化。
但是const 能修改的前提必须是可读可写的,比如栈上的变量,全局的就不行。 - CSDN关于const存储位置的讨论
CSDN总结的blog, 我觉得写的还可以
const对象的范围
默认为文件局部变量。
注意: 非const变量默认为extern。要使const变量能够在其他文件中访问,必须在文件中显式地指定它为extern。
非const修饰的变量在不同文件的访问
1 | // file1.cpp |
const 修饰的变量在不同文件的访问
const 修饰的想外部访问,定义的时候就得加 extern;且需要初始化。
1 | // file1.cpp |
extern const 未初始化会报链接错误:无法解析的外部符号;
const用法
const修饰普通类型的变量
1 | const int b = 10; |
作为常量,直接赋值修改是不行的哦!
作为常量,必须初始化哦!
C2734 “const_value”: 如果不是外部的,则必须初始化常量对象。
const与指针
1 | const char * a; // 指向常量的指针,a 指针类型是 const char 的,即指向的内容不能被修改。 |
常量指针: 指向的对象是常量。不能修改指针所指向的内容。
const 位于 * 的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
1 | // 编译环境 msvc2017 |
常量指针的注意点:
- const_ptr_a 指针不用赋初值,但不能通过 const_ptr_a 修改所指向的对象的值;
- 不能使用void指针保存const对象的地址,必须使用const void类型的指针保存const对象的地址;
- 可以把非const对象的地址赋值给 const 对象的指针;但不能通过 const 定义的那个指针修改对象值。
指针常量: 指针本身是常量。不能修改指针的地址。
const 位于 * 的右侧,const就是修饰指针本身,即指针本身是常量。
指针常量的注意点
- const指针必须初始化,且const指针的值不能修改。(就是说const指针存储的地址不能动哦)
1
2
3
4
5
6
7
8// 编译环境 msvc2017
int main() {
int num=0;
int * const ptr=# //const指针必须初始化!且const指针的值不能修改
int * t = # // 通过取地址得到 num 的地址,就可以修改 num的值。
*t = 1;
// 总结一下:*ptr,*t 其实都是保存的 num的地址,只不过 *ptr是const修饰的,不能直接改num的值。
}
指向常量的常指针
1 | // 编译环境 msvc2017 |
知乎关于const指针讲的比较容易懂的blog
通俗易懂理解什么是常量指针和指针常量——知乎
const应用到函数中
- const修饰函数返回值
- const int —— 本身无意义,函数返回本身就是要赋值给其他变量的。
1
2// 编译环境 msvc2017
const int func1();
- const int —— 本身无意义,函数返回本身就是要赋值给其他变量的。
- const int* —— 指针指向的内容不变
1
2// 编译环境 msvc2017
const int* func2();
- const int* —— 指针指向的内容不变
- int* const —— 指针本身即指针的地址不能变
1
2// 编译环境 msvc2017
int *const func2();
- int* const —— 指针本身即指针的地址不能变
- const修饰函数参数
- 传过来的参数以及指针本身在函数内不可变,没有意义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 编译环境 msvc2017
void func(const int var); // 传递过来的参数不可变
void func(int *const var); // 指针本身不可变
void TestConstPtr(int *const var) {
int test = *var;
}
int main() {
int num = 3211;
int * const ptr_num = # //const指针必须初始化!且const指针的值不能修改
TestConstPtr(ptr_num);
}
// 确认 结果:
// &ptr_num:
// &var:
// 上面2个地址是不同的,所以 var 是函数参数的临时变量。值传递方式, 形参本身会产生临时变量,参数无需保护,所以加不加const没有意义。
const int var 和 int *const var 都是值传递。
理解下 int *const var 为什么没有意义?
答1:const修饰的是指针本身,所以此处只是修饰 var 本身不变;
答2:另外,指针传递的时候,var 指向的数据依然可能被修改,所以只限制 var 本身没有意义。
- 传过来的参数以及指针本身在函数内不可变,没有意义
- 参数指针指向的内容为常量不可变
1
void StringCopy(char *dst, const char *src); // 限定 char* 可以保证指向的内容不被修改
试图修改 src 时,编译会报错。
- 参数指针指向的内容为常量不可变
- 参数为引用,为了增加效率同时防止修改
1
2
3
4
5
6
7
8
9
10void funcA(const A a); // 值传递,效率低,对象的构造、复制、析构都会消耗时间。
void funcA(const A& a); // 使用引用——变量的别名,避免值传递的开销。
void funcA(const A& a); // const限定对象的引用
void funcA(const std::string& value); // 具体例子:std::string
// 一个疑问: 是否所有的都用引用更好?
// 答:没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
void funcA(const int& a);1. 引用只是参数的别名而已,不需要产生临 时对象。 2. const 限定的引用,可以避免引用被修改。
- 参数为引用,为了增加效率同时防止修改
const 引用传递的使用场景
- 非内部数据做输入参数:建议使用 const 引用,提高效率;
- 内部数据类型做输入参数:不建议用,比如 int、double 等类型;
const类中的用法
const成员函数:只要不修改数据成员,就应该声明为const;
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
const 成员函数调用非const成员函数的不行的哦!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// const成员函数调用 非const成员函数示例,编译报错
// E1086 对象含有与成员 函数 "Apple::FuncB" 不兼容的类型限定符
// C2662: “int Apple::FuncB(void)”: 不能将“this”指针从“const Apple”转换为“Apple &”
/*
解释下 C2662: “int Apple::FuncB(void)”: 不能将“this”指针从“const Apple”转换为“Apple &”
FunA中调用FuncB的时候,要加入this。
this->FuncB();
≈
const Apple* this->FuncB(); // FuncB() 本身是非const的,所以报错了。
*/
class Apple {
public:
Apple(int i) : apple_number(i){
app_id = i;
}
int FuncA() const {
int rest = 123;
FuncB(); // error, 不能将“this”指针从“const Apple”转换为“Apple &”
return rest;
}
int FuncB() {
return 123;
}
private:
int people[100];
const int apple_number;
const int app_code = 312;
int app_id;
};理解下为什么 const 对象只能访问 const 成员函数?为什么const成员函数中调用非const成员就会报错?
答:理解下 this 指针就能明白为什么了。 this 指针是成员函数默认参数,也是有类型的,const 成员类型就是const的。
而const成员变量
- 必须通过初始化列表进行初始化 or 定义的时候初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Apple {
public:
Apple(int i) {
apple_number = i; // error,编译错误,『表达式必须是可以修改的左值』。
apple_number = 123; // 如上一样。
}
Apple(int i) : apple_number(i) { // 放到初始化列表是OK的
}
Apple(int i) : apple_number(100) { // 不使用构造参数也是OK的
}
int FuncA(int posx, int posy) {
return posx + posy;
}
private:
int people[100];
const int apple_number;
const int app_code = 312; // const int 这么初始化也是OK的。
int appid;
};
- 必须通过初始化列表进行初始化 or 定义的时候初始化。
查看C++类的内存结构方法
- 查看所有类:/d1 reportAllClassLayout
- 查看单个类:/d1 reportSingleClassLayoutXXX(XXX为类名)
/d1 参数统一放到 C/C++ → 命令行:其他选项
查看C++类的内存结构方法_例子写的很好的blog1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// 一个简单的类内存布局,没有虚函数等
/*
* 编译的时候会输出布局
1>class Apple size(412):
1> +---
1> 0 | people
1>400 | apple_number
1>404 | app_code
1>408 | app_id
1> +---
*/
// 如上,静态成员归属于所有类,所以布局上看不到。
class Apple {
public:
Apple(int i) : apple_number(i){
app_id = i;
}
int FuncA() const {
int rest = 123;
FuncB();
return rest;
}
int FuncB() {
return 123;
}
private:
int people[100];
const int apple_number;
const int app_code = 312;
int app_id;
static int s_test_a; // 类的static 类内声明,不能初始化哦!
};
int Apple::s_test_a = 212; // static 必须类外初始化!!!
理解this指针
先说编译器怎么做的?
this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给this。
this 形参是隐式的,并不出现在代码中,而是编译阶段由编译器默默地将它添加到参数列表中。且只有非静态成员函数哦!
成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息。
所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。示例:int FuncA(int posx, int posy); 编译时可能变成了 int FuncA(Apple* this, int posx, int posy) 【不一定是这个结果,只用于示例说明】
总结一下this是什么?
答: this是编译器加给c++类的非static成员函数的额外参数,用于管理类的成员函数和变量。作为参数也有类型限定,比如使用const。
作为参数,this指针也是有类型的。
this的类型取决于成员函数的声明情况。
const 方法时,指针类型就是 const Apple, 非const 就是 Apple 。
1 | int FuncA(int posx) { |
C++中的this指针_geeksforgeeks
C语言中文对于this指针的讲解_最后的this是什么说的还可以
const和#define区别
- const
修饰的是一个真正的变量,对于变量,你当然可以做任何事情。
因为常量值在编译时已经被替换和折叠,所以不存在性能问题。 - #define:
参考链接
- 主要是参考Github上的c++那些事:https://github.com/Light-City/CPlusPlusThings
- MSDN对于const的说明:https://learn.microsoft.com/zh-cn/cpp/cpp/const-cpp?view=msvc-170
- 极客笔记c++ const的使用:https://deepinout.com/cpp-tutorial/cpp-tutorial-const-usage.html