[栖岸计划]智英带你学C语言(新...

物理
[栖岸计划]智英带你学C语言(新版回归)

用户头像
暂缓开通站点增加啦! 更新于2026-5-14 01:36:09

上学期一直没写,一个重要原因是我自己的C语言也不是特别熟练,然而这学期又选修了一门系统级编程,智英感觉自己又行了,所以把原帖删了重新开一个发在这


前言仍然是25年10月发的原文:

看到这题目可能就有人要叫了,现在大家要么用Python,要么用C++,C是什么上古语言???

不过呢,C语言能够出现在大学教材里面,肯定也是有它的原因的

作为现在基础课中几乎唯一面向过程的高级语言(Fortran:你有没有考虑过我的感受),C语言确实可用于加深对编程的理解

个人觉得,如果把C语言学明白了,那么面向对象的语言也会更容易学习一些

更何况,C语言也没有有些人想象的那么上古,以下是今年(2025年)9月的TIOBE指数排行(TOP10):

image.png

C语言的比率高于Java,甚至逼近C++。毕竟,还有大量的操作系统是用C语言写的,甚至连Python的解释器使用的都是C语言

(这里需要讲到C语言的一个特点,就是有些C语言的编译器可以用C语言自身开发!比较通俗的解释是,先用汇编等低级语言编译一个“编译器”的雏形,然后将其用于C语言编译器的编译)

所以,大家也看到了,这是一个C语言的教学帖,本人会把C语言基础知识放在这里,大致涵盖我现在正在学习的内容,也希望跟大家一起好好学C,天天向上!


第一章 编译器与VS Code的下载与安装

1.1 编译环境的配置

现在有一个好消息和一个坏消息。

好消息:Linux系统自带GCC编译器。

坏消息:想必大多数人用的是Windows系统。Windows系统不但没有自带的编译器,哪怕是已经在Linux环境下(如虚拟机、WSL)编译出来的程序,都无法完成运行。

为了解决这个问题,我们可以下载安装MSYS2。以下是下载的方法(以清华镜像源为例):

从清华镜像源网页首页开始,依次点击msys2,distrib,x86_64目录,或者直接输入下面的链接:

image.png

点击最新的exe文件,即可进行下载。下载完成后,在“下载”文件夹点击安装包,即可进行安装(不断点击Next):

image.png

这里我把下载目录放在了D盘(因为智英电脑的C盘已经飘红很多次了)。除了这里,其他所有的内容都可以跳过。

image.png

上图为最终的完成页面。选择Run MSYS2 now,然后点击Finish,就会自动打开MSYS2 UCRT64的终端。

image.png

对MSYS2本身下载和安装的步骤均已完成,然而,为了完成编译,我们还需要为其配置GCC工具链(如MinGW-w64)。一般我们使用Pacman对其进行管理。具体方法如下:

首先,向刚刚打开的终端输入如下命令:

sed -i "s#https\?://mirror.msys2.org/#https://mirrors.tuna.tsinghua.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist*

这一步骤将Pacman的默认镜像地址替换为清华镜像源。然后再执行如下命令:

pacman -Sy

pacman -S --needed base-devel mingw-w64-ucrt-x86_64-toolchain

会出现如下提示:

image.png

直接回车。接下来会有Y/n确认:

image.png

输入Y,然后回车。就能看到工具链的安装:

image.png

安装需要2~3分钟时间。安装完成后,直接退出即可。

既然安装完成了,那么就可以使用了吗?未必!有可能出现了如下的情况:

image.png

问题出在哪呢?

如果看过[栖岸计划](回归学术区)LaTeX:从公式,到排版 - 质心论坛的第一章,应该很容易发现问题在哪里:没有完成环境变量的配置!

配置方法详见LaTeX的帖子,基本相同。路径:安装目录\ucrt64\bin。重启PowerShell,再输入命令,就可以找到:

image.png

1.2 VS Code的下载,安装与使用

下载和安装不算复杂。直接在网上搜索VS Code,在官网下载,然后在本地“下载”文件夹中找到安装包,双击安装即可。重点在于,以C语言为背景,我们如何使用它。

我们知道,要由C语言写的程序得到可执行文件,需要进行编译。编译的快捷键为Ctrl+Shift+B。然而,如果我们对新安装的VS Code直接进行操作,会出现这样的情况:

image.png

它无法帮你找到编译器,因此无法完成编译。为了让它找到我们刚刚下载安装的GCC工具链,我们需要安装拓展C/C++ Extension Pack。安装方法如下:

找到左侧一栏自上往下第六个图标(四个正方形,三个正放,一个斜放),在应用商店中搜索C/C++ Extension Pack:

image.png

直接点击最上面的拓展的“安装”键即可。完成后,再按Ctrl+Shift+B,我们看到的内容就和刚才不同:

image.png

点击最上面的一栏即可。效果如下:

image.png

注意左侧显示文件夹中已经多了一个文件test.exe。点击终端最下面一行文字下方,出现光标,按任意键即可退出完成编译的终端。

关闭后,终端页面会变成这个样子:

image.png

点击方框的位置,输入./可执行文件名称(例如,这里就是./test.exe),即可展示运行结果。

image.png

当然,还有另一种方法能直接完成从编译到运行的全部过程,即Ctrl+F5快捷键。(严格来说,这快捷键跟调试有一定关系,这点后面再详细说。)

我们回到编译前。点击Ctrl+F5,会出现如下对话框:

image.png

点击后,我们就可以直接在终端看到最终的运行结果。

image.png

注意左边的文件夹。不止多了一个可执行文件test.exe,还多了一个叫.vscode的子文件夹。展开子文件夹,里面只有一个文件tasks.json:

image.png

这个文件的一个作用是,此后无论再使用Ctrl+Shift+B还是Ctrl+F5进行编译/运行,都无需再向刚才一样在页面上方进行选择。这确实起到了简化的作用。


第二章 第一个C语言程序

刚才的图片中展示了这样一个完整的C语言程序:

#include <stdio.h>

int main() {

printf("Hello, world!\n");

return 0;

}

运行结果是在控制台打印了"Hello, world!"。与Python只用一句

print('Hello, world!')

即可实现相同功能不同,由于C语言写的程序有不少必要的组成部分,所以长度更长。下面我们来拆解这段代码。

2.1 预处理指令

我们看到第一行:

#include <stdio.h>

类似这种以#号开头的指令会在编译之前执行,作为编译的准备工作,因此被称为预处理指令。预处理指令主要分为以下几种:

2.1.1 头文件包含

即#include。后面跟着由<>或""包裹的文件名,表示导入在这些头文件中声明的函数、结构体,以及头文件中的宏定义等信息。在入门阶段,我们重点关注的是如何调用头文件中声明的函数。换句话说,如果我在某个头文件A.h中声称“你可以调用这个函数”,那么我在程序开头添加#include "A.h"这一语句,就可以调用上面这里提到的函数。

习惯上,在包含头文件时,标准库头文件用<>,自己写的头文件用"",这与头文件的搜索路径相关。前者有时也可以混用"",但后者绝大多数时候不能使用<>,后文会详细讲解。

(关于函数的声明、定义与调用,后文中会详细解释,这里先按下不表)

具体到上面这段代码,我们需要调用格式化输出函数printf。C语言没有内置的输入和输出,需要先包含头文件stdio.h(或者自己实现),才能从中调用scanf,printf等用于输入输出的函数。因此,上面程序的头文件包含指令是必要的。我们可以尝试,删除这一指令后,会发生什么:

image.png

报错信息显示,printf函数存在不兼容的隐式声明,建议包含stdio.h或者自行提供printf函数的声明。这也正是没有包含必要的头文件会带来的后果。由于输入和输出极为必要,绝大多数程序都会直接或间接包含stdio.h。

关于头文件与#include指令的更详细讲解,以及如何自己写头文件,远超本章的入门需求,将在后文中提到。

2.1.2 宏定义

即#define。原版代码中没有呈现,但我们稍作修改:

image.png

上面的#define STR "Hello, world!\n"指令告诉预编译器,需要将后面所有的STR替换为"Hello, world!\n"。于是后面的STR被替换为"Hello, world!\n",此时代码已与原版没有任何区别。从而最终仍然打印"Hello, world!"。

看起来,既然宏定义仅仅是对后面代码的直接替换,完全可以在手写时就在后面的代码使用替换的结果。实际上,#define对于编写和阅读代码都有很重要的作用,后文会详细提到。

需要注意的是,这里的STR看起来像一个单独变量,实际不是。由于宏定义仅仅是单纯的文本替换,我们完全可以写一个这样的程序:

image.png

是的,它通过了编译,且完全能正常输出。当然,没有任何一个正常人会这么写代码,这一极端的例子,不过是更具体地体现了“文本替换”的含义。

2.1.3 条件编译和其他指令

条件编译语句有很多种,包括#if, #else, #elif, #endif, #ifndef, #ifdef等。除此之外,预处理指令还有#error等,有不同的用途。这些对于初学者而言,难度较大,此处略去,在后文中都会出现。

2.2 main函数

在预处理指令之后,我们看到整个程序由这些部分构成:

int main() {

//此处有一系列的代码

return 0;

}

这里的int main()(或写作int main(void))被称为主函数(main函数),其作用是给运行程序提供入口。执行程序时,系统会调用main函数,完成main函数内部内容的运行。

一般在main函数的定义内部,最后需要以return 0;结尾。这里return后面跟的数表示函数的返回值,换句话说,就是函数运行的结果。main函数返回0表示程序正常运行,返回其他值表示程序异常。例如,我们对上面程序里main函数的返回值改为1:

image.png

看起来它还是正常输出了"Hello, world!",实际上控制台的红叉告诉我们,程序异常退出了。这就是main函数返回值不为0的结果。有的时候,我们会刻意把某些情况下main函数的返回值设为非零数,其目的往往是为了在程序出现异常时,能通过不同的返回值设置,找到出问题的地方。例如下面的程序:

image.png

这个程序比较复杂,所以这里语言解释其含义:从键盘输入一系列内容,如果输入的是整数,那么把这个整数打印出来;如果输入的不是整数,那么就报错。下面控制台显示的内容:当输入1,114514,-11等整数时,程序把这个整数打印了一遍;但输入的是abc时,程序报错。此时,我们就可以通过查看返回值,找到程序出错的地方。如果我们把这个return 1;删掉,让它正常返回,那么结果会变成这样:

image.png

输入的是整数的时候,看起来一切正常;但输入的是字符串时,程序正常返回,并输出了一个奇怪的整数。此时,我们再要去排查问题,难度就会更大。更何况实际进行编程时,往往涉及到许多文件,每个文件都可能有上百行,如果不进行这些设置,debug会成为一件极度令人痛苦的事。(当然,即便有这些设置,debug也很令人痛苦,但至少比这种都不知道错在哪要好得多)

(上面为什么会输出一个奇怪的整数?因为声明n时,其会被初始化为一个“垃圾值”。详细内容后面会再提到。)

main函数还有一些不标准的写法。在讲解之前先强调一遍:尽量不要用以下两种方法写main函数!

一般情况下,如果main函数的返回值为0,那么return 0;语句可以省略。例如,下面的程序也是正确的:

image.png

或者,有些时候main函数的返回值类型会被设为void(返回一个无类型变量,简单理解,就是没有返回值),而非int:

image.png

不过,有些编译器不支持第一种操作;第二种操作严格来说属于未定义行为,更多的编译器不允许这种行为,风险比第一种操作还要高。所以绝大多数情况下,我们还是会完整书写。

这里顺带提一下编程习惯的问题。有的时候,有些代码是能通过某些编译器编译,能正常运行的,但对于另外一些编译器,这些代码就会出错,存在风险。所以,在编程过程中,要尽量避免这样的行为!

收起
24
17
共2条回复
时间正序
用户头像
一条大章鱼
22天前

(水评致歉)

智英佬这个还更吗(期待ing

用户头像
白芥呀~
18天前
不敢笑,因为之前坚持用了好久的go……

印象里华为系统对于C做了类似hish的东西,可以在平板上学习使用,等周末看看是什么以及怎么使用再回来分享