
C 语言这门课我前前后后带过不少零基础学员也见过太多人买了一堆书、收藏一堆视频结果卡在最开始的概念上。第 1 讲直接讲“常见概念”其实就是先把那些绕不开的术语、逻辑和流程讲清楚让你知道“C 语言到底是什么”“程序是怎么跑起来的”“我该从哪里下手”。这一讲不会让你写出复杂的程序但能让你在后面每一讲都不迷路。这套笔记适合三类人完全没写过代码的纯小白学过 Python 但想补底层认知的开发者以及准备参加信息学竞赛或计算机等级考试、需要把 C 语言基础打扎实的学生。内容会尽量用生活化的类比把每个概念拆开揉碎同时配合真实的代码片段说明你可以照着敲一遍。1. 为什么现在还在学 C 语言1.1 C 语言到底解决什么问题很多人问我现在 Python、Java 这么火为什么还要学 C我的回答一直是C 语言是理解计算机的“窗口”。Python 用起来很舒服是因为它帮你做了大量底层工作但程序在内存里怎么存数据、函数怎么被调用、文件怎么读写这些都藏在 Python 解释器后面。C 语言不一样它让你直接面对内存、指针、数组这些真实存在的逻辑代码写出来是什么样机器大体上就按什么样执行。C 语言出生于上世纪 70 年代但到今天操作系统内核、嵌入式设备、数据库底层、游戏引擎、通信协议栈里依然到处是 C 语言的身影。想往底层开发、嵌入式、单片机方向走C 是绕不开的。哪怕你以后做上层应用懂 C 也能让你更清楚 Python 的列表为什么比数组灵活、Java 的垃圾回收到底在干什么。1.2 这套笔记怎么用这套“C 语言系统入门教程”定位是零基础学习笔记。每一讲都围绕一个主题展开第 1 讲先扫盲把常见概念串起来。我建议你跟着每一讲的思路走遇到代码就亲手敲一遍不要只看不练。学 C 语言没有捷径敲代码的量和理解程度基本成正比。2. 学习前的环境准备装好你的开发工具2.1 编译器怎么选C 语言是编译型语言写好的代码需要用编译器翻译成机器能执行的程序。经常有人问“C 语言一般用什么编程软件”其实核心是选一个合适的编译器和编辑器组合。对于零基础我的建议分两种情况Windows 环境下如果你不想折腾直接用 Dev-C 或者 Code::Blocks它们内置了 GCC 编译器下载安装就能写代码、编译、运行。Dev-C 界面老旧但对初学者完全够用学校机房很多也用它。如果你想用专业一点的编辑器推荐 Visual Studio Code 安装 C/C 插件再配 MinGW-w64 的 GCC 编译器。这种方式配置稍复杂但写代码体验更好适合愿意折腾的同学。Visual Studio 的 C 组件也可以体积大、功能全适合用 Windows 且不介意磁盘占用的人。我自己平时写示例用的比较多的是 VSCode GCC 的组合。实际演示时我通常用在线编译器比如 OnlineGDB快速验证逻辑但长期学习我还是建议你装一个本地环境因为在线平台对文件读写、调试的支持有限。提示环境装完先跑通一个 Hello World确认编译器能用别花太多时间在工具上。工具只是手段理解概念才是目的。2.2 从源码到可执行文件编译流程C 语言的源文件通常以.c结尾。比如你写了一个hello.c里面是简单几行代码。要让计算机执行它需要经过以下流程预处理处理以#开头的指令比如#include stdio.h就是告诉编译器把标准输入输出库的声明内容包含进来。编译把预处理后的代码翻译成汇编语言。汇编把汇编语言转换成机器码生成目标文件.o或.obj。链接把目标文件和库文件组合成最终的可执行文件Windows 下是.exe。理解这个流程有什么好处很多新手报错时看见“无法打开源文件”“未定义标识符”就慌了其实这些错误往往发生在编译阶段原因可能是头文件路径不对、函数名拼错、缺少链接库等。知道编译分几步你排查问题的时候就能更准确地判断错误出在哪个环节。3. C 语言核心概念梳理零基础必须搞懂的 10 个关键词3.1 main 函数程序的入口每一个 C 程序都有且只有一个main函数。程序运行时操作系统会调用它。看一个最简单的例子#include stdio.h int main() { printf(Hello, World!\n); return 0; }int main()表示这个函数返回整型return 0;表示向系统反馈“程序正常结束”。有些教材写void main()但在标准 C 中这是不规范的写法建议一律写int main()。有的同学会问为什么程序必须从 main 开始这是编译器和运行环境的约定main 就是 C 程序约定的入口地址。你写的其他函数都是被 main 直接或间接调用的。3.2 变量、常量与数据类型C 语言是静态类型语言意思是你用变量前必须声明它的类型。数据类型决定了变量占用多大内存、能够存放什么值。常见基础类型类型占位符典型大小64位系统含义int%d4 字节整数float%f4 字节单精度小数double%lf8 字节双精度小数char%c1 字节字符long long%lld8 字节更大的整数定义变量时你应该养成立即初始化的习惯int age 20; float score 89.5; char grade A;如果没有初始化变量的值是“不确定的”打印出来可能是一个随机数这个问题在后续开发中会经常坑到新手。常量用const关键字修饰表示这个值不能被修改const double PI 3.14159;3.3 输入输出函数printf 和 scanfC 语言没有内置的输入输出语句而是通过标准库函数完成。printf(格式串, 变量列表)是按格式输出。scanf(格式串, 变量)是从键盘输入注意变量名前面要加因为scanf需要知道把数据存到哪个地址。一个常用例子#include stdio.h int main() { int num; printf(请输入一个整数); scanf(%d, num); printf(你输入的是%d\n, num); return 0; }我第一次讲scanf的时候几乎每个同学都会漏掉。这里可以理解为scanf要修改你变量的值所以必须知道变量在内存中的地址就是取地址符号。如果忘了加程序会试图往一个不该写入的地方写数据轻则读出的值不对重则程序崩溃。3.4 运算符与优先级C 语言的运算符分为几类算术运算符 - * / %、赋值运算符、关系运算符 ! 、逻辑运算符 || !、位运算符 | ^ ~ 。新手很容易踩两个坑与的区别。是赋值是判断相等。在 if 语句里误写成if (a 1)程序不会报错但逻辑完全错误。运算符优先级记不住。建议多用括号不要靠记忆。比如a * b c和a * (b c)含义不同写得清楚比显得高级重要。位运算符在入门阶段可以先了解后续做算法或底层开发时再深入。比如左移一位相当于乘以 2右移一位相当于整除 2在竞赛题里常用位运算做快速乘除。3.5 流程控制if、switch、while、do-while、forC 语言是结构化编程语言三大结构是顺序、选择、循环。选择结构用 if-else 和 switch。写法if (score 60) { printf(及格\n); } else { printf(不及格\n); }循环有三种实现while、do-while、for。很多人纠结 while 和 do-while 的区别。一句话while 先判断后执行有可能一次都不执行do-while 先执行一次再判断至少执行一次。用一个场景说明int n 0; while (n 0) { printf(这句话不会输出); }同样 n 0换成 do-whiledo { printf(这句话至少输出一次); } while (n 0);for 循环常用于固定次数的遍历比如从 1 加到 100int sum 0; for (int i 1; i 100; i) { sum i; } printf(sum %d\n, sum);这里i表示循环变量每次加 1。for 的三个表达式分别负责初始化、条件判断、步进变化理解这一点就掌握了 for 循环的本质。3.6 函数写代码的积木函数把一段逻辑封装起来可以重复调用。C 程序本身就是由一个或多个函数组成的。函数有返回值类型、函数名、参数列表、函数体。int add(int a, int b) { return a b; } int main() { int result add(3, 5); printf(%d\n, result); return 0; }函数的参数和返回值都是“传值”的也就是说调用函数时传入的是值拷贝函数内部修改参数不会影响外部变量。如果想在函数里修改变量需要传入指针这在后面讲指针时会展开。3.7 数组同类型数据的集合数组是一段连续内存中存储的同类型元素集合。定义方式int scores[5] {90, 85, 78, 92, 88};数组下标从 0 开始scores[0]是第一个元素scores[4]是最后一个。这个设计让很多新手不适应但必须刻在脑子里长度为 n 的数组下标范围是 0 到 n-1。访问越界是 C 语言最常见的坑。C 语言不像 Java 那样检查数组边界你写scores[5]编译器不一定报错但会把相邻内存的数据也读出来结果完全不可预测。有人调侃这是 C 语言“相信程序员”的体现实际上你应该自己管理好边界。3.8 指针C 语言的核心难点指针是所有 C 语言学习者绕不开的关卡。简单理解指针就是存放内存地址的变量。int x 10; int *p x; // p 存放 x 的地址 printf(%d\n, *p); // 通过 *p 访问 x 的值这里是取地址*是解引用。可以把内存想象成一排编了号的柜子变量 x 存在某个柜子里p 存的是这个柜子的编号。通过编号能拿到柜子里的东西这就是指针的意义。为什么要这么麻烦因为有些场景不知道变量名只拿到地址有些场景需要在函数间共享一块内存有些场景需要动态分配内存。指针用好了程序灵活且高效用不好段错误Segmentation Fault就会频繁出现。初学者不要害怕指针把它当成“地址”来理解多画图辅助思维。比如int *p不是说 p 是“int 类型的指针”而是“p 是指向 int 类型变量的指针”可以从右往左读。3.9 字符串字符数组的特殊形式C 语言没有专门的字符串类型字符串是用字符数组或字符指针表示的。字符串以\0结尾这是字符串的结束标志。char str1[] hello; char *str2 world;str1是数组形式内容可以修改str2是指针形式指向一个字符串常量在部分环境下修改它会导致问题。比较字符串内容要用strcmp函数不能直接写str1 str2因为那样比较的是地址而不是内容。常用字符串函数包括strlen(s)计算字符串长度strcpy(dest, src)复制字符串strcmp(s1, s2)比较字符串内容strcat(dest, src)拼接字符串标题里提到的“字符串逆序 c 语言 pta”就是这类问题的典型练习题做法是双指针一个指向头一个指向尾交换字符后向中间靠拢。3.10 结构体把相关数据打包当几个数据项经常一起出现可以用结构体把它们组织起来。比如描述一个学生struct Student { char name[20]; int age; double score; }; int main() { struct Student s1; strcpy(s1.name, 张三); s1.age 18; s1.score 92.5; printf(%s %d %.2f\n, s1.name, s1.age, s1.score); return 0; }结构体是 C 语言实现复杂数据建模的基础。后面学链表、树、图的时候节点类型几乎都用结构体定义。比如竞赛题里的“物流网络”“环线”这类题目描述节点和边关系时经常用到结构体数组。3.11 文件读写与内存管理文件读取是 C 语言里非常实用的能力。核心函数是fopen、fprintf、fscanf、fclose。fprintf 和 printf 用法类似只是多了个文件指针参数FILE *fp fopen(data.txt, r); if (fp NULL) { printf(文件打开失败\n); return 1; } int a; fscanf(fp, %d, a); // 从文件里读一个整数 printf(%d\n, a); fclose(fp);注意fopen返回 NULL 表示打开失败一定要及时判断否则后面的操作会崩。动态内存管理使用malloc、free。这是 C 语言和高级语言差异最大的一环int *arr (int *)malloc(10 * sizeof(int)); if (arr NULL) { printf(分配失败\n); return 1; } // 使用 arr[0] 到 arr[9] free(arr);free之后指针要置空避免产生“野指针”。这块内容在后面的内存管理专题里会详细讲第 1 讲先知道“动态内存需要手动释放”就够了。4. 新手最容易踩的坑结合常见问题实录4.1 scanf 忘记加 导致程序异常我见过不止一个同学排错排了一下午最后发现是scanf(%d, num);少了一个。编译器有时候只给警告不报错导致你还能运行但输入的数据根本没写进变量。强烈建议一开始写 scanf 的时候就把当成固定搭配。4.2 数组越界却不报错数组越界是“静默错误”的典型代表。程序可能正常运行也可能崩溃全看你踩到了哪块内存。排查方法很简单写循环的时候检查下标范围加上打印语句验证。竞赛题里数组越界也是扣分重灾区GESP、CSP 这类考试往往用边界数据测试你的代码越界基本必挂。4.3 字符串比较直接用 这是新手在字符串处理中最常犯的错误。C 语言里字符串就是字符数组直接比较数组名实际上是在比较地址永远不可能相等。正确做法是用strcmpif (strcmp(str1, yes) 0) { printf(相等\n); }4.4 未初始化变量直接使用定义变量后如果没有赋初值它的内容是不确定的。有的编译器会把它自动设为 0但在很多环境下不会。稳妥起见声明变量时尽量同时初始化。4.5 中文输入法导致的符号错误写代码必须在英文输入法状态下输入括号、分号、引号。中文分号、中文引号会导致编译错误而且报错信息可能指错位置。更隐蔽的是中文感叹号在字符串里有时候能通过编译取决于编译器但语义完全不对。4.6 编译报错“无法打开源文件”怎么办这是 VSCode 或 Visual Studio 用户经常遇到的问题。可能的原因包括头文件路径没配置好编译器找不到stdio.h所在的库目录。源文件本身路径包含中文或特殊符号导致编译环境解析失败。编译器没有正确安装或者环境变量没配置。排查顺序先确认 GCC 或 cl 命令能否在终端里运行再检查 C/C 插件的编译器路径设置最后把源文件放到一个纯英文目录下试试。5. 课后练习建议把概念变成能力5.1 先做“读代码”练习很多零基础同学一上来就写代码发现写不出来就开始怀疑自己。其实学编程初期“读代码”是非常高效的练习方式。找一段不太长的示例代码逐行分析这一行声明了什么变量类型是什么这个函数接收什么参数返回什么这个循环会执行几次这段程序最终输出什么把这些问题在纸上回答一遍再上机运行验证。这个过程能极大地巩固语法和逻辑理解。5.2 从简单题目开始刷推荐练习内容从比赛入门题开始。比如“梦中的统计”这类基础题就是统计数字出现次数练习数组、循环、取模运算。GESP 三级、五级的题目也适合检验概念掌握程度。做这类题目的目的是练基本功而不是为了炫技。解题时按四个步骤走分析需求、设计数据结构、写出伪代码、翻译成 C 代码。以“春游”这类题为例考察的往往是组合逻辑和边界判断。拿到题先列出输入范围、输出条件再考虑用多重 if 还是 switch。这类题做多了你会慢慢习惯“先想清楚再写代码”的流程而不是打开编辑器就噼里啪啦打。5.3 调试技巧学会用打印和断点初学阶段不建议依赖复杂的调试器但最基本的调试思路要有。我会推荐一个“二分定位”法如果程序结果不对先判断是输入环节、计算环节还是输出环节出了问题。在关键变量处加printf打印逐步缩小排查范围。比如你发现求和的程序结果总是少个 1那就把每次循环的 i 和 sum 都打印出来一眼就能看出是循环边界写错还是累加逻辑写错。学会打印你就掌握了一半的调试能力。6. 给零基础同学的几句实在话这一讲的内容看起来多但不需要全记住。编程不是一个“背诵”的科目而是“逐渐熟悉”的过程。刚开始觉得混乱非常正常我自己当年第一次看指针也懵了很久。我的建议是把这一讲当作一张地图不用强行记住每一处细节。后面每一讲都会针对具体主题深入展开到时候再回来查阅概念效果更好。另外遇到不懂的术语就用搜索引擎查。比如洛谷、编程题单、PTA 这些平台上有大量练习题和题解很多入门问题都能在社区里找到讨论。自己动手解决一个报错比看十篇博客都有用。最后强调一点C 语言入门最忌讳“眼高手低”。看视频、看教程只看不练永远学不会。哪怕只是照着敲一遍再改几个参数也比干看强得多。下一讲开始我们会写真正的代码到时候你可以把这一讲的概念全部用起来。