前言
本文为《C 专家编程》一书的阅读笔记。希望可以帮助大家解决指针赋值和 const 方面的问题。
指针的赋值
问题
将一个类型为
char **
的值赋值给一个const char **
类型的对象是否合法呢?
先说结果,在 vs 2022 的环境下,「初始化」:无法从 “char **” 转换为 “const char **”。
用 gcc 8.3.1 编译会出现以下警告:warning: initialization of ‘const char **’ from incompatible pointer type ‘char **’ 。
为什么不能赋值呢,下面就让我们一步步探索这其中的奥妙。
ANSI C 有关简单赋值的标准
要使上述的赋值形式合法,必须满足下列条件:
-
两个操作数都是指向有限定符或无限定符的相容类型的指针
-
左边指针所指向的类型必须具有右边指针所指向类型的全部限定符
还有一个关于类型的说明:const float *
类型不是一个有限定符的类型——它的类型是指向一个具有 const 限定符的 float 类型的指针,也就是说 const 限定符是修饰指针所指向的类型,而不是指针本身。
问题解决
在解决问题之前,我们先来看一组简单的。
char * 和 const char *
char *
和 const char *
是匹配的。
它之所以合法,是因为在下面的代码中:
char c = 'q';
char* cp = &c;
const char* cpp = NULL;
cpp = cp;
- 左操作数是一个指向 char 类型的指针
- 有 const 限定符修饰的 char 类型
- 右操作数是一个指向 char 类型的指针
- 没有限定符修饰的 char 类型
- char 类型与 char 类型是相容的,左操作数所指向的类型具有右操作数所指向类型的限定符
注意,反过来就不能进行赋值。
char ** 和 const char **
由上面的知识我们可以得知,char **
和 const char **
都是没有限定符的指针类型,但他们的指向的类型不一样(前者指向 char *
,后者指向 const char *
),这违反了上面赋值标准的第一条,所以它们是不相容的。
用这种方式理解这个有一点困难,可以用下面这种方法进行理解:
char c = 'q';
char* cp = &c;
char** pp1 = &cp;
const char** pp2 = NULL;
pp2 = pp1;
- 左操作数的类型是
const char **
,它是一个指向const char *
类型的指针const char *
是一个没有限定符的指针,它指向一个带有 const 限定的 char 类型
- 右操作数的类型是
char **
,它是一个指向char *
的指针char *
是一个没有限定符的指针,它指向一个没有限定符的 char 类型
const char *
和 char *
是相容的,而且他们本身没有限定符,所以符合标准的约束条件,两者之间的赋值是合法的。
但 const char **
和 char **
之间的关系又有不同,虽然二者都没有限定符,但二者所指向的对象类型不相容,所以不能进行赋值。
const 修饰
const 修饰变量
首先,关键字 const 并不能把变量变成常量。在一个变量前加上 const 限定符只是表示这个变量不能被赋值。也就是说 const 修饰的变量是只读的,不可以被直接修改,但它不能防止被间接修改。例如:
#include <stdio.h>
int main() {
const int i = 10;
int* p = (int*)&i;
printf("before:%d\n", i);
*p = 20;
// 这里打印值变成了 20,说明可以间接修改
printf("after:%d\n", *p);
return 0;
}
const 修饰指针
const 修饰指针变量有多种位置,下面我们将逐个介绍。
const int* p
注:const int *p
与 int const *p
写法不同,作用是一样的。
这种写法的意思是:const 修饰 *p
,不能通过解引用(*p
)的方式直接修改所指向的变量,但可以通过改变指针指向的方式来修改 *p
。例如:
#include <stdio.h>
int main() {
// 通过下面直接解引用的方式修改编译器会报错
// int i = 10;
// const int* p = &i;
// *p = 20;
int i = 10;
const int* p = &i;
printf("before:%d\n", *p);
int j = 20;
// 通过改变 p 的指向,可以间接修改 *p 值
p = &j;
printf("after:%d\n", *p);
return 0;
}
int* const p
这种写法的意思是:const 修饰 p,不能通过改变指针指向的方式修改 *p
的值,但可以通过解引用(*p
)的方式直接修改所指向的变量。例如:
#include <stdio.h>
int main() {
int i = 10;
int* const p = &i;
printf("before:%d\n", *p);
// 不能改变 p 的指向,但可以直接解引用修改值
*p = 20;
printf("after:%d\n", *p);
return 0;
}
const int* const p
这种写法是同时修饰 p 和 *p,既不能改变 p 的指向,也不能用解引用直接修改。