请问char * const *(*next)();中的next是个什么东东?
1. 声明与定义
C 语言中的对象必须有且只有一个定义,但它可以有多个 extern 声明。(这里所说的对象跟 C++中的对象并无关系,这里的对象只是跟链接器有关的“东西”,比如函数和变量)
定义是一种特殊的声明,它创建了一个对象;
声明简单地说明了在其他地方创建的对象的名字,它允许你使用这个名字。
术语 | 出现次数 | 作用 |
---|---|---|
定义 | 只能出现在一个地方 | 确定对象的类型并分配内存,用于创建新的对象。例如:int my_array[100]; |
声明 | 可以多次出现 | 描述对象的类型,用于指代其他地方定义的对象(例如在其他文件里)。例如:extern int my_array[]; |
声明相当于普通的声明,它所说明的并非自身,而是描述其他地方创建的对象。extern 对象声明告诉编译器对象的类型和名字,对象的内存分配则在别处进行。(由于并未在声明中为数组分配内存,所以并不需要提供关于数组长度的信息。)
定义相当于特殊的声明,它为对象分配内存。
2. 指针常量与常量指针
2.1. 关键字const
c语言里面有个关键字const,它是定义只读变量(常变量),您若编写下列函数片段:
const int a = 10;
a = 11;
编译器将提醒你有错误:“表达式必须是可修改的左值(<-更多关于左值)”,这就表示你不能去修改一个本该是存放常量的地址里面的值,也就是其无法放在赋值符号=的左边
2.2. const&指针
上面举例说明了const如何修饰int类型,下面的写法都是一样的
const int a = 10;
int const a = 10;
然而,const与指针一起使用的时候,情况将有所不同,如下代码片段
int z = 10;
int a = 10;
const int * pa = &a;
int b = 10;
int const * pb = &b;
int c = 10;
int* const pc = &c;
*pa = 1;//error
*pb = 1;//error
*pc = 1;//right
pa = &z;//right
pb = &z;//right
pc = &z;//error
上面代码片段中列出了三种声明:
-
const int *p;
-
int const *p;
-
int *const p;
其中前两种,指针p指向的地址里面的值是不能改动的,也就是说const修饰的是*p(p为常量指针),最后一种指针p的值是不能修改的,也就是说const修饰的是指针p(p为指针常量)
当然你也可以让指针为常量,指针指向的也为常量,也就是常量指针常量: -
const int * const p;
-
int const * const p;
或许我们已经看出const的修饰规则了,那么继续往下阅读
3. 奇怪的C语言的设计哲学
对象的声明形式与它的使用形式尽可能相似
这是C语言从它的前辈那里接棒后,支持二进制外的其他数据类型(例如int)时产生的设计哲学
比如定义一个指向int类型变量a的指针时使用表达式p(int *p = &a
),使用的时候也是用p这样的表达来引用或使用指针(*p = 1
)指向的int数据。这样使定义(声明)与使用一致,但是会让我们这种从左往右阅读的人类,很难推断诸如int *p[3]到底是一个 int 类型的指针数组,还是一个指向 int数组的指针(其实这是一个int 类型的指针数组)。
“声明的形式和使用的形式相似”即使在当时也不像是一个特别好的主意。把两种截然不同的东西做成同一个样子真的有什么重要意义吗?贝尔实验室的学究们也承认此批评有理,但他们坚决死扛原来的决定,至今依然。一个比较好的声明指针的方法是:
int &p;
它至少能提示 p 是一个整型数的地址。这种语法现已被 C++采纳,用于表示参数的传址调用。
3.1. 声明的优先级规则
理解 C 语言声明的优先级规则
- 声明从它的名字开始读取,然后按照优先级顺序依次读取。
- 优先级从高到低依次如下。
2.1. 声明中被括号括起来的那部分。
2.2. 后缀操作符:括号()表示这是一个函数、方括号[]表示这是一个数组
2.3. 前缀操作符:星号*表示“指向……的指针”。 - 如果 const 和(或)volatile 关键字紧跟类型说明符(如 int、long 等),那么它作用于类型说明符。在其他情况下,const 和(或)volatile 关键字作用于它左边紧邻的指针星号。
比如用上面的规则去解读文章开头的char * const *(*next)();
适用规则 | 解 释 |
---|---|
1 | 首先,看变量名 next,并注意到它直接被括号所括住 |
2.1 | 所以先把括号里的东西作为一个整体,得出“next 是一个指向……的指针” |
2 | 然后考虑括号外面的内容,在星号前缀和括号后缀之间作出选择 |
2.2 | 规则告诉我们优先级较高的是右边的函数括号,所以得出“next 是一个函数指针,指向一个返回……的函数” |
2.3 | 再次,处理前缀“*”,得出指针所指的内容 |
3 | 最后,把 char * const 解释为指向字符的常量指针 |
所以一通解释完,可以知道这个声明表示“next 是一个指针,它指向一个函数,该函数返回另一个指针,该指针指向一个类型为 char 的常量指针”
3.2. 图表分析声明:
当然这儿也有直观的图表解码声明(解密式编程)
一开始,我们从左边开始向右寻找,直到找到第一个标识符。当声明中的某个符号与图中所示匹配时,便把它从声明中处理掉,以后不再考虑。在具体的每一步骤上,我们首先查看右边的符号,然后再看左边。当所有的符号都被处理完毕后,便宣告大功告成
比如还是分析上面的char * const *(*next)();
:
剩余的声明(从最左边的标识符开始) | 所采取的下一步骤 | 结 果 |
---|---|---|
char * const *(*next) ( ); | 第 1 步 | 表示“next 是……” |
char * const *(* ) ( ); | 第 2、3 步 | 不匹配,转到下一步,表示“next 是……” |
char * const *(* ) ( ); | 第 4 步 | 不匹配,转到下一步 |
char * const *(* ) ( ); | 第 5 步 | 与星号匹配,表示“指向……的指针”,转第 4 步 |
char * const *( ) ( ); | 第 4 步 | “(”和“)”匹配,转到第 2 步 |
char * const * ( ); | 第 2 步 | 不匹配,转到下一步 |
char * const * ( ); | 第 3 步 | 表示“返回……的函数” |
char * const * ; | 第 4 步 | 不匹配,转到下一步 |
char * const * ; | 第 5 步 | 表示“指向……的指针” |
char * const ; | 第 5 步 | 表示“只读的……” |
char * ; | 第 5 步 | 表示“指向……的指针” |
char ; | 第 6 步 | 表示“char” |
拼在一起,读作:
“next 是一个指向函数的指针,该函数返回另一个指针,该指针指向一个只读的指向 char的指针”
看懂了的话可以试试char *(* c[10])(int **p);
4. 参考与引用:
- 《C专家编程》第三、四章
书籍pdf可以来我的博客寻找