适合 Python 程序员的 C

2023-10-13

本教程的目的是让经验丰富的 Python 程序员快速掌握 Python 的基础知识C语言以及如何使用它CPython 源代码。它假设您已经对 Python 语法有一定的了解。

也就是说,C 是一种相当有限的语言,它在 CPython 中的大部分用法都属于一小组语法规则。达到理解代码的程度比能够有效地编写 C 语言要小得多。本教程针对第一个目标,但不是第二个目标。

在本教程中,您将学习:

  • 什么是C预处理器是什么以及它在构建 C 程序中扮演什么角色
  • 如何使用预处理器指令操作源文件
  • 如何C语法相比于Python语法
  • 如何创建循环, 功能, 字符串,以及 C 中的其他功能

Python 和 C 之间最显着的区别之一是 C 预处理器。你会先看看那个。

笔记:本教程改编自附录“Python 程序员的 C 简介”,CPython 内部原理:Python 解释器指南.

免费下载: 从 CPython Internals: Your Guide to the Python 3 Interpreter 获取示例章节向您展示如何解锁Python语言的内部工作原理、从源代码编译Python解释器以及参与CPython的开发。

C 预处理器

顾名思义,预处理器在编译器运行之前在源文件上运行。它的功能非常有限,但您可以在构建 C 程序时充分利用它们。

预处理器生成一个新文件,这是编译器实际处理的文件。预处理器的所有命令都从一行的开头开始,并带有#符号作为第一个非空白字符。

预处理器的主要目的是在源文件中进行文本替换,但它也会执行一些基本的条件代码#if或类似的陈述。

您将从最常见的预处理器指令开始:#include.

#include

#include用于将一个文件的内容拉入当前源文件。没有什么复杂的#include。它从文件系统读取文件,对该文件运行预处理器,并将结果放入输出文件中。这个做完了递归地对于每个#include指示。

例如,如果您查看 CPythonModules/_multiprocessing/semaphore.c 文件,然后在顶部附近您将看到以下行:

#include "multiprocessing.h"

这告诉预处理器提取整个内容multiprocessing.h并将它们放入输出文件的这个位置。

您会注意到有两种不同的形式#include陈述。其中之一使用引号("")指定包含文件的名称,另一个使用尖括号(<>)。区别在于在文件系统上查找文件时搜索的路径。

如果你使用<>对于文件名,那么预处理器将仅查看系统包含文件。在文件名周围使用引号将强制预处理器首先查找本地目录,然后回退到系统目录。

#define

#define允许您进行简单的文本替换,并且还可以发挥作用#if您将在下面看到指令。

最基本的是,#define允许您定义一个新符号,该符号将替换为预处理器输出中的文本字符串。

继续在semphore.c,你会发现这一行:

#define SEM_FAILED NULL

这告诉预处理器替换每个实例SEM_FAILED低于此点的文字字符串NULL在代码发送到编译器之前。

#define项目也可以采用参数,就像这个特定于 Windows 的版本一样SEM_CREATE:

#define SEM_CREATE(name, val, max) CreateSemaphore(NULL, val, max, NULL)

在这种情况下,预处理器将期望SEM_CREATE()看起来像一个函数调用并具有三个参数。这通常被称为。它将直接将三个参数的文本替换为输出代码。

例如,在第 460 行semphore.c, 这SEM_CREATE宏的使用方式如下:

handle = SEM_CREATE(name, value, max);

当您针对 Windows 进行编译时,该宏将被扩展,使得该行看起来像这样:

handle = CreateSemaphore(NULL, value, max, NULL);

在后面的部分中,您将看到该宏在 Windows 和其他操作系统上的定义有何不同。

#undef

该指令将从中删除任何先前的预处理器定义#define。这使得有可能有​​一个#define仅对文件的一部分有效。

#if

预处理器还允许条件语句,允许您根据某些条件包含或排除文本部分。条件语句以#endif指令,也可以利用#elif#else用于微调。

有以下三种基本形式#if您将在 CPython 源代码中看到:

  1. #ifdef <macro>如果定义了指定的宏,则包括后续的文本块。你也可能会看到它写成#if defined(<macro>).
  2. #ifndef <macro>如果指定的宏是,则包括后续的文本块not定义的。
  3. #if <macro>如果定义了宏,则包括后续文本块and它评估为True.

请注意,使用“文本”而不​​是“代码”来描述文件中包含或排除的内容。预处理器对 C 语法一无所知,也不关心指定的文本是什么。

#pragma

编译指示是对编译器的指令或提示。一般来说,您可以在阅读代码时忽略这些,因为它们通常涉及代码的编译方式,而不是代码的运行方式。

#error

最后,#error显示一条消息并导致预处理器停止执行。同样,您可以放心地忽略这些内容来阅读 CPython 源代码。

Python 程序员的基本 C 语法

本节不会涵盖all本书重点介绍了 C 语言的各个方面,也不是为了教您如何编写 C 语言。它将重点关注 Python 开发人员第一次看到时感到不同或感到困惑的 C 语言方面。

一般的

与 Python 不同,空格对于 C 编译器来说并不重要。编译器并不关心您是否跨行分割语句或将整个程序塞进一行很长的行中。这是因为它对所有语句和块使用分隔符。

当然,解析器有非常具体的规则,但一般来说,您只需知道每个语句以分号结尾就能够理解 CPython 源代码(;),所有代码块都用大括号 ({}).

此规则的例外是,如果一个块只有一条语句,则可以省略花括号。

C 中的所有变量都必须是宣布,这意味着需要有一个单独的语句来表明类型该变量的。请注意,与 Python 不同,单个变量可以保存的数据类型不能更改。

这里有一些例子:

/* Comments are included between slash-asterisk and asterisk-slash */
/* This style of comment can span several lines -
   so this part is still a comment. */

// Comments can also come after two slashes
// This type of comment only goes until the end of the line, so new
// lines must start with double slashes (//).

int x = 0; // Declares x to be of type 'int' and initializes it to 0

if (x == 0) {
    // This is a block of code
    int y = 1;  // y is only a valid variable name until the closing }
    // More statements here
    printf("x is %d y is %d\n", x, y);
}

// Single-line blocks do not require curly brackets
if (x == 13)
    printf("x is 13!\n");
printf("past the if block\n");

一般来说,您会发现 CPython 代码的格式非常清晰,并且通常在给定模块中坚持单一风格。

if声明

在C中,if工作原理与 Python 中的工作方式大致相同。如果条件为真,则执行以下块。这elseelse ifPython 程序员应该非常熟悉语法。请注意,Cif声明不需要endif因为块是由{}.

C 里有一个简写ifelse声明称为三元运算符:

condition ? true_result : false_result

您可以在以下位置找到它:semaphore.c其中,对于 Windows,它定义了一个宏SEM_CLOSE():

#define SEM_CLOSE(sem) (CloseHandle(sem) ? 0 : -1)

该宏的返回值将是0如果函数CloseHandle()回报true-1否则。

笔记:CPython 源代码的部分支持和使用布尔变量类型,但它们不是原始语言的一部分。 C 使用一个简单的规则解释二进制条件:0或者NULL是假的,其他一切都是真的。

switch声明

与Python不同,C还支持switch。使用switch可以看作是扩展的快捷方式ifelseif链。这个例子来自semaphore.c:

switch (WaitForSingleObjectEx(handle, 0, FALSE)) {
case WAIT_OBJECT_0:
    if (!ReleaseSemaphore(handle, 1, &previous))
        return MP_STANDARD_ERROR;
    *value = previous + 1;
    return 0;
case WAIT_TIMEOUT:
    *value = 0;
    return 0;
default:
    return MP_STANDARD_ERROR;
}

这对返回值执行切换WaitForSingleObjectEx()。如果值为WAIT_OBJECT_0,然后执行第一个块。这WAIT_TIMEOUTvalue 结果在第二个块中,其他任何内容都与default堵塞。

请注意,正在测试的值,在本例中为返回值WaitForSingleObjectEx(),必须是整数值或枚举类型,并且每个case必须是一个常数值。

循环

C语言中共有三种循环结构:

  1. for循环
  2. while循环
  3. dowhile循环

for循环的语法与 Python 完全不同:

for ( <initialization>; <condition>; <increment>) {
    <code to be looped over>
}

除了循环中要执行的代码之外,还有三个代码块控制循环for环形:

  1. <initialization>当循环开始时,该部分只运行一次。它通常用于将循环计数器设置为初始值(并且可能用于声明循环计数器)。

  2. <increment>代码在每次通过循环的主块后立即运行。传统上,这将增加循环计数器。

  3. 最后,<condition>在之后运行<increment>。当此条件返回 false 时,将评估此代码的返回值并中断循环。

这是一个来自模块/sha512module.c:

for (i = 0; i < 8; ++i) {
    S[i] = sha_info->digest[i];
}

这个循环将运行8次,与i递增自07,并将在检查条件时终止i8.

while循环实际上与它们相同Python 对应项。这dowhile然而,语法有点不同。的条件为dowhile循环不会被检查,直到第一次执行循环体。

有很多这样的例子for循环和whileCPython 代码库中的循环,但是dowhile未使用。

功能

C 中函数的语法类似于在Python中,此外还必须指定返回类型和参数类型。 C 语法如下所示:

<return_type> function_name(<parameters>) {
    <function_body>
}

返回类型可以是 C 中的任何有效类型,包括内置类型,例如intdouble以及自定义类型,例如PyObject,如本例所示semaphore.c:

static PyObject *
semlock_release(SemLockObject *self, PyObject *args)
{
    <statements of function body here>
}

在这里您可以看到几个正在发挥作用的 C 特定功能。首先,请记住空格并不重要。许多 CPython 源代码将函数的返回类型放在函数声明其余部分上方的行上。这就是PyObject *部分。您将仔细查看以下内容的使用*稍后,但现在重要的是要知道可以在函数和变量上放置几个修饰符。

static是这些修饰符之一。有一些复杂的规则控制着修饰符的运作方式。例如,static修饰符在这里的含义与将其放在变量声明前面时的含义非常不同。

幸运的是,在尝试阅读和理解 CPython 源代码时,您通常可以忽略这些修饰符。

函数的参数列表是以逗号分隔的变量列表,类似于 Python 中使用的参数列表。同样,C 要求每个参数都有特定的类型,所以SemLockObject *self表示第一个参数是一个指向SemLockObject并被称为self。请注意,C 中的所有参数都是位置参数。

让我们看看该语句的“指针”部分是什么意思。

为了提供一些上下文,传递给 C 函数的参数都是按值传递,这意味着该函数对值的副本进行操作,而不是对调用函数中的原始值进行操作。为了解决这个问题,函数会经常传入函数可以修改的一些数据的地址。

这些地址称为指针并且有类型,所以int *是一个指向整数值的指针,并且其类型不同于double *,它是一个指向双精度浮点数的指针。

指针

如上所述,指针是保存值地址的变量。这些在 C 中经常使用,如下例所示:

static PyObject *
semlock_release(SemLockObject *self, PyObject *args)
{
    <statements of function body here>
}

在这里,self参数将保存地址,或一个指向, ASemLockObject价值。另请注意,该函数将返回一个指向PyObject价值。

笔记:要深入了解如何在 Python 中模拟指针,请查看Python 中的指针:有什么意义?

C中有一个特殊的值叫做NULL这表明指针没有指向任何东西。您将看到分配给的指针NULL并检查NULL贯穿 CPython 源代码。这很重要,因为对于指针可以具有的值几乎没有限制,并且访问不属于程序一部分的内存位置可能会导致非常奇怪的行为。

另一方面,如果您尝试访问内存NULL,那么你的程序将立即退出。这可能看起来不太好,但通常更容易找出内存错误,如果NULL与修改随机存储器地址相比被访问。

弦乐

C 没有字符串类型。许多标准库函数都是围绕这个约定编写的,但没有实际的类型。相反,C 中的字符串存储为数组char(对于 ASCII)或wchar(对于 Unicode)值,每个值包含一个字符。字符串标有空终止符,它有一个值0通常在代码中显示为\\0.

基本的字符串操作如strlen()依靠这个空终止符来标记字符串的结尾。

因为字符串只是值的数组,所以不能直接复制或比较它们。标准库有strcpy()strcmp()函数(及其wchar表兄弟)用于执行这些操作以及更多操作。

结构体

本次 C 迷你之旅的最后一站是如何在 C 中创建新类型:结构体。这struct关键字允许您将一组不同的数据类型组合在一起形成新的自定义数据类型:

struct <struct_name> {
    <type> <member_name>;
    <type> <member_name>;
    ...
};

这部分示例来自模块/arraymodule.c显示了一个struct宣言:

struct arraydescr {
    char typecode;
    int itemsize;
    ...
};

这将创建一个新的数据类型,称为arraydescr它有很多成员,其中前两个是char typecodeint itemsize.

结构体经常被用作结构体的一部分typedef,它为该名称提供了一个简单的别名。在上面的例子中,所有新类型的变量都必须用全名声明struct arraydescr x;.

你会经常看到这样的语法:

typedef struct {
    PyObject_HEAD
    SEM_HANDLE handle;
    unsigned long last_tid;
    int count;
    int maxvalue;
    int kind;
    char *name;
} SemLockObject;

这将创建一个新的自定义结构类型并为其命名SemLockObject。要声明这种类型的变量,您可以简单地使用别名SemLockObject x;.

结论

您的 C 语法的快速演练到此结束。尽管此描述仅触及 C 语言的表面,但您现在已经拥有足够的知识来阅读和理解 CPython 源代码。

在本教程中,您学习了:

  • 什么是C预处理器是什么以及它在构建 C 程序中扮演什么角色
  • 如何使用预处理器指令操作源文件
  • 如何C语法相比于Python语法
  • 如何创建循环, 功能, 字符串,以及 C 中的其他功能

现在您已经熟悉了 C,您可以通过探索 CPython 源代码来加深对 Python 内部工作原理的了解。快乐Python!

笔记:如果您喜欢在此示例中学到的内容CPython 内部原理:Python 解释器指南,那么一定要检查一下本书的其余部分.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

适合 Python 程序员的 C 的相关文章

  • Python 的 urllib.request 用于 HTTP 请求

    目录 使用 urllib request 的基本 HTTP GET 请求 The Nuts and Bolts of HTTP Messages 了解什么是 HTTP 消息 了解 urllib request 如何表示 HTTP 消息 关闭
  • Django Ninja 的隐蔽 REST API(摘要)

    在本课程中 您已经了解了 Django Ninja REST API 库的所有内容 使用 Ninja 您可以 使用装饰器快速包装 Django 视图创建 REST API 端点 使用类型注释定义变量和参数 写Schema和ModelSche
  • Python 中的 Dijkstra 算法(查找最短路径)

    Dijkstra算法的工作原理是通过迭代找到节点的最短距离值 直到达到实际的最短距离 Dijkstra 算法的一个关键方面是它使用优先队列从尚未处理的节点集中选择具有最小暂定距离的顶点 当前节点被标记为已访问 并检查其所有邻居节点是否有更优
  • 使用 Pandas read_excel 读取 Excel 文件

    Pandas read excel是一个函数蟒蛇熊猫库允许我们在 Python 中读取 Excel 文件并将其转换为数据框 object read excel函数可以导入具有不同扩展名的Excel文件 例如 xls xlsx xlsm和 o
  • Pandas where() 方法:带条件过滤

    The where中的方法Pandas允许您根据条件过滤 DataFrame 或 Series 类似于 SQL 的 WHERE 子句 您是否曾经发现自己需要根据特定条件替换 DataFrame 中的某些值 或者可能想要屏蔽不符合某些条件的数
  • Linux find 命令:综合指南

    The findLinux 中的命令是一个功能强大的实用程序 用于根据您指定的条件搜索和定位文件和目录 它可以按名称 大小 类型 权限 日期和许多其他标准快速定位文件 目录 hide 1 基本语法 2 Finding Files by Na

随机推荐

  • 了解 Linux Sed 命令中的保持缓冲区

    保持缓冲区在sed允许您临时存储和检索输入行 将其视为辅助存储器 您可以在使用主模式空间时放置数据 当您需要一次执行涉及多行的操作时 保持缓冲区特别有用 保留缓冲区不是立即处理和打印每一行 而是让您保存一行 处理其他行 然后在需要时返回保存
  • Python zip 函数教程(简单示例)

    The zip function 是一个内置的 Python 函数 它接受两个或多个序列或集合 如列表或字符串 并创建一个并行聚合每个集合中的元素的迭代器 这种组合这些值的过程称为 压缩 它源于将两个单独的项目集合压缩在一起的想法 目录 h
  • Linux 测试命令:Bash 中的比较

    The testLinux 中的命令是一个命令行实用程序 用于检查和评估条件 它经常被用在bash 脚本测试文件属性 比较字符串和数字 并支持 AND OR 和 NOT 运算的复杂逻辑评估 目录 hide 1 语法和返回值 2 File T
  • Python 中的无穷大万无一失的指南

    数字是编程不可或缺的一部分 因此 编程语言支持各种数据类型来表示不同类型的数字 并提供各种使用它们的方法 这些数据类型中的每一种都对其可以表示的数字范围有一定的限制 有些可以代表小范围的数字 而另一些则支持很大的数字范围 根据我们的用例 我
  • Linux 中的 Grep 命令(附示例)

    在本教程中 您将学习如何使用非常重要的grepLinux 中的命令 我们将讨论为什么掌握这个命令很重要 以及如何在命令行的日常任务中使用它 让我们通过一些解释和示例来深入探讨 目录 hide 1 为什么我们使用 grep 2 查找字符串 3
  • 如何一步步安装Linux

    如何安装Linux 当您选择了最佳 Linux 发行版 现在是时候了解如何安装 Linux 了 如果你想安装Linux 有两种方法可以实现 第一种方式就是下载您想要的 Linux 发行版并将其刻录到 DVD 或 USB 记忆棒中 然后用它启
  • Python 字符串插值(制作动态字符串)

    字符串插值是将变量值替换为字符串中占位符的过程 这是 Python 中的一项强大功能 使您能够通过在运行时将变量的值嵌入或替换到字符串中来创建动态字符串 Python支持多种格式化字符串和执行字符串插值的方式 使得格式字符串更容易维护 也更
  • Python相关矩阵教程(使用Pandas)

    在本博客中 我们将介绍一个重要的多变量数据描述性统计量 称为相关矩阵 我们将学习如何在 Python 中使用以下命令创建 绘制和操作相关矩阵Pandas 我们将讨论以下主题 目录 hide 1 What is the correlation
  • 第 129 集:在 Python 中使用省略号以及 CPython 3.12 的目标

    第 129 集 在 Python 中使用省略号以及 CPython 3 12 的目标 真正的 Python 播客 2022 年 10 月 14 日56m RSS Apple Podcasts Google Podcasts Spotify
  • 互动

    无论您是在学习基本的 Python 数据结构 字符串 列表 字典等 第一次 或者您正在调试应用程序 交互式 Python shell 将是您最好的学习工具之一 使用交互式 Python shell 有时也称为 Python REPL 首先确
  • Python 指导委员会

    在本课程中 您将了解Python 指导委员会 从技术上来说 Python 的治理不是语言特征 然而 Python 3 8 是第一个不是在仁慈独裁统治下开发的 Python 版本 吉多 范罗苏姆 Python 语言现在由指导委员会由五位核心开
  • 列表:可变和动态

    在本课程中 您将探索 Python 列表如何可变和动态 Python 中的许多类型是不可变的 整数 浮点数 字符串 并且 正如您将在本课程后面学到的 元组都是不可变的 一旦创建了这些对象之一 就无法对其进行修改 除非您将该对象重新分配给新值
  • 在屏幕上绘图

    在本课程中 您将开始使用Surface 回想一下 一个表面是一个可以在其上绘图的矩形对象 就像一张白纸 这screen对象是一个Surface 并且您可以创建自己的Surface与显示屏分离的物体 您将用白色填充屏幕 并添加一个新的Surf
  • 数据科学数学

    数据科学数学 学习路径 技能 统计 相关性 线性回归 逻辑回归 在这个学习路径中 您将获得在数据科学方面取得进步所需的数学基础 其他资源 真正的 Python 数据科学主题 真正的 Python 机器学习主题 数据科学数学 学习路径 5 种
  • Python 基础练习:文件系统操作(概述)

    In Python 基础知识 文件系统操作 您学习了如何使用 Python 处理文件和文件夹 作为一名程序员 您将使用pathlib和shutil要完成的模块文件系统操作不依赖你的图形用户界面 GUI 虽然您已经进行了大量文件系统操作的实践
  • 使用 Python 和 ggplot 绘制数据图表

    在本课程中 您将学习如何使用ggplot在Python中使用创建数据可视化图形语法 图形语法是一种高级工具 可让您以高效且一致的方式创建数据图 它抽象了最底层的细节 让您专注于为数据创建有意义且美观的可视化效果 有几个 Python 包提供
  • MicroPython 入门(概述)

    您对物联网 家庭自动化和互联设备感兴趣吗 你有没有想过建造一把爆能枪 一把激光剑 甚至你自己的机器人会是什么样子 如果是这样 那么您很幸运 微Python可以帮助您完成所有这些事情以及更多 在本课程中 您将了解 这历史微Python的 这差
  • Jupyter 终端及更多

    Jupyter Notebooks 不仅可以让您启动笔记本 在本课程中 您将学习如何启动新终端以及如何在浏览器中创建文件夹或文件
  • 2021 年 8 月 11 日

    主持人大卫 阿莫斯回答会员的问题 本周 Real Python 社区经理 Andres Pineda 也加入了 David 的行列 在这次会议上 我们讨论了 Python 新闻和更新 如何开始使用 Python 进行日志记录 如何学习编写更
  • 适合 Python 程序员的 C

    目录 The C Preprocessor 包括 定义 undef if pragma 错误 Basic C Syntax for Python Programmers 一般的 if 语句 switch 语句 循环 功能 指针 弦乐 结构体