《征服 C 指针》摘录6:解读 C 的声明

一、混乱的声明——如何自然地理解 C 的声明?

通常,C 的声明

int hoge;

这样,使用“类型 变量名;”的形式进行书写。

可是,像“指向 int 的指针”类型的变量,却要像下面这样进行声明:

int *hoge_p;

似乎这里声明了一个名为 *hoge_p 的变量,而实际上,这里声明的变量是 hoge_p,hoge_p 的类型是“指向 int 的指针”。

因为这种声明方式不太好理解,所以有人提出将 * 靠近类型这一侧进行书写,如下:

int* hoge_p;

的确,这种书写方式符号“类型 变量名;”的形式。但是在同时声明多个变量的情况下就会出现破绽:

/* 声明 2 个“指向 int 的指针”? ——其实不是,此时 piyo_p 是 int 类型变量 */
int* hoge_p, piyo_p;

此外,数组也是 C 的一种类型,比如

int hoge[10];

这样的写法,就不符合“类型 变量名;”的形式。

说一些题外话,Java 在声明“int 的数组”时,通常写成

int[] hoge;

的形式,这样好像是符合“类型 变量名;”的形式。至少在这一点上,Java 的语法比起 C 显得更为合理。可是,Java 为了让 C 程序员更容易地将程序向 Java 移植,竟然也兼容 int hoge[] 这样的写法。这种不伦不类的做法倒还真像Java的风格。

二、用英语来阅读

我认为像 int *hoge_p; 还有 int hoge[10]; 这样的声明方式很奇怪

对于这种程序的声明方式,可能也有很多人感觉不到什么别扭的地方。那就再看下面的这个例子(经常被使用):

char *color_name[] = {
	"red",
	"green",
	"blue"
};

这里声明了一个“指向 char 的指针的数组”。

我们还可以像下面这样声明一个“指向将 double z作为参数并且返回 int 的函数的指针”:

int (*func_p)(double);

关于这样的声明,在 K&R 中有下面这样一段说明:

int *f();  /* f: 返回指向 int 指针的函数 */

int (*pf)();  /* pf: 指向返回 int 的函数的指针 */

这两个声明最能说明问题。在这里,因为 * 是前置运算符,它的优先级低于 (),为了让连接正确地进行,有必要加上括号。

首先,这段文字中有谎言

声明中 *、() 和 [] 并不是运算符。在语法规则中,运算符的优先顺序是在别的地方定义的。

先将这个问题放在一边。如果你老老实实地去读这段文字,该会嘀咕“是不是搞反了”。如果说

int (*pf)();

是指向函数的指针,使用括弧先将星号(指针)括起来岂不是很奇怪?

这个问题的答案,等你明白过来就会觉得非常简单。C 语言本来是美国人开发的,最好还是用英语来读

以上的声明,如果从 pf 开始以英语的顺序来读,应该是下面这样:

pf is pointer to function returning int

翻译诚中文,则为

pf 为指向返回 int 的函数的指针。

要点

用英语来读 C 的声明。

三、解读 C 的声明

在这里,向读者介绍阅读 C 语言声明的方法:机械地向前读。

为了把问题变得更简单,我们在这里不考虑 const 和 volatile。接下来遵循以下步骤来解释 C 的声明。

1、首先着眼与标识符(变量名 或者  函数名)。

2、从距离标识符最近的地方开始,依照优先顺序解释派生类型(指针、数组 和 函数)。优先顺序说明如下,

(1)、用于整理声明内容的括弧

(2)、用于表示数组的[],用于表示函数的()

(3)、用于表示指针的 *

3、解释完成派生类型,使用 “of”,“to”,“returning”将它们连接起来。

4、最后,追加数据类型修饰符(在左边,int、double 等)。

5、英语不好的人,可以倒序用中文解释。

数组元素个数和函数的参数属于类型的一部分。应该将它们作为附属于类型的属性进行解释。

比如,

int (*func_p)(double);

(1) 首先着眼于标识符。

int (*func_p)(double)

英语的表达为:

func_p is

(2) 因为存在括号,这里着眼于 *。

int (*func_p)(double);

英语的表达为:

func_p is pointer to

(3) 解释用于函数的(),参数 double。

int (*func_p)(double);

英语的表达式为:

func_p is pointer to function(double) returning

(4) 最后,解释数据类型修饰符 int。

int (*func_p)(double);

英语的表达为:

func_p is pointer to function(double) returning int

(5) 翻译成中文:

func_p 是指向返回 int 的函数的指针。

使用和上面相同的方式,我们对各种各样的声明进行解读,如下表所示:

C 语言 英语表达 中文表达
int hoge; hoge is int hoge 是int
int hoge[10]; hoge is array(元素个数10) of int hoge 是 int 的数组(元素个数10)
int hoge[10][3]; hoge is array(元素个数10) of array(元素个数3) of int hoge 是 int 数组(元素个数3)的数组(元素个数10)
int *hoge[10]; hoge is array(元素个数10) of pointer to int hoge 是指向 int 的指针的数组(元素个数 10)
double (*hoge)[3]; hoge is pointer to array(元素个数3) of double hoge 是指向 double 的数组(元素个数3)的指针
int func(int a); func is function(参数为 int a) returning int func 是返回 int 的函数(参数是 int a)
int (*func_p)(int a) func_p is pointer to function(参数为 int a) returning int func_p 是指向返回 int 的函数(参数为 int a)的指针

正如大家看到的这样,C 语言的声明不能从左右按顺序解读(无论是英语、中文、还是日语),而是左右来回地解读。

K&R 中指出:在 C 语言中,变量的声明仿效表达式的语法。可是,勉强地去模拟本质上完全不同的事物,结果就是“四不像”。

“使声明的形式 和 使用的形式相似”是 C (还有从 C 派生的 C++、Java 等语言)特有的 奇怪的语法

K&R 中同时也写道:

C 的声明语法,特别是指向函数指针的语法,受到了严厉的批评。

四、类型名

在 C 中,除标识符以外,有时候还必须定义“类型”。具体来说,遇到以下情况需定义“类型”:

(1)、在强制转型运算符中

(2)、类型作为 sizeof 运算符的操作数

比如,将强制转型运算符写成下面的形式:

(int *)

这里指定“int *”为类型名。

从标识符的声明中,将标识符取出后,剩下的部分自然就是类型名。

如下表所示,类型名的写法:

声 明 声明的解释 类型名 类型名的解释
int hoge; hoge 是 int int int 类型
int *hoge; hoge 是指向 int 的指针 int * 指向 int 的指针类型
double (*p)[3]; p 是指向 double 的数组(元素个数 3)的指针 double(*)[3] 指向 double 的数组(元素个数 3)的指针类型
void (*func)(); func 是指向返回 void 函数的指针 void (*)() 指向返回 void 函数的指针类型

上表最后2个例子中,括起星号的括弧 (*) 好像有点多余,但是一旦去掉括弧,意思就完全不一样了

1、(double *[3]) 是将 double *hoge[3] 的标识符去掉后形成的,所以这个类型名被解释成“指向 double 的指针的数组”。

2、int (*func_table[10])(int a) 可以解释成“指向返回 int 的函数(参数为 int a)的指针的数组(元素个数 10)”。

延伸阅读:

《征服 C 指针》摘录1:什么是空指针?区分 NULL、0 和 ‘\0‘

《征服 C 指针》摘录2:C变量的 作用域 和 生命周期(存储期)

《征服 C 指针》摘录3:数组 与 指针

《征服 C 指针》摘录4:函数 与 指针

《征服 C 指针》摘录5:函数形参 和 空的下标运算符[]

《征服 C 指针》摘录6:解读 C 的声明

《征服 C 指针》摘录7:练习——挑战那些复杂的声明

时间: 2024-10-14 07:00:58

《征服 C 指针》摘录6:解读 C 的声明的相关文章

读书笔记《征服C指针》----C的声明是这样解读的

解读C的声明的最好方法是用英语来阅读,道理很简单,因为C语言是美国人发明的. 为了把问题变得更简单,在这里暂不考虑const和volatile.接下来遵循以下步骤来解释C的声明. 首先着眼于标识符(变量名或者函数名). 从距离标识符最近的地方开始,依照优先顺序解释派生类型(指针.数组.和函数).优先顺序说明如下: 用于整理声明内容的括弧. 用于表示数组的[],用于表示函数的(). 用于表示指针的*. 解释完成完成派生类型,使用“of”.“to”.“returning”将它们连接起来. 最后,追加

《征服 C 指针》摘录5:函数形参 和 空的下标运算符[]

一.函数的形参的声明 C 语言可以像下面这样声明函数的形参: void func(int a[]) {     // ... } 对于这种写法,无论怎么看都好像要向函数的参数传递数组. 可是,在 C 中是不能够将数组作为函数的参数进行传递的.无论如何,在这种情况下,你只能传递指向数组初始元素的指针. 在声明函数形参时,作为类型分类的数组,可以被解读成指针. void func(int a[]) { } 可以被自动地解读为 void func(int *a) { } 此时,就算你定义了数组的元素的

征服C指针-C的变量种类

C语言的变量具有区间性的作用域.在开发一些小程序的时候也许我们并不在意作用域的必要性.可是,当你书写几万行,甚至几十万行的代码的时候,没有作用域肯定是不能忍受的.C语言有如下三种作用域. 1. 全局变量 在函数之外声明的变量,默认地会成为全局变量.全局变量在任何地方都是可见的.当程序被分割为多个源代码文件进行编译时,声明为全局变量的变量也是可以通过关键字extern从其他源代码文件中引用的. 2. 文件内部的静态变量 就算对于像全局变量那样呗定义在函数外面的变量,一旦添加了static,作用域就

《征服 C 指针》摘录4:函数 与 指针

一.指向函数的指针 函数名可以在表达式中被解读成“指向函数的指针”,因此,正如代码清单 2-2 的实验那样,写成 func 就可以取得指向函数的指针. “指向函数的指针”本质上也是指针(地址),所以可以将它赋给指针型变量. 比如有下面的函数原型: int func(double d); 保存指向此函数的指针的变量的声明如下: int (*func_p)(double); 然后写成下面这样,就可以通过 func_p 调用 func, int (*func_p)(double); // 声明 fun

《征服 C 指针》摘录3:数组 与 指针

一.数组 和 指针 的微妙关系 数组 是指将固定个数.相同类型的变量排列起来的对象. 正如之前说明的那样,给指针加 N,指针前进“当前指针指向的变量类型的长度 X N”. 因此,给指向数组的某个元素的指针加 N 后,指针会指向 N 个之后的元素. #include <stdio.h> int main(void) { int array[5]; int *p; int i; /* 给数组 array 的各元素设定值 */ for (i = 0; i < 5; i++) { array[i

《征服 C 指针》摘录2:C变量的 作用域 和 生命周期(存储期)

在开发一些小程序的时候,也许我们并不在意作用域的必要性.可是,当你书写几万行,甚至几十万行的代码的时候,没有作用域肯定是不能忍受的. C 语言有如下 3 种作用域. 1.全局变量 在函数之外声明的变量,默认地会成为全局变量.全局变量在任何地方都是可见的.当程序被分割为多个源代码文件进行编译时,声明为全局变量的变量也是可以从其他源代码文件中引用的. 2.文件内部的静态变量 就算对于像全局变量那样被定义在函数外面的变量,一旦添加了 static,作用域就只限定在当前所在的源代码文件中.通过 stat

《征服 C 指针》笔记6:练习——挑战那些复杂的声明

应该是小试牛刀的时候了. 在 ANSI C 的标准库中,有一个 atexit()函数.如果使用这个函数,当程序正常结束的时候,可以回调一个指定的函数. atexit()的原型定义如下: int atexit(void (*func)(void)); 1.首先着眼于标识符. int atexit(void (*func)(void)); 英语的表达为: atexit is 2.解释用于函数的(). int atexit(void (*func)(void)); 英语的表达为: atexit is

征服C指针

1.局部变量通常在它所在的语句块结束的时候被释放.如果你不想释放某个局部变量,可以在局部变量上加上 static 进行声明 2.C 中有三种内存领域的寿命.q 静态变量的寿命从程序运行时开始,到程序关闭时结束.q 自动变量的寿命到声明该变量的语句块执行结束为止.q 通过 malloc() 分配的领域的寿命到调用 free() 为止. 3.静态变量是从程序启动到运行结束为止持续存在的变量.因此,静态变量总是在虚拟地址空间上占有固定的区域 4.所谓的“返回地址”,是指函数处理完毕后应该返回的地址.

C++指针的概念解读[超详细]

转自:http://www.codeceo.com/article/cpp-pointer.html 指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址.要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区.让我们分别说明. 先声明几个指针放着做例子: 例一: int *ptr; char *ptr; int **ptr; int (*ptr)[3]; int *(*ptr)[4]; 指针的类型 从语法