Kyle Chen's Blog

Action speaks louder than Words

0%

C++中的const关键字

const与基本变量

img

img

const与extern

问题

正常来说, 在B.cpp中定义了变量int i = 0 则A.cpp中使用extern int i 使用这个变量i 但是现在的问题是 如果你在B.cpp中定义的这个变量i是const的 那么用同样的方式在A.cpp中引用这个变量 并编译的时候 g++ A.cpp B.cpp -o test 就会发现出现了未定义引用的错误

原因

错误的原因在这篇文章中说的很清楚 https://zhuanlan.zhihu.com/p/272828566

总结一下就是 如果变量被const定义的话 就会修改这个变量的链接属性 那么这个变量就不能被外部的文件链接了

解决措施

解决的措施在这篇文章中也说的很清楚 https://zhuanlan.zhihu.com/p/272828566

总结一下就是 在B.cpp的该变量i前面也加上extern关键字就可以了 因为extern有一个不太被关注的作用是, 在C++中,它可以改变const变量的链接属性。 让这个变量变得能够被外部文件链接到

标准用法

正确用法如下 img

img

const与指针

指向常量的指针(const在*号前)

  • 不能通过指针改变其所指向对象值
  • 即能指向常量对象也能指向非常量对象

常量指针 (const在*号后)

  • 其所指向的地址不变
  • 可以通过常量指针改变所指向对象的值

img

img

const与类型转换

  • 在C语言中,const限定符通常被用来限定变量,用于表示该变量的值不能被修改。
  • 而const_cast则正是用于强制去掉这种不能被修改的常数特性,但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。
  • 用法:const_cast (expression) 该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。 常量指针被转化成非常量指针,并且仍然指向原来的对象; 常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
[例3]一个错误的例子:

const int a = 10;
const int * p = &a;
*p = 20; //compile error
int b = const_cast<int>(a); //compile error
在本例中出现了两个编译错误,第一个编译错误是*p因为具有常量性,其值是不能被修改的;另一处错误是const_cast强制转换对象必须为指针或引用,而例3中为一个变量,这是不允许的!
[例4]const_cast关键字的使用

#include<iostream>
using namespace std;

int main()
{
const int a = 10;
const int * p = &a;
int *q;
q = const_cast<int *>(p);
*q = 20; //fine
cout <<a<<" "<<*p<<" "<<*q<<endl;
cout <<&a<<" "<<p<<" "<<q<<endl;
return 0;
}
在本例中,我们将变量a声明为常量变量,同时声明了一个const指针指向该变量(此时如果声明一个普通指针指向该常量变量的话是不允许的,Visual Studio 2010编译器会报错)。



之后我们定义了一个普通的指针*q。将p指针通过const_cast去掉其常量性,并赋给q指针。之后我再修改q指针所指地址的值时,这是不会有问题的。

最后将结果打印出来,运行结果如下:
10 20 20
002CFAF4 002CFAF4 002CFAF4

查看运行结果,问题来了,指针p和指针q都是指向a变量的,指向地址相同,而且经过调试发现002CFAF4地址内的值确实由10被修改成了20,这是怎么一回事呢?为什么a的值打印出来还是10呢?

其实这是一件好事,我们要庆幸a变量最终的值没有变成20!变量a一开始就被声明为一个常量变量,不管后面的程序怎么处理,它就是一个常量,就是不会变化的。试想一下如果这个变量a最终变成了20会有什么后果呢?对于这些简短的程序而言,如果最后a变成了20,我们会一眼看出是q指针修改了,但是一旦一个项目工程非常庞大的时候,在程序某个地方出现了一个q这样的指针,它可以修改常量a,这是一件很可怕的事情的,可以说是一个程序的漏洞,毕竟将变量a声明为常量就是不希望修改它,如果后面能修改,这就太恐怖了。

在例4中我们称“*q=20”语句为未定义行为语句,所谓的未定义行为是指在标准的C++规范中并没有明确规定这种语句的具体行为,该语句的具体行为由编译器来自行决定如何处理。对于这种未定义行为的语句我们应该尽量予以避免!

从例4中我们可以看出我们是不想修改变量a的值的,既然如此,定义一个const_cast关键字强制去掉指针的常量性到底有什么用呢?我们接着来看下面的例子。


例5:

#include<iostream>
using namespace std;

const int * Search(const int * a, int n, int val);

int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int val = 5;
int *p;
p = const_cast<int *>(Search(a, 10, val));
if(p == NULL)
cout<<"Not found the val in array a"<<endl;
else
cout<<"hvae found the val in array a and the val = "<<*p<<endl;
return 0;
}

const int * Search(const int * a, int n, int val)
{
int i;
for(i=0; i<n; i++)
{
if(a[i] == val)
return &a[i];
}
return NULL;
}


在例5中我们定义了一个函数,用于在a数组中寻找val值,如果找到了就返回该值的地址,如果没有找到则返回NULL。函数Search返回值是const指针,当我们在a数组中找到了val值的时候,我们会返回val的地址,最关键的是a数组在main函数中并不是const,因此即使我们去掉返回值的常量性有可能会造成a数组被修改,但是这也依然是安全的。
对于引用,我们同样能使用const_cast来强制去掉常量性,如例6所示。
例6:


#include<iostream>
using namespace std;

const int & Search(const int * a, int n, int val);

int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int val = 5;
int &p = const_cast<int &>(Search(a, 10, val));
if(p == NULL)
cout<<"Not found the val in array a"<<endl;
else
cout<<"hvae found the val in array a and the val = "<<p<<endl;
return 0;
}

const int & Search(const int * a, int n, int val)
{
int i;
for(i=0; i<n; i++)
{
if(a[i] == val)
return a[i];
}
return NULL;
}
了解了const_cast的使用场景后,可以知道使用const_cast通常是一种无奈之举,同时也建议大家在今后的C++程序设计过程中一定不要利用const_cast去掉指针或引用的常量性并且去修改原始变量的数值,这是一种非常不好的行为。

const与constexpr

https://blog.csdn.net/woxiaohahaa/article/details/78512576

https://blog.51cto.com/u_15346415/5171568

constexpr与普通变量

constexpr变量

  • 将变量声明为constexpr以便于由编译器检测一个表达式是否为一个常量表达式,而const没有此功能:
1
2
3
4
5
int a = 3;
int b = 4;
scanf("%d", &a);
constexpr int value1 = a + b;//编译器报错,表达式a + b不是常量表达式
const int value2 = a + b;//正常初始化,不会报错
  • 因此,如果你想用一个你认为是常量的表达式来初始化一个变量,不妨将这个变量声明为constexpr,让编译器为你检测一下。
  • 只有字面值类型才能声明为constexpr变量。(基本算数类型(bool,int…),引用,指针…)。

constexpr与指针引用

当一个指针声明为constexpr时,相当于:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;
int a = 3;
int main()
{
constexpr int *p1 = &a;
//两者等价,表示指针为常量,对象的值可以修改。
int * const p1 = &a;
system("pause");
return 0;
}

所以,如果想要声明一个指针常量指向一个整型常量,则可以有如下操作:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;
int a = 3;
int main()
{
constexpr const int *p1 = &a;
//两者等价,指针为常量,指向一个整型常量
const int *const p3 = &a;
system("pause");
return 0;
}

constexpr指针和引用只能指向所有函数体之外的变量(全局变量)或者函数体内的静态变量。

constexpr与函数

constexpr函数

constexpr函数 可以实现编译期函数(即函数在编译期执行完毕,并在调用处进行替换): #include using namespace std; //运算n的阶乘 constexpr int factorial(int n) { return n == 0 ? 1 : n * factorial(n - 1); } int main() { cout << factorial(10) << endl; system(“pause”); return 0; } 该函数也可以在运行期执行: #include using namespace std; constexpr int factorial(int n) { return n == 0 ? 1 : n * factorial(n - 1); } int main() { int a = 3; scanf_s(“%d”, &a); cout << factorial(a) << endl; system(“pause”); return 0; } 可以对constexpr变量进行初始化: #include using namespace std; constexpr int factorial(int n) { return n == 0 ? 1 : n * factorial(n - 1); } int main() { constexpr int value = factorial(10); system(“pause”); return 0; } 规定:

  • 函数的返回值以及参数都必须为字面值类型;
  • 函数只能有一条return语句(C++14后无该要求); 函数不一定返回常量表达式,但如果要初始化一个constexpr变量,则必须返回常量表达式(参数也必须为常量或常量表达式);
  • 函数被隐式的声明为内联函数;
  • 函数内部可以声明变量(声明之后是运行期还是编译期?),可以用using声明,空语句,类型别名,循环,判断语句等,但cout不行。

const与引用

https://kyleandkelly.github.io/2022/05/29/C-%E4%B8%AD%E7%9A%84%E5%BC%95%E7%94%A8/

const与函数形参

https://kyleandkelly.github.io/2022/05/29/C-%E4%B8%AD%E7%9A%84%E5%BC%95%E7%94%A8/

const与类成员函数

原理

img

使用

img

const顶层或底层

img