const
的小知识:const 变量类型* 指针名(const int* p)
有如下代码,我们通过指针p
去修改一个变量num
的值:
void test02()
{
int num = 10;
int* p = #
*p = 20; // num会被修改成20
printf("%d\n", num); // 输出:20
}
此时如果我们对以上代码进行一个修改:在int* p = &num
前加上一个const
修饰,代码如下:
void test02()
{
int num = 10;
const int* p = # // const放在指针类型变量(int* p)前
*p = 20;
}
此时第5行代码会报错:表达式必须是可修改的左值
这是因为我们在定义指针变量p
的时候,这个const
让这个指针p
变成了一个常量指针(理解:一个指向常量的指针,也就是说这个指针所指向的变量是一个常量,其值是不可用修改的;或者说我们解引用*p
访问到的这个变量变成了一个常量,其值不可以修改)
此时*p
访问到的是一个常量,常量无法修改,也即无法赋值,所以编译器会报错如上。
快速的理解就是:我们看到了const int* p = &num
,也就等价于有const *p
,那么也就是*p
是常量,不能*p = 20
赋值修改
所以当我们将**const
放在指针变量定义(int* p)
时,指针类型(int*)
的前面,此时就可以实现我们所定义的这个指针p
所指向的内容*p
不可以被赋值修改。**
还应注意:常量指针使得我们对p
解引用*p
时无法修改*p
中的内容,但如果我们通过num
去修改还是可以的:
void test02()
{
int num = 10;
const int* p = #
// *p = 20; // 编译器报错:表达式必须是可修改的左值
num = 20; // num仍然可以修改
printf("%d\n", num); // 输出:20
}
以上代码的理解方式:
也就是说,常量指针是使得*p变成了一个常量,但
num
本身仍是一个变量。或者这样说,在上面的代码中,
num
中的变量值有两种方式可以去访问,一种是通过变量名num
,另一种是通过指针*p
,那么指针常量是使得*p
这种访问方式下:无法修改*p
中的内容(10),但是并没有禁止变量名的访问方式修改变量值。还可以这样说,常量指针只是限制了指针访问的这个方式/这个角度去访问这个变量时,这个变量成了一个常量,注意是在指针访问的这个角度去看这个变量
num
时有这种效果,但实际上num
这个变量本质上还是一个变量,所以其仍可以通过自身的变量名num
去修改其中的值
但是当我们把代码修改成如下时:
void test02()
{
const int num = 10; // const修饰变量名
int* p = # // 取消const对指针的修饰
*p = 20; // *p可以修改num中的值
// num = 20; // 报错:表达式必须是可修改的左值
printf("%d\n", num); // 输出:20
}
我们采用上面提到的3种理解方式中的第2种来说明这个问题:
当我们在定义变量
num
的时候,如果在num
前加上const
就会导致变量名访问变量的这种方式:无法修改num
中的变量值(10),但是此时并没有禁止*p
的访问方式修改变量值。
综上所述:
const
修饰,就会导致相应的变量访问方式无法修改变量值:
const
加在变量名前:const 变量类型 变量名(const int num)
就使得变量名的访问方式变量名 = 值(num = 20)
无法修改变量值,但不限制指针(解引用)的访问方式*指针名 = 值(*p = 20)
修改变量值。const
加在指针前:const 变量类型* 指针名(const int* p)
就使得指针(解引用)的访问方式*指针名 = 值(*p = 20)
无法修改变量值,但不限制变量名的访问方式变量名 = 值(num = 20)
修改变量值。*p
这种方式不可改,变量名的方式仍可以改)
const 变量类型* 指针名(const int* p)
那么如果我们想使得两种访问方式都不能修改变量值该怎么做呢?不用说,大家肯定都想得到了,我们直接放出代码:
void test02()
{
const int num = 10; // const修饰变量名
const int* p = # // const修饰指针
// *p = 20; // 报错:表达式必须是可修改的左值
// num = 20; // 报错:表达式必须是可修改的左值
printf("%d\n", num);
}
我们直接在定义这些变量的时候,前面都加上const
,那么无论哪种访问方式,它们的修改/赋值功能就都会被禁止。
那么以上是常量指针的相关知识。从上述我们知道常量指针是指针所指向的内容*p
不可改,那么我们又有一个问题:既然指针所指向内容不可改,那**指针本身可以改吗?**我们写一个测试代码,如下:
void test02()
{
int num1 = 10;
int num2 = 20;
const int* p = &num1; // const修饰指针
// *p = 20; // 报错:表达式必须是可修改的左值
p = &num2; // 可以执行
}
可以发现,常量指针所指向的内容*p
是改不了,但是指针本身可以改,也就是说,指针的指向可以改(上面的案例中p
本来是指向num1
的,后来修改指向了num2
)
那么我们如果想让指针本身不可改(或者说指针的指向不可以改),又该如何操作呢?接下来我们介绍指针常量的知识:
变量类型* const 指针名(int* const p)
对于常量指针我们的理解是:指向常量的指针。那么很自然的,指针常量的理解就应该是说,这个指针本身就是个常量,不可以被修改。
我们观察我们上面写过的代码:
const int* p = &num1;
这行代码所定义的变量p
就被我们称为常量指针,对比其书写的位置也是刚刚好:先是常量const
,再是指针*
,所以被我们称为常量指针,那么聪明的我们自然会猜测:指针常量的书写应该就应该是…(注意这里所谓的书写位置对于指针常量和常量指针的理解,有助于我们记忆和区分这两种指针)
int* const p = &num1;
这样?…
没错,指针常量还真就是这样!其定义语法就是:变量类型* const 指针名(int* const p)
此时如果我们在程序中定义这样的指针常量:
void test02()
{
int num1 = 10;
int num2 = 20;
int* const p = &num1; // 定义指针常量
*p = 20; // 可以执行
// p = &num2; // 报错:表达式必须是可修改的左值
}
会发现指针p
(是一个指针常量)本身是无法修改的(也即其指向是无法修改的),但是其指向的内容*p
可以访问和修改
这就是指针常量:指针本身(或者说指针的指向)无法修改
对比常量指针和指针常量的定义语法,我们如何快速地注意和区分这两者的称谓和功能呢?
常量指针:const 变量类型* 指针名(const int* p)
指针常量:变量类型* const 指针名(int* const p)
1.首先最容易的就是通过我们上面说到过的书写位置:
常量指针的const
是放在*
前的,所以我们按照这样的书写顺序就可以称之为:常量const
指针*
而指针常量的*
是放在const
后的,所以我们同样按照书写顺序就可以称之为:指针*
常量const
2.上面区分了二者的叫法,那又如何快速区分二者的功能呢?如下:
const 变量类型* 指针名(const int* p)
,省略中间的int
,简化之后就是 const *p
,也就是说*p
就是个常量,不可以修改。也就是我们上面说的:常量指针所指向的内容是常量,不可以修改。 但此时p
本身可以修改,原因就是const
修饰的是*p
,而不是p
。
变量类型* const 指针名(int* const p)
,简化之后就是 const p
,也就是说p是个常量,不可以修改,也同样对应我们上面说的:指针常量本身(或者说指针的指向)不可用修改。 但此时*p
的内容可以修改,原因就是const
修饰的是p
,而不是*p
。
那么由以上的知识我们可以继续思考,如果要使得一个指针p不仅所指向的内容*p
不可修改,其本身p(其指向)不可修改又该如何做呢?相信大家肯定都想得到,这里我们直接放出代码:
void test02()
{
int num1 = 10;
int num2 = 20;
const int* const p = &num1; // p即是常量指针,又是指针常量,它同时具有两种这两种指针的特点
// *p = 20; // 报错:表达式必须是可修改的左值
// p = &num2; // 报错:表达式必须是可修改的左值
}
可以看到此时无论是我们去修改指针所指向的内容或者修改指针本身都会报错,也就是说两者都改不了!
以上是我关于指针常量和常量指针的个人小见解,内容总结自比特鹏哥的C语言教学视频:25.VS环境-C语言实用调试技巧(2)哔哩哔哩_bilibili
加上一些个人的思考和理解,有什么问题欢迎大家批评指正~
也欢迎大家任何的想法和知识交流!