目录
“const”在C++中用来修饰一个变量变为常量,本篇文章我们来介绍一下const的具体用法。
一. const修饰类型
1.1const修饰一般数据类型
const int a{ 10 };
int const a{ 10 }; //与上面等价
const int arr[10]{ 1,2,3 };
int const arr[10]{ 1,2,3 }; //与上面等价
其中,const都在a左边,即修饰的是变量a为常量,不可修改其值,即只可读不可写。
注:这里没有a = 10,而是a{10},因为c++对于变量的赋值可以直接用{},
#include<iostream> typedef struct Stduent { char s_id[20]; char s_name[20]; int s_age; }Student; int main() { int a{ 10 }; int* p{ NULL }; int arr[10]{ 1,2,3 }; Student s1{ "200409","lihua",18 }; }
1.2const修饰指针类型
1.2.1修饰常量指针
int a{ 10 };
int b{ 20 };
const int* p = &a;
*p = 20; //err
p = &b; //right
指针有两个值,分别为指针本身的值和解引用的值。
当const在*p的左边时,代表了*p是常量不可改变其值,而*p是解引用的意思,所以不能通过解引用来修改a的值,但是可以修改指针本身的值让其指向b的地址。
1.2.2修饰指针常量
int a{ 10 };
int b{ 20 };
int* const p = &a;
p = &b; //err
*p = 20; //right
当const在p的左边,在*右边时,代表p本身是常量不可改变,所以不能修改指针的指向来修改值,但可以通过解引用来修改。
1.2.3二者都修饰
int a{ 10 };
int b{ 20 };
const int* const p = &a;
p = &b; //err
*p = 20; //err
当两边都有const时,代表了指针本身不能修改并且也无法通过解引用改变其值。
二. const在.c和.cpp文件中的区别
我们知道,在vs2019中 , .c文件运用的是c语言编译政策,.cpp文件运用的是c++编译政策,二者有一些明显区别。
1.
#include<stdio.h>
int main()
{
const int a = 10;
int* p = (int*)&a;
int b = 0;
*p = 100;
b = a;
printf("a=%d b=%d *p=%d", a, b, *p);
}
该代码在.c文件下运行结果是
而在.cpp文件下运行的结果却是
为什么会出现这样的情况呢?
这是因为我们编写的是源码,而源码是计算机无法直接执行的,而是需要经过预编译、编译、汇编才能生成计算机可以执行的二进制码,而在这个过程中,由于.c和.cpp的编译政策不一样,导致最终的可执行代码也不一样。
当我们将代码运行后转到反汇编时就会发现,
在.cpp中
0A在十六进制中的数值为10,即把10传给b而不是把a传给b,这样就会导致b等于一个确定的值不会改变,而因为编译政策的不同,const int a = 10 在c++中以常量为主,所以a的值也不会变。
在.c中
这里是把a先传递给cpu的寄存器,再将寄存器中的数据传给b,使得b的值为a,而 const int a = 10在c语言中以变量为主,当*p解引用时,a的值也会随之改变。
2.
我们再来看这样一个代码
const int len = 10;
int ar[len] = { 1,2,3,4 };
这个代码在.c文件下编译出现错误,因为c语言中const 还是以变量为主,而数组元素的个数必须为常量。
在 .cpp文件下编译通过,因为c++中const以常量为主。
三. 常变量和指针
在.cpp文件
const int a = 10;
int* p1 = &a; //err
const int* p2 = &a; //ok
int const* p3 = &a; //ok
int* const p4 = &a; //err
const int * const *p5 = &a; //ok
第一个错误的原因是 p1指针没有进行限定,导致可以解引用改变a的值,而a是常量无法更改其值。
第二个正确,因为const在*p2左边,意思是无法通过解引用改变a的值。
第三个和第二个同理。
第四个错误的原因是const在p4左边,在*右边,表示对指针本身进行了一个常量限定,而仍然可以通过解引用改变值,而a的值是常量无法更改。
第五个与第二个同理。
如果一定要用第一个编译通过,可以进行强转。
int *p1 = (int*) &a;
但是这样并不安全,因为这样可以通过解引用改变a的值,而a是一个常量,二者相矛盾。
安全的做法是
int* p5 = const_cast<int*>(&a);
这样做可以将a变量的常性去掉给*p,这样*p就可以通过解引用来改变a的值。
四. 全局变量与局部变量的初始化
在编写代码的过程中,如果没有对全局变量和局部变量进行赋值,那么系统自动给他们的初始值是多少?
4.1 普通变量的初始化
#include<stdio.h>
typedef struct Student
{
char s_id[2];
char s_name[2];
int s_age;
}Student;
int g_max;
double g_dx;
Student s1;
int main()
{
int a;
double dx;
Student s2;
return 0;
}
我们定义了全局变量g_max,g_dx,s1,局部变量a,dx,s2,程序运行后,对他们的值进行监视
全局变量:
局部变量:
由此得出结论:全局变量未初始化时,系统自动初始化为0;而局部变量未初始化时,系统初始化为随机值。
4.2 常性变量的初始化
#include<stdio.h>
typedef struct Student
{
char s_id[2];
char s_name[2];
int s_age;
}Student;
const int g_max;
const double g_dx;
const Student s1;
int main()
{
const int a;
const double dx;
const Student s2;
return 0;
}
程序编译出现错误,原因是没有给一个初始值。
注:对于内置类型,不管是全局变量还是局部变量,只要加了常性修饰,就必须要对其赋值进行初始化。
但是对于自己设计的类型,如果是全局变量,加了常性修饰后,如果就算不对其进行初始化,系统将自动赋值为0;如果是局部变量,则是随机值。
#include<stdio.h>
typedef struct Student
{
char s_id[2];
char s_name[2];
int s_age;
}Student;
const Student s1;
int main()
{
const Student* ip = &s1;
const Student s2;
return 0;
}
4.3 指针的初始化
const int* ip; //ok
int* const sp; //err
第一个是可以编译通过的:虽然对*ip进行常量限定,无法通过解引用改变其值,但是可以给指针本身进行赋值,如 ip = &a。
第二个无法编译通过:对sp本身进行限定,无法指向一个有效的地址空间,定义这样的指针没有任何意义,所以对于这样的指针必须要进行赋值才能编译通过 ( int* const sp = &a //ok )
4.4例题分析
int main()
{
int a = 10, b = 20;
const int* p = &a;
int* s1 = p; //1 err
const int* s2 = p; //2 ok
int* const s3 = p; //3 err
const int* const s4 = p; //4 ok
}
1. 由于s1没有加const修饰,可以通过解引用改变,而p不能解引用。
2. p与s2都无法解引用改变其值。
3. s3虽然可以改变自身的指向,但它还是不能解引用与p违背。
4.与2相同。
int main()
{
int a = 10, b = 20;
int * const p = &a;
int* s1 = p; //1 ok
const int* s2 = p; //2 ok
int* const s3 = p; //3 ok
const int* const s4 = p; //4 ok
}
1. s1可以改变指向指向别的值,如s1=&b,但并不会影响p的改变。
2. s2可以改变指向但不影响p的改变。
3. p和s3都不能改变自身的指向。
4. 与3相同。