18.2 基础 C 语法
本节不会涵盖 C 的所有内容,也不打算教你如何编写 C。它将重点介绍 C 的不同方面,或者在 Python 开发人员第一次看到它们时感到困惑的方面。
通用知识
与 Python 不同,空格对 C 编译器并不重要。编译器不在乎你是跨行拆分语句,还是把整个程序挤成一条很长的行。这是因为它对所有语句和块都使用分隔符。
当然,解析器有非常具体的规则,但一般来说,只要知道每个语句以分号(;)结尾,以及所有代码块都用大括号({})包围,你就能理解 CPython 源码。
此规则的例外情况是如果块只有一条语句,则可以省略大括号。
C 中的所有变量都必须被声明,这意味着需要有一条语句表示该变量的类型。请注意,与 Python 不同,单个变量可以容纳的数据类型不能更改。
让我们来看看一些例子:
代码不翻译
一般来说,你看到的 CPython 代码格式会非常整洁,并且通常在给定的模块中会坚持单一风格。
if 语句
在 C 中,if
的工作方式通常与 Python 中的工作方式相同。如果条件为 true,则执行下面的代码块。Python 程序员应该非常熟悉 else
和 elseif
语法。请注意,C 中的 if
语句不需要使用 endif
,因为代码块由 {} 来分隔。
if...else
语句在 C 中的简写被称为三元运算符:
你可以在 semaphore.c
中找到它,对于 Windows 而言,它定义了 SEM_CLOSE()
宏:
如果函数 CloseHandle()
返回 true
,则此宏的返回值将为 0
,否则为 -1
。
注
布尔变量类型在部分 CPython 源码中受支持和使用,但它们不是原始语言的一部分。C 使用简单的规则解释二进制条件:0 或 NULL 为假,其他一切都为真。
switch 语句
与 Python 不同的是,C 还支持 switch
。使用 switch
可以被视为扩展 if...else
链的快捷方式。此示例来自 semaphore.c:
代码不翻译
这将基于 WaitForSingleObjectEx()
的返回值执行切换。如果该值为 WAIT_OBJECT_0
,则执行第一个代码块。WAIT_TIMEOUT
值会执行第二个代码块,其他任何内容都与 default
代码块匹配。
需要注意的是 switch
中的值都会被测试校验。在本例中,来自 WaitForSingleObjectEx()
的返回值必须是整数值或枚举类型,并且每一个 case
都必须是常量值。
循环
C 语言中有三种循环结构:
for
循环;while
循环;do ... while
循环。
让我们依次看一下每一个循环结构:
for
循环的语法与 Python
完全不同:
除了要在循环中执行的代码外,还有三个代码块控制 for 循环:
当循环启动时,
<initialization>
部分正好运行一次。它通常用于将循环计数器设置为初始值(可能还用于声明循环计数器)。<increment>
代码在每次通过循环的主代码块后立即运行。一般情况下,这将增加循环计数器。最后,
<condition>
在<increment>
之后运行。将计算此代码的返回值,当此条件返回 false 时,循环将中断。
下面是 Modules/sha512module.c
中的一个示例:
此循环将运行 8 次,i
从 0 递增到 7,当条件被检查且 i
为 8 时将终止。
while
循环实际上与 Python 对应的循环相同。但是,do...while
语法有点不同。到第一次执行循环体之后才会检查 do...while
循环中的条件。
在 CPython 代码基线中,有许多 for
循环和 while
循环实例,但是 do..while
没有被使用到。
函数
C 中的函数语法与 Python 中的语法相似,只是必须指定返回类型和参数类型。C 语法是这样的:
返回类型可以是 C 中的任何有效类型,包括内置类型,如:int
和 double
,以及自定义类型,如:PyObject
,如来自 semaphore.c
中的示例所示:
在这里,你可以看到一些 C 相关的特性正在发挥作用。首先,记住空格并不重要。CPython 源代码的大部分内容都将函数的返回类型放在函数声明的上方。这就是 PyObject *
部分。稍后,你将仔细研究 *
的用法,但现在重要的是要知道,你可以在函数和变量上放置一些修饰符。
static
是这些修饰符之一。有一些复杂的规则来管理修饰符的操作方式。例如,这里的 static
修饰符的含义与将其放在变量声明前面时非常不同。
幸运的是,在尝试阅读和理解 CPython 源代码时,你通常可以忽略这些修饰符。
函数的参数列表是一个逗号分隔变量列表,类似于你在 Python 中使用的。同样,C 需要每个参数的具体类型,因此 SemLockObject *self
表示第一个参数是指向 SemLockObject
的指针,并被称为 self
。请注意,C 中的所有参数都是位置参数。
让我们来看看该语句的“指针”部分是什么意思。
为了给出一些上下文,传递给 C 函数的参数都是按值传递的,这意味着函数在调用函数中的值的副本上操作,而不是在原始值上操作。为了解决这个问题,函数将经常传入函数可以修改的一些数据的地址。
这些地址被称为指针,并具有类型,因此 int *
是指向整数值的指针,与 double * 类型不同,double * 是指向双精度浮点数的指针。
指针
如上所述,指针是保存值地址的变量。这些在 C 中经常使用,如本示例所示:
在这里,self
参数将保存 SemLockObject
值的地址或指向 SemLockObject
值的指针。另外请注意,该函数将返回指向 PyObject
值的指针。
在 C 中,有一个名为 NULL 的特殊值,它表示指针不指向任何东西。在整个 CPython 源码中,你将看到分配给 NULL 并根据 NULL 进行检查的指针。需要注意的一点是指针可以具有什么值的限制很少,而且访问不属于程序的内存位置可能会导致非常奇怪的行为。
另一方面,如果你尝试访问 NULL 的内存,则你的程序将会立即退出。这似乎不太好,但访问 NULL
通常比修改随机内存地址更容易找出内存漏洞。
字符串
C 没有字符串类型。有一个惯例:许多标准库函数都是围绕它编写的,但没有实际的类型。相反,C 中的字符串存储为 char
(对于 ASCII 而言)或 wchar
(对于 Unicode 而言)值的数组,每个值都包含一个字符。字符串用空终止符标记,其值为 0
,通常在代码中显示为\0
。
基本的字符串操作,如 strlen()
依赖于此空终止符来标记字符串的结尾。
因为字符串只是值的数组,所以不能直接复制或比较它们。标准库有 strcpy()
和 strcmp()
函数(及其wchar
表亲)用于执行这些操作等。
结构体
在这个 C 迷你之旅的最后一站是如何在 C 中创建新的类型:结构体。struct
关键字允许你将一组不同的数据类型组合在一起,形成一个新的自定义数据类型:
Modules/arraymodule.c
中的部分示例展示了一个 struct
声明:
这将创建一个名为 arraydescr
的新数据类型,它有许多成员,其中前两个是char typecode
和 int itemsize
。
结构体通常被用作 typedef
的一部分,其为结构体名提供了一个简单的别名。在上面的示例中,新类型中的所有变量都必须被一个全名进行声明:struct arraydescr x;
。
你会经常看到这样的语法:
这将创建一个新的自定义结构类型,并为其命名为 SemLockObject
。要声明此类型的变量,你可以简单地使用别名:SemLockObject x;
。
Last updated