ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C语言入门基础大全:语法、指针、数组与文件操作避坑指南

C语言入门基础大全:语法、指针、数组与文件操作避坑指南

简介:《C语言基础大全》是一份面向C语言初学者与入门复习者的PDF学习资料,聚焦基本语法、数据类型、运算符、控制结构、函数、指针及文件操作等核心知识,帮助读者建立从表达式、语句块到数组、结构体、指针应用的系统认知,也可作为课堂学习后的梳理材料与考前查漏补缺的速查手册。整包共1个PDF文件,大小约68KB,内容以条理化的知识点归纳为主,篇幅轻便,便于在电脑或移动设备上随时查阅。目前已有901人学习,说明其在C语言入门群体中具有一定参考价值。资料将七大模块按层次展开,既覆盖int、char、float、double等类型差异,也涉及if...else、switch、for、while等控制流程,以及函数定义调用、指针与数组关系、fopen/fclose、fscanf/fprintf等文件操作要点,适合希望用较短时间搭起C语言基础框架的读者反复翻阅。

1. 先别被吓退:C 语言到底是一门什么样的语言

如果你搜索过 C 语言是什么,多半会看到一堆这样的词:底层、高效、指针、内存。看完的第一反应往往是——这不像给新手准备的东西。带新人的时候我见过太多人卡在这一步,不是学不会,而是被名词提前吓退了。换个说法你可能就懂了:C 语言像一套最基础的工具箱,锤子、螺丝刀、扳手,每样都很朴素,可你后来用的几乎所有高级工具,拆开外壳里面都是这几样东西。C 语言入门之所以被反复推荐,是因为它把「程序到底怎么和数据打交道」这件事摊开给你看:变量放在哪、类型有多宽、地址怎么算。学会它,再看别的语言会快很多,因为你已经知道底下在发生什么。这份《C语言基础大全.pdf》覆盖的正是这条路径:基本语法、数据类型、运算符、控制结构、函数、指针、文件操作,一个入门者该知道的骨架都在里面。它适合完全没写过代码的人,也适合写过一点脚本、却说不清内存和指针的人。

2. 第一次跑通:五行代码在屏幕上打出一行字

这一章的目标很单纯:让你在半小时内看到一个属于自己的程序跑起来。跑通之前不要纠结原理,先把「写代码 → 编译 → 运行」这条流水线走一遍,走通了心里就有底,后面学什么都不会发怵。

2.1 先认识 C 程序的固定骨架

每个 C 程序外面都套着一个几乎不变的壳。这个壳由三部分组成:一行#include,它相当于给编译器递一张工具清单,告诉它「我后面要用到这些现成的功能」;一个叫main的函数,它是整个程序的入口,操作系统启动程序后第一件事就是找到它;以及函数体外面那对大括号,把属于main的所有内容圈在里面。

新手最容易忽略的是:C 语言没有「随便写写就能跑」的待遇。它需要先编译——把你写的文本翻译成机器能执行的程序,然后才能运行。所以你在编辑器里敲完代码,只是完成了第一步,还得敲一条命令去编译它。很多人第一次卡住不是代码写错,而是以为保存完文件程序就自动跑起来了。

编译器你可以理解成一个翻译官,它逐行读你的代码,语法有半点不对就拒绝翻译,并且告诉你第几行出了问题。它的报错有时确实不够友好,但入门阶段九成的报错都集中在两类:少了分号,或者括号没配对。先记住这一点,比记住任何语法都实用。

2.2 第一个例子:五行代码打印一句话

打开任意一个纯文本编辑器,新建文件hello.c(后缀必须是.c,别用 Word),把下面这段敲进去。敲的时候不要复制,手打一遍,手感很重要。

#include <stdio.h> /* 递工具清单:要用输入输出相关的功能 */ int main(void) /* 程序入口,操作系统从这里开始执行 */ { printf("Hello, C\n"); /* 往屏幕上打印一行字,\n 表示换行 */ return 0; /* 告诉系统:程序正常结束,返回 0 */ }

保存后,打开命令行,进到文件所在目录,执行下面两条命令:

gcc hello.c -o hello # 把 hello.c 编译成名为 hello 的可执行文件 ./hello # 运行当前目录下的 hello

屏幕上出现Hello, C就成功了。逐个拆开看:gcc是编译器的命令名;hello.c是源文件,告诉它翻译谁;-o hello里的-o是 output 的缩写,意思是「输出的可执行文件叫 hello」,不加这个参数的话,编译器会默认给你生成一个名字很别扭的文件;./表示「当前目录下」,不加的话系统可能去别的地方找这个程序,然后告诉你找不到。运行结果里的return 0不会显示在屏幕上,它是给操作系统看的「成功信号」,0 代表一切正常。

如果你的机器提示找不到编译器,那不是代码的问题,是环境还没装。入门阶段装好编译器是必须迈的一步,不同系统的安装方式不一样,去搜「你的系统 + 安装 C 编译器」就能找到对应步骤。这一步我建议你自己动手走一遍,别让别人替你装好,因为后面你会反复用到它。

2.3 语句、表达式、语句块:C 里最小的三个单位

C 的代码是由一条条语句堆出来的,语句最明显的标志是结尾那个分号。分号在 C 里不是标点符号,它是「这句话说完了」的硬性标记,少一个编译器就不认。允许你把多条语句写在同一行,也允许一条语句折成好几行写,但入门阶段不建议这么干,一行一条语句,出问题时看行号能立刻定位。

表达式是另一回事。表达式是能算出一个值的东西,比如1 + 2、a * b、x > y,它们都有结果。语句通常包含表达式,但语句是一个完整动作,表达式只是一段计算。理解这个区别,后面看到「把表达式的值赋给变量」这类说法就不会晕。

语句块就是一对大括号包起来的一堆语句,也叫复合语句。它的作用是「把几条语句当成一条来用」,后面讲 if 和循环的时候你会天天见到它。缩进不是语法要求,编译器根本不看你的缩进,但人要看的,四个空格或者一个 Tab 都行,从头养成习惯,比后面改省事得多。

2.4 注释和空格:写给未来的自己

注释是给人和自己看的,编译器会直接跳过。C 提供两种写法://管到行尾,适合写一句简短的说明;/* ... */可以跨多行,适合在函数上方解释一段逻辑。有一条容易被忽略的规则:多行注释不能嵌套,你在/* */里面再写一个/*,编译器会从第一个*/处结束,后面的内容就被当成代码了,报错会让你莫名其妙。踩过一次这个坑,之后就记住了。

空格和换行在 C 里几乎都是自由的,除了字符串内部和关键字之间,多一个少一个不影响运行。但别因此就写成一行密密麻麻的代码,读代码的时间远多于写代码的时间,尤其是过两周回头看自己写的东西。我一般的习惯是:运算符两边留空格,逗号后面留空格,每个大括号另起一行,这不是强迫症,是给自己留后悔药。

提示:编译报错时先看最上面那一条,编译器后面的报错常常是被第一条带偏的。改完第一条再重新编译一次,别一口气改十行。

3. 数据类型、运算符和控制结构:把「算」和「选」写对

语法骨架搭起来之后,真正决定程序对不对的是这几件事:数据用什么类型装、算的时候会不会丢精度、该走哪个分支。这一章是 C 语言入门里最「磨人」的部分,但也是最值回票价的部分,很多人后面写出的玄学 bug,根子都在这。

3.1 四个基本类型:先记住它们各占几个字节

C 里的每个变量都得先声明类型,这件事不像脚本语言那样能省。原因很直白:编译器要提前知道给你留多大一块内存。入门阶段先盯住四个基本类型就够了。

类型常见字节数装什么举例
int4整数int age = 18;
char1单个字符char c = 'A';
float4单精度小数float f = 3.14f;
double8双精度小数double d = 3.14159;

字节数是「常见大小」,不是铁律,不同平台可能有差异,入门阶段按这张表理解就行。char表面装字符,本质装的是一个小整数,字符和它对应的编号之间可以互相转,这一点后面处理字符串时会反复用到。float精度低、double精度高,需要精确一点的计算优先用double,除非你在意存储空间。

除了这四个,还有构造类型:数组装一串同类型的数据,结构体把不同类型的数据打包成一个整体,共用体让几个成员共享同一块内存,枚举给一组整数常量起名字。指针类型和空类型void也在这个体系里,前者放到下一章专门说。刚上手不用急着一次吃透,先把 int、char、double 用熟。

3.2 运算符:除法和取余最容易出错

运算符分几类:算术的+ - * / %和自增自减++ --;赋值的=;关系的< <= > >= == !=;逻辑的&& || !;还有一个三目运算符条件 ? 值1 : 值2。名字听着多,实际写起来就那么几个组合。

真正的坑在除法。C 里两个整数相除,结果还是整数,小数部分直接被丢掉,不四舍五入,是直接砍掉。看下面这段:

#include <stdio.h> int main(void) { int a = 7, b = 2; printf("%d\n", a / b); /* 整数除法,结果是 3,不是 3.5 */ printf("%d\n", a % b); /* 取余,7 除以 2 余 1 */ printf("%.2f\n", 7.0 / 2); /* 有一个是小数,结果才是 3.50 */ return 0; }

%d是整数的占位符,%.2f表示保留两位小数输出浮点数,\n是换行。第一行输出3,因为它俩都是 int,编译器按整数规则算;第三行输出3.50,因为7.0是小数,整个表达式被提升成浮点运算。想让两个整数算出小数,就把其中一个改成小数,或者做一次强制转换,写成(double)a / b。

还有两个高频失误。一个是把==写成=,if (a = 1)不会报错,它会把 1 赋给 a,然后判断这个赋值结果是不是真,结果永远是真,逻辑直接歪掉。另一个是浮点数用==比较,小数在内存里本来就存不准,两个「应该相等」的小数可能差一点点,正确做法是看它们的差是否小于一个很小的数。这两条吃过亏之后基本就长记性了。

3.3 选择结构:if 和 switch 分别在什么时候用

程序要会拐弯,靠的就是 if 和 switch。if...else适合范围判断和条件组合,switch适合「一个变量有若干个固定取值,各自走不同分支」的场景。

#include <stdio.h> int main(void) { int score = 86; if (score >= 90) { printf("优秀\n"); } else if (score >= 60) { printf("及格\n"); /* 86 落在这个区间,走这里 */ } else { printf("不及格\n"); } int level = 2; switch (level) { case 1: printf("初级\n"); break; /* 忘了 break 会继续往下执行 */ case 2: printf("中级\n"); break; default: printf("未定义\n"); } return 0; }

if 的判断从前往后,第一个成立的分支执行完就跳出整条链,所以条件的顺序有讲究:范围大的判断如果放前面,后面的分支永远轮不到。switch 的每个 case 结尾要写break,不写的话程序会「穿透」到下一个 case 继续执行,输出会多出好几行,这个现象我第一次见的时候愣了半天,以为是编译器坏了。

if 能做的事 switch 不一定能做,反过来也成立。判断条件是区间、或者要同时满足多个条件,用 if;判断的是几个确定的离散值,用 switch 读起来更清楚。入门阶段写错分支不要紧,先把两种都写几遍,形成肌肉记忆。

3.4 循环结构:for 管次数,while 管条件

重复的活交给循环。for适合「我知道要循环几次」,while适合「我不知道几次,但知道什么时候停」,do...while的特点是至少执行一次,再判断要不要继续。

#include <stdio.h> int main(void) { int sum = 0; for (int i = 1; i <= 100; i++) { /* i 从 1 走到 100,共 100 次 */ sum += i; /* 等价于 sum = sum + i */ } printf("1 到 100 的和是 %d\n", sum); int n = 0; while (n < 3) { /* 条件成立就继续,不成立就退出 */ printf("第 %d 次\n", n + 1); n++; } return 0; }

for括号里三段用分号隔开:初始化、循环条件、每轮结束后的动作。i++是i = i + 1的简写,写在后面表示先用再加。sum += i同理,是一种省略写法,读代码时把它展开成完整形式就不会看错。

循环最经典的翻车是死循环:条件永远成立,程序卡在那儿不动,只能强行关掉。判断条件里的变量没更新、或者更新方向搞反了,都会造成这个结果。另一个坑是循环变量的边界,i <= 100和i < 100差一次,差这一次结果就全错。写完循环,拿几个小数字在心里跑一遍,比盯着屏幕看管用。

注意:for里声明的循环变量,出了循环就失效了。后面还要用这个值的话,把它声明在循环外面。

4. 函数、指针和文件:把代码装进盒子,把结果存成文件

前三章让你的程序会算、会选、会重复。这一章解决三个更进一步的问题:代码怎么复用、数据在内存里到底住哪儿、结果怎么留下来。指针被很多人当成 C 语言的分水岭,其实它没那么玄,换个说法就好懂了。

4.1 函数:把一段代码装进一个盒子里

函数就是给一段代码起个名字,需要的时候喊一声。好处有两个:不用重复写同样的逻辑,改动的时候只改一处。一个函数由四样东西构成:返回类型、函数名、形参列表、函数体。

#include <stdio.h> int add(int a, int b) /* 返回 int,名字叫 add,收两个 int */ { return a + b; /* 把结果交回给调用它的人 */ } void say_hi(void) /* 不返回任何值,参数列表为空 */ { printf("hi\n"); } int main(void) { int r = add(3, 5); /* 调用:实参 3 和 5 传给形参 a 和 b */ printf("%d\n", r); say_hi(); return 0; }

形参是函数定义里的「占位的名字」,实参是调用时真正传进去的值。返回类型写void就表示这个函数不返回东西,只干活。函数必须先声明或者先定义再使用,编译器是从上往下读的,你把函数写在 main 后面又没提前打招呼,它就会报找不到这个函数。

准确地说,实参传给形参传的是值,函数里改形参不会影响外面的实参。这一点新手很容易误会,写个函数想把变量改掉,结果外面发现没变。要真的改到外面,就得用指针,这正是下面要说的事。

4.2 指针:变量在家里的门牌号

变量住在内存里,每块地方都有地址。指针就是专门用来存地址的变量——它不是房子,是写着门牌号的那张纸条。两个符号必须分清:&取地址,*顺着地址取内容。

#include <stdio.h> int main(void) { int a = 10; int *p = &a; /* p 存的是 a 的地址,类型是 int 指针 */ printf("%d\n", a); /* 直接读:10 */ printf("%d\n", *p); /* 顺着 p 找到 a,读出来也是 10 */ *p = 20; /* 顺着 p 找到 a,把里面改成 20 */ printf("%d\n", a); /* 此时 a 变成 20 */ return 0; }

int *p里的星号是声明的一部分,表示「p 是个指针,指的东西是 int」;用的时候写*p,意思是「p 指向的那个位置上的值」。同样是星号,位置和语境不同,含义就不同,这是新手最容易被绕晕的地方。判断方法很简单:声明里的星号贴在类型或变量前,是类型的一部分;表达式里的星号是「取内容」的动作。

指针之所以让人翻车,是因为它太自由了:你可以声明一个指针却不让它指向任何东西,然后就去读它。这种没初始化的指针里存的是随机地址,读它程序可能立刻崩,也可能悄悄改掉别的变量。血泪经验是:声明指针的时候顺手初始化,暂时不知道指向谁就先给个空值,用之前确认它有效。还有一个后果是悬空指针——它指向的内存已经被释放,但指针还留着那个地址,这时候再动它就是踩雷。

4.3 指针和数组:同一条街上的门牌

数组是一排连续住着的房间,数组名在大多数场合会自动变成第一个房间的地址。所以指针和数组有天然的联系:a[i]和*(a + i)是等价的,a + i就是往后数第 i 个位置的地址。

#include <stdio.h> int main(void) { int a[5] = {10, 20, 30, 40, 50}; int *p = a; /* 等价于 &a[0],指向首元素 */ printf("%d\n", a[2]); /* 30 */ printf("%d\n", *(p + 2)); /* 也是 30,地址往后挪两个元素 */ for (int i = 0; i < 5; i++) { printf("%d ", *(p + i)); /* 遍历一遍 */ } return 0; }

数组元素在内存里是紧挨着的,所以「往下挪几个位置」就等于「取第几个元素」。这也是为什么指针能配合数组做遍历、做字符串处理,C 里字符串本质就是一个以特殊结束符收尾的字符数组,用指针走一遍就是常规操作。

需要提醒的是:指针能挪,不代表能随便挪出边界。C 不会帮你检查越界,你写a[5]命中的是数组外面的位置,读出来是垃圾值,写进去可能改坏别的变量,而且可能当场不报错。等你发现某个完全无关的变量值莫名其妙变了,回头查原因能耗掉一整天。写循环遍历数组时,条件一定盯紧。

4.4 文件操作:把结果存成文件

程序里的数据默认只在运行期间活着,关掉就没了。要留下来就得写文件。C 的标准做法是三步:打开、读写、关闭。

#include <stdio.h> int main(void) { FILE *fp = fopen("score.txt", "w"); /* "w" 是写入模式,文件不存在会新建 */ if (fp == NULL) { /* 打开失败一定检查,比如没权限 */ printf("打开失败\n"); return 1; } fprintf(fp, "张三 %d\n", 86); /* 像 printf 一样往文件里写 */ fclose(fp); /* 用完必须关,不关可能丢数据 */ char name[32]; int score; fp = fopen("score.txt", "r"); /* "r" 是读取模式 */ if (fp != NULL) { fscanf(fp, "%s %d", name, &score); /* 注意 score 前面要加 & */ printf("%s %d\n", name, score); fclose(fp); } return 0; }

fopen的第二个参数决定行为:"w"写入,文件已存在会被清空;"a"追加,写在末尾;"r"读取,文件不存在就返回空。fprintf和printf用法几乎一样,只是多了一个「写到哪」的参数;fscanf和scanf同理,读的时候非字符数组的变量前面要加&。fseek和ftell用来在文件里跳位置和查当前位置,等你要随机读某一行再深究。

关闭文件这件事不要偷懒。程序正常结束通常会替你收尾,但如果中途崩溃或者你忘了关,缓冲区里的内容可能根本没落到磁盘上,检查半天以为代码写错了,其实只是没关文件。养成「开一个就配一个关」的习惯,比什么都省事。

5. 新手避坑清单:这 5 个错误几乎人人都要犯一遍

下面这五条是我在带人时见得最多的,每一个都写得出手的表现:它不抽象,你遇上时能立刻对上号。

5.1 现象:编译直接报错,提示少了分号或括号

现象很直观,编译器告诉你第几行有问题,程序根本没生成。原因通常有两个:一是真的漏了分号,二是分号用了中文输入法打出来的全角符号,看着一模一样,编译器不认。括号不配对也有同样的迷惑性,少一个}的时候,报错常常指向文件末尾,让你以为错在最后一行。

解决办法:看到这类报错,先看提示的那一行和它的上一行,八成短短一行里就藏着问题;再检查输入法是不是切回了英文。养成一个习惯——写完一个大括号立刻补上配对的那一个,再往里填内容,比写完再回头找快得多。

5.2 现象:程序跑起来了,但算出来的平均分永远是整数

这种问题最气人,程序不报错,结果就是不对。比如总分 259、人数 3,你期望 86.33,屏幕上显示 86。原因就是两个 int 相除,小数部分被直接砍掉,而且不四舍五入。这不是编译器抽风,是 C 的整数除法规则。

解决办法:参与除法运算的至少有一个是小数,或者把被除数强制转成浮点,例如(double)total / count;输出时用%.2f控制小数位。改完再拿一组除不尽的数据验一遍,别拿 100 除以 4 这种自带整数的例子验证,验不出问题。

5.3 现象:数组明明没写错,别的变量却莫名其妙变了

程序跑着跑着,某个完全无关的变量值变了,或者循环突然提前结束,甚至有时候换个编译器结果又不一样。这种玄学现象多半是数组越界:你访问了a[10],但数组只有 10 个元素,下标最大是 9。C 不会给你任何提示,它老老实实按你给的地址读写,写进去的东西把隔壁变量覆盖了。

解决办法:写循环时把条件反复念一遍,i < n还是i <= n差一次;数组长度用常量存起来,循环里引用这个常量,别到处写死数字。真遇到这种玄学,先把每个数组的访问范围打印出来,比盯着代码猜快。

5.4 现象:程序运行时直接崩掉,或者读到一串乱七八糟的值

指针相关的崩溃往往没有编译错误,运行时才炸。常见原因是:声明了指针却没初始化就使用;指针指向的变量已经失效了还在用;函数里想改外面的变量却传了值而不是地址。

解决办法:声明时立刻初始化,不确定就先给空值;用之前判断一下;要改外部变量就把地址传进去,函数形参写成指针类型,调用时加&。这条我第一次学的时候也不信邪,硬写了一次,结果花了半个晚上才定位到一行没初始化的声明。

5.5 现象:输入数据的时候程序卡住,或者读进来的数是个天文数字

用scanf读整数,程序好像在等你输入,输入完却输出一个巨大的数字。原因基本只有一个:变量前面忘了加&。scanf需要知道把数据放到哪个地址,你只给了它名字,它拿到的是一个值。

解决办法:除了字符数组名本身已经是地址、不用加&之外,其他变量都加上。scanf的格式串里%d对应 int,%f对应 float,%lf对应 double,写错也会读出垃圾值。这类问题看输出就知道——数字大得离谱,先检查&。

6. 学完之后往哪走:三条能自己往下走的路

基础语法过一遍只是拿到了入场券,接下来最怕的是「不知道下一步干什么」,于是原地打转,把同一份入门材料翻了三遍。我给新手的建议是按三条路并行往前走,别一次只走一条。

第一条路,把数组和字符串吃透。它们是 C 里出现频率最高的东西,字符串本质是字符数组,配合指针能做很多事。练习方式很土但有效:自己写一个求字符串长度的函数、一个把字符串倒过来的函数,不许用现成的库函数,写完再对照库函数看看人家怎么处理的边界情况。

第二条路,啃结构体和内存布局。结构体把不同类型的数据打包成一个整体,是后面写稍大一点程序的必需品。你可以试着定义一本书的结构体,包含书名、价格、页数,然后用结构体数组存十本书,写排序和查找。这一步走通,你对「数据怎么组织」的感觉会明显不一样。

第三条路,练调试而不是练语法。新手最大的差距不在会不会写,而在出错之后能不能自己找到位置。学会看编译器的报错行号,学会在关键位置打印变量的值,学会把一个大函数拆成几个小函数分别验证——这三件事比多背十个语法点有用。

下面这张表可以当作未来一个月的行动清单:

阶段目标具体做法
第 1 周语法顺手每天写 3 个小程序,输入输出 + 循环 + 判断
第 2 周函数和数组把第一周的代码重构,拆成函数,用数组存数据
第 3 周指针和结构体用结构体数组做一个小型的成绩统计程序
第 4 周文件和小项目把统计结果写进文件,再读回来验证

不要一上来就挑大项目,几十行的小程序反复写,收益比一个卡三天的大项目高得多。

我第一次学指针的时候,写完一个交换两个变量的函数,运行完发现外面的值压根没变,反复检查语法也没看出问题,最后才明白传进去的是值的副本。从那以后我每次写完一个函数,都强制自己单独跑一遍,先确认它对,再放进主程序里,绝不把没验证过的代码拼进去。这个习惯省下的时间,比任何技巧都多。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进