Logo
Section 1 / 4

基本规则

了解C语言中使用的类型系统。 掌握一种简单的方法,用于理解复杂的声明。

导入

基本规则

了解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* 类型指针的数组。
但我们真正想要的是一个指向整数数组的指针。为了得到正确的类型,我们使用 () 提高 * 的优先级,使其在 [] 之前被解析。

右左法则

我们只需要掌握一个通用规则来解读复杂的声明:

  1. 从标识符(名称)开始。
  2. 尽可能向右移动,直到遇到闭合括号为止。
  3. 从标识符开始尽可能向左移动,直到遇到开放括号为止。
  4. 跳出内层括号后重复上述过程。

让我们以一个函数指针数组的声明为例,来演示这一过程。

1void(*funcPtr[])(int);

请查看下方动画演示。

更多示例

现在应用该法则来分析我们在讨论运算符优先级时用到的例子。分别阅读两个声明,并注意 () 带来的区别。

1int(*ptr)[10]; 2int* arr[10];

先来看第一个:

再来看第二个:

在查看幻灯片前,请尝试自己分析这两个声明,或者自行构造类似例子进行练习!

栈和局部内存

阅读复杂的指针声明