基本规则
了解C语言中使用的类型系统。 掌握一种简单的方法,用于理解复杂的声明。
简介
我们已经见过不少涉及指针的声明,例如指向变量的指针、指向数组的指针,以及指向函数的指针。以下是一些示例:
1int* ptr; // ptr 是一个指向 int 的指针 2int x[10]; // x 是一个包含 10 个元素的数组 3void(*funcPtr)(int x); // funcPtr 是一个指向函数的指针,该函数接受一个 int 参数并返回 void
这些声明本身读起来相当直观,但当我们开始将它们组合起来时,理解起来就会变得复杂。我们之前并未定义明确的阅读规则,但可以通过观察声明的结构来直观地理解。
另一方面,我们也见过如下形式的声明:
1char* x[10]; // x 是一个包含 10 个指向 char 的指针的数组 2void(*funcPtr[])(int); // funcPtr 是一个指向函数的指针数组,每个函数接受一个 int 参数并返回 void
这些声明如果不借助清晰的规则和逐步分析的方法,读起来会非常困难。
派生类型
回想一下在早期课程中提到的内容:可以把指针看作一种数据类型,类似于 int、float 等常见类型。这个说法只是部分正确。实际上,指针、数组和函数类型统称为派生数据类型。它们扩展了基本数据类型(如 int、float、char)的功能。
我们将同时面对标准类型和派生类型。我们已经了解如何阅读它们,但再回顾一遍:
*表示指针类型,读作“指向”。[]表示数组类型。- 我们可能遇到带大小的数组(
[SIZE])或不带大小的数组([])。 - 如果数组有大小,读作“大小为 size 的 type 数组”。
- 如果数组没有指定大小,读作“type 数组”。
- 我们可以选择忽略大小,始终将数组视为无大小形式。但仅限于数组的第一维(即退化为指针的那一维)。记住,
arr[5][10]只会丢失第一维的信息,等价于arr[][10]。不要忽略第二维及以后各维的大小。
- 我们可能遇到带大小的数组(
()或(args)表示函数定义,读作“返回 type 并接受 args 类型参数的函数”。
运算符优先级
由于表达式可能同时包含三种派生类型,我们需要规定一个阅读顺序。虽然通用规则可以处理这个问题,但为了更清晰,我们也可以明确它们的优先级。
注意:
()和[]的优先级高于*。
运算符优先级解释了为什么过去我们使用 () 来改变运算顺序。例如,如果我们希望声明一个指向包含 10 个整数的数组的指针,必须写成:
1int(*ptr)[10]; // ptr 是一个指向大小为 10 的整数数组的指针
如果去掉括号,就变成 int *ptr[10]。由于 [] 的优先级高于 *,系统会先解析 [],再解析 *,结果是 ptr 被解释为一个包含 10 个 int* 类型指针的数组。
但我们真正想要的是一个指向整数数组的指针。为了得到正确的类型,我们使用 () 提高 * 的优先级,使其在 [] 之前被解析。
右左法则
我们只需要掌握一个通用规则来解读复杂的声明:
- 从标识符(名称)开始。
- 尽可能向右移动,直到遇到闭合括号为止。
- 从标识符开始尽可能向左移动,直到遇到开放括号为止。
- 跳出内层括号后重复上述过程。
让我们以一个函数指针数组的声明为例,来演示这一过程。
1void(*funcPtr[])(int);
请查看下方动画演示。
更多示例
现在应用该法则来分析我们在讨论运算符优先级时用到的例子。分别阅读两个声明,并注意 () 带来的区别。
1int(*ptr)[10]; 2int* arr[10];
先来看第一个:
再来看第二个:
在查看幻灯片前,请尝试自己分析这两个声明,或者自行构造类似例子进行练习!