C语言中const

  1. const修饰的变量是只读的, 本质还是变量
  2. const修饰的局部变量在栈上分配空间
  3. const修饰的全局变量在全局数据区分配空间
  4. const只在编译期有用,在运行期无用
  5. const修饰的变量不是真的常量,它只是告诉编译器该变量不能出现在赋值符号的左边。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main(){

const int cc = 1;

int *p = (int *)&cc;

printf("cc = %d\n",cc);

*p = 2;

printf("cc = %d\n",cc);

return 0;

}

通过指针修改const局部变量的值不会报错并且可以修改成功

1
2
3
4
fengyun@ubuntu:~/share$ gcc test.c -o test
fengyun@ubuntu:~/share$ ./test
cc = 1
cc = 2

现代C语言编译器中,修改const全局变量将导致程序崩溃

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

const int g_cc = 1;

int main(){

int *p = (int *)&g_cc;

printf("g_cc = %d\n",g_cc);

*p = 2;

printf("g_cc = %d\n",g_cc);

return 0;

}
1
2
3
4
fengyun@ubuntu:~/share$ gcc test.c -o test
fengyun@ubuntu:~/share$ ./test
g_cc = 1
段错误 (核心已转储)

注意:
标准C语言编译器(比如bcc32编译器)不会将const修饰的全局变量存储于只读存储区中,而是存储于可修改的全局数据区,其值依然可以改变。

结论:

  1. C语言中的const使得变量具有只读属性
  2. 现代C编译器中的const将具有全局生命周期的变量存储于只读存储区(比如static 修饰的局部变量,全局变量)
  3. C语言中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
#include <stdio.h>

const int g_array[5] = {0};

void modify(int* p, int v)
{
*p = v;
}

int main()
{
int const i = 0;
const static int j = 0;
int const array[5] = {0};

modify((int*)&i, 1); // ok
modify((int*)&j, 2); // error
modify((int*)&array[0], 3); // ok
modify((int*)&g_array[0], 4); // error

printf("i = %d\n", i);
printf("j = %d\n", j);
printf("array[0] = %d\n", array[0]);
printf("g_array[0] = %d\n", g_array[0]);

return 0;
}

修改全局变量和static变量,在gcc编译器下运行

1
2
3
fengyun@ubuntu:~/share$ gcc test.c -o test
fengyun@ubuntu:~/share$ ./test
段错误 (核心已转储)

但如果在bcc32编译器中运行不会出错,因为bcc编译器很好地支持了C语言标准变量规范。

image-20220220095153728

const修饰函数参数表示在函数体内不希望改变参数的值
const修饰函数返回值表示返回值不可改变,多用于返回指针的情形
小贴士:
C语言中的字符串字面量存储于只读存储区中,在程序中需要使用const char*指针。

1
2
3
4
5
6
#include <stdio.h>
int main()
{
const char* s = "feng yun"; //字符串字面量
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

const char* f(const int i)
{
//i = 5;

return "feng yun";
}

int main()
{
char* pc = f(0);

printf("%s\n", pc);

pc[6] = '_';

printf("%s\n", pc);

return 0;
}

如果直接编译会有一个警告,运行后会出现段错误,原因就是修改了const char*的值。

1
2
3
4
5
6
7
8
fengyun@ubuntu:~/share$ gcc test.c -o test
test.c: In function ‘main’:
test.c:12:16: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
12 | char* pc = f(0);
| ^
fengyun@ubuntu:~/share$ ./test
feng yun
段错误 (核心已转储)

volatile

  1. volatile可理解为“编译器警告指示字”
  2. volatile告诉编译器必须每次去内存中取变量值
  3. volatile主要修饰可能被多个线程访问的变量
  4. volatile也可以修饰可能被末知因数更改的变量

image-20220220101142533

对于const volatile int i = 0
我们在内存里定义了一个只读变量i,不能出现是左值,编译器每次都到内存里面取值而不做任何优化。

小结

  1. const使得变量具有只读属性
  2. const不能定义真正意义上的常量
  3. const将具有全局生命期的变量存储于只读存储区
  4. volatile强制编译器减少优化,必须每次从内存中取值

image-20220220101537513

C++中const

C与C++

  1. C语言中的const使得变量具有只读属性
  2. const将具有全局生命周期的变量存储于只读存储区
  3. const不能定义真正意义上的常量!

而我们在C语言中定义真正的常量只能用枚举enum。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main()
{
const int c = 0;
int* p = (int*)&c;//注意需要强制类型转换 const int* -> int*

printf("Begin...\n");

*p = 5;

printf("c = %d\n", c);
printf("*p = %d\n", *p);

printf("End...\n");

return 0;
}

同一个程序在C语言和C++语言的运行结果竟然会截然不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fengyun@ubuntu:~/share$ gcc test.c -o test
fengyun@ubuntu:~/share$ ./test
Begin...
c = 5
*p = 5
End...


fengyun@ubuntu:~/share$ g++ test.c -o test
fengyun@ubuntu:~/share$ ./test
Begin...
c = 0
*p = 5
End...

C语言const int c,c的值修改成功,但C++并没有成功。但是int* p = (int*)&c,指针p指向的值似乎都修改成功了。

这是什么原因?

C++在C的基础上对const进行了进化处理

-当碰见const声明时在符号表中放入常量
-编译过程中若发现使用常量则直接以符号表中的值替换
-编译过程中若发现下述情况则给对应的常量分配存储空间
●对const常量使用了 extern,是全局并且在其它文件中使用
●对const常量使用&操作符
注意:
C+ +编译器虽然可能为const常量分配空间,但不会使用其存储空间中的值。这是为了兼容C语言,C语言可以通过指针访问甚至修改const变量,C++也可以通过指针访问const常量分配的空间,但是这个空间的值与const常量无关,const常量是存储在符号表内部。

image-20220220103022262

const与define

C+ +中的const常量类似于宏定义
一const int C = 5;≈#define C 5

C + +中的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
#include <stdio.h>

void f()
{
#define a 3
const int b = 4;
}

void g()
{
printf("a = %d\n", a);
printf("b = %d\n", b);
}

int main()
{
const int A = 1;
const int B = 2;
int array[A + B] = {0};
int i = 0;

for(i=0; i<(A + B); i++)
{
printf("array[%d] = %d\n", i, array[i]);
}

f();
g();

return 0;
}

编译运行这个程序,可以看到

gcc编译器会出错,因为对于int array[A + B],A和B均为只读变量,两个变量的值只有在运行的时候才知道,编译器不知道值因此会报错

g++编译器不会出错,编译器会从内部的符号表取值,取得A=1,B=2,因此编译器不会出错

1
2
3
4
5
6
7
8
9
10
11
12
fengyun@ubuntu:~/share$ gcc test.c -o test
test.c: In function ‘main’:
test.c:19:5: error: variable-sized object may not be initialized
19 | int array[A + B] = {0};
| ^~~
test.c:19:25: warning: excess elements in array initializer
19 | int array[A + B] = {0};
| ^
test.c:19:25: note: (near initialization for ‘array’)

fengyun@ubuntu:~/share$ g++ test.c -o test
fengyun@ubuntu:~/share$

运行g++编译出来的程序

1
2
3
4
5
fengyun@ubuntu:~/share$ ./test
array[0] = 0
array[1] = 0
array[2] = 0
a = 3

而注意#define a 3宏是由预处理器处理,编译器根本不知道宏这个东西,a被全部替换,没有作用域和类型的概念

const引用【特殊】

  • 在C++中可以声明const引用
  • const Type& name = var ;
  • const引用让变量别名拥有只读属性
1
2
3
4
5
6
7
int a = 4;
const int& b = a; //让b拥有只读属性但不修改a的属性
int* p = (int*) &b;

b = 5;// Error ,只读变量

*p = 5;//Ok,修改变量a的值

注意const int& b = a;让b拥有只读属性但不修改a的属性

当使用字面常量对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名

1
2
3
4
5
6
const int& b = 1; //ok
int* p = (int*) &b;

b = 5;// Error ,只读变量

*p = 5;//Ok,修改变量a的值

结论:使用常量对const引用初始化后将生成一个只读变量! ! !

const的一点疑问

const什么时候是常量?const什么时候是只读变量?

const常量的判别准则

  • 只有用字面量初始化的const常量才会进入符号表
  • 使用其它变量初始化的const 常量仍然是只读变量
  • volatile修饰的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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>

int main()
{
const int x = 1; //C++为了兼容C语言会给x分配内存空间,通常这个空间不用,会将x放入符号表中
const int& rx = x; //代表分配给x的四个字节的空间的别名

int& nrx = const_cast<int&>(rx); //nrx与rx代表相同的内存空间

nrx = 5;

printf("x = %d\n", x); //1
printf("rx = %d\n", rx); //5
printf("nrx = %d\n", nrx); //5
printf("&x = %p\n", &x); //0x7ffd54835e10
printf("&rx = %p\n", &rx); //0x7ffd54835e10
printf("&nrx = %p\n", &nrx); //0x7ffd54835e10

volatile const int y = 2; //只读变量
int* p = const_cast<int*>(&y);

*p = 6;

printf("y = %d\n", y); //6
printf("p = %p\n", p); //0x7ffd54835e14

const int z = y; //只读变量

p = const_cast<int*>(&z); //

*p = 7;

printf("z = %d\n", z); //7
printf("p = %p\n", p); //0x7ffd54835e18

char c = 'c';
char& rc = c;
const int& trc = c; //生成一个新的只读变量

rc = 'a';

printf("c = %c\n", c); //a
printf("rc = %c\n", rc); //a
printf("trc = %c\n", trc); //c

return 0;
}

const关键字能否修饰类的对象?如果可以,有什么特性?

  • const关键字能够修饰对象
  • const修饰的对象为只读对象
  • 只读对象的成员变量不允许被改变
  • 只读对象是编译阶段的概念,运行时无效

C++const成员函数

  • const对象只能调用const的成员函数
  • const成员函数中只能调用const成员函数
  • const成员函数中不能直接改写成员变量的值