简介《C语言基础全》是一份面向C语言初学者与需夯实基础的程序员的PDF资料系统梳理了程序设计与C语言核心语法。内容从计算机如何执行程序讲起清晰对比编译与解释两种方式的优劣并梳理了从KR到ANSI C、C95、C99的标准演化在变量与输入部分重点剖析了scanf函数的返回值含义帮助读者避开输入格式化中的常见误区表达式与运算符章节则聚焦优先级、复合赋值与递增递减等易混淆点配合计算最大公约数、时间差换算、英制单位转换等经典实例帮助理解。资源包仅含1个PDF文件压缩后大小约1.57MB篇幅紧凑兼顾通读与查漏。目前已有13508人学习浏览适合作为高校C语言课程或自学的配套材料尤其适合刚接触编程、想迅速建立语法框架与程序设计思路的读者。1. C语言基础这份PDF能帮你什么从最大公约数看懂核心脉络拿到这份《C语言基础全.pdf》你可能会以为它又是一本从头讲语法的手册。翻完一遍发现它的定位不太一样这是一份按学习周推进的教学讲义一共六个周次从程序设计与C语言起源讲起依次把计算、判断、循环、循环控制、数据类型这几大块依次铺开。最大的特点是每个知识点后面都跟着可以直接编译运行的代码实例最大公约数、猜数游戏、整数逆序这些练习被反复用在不同的语法阶段讲同一道题的不同写法。对于正在上C语言课但总感觉「老师讲的我都会一写就错」的初学者或者准备期末复习但知识点零散的人这份资料能帮你把散落的知识点按一个合理的学习顺序串起来。全文不像手册那样干燥讲课的口语化痕迹还在读起来比大部头教材轻松不少。2. 表达式与运算符计算顺序、优先级和类型自动提升2.1 赋值也是运算左值右值别理解错C语言里赋值符号不是命令而是运算符。这意味着a 6本身是有值的它的结果就是被赋予的值6。正因为如此你才能写出a b 6这种链式赋值它等价于a (b 6)先给b赋值6再把6赋给a。很多教材把这个知识点一句话带过但这份讲义用一个特殊例子点透了它int a 6; int b; int c 1 (b a);这段代码里(b a)是一个完整的赋值表达式它的值是a的值也就是6所以c最终得到7。从这里也能看出C的赋值运算符优先级极低只比逗号运算符高几乎排在最后。实际写代码时我不太建议把赋值嵌进复杂表达式里讲义里也专门提到了「嵌入式赋值」不利于阅读、容易出错。理论懂了就行工程上别这么写。2.2 整数除法、浮点提升和时间差计算C语言里整数和整数相除会直接截断小数部分这和数学里的除法有本质区别。但当你把浮点数和整数混在一起运算时整数会自动转换成浮点数。讲义里的英制身高转换代码是理解这个特性最好的入口#include stdio.h int main(int argc, char const *argv[]) { printf(请分别输入身高的英尺和英寸 如输入\5 7\表示5英尺7英寸); int foot; int inch; scanf(%d %d, foot, inch); printf(身高是%f米。\n, ((foot inch/12.0) * 0.3048)); return 0; }如果这里写成inch/12而不是inch/12.0当inch小于12时结果直接就是0整个身高计算就全错了。关键就在12.0这个浮点字面量它把整个表达式提升为浮点运算。同理计算时间差时先把小时和分钟统一换算成「分钟总数」再做差最后用/60和%60拆回小时和分钟int t1 hour1*60 minute1; int t2 hour2*60 minute2; int t t2 - t1; printf(时间差是%d小时%d分。\n, t/60, t%60);注意这里t/60是整数除法取商t%60是取余数。这种先放大再缩小的方法在底层驱动开发里经常用来避免浮点误差。2.3 运算符优先级表与自增自减的前后缀差异讲义里有一张很完整的优先级表从高到低大概是括号、单目运算符、乘除取余、加减、关系运算、相等判断、逻辑与、逻辑或、赋值。掌握它的核心规律是算术运算高于关系运算关系运算高于逻辑运算赋值运算几乎垫底。有个常见误用是if (a b)这种写法因为赋值表达式也有值当b非零时a被赋值为b且条件判定为真编译器不报错但逻辑完全错了。自增自减运算符和--的前后缀差异是另一个高频考点int count 5; int result1 count; // result1得到5count变成6 int result2 count; // result2得到7count变成7后缀形式先返回原来的值再做自增前缀形式先自增再返回新值。这个差异在循环里控制下标时尤其容易踩坑。讲义还讲了一个背景当年PDP-11机器上有INC和DEC专用指令C编译器遇到和--能直接编译成这两条指令提速。现在编译器优化能力强了写a a 1也会生成相同指令保留下自增自减纯粹是习惯和代码简洁考虑。3. 判断与循环代码组织从if到break的尺度控制3.1 关系运算结果只有0和1单一出口原则关系运算 !的运算结果只有0或1这个设计贯穿整个C语言。判断语句if (条件)只关心括号里的值是零还是非零非零就执行。所以if (a b)能通过编译因为它是一个合法表达式值为b的原始值b为0时条件为假b非零时条件为真。讲义特别强调了和的混用问题建议在判断语句里把常量写在左边if (5 x) { ... }这样万一少写一个等号变成if (5 x)编译器会直接报错因为不能给常量赋值。关于if-else的结构组织讲义提出了「单一出口」原则。对比两种写法// 非单一出口 if(total amount) total amount 10; // 单一出口 int f; if(x 0) f -1; else if(x 0) f 0; else f 2*x; printf(%d, f);单一出口的核心思路是在分支里只做变量赋值所有printf放在整个判断结构结束之后。这样代码的逻辑路径清晰调试时只需要在出口处打一个断点就能看到所有分支产生的最终结果。后续如果要在输出前加格式处理也不必去改动每个分支内部。3.2 switch-case的贯穿行为要用break刹住switch-case在讲义里被定义为「基于计算的跳转」计算控制表达式后程序直接跳到匹配的case标号处然后顺序往下执行。这个「顺序往下」就是最容易出问题的地方因为没有break就会贯穿执行后面的所有case直到遇到break或switch结束。看这个例子switch (type) { case 1: case 2: printf(你好\n); break; case 3: printf(晚上好\n); case 4: printf(再见\n); break; default: printf(啊什么啊\n); break; }当type为1或2时都会执行「你好」因为case 1没有break直接贯穿到case 2的执行体里。当type为3时不仅会输出「晚上好」还会继续贯穿到case 4输出「再见」。所以有两种常见用法一是故意利用贯穿让多个case共享同一段代码二是每个case独立逻辑时必须写break。另外要注意控制表达式只能是整数类型的结果case后面跟的必须是常量C99开始支持const常量表达式。3.3 三种循环的选型逻辑与整数分解的循环写法讲义在循环部分给出了非常实用的选型建议有固定次数用for循环必须执行一次用do-while其他情况用whilefor循环本质上是一个计数循环适合明确知道循环次数或边界值的场景while循环适合不知道要循环多少次、需要靠条件退出场景do-while保证至少执行一次循环体适合「先做再判断」的交互流程比如猜数游戏里必须先让用户猜一次才能判断对错。整数分解和逆序是循环应用的经典练习。分解整数靠两个操作%10取个位/10去掉个位。正序输出则需要先算出数字的位数用mask来控制输出int mask 1; int t x; while (t 9) { t / 10; mask * 10; } printf(x%d, mask%d\n, x, mask); do { int d x / mask; printf(%d, d); if (mask 9) { printf( ); } x % mask; mask / 10; } while (mask 0);第一段while循环计算出最高位的权重mask比如x是456时mask得到100。然后do-while循环里每次用x/mask取出最高位、用x%mask去掉最高位mask再缩小十倍处理下一位。逆序输出则更简单每次取个位数逐步乘10累加int ret 0; while (x 0) { digit x % 10; ret ret * 10 digit; x / 10; }这段代码对x700时输出7而不是007因为数字7被当作数值而不是字符串处理。如果要求以007格式输出得用printf的格式控制这是数值和字符串处理的本质区别。3.4 多重循环的break只跳一层goto是最后的后悔药讲义里给出了一个凑硬币的三重循环例子需求是用1角、2角和5角硬币凑出指定金额。最外层循环控制1角数量中间层控制2角数量内层控制5角数量当找到第一组可行解时退出所有循环。这里有个关键的坑break只能跳出最内层循环不能一次跳出三层。讲义给了两种解法一种是定义退出标志变量逐层检查int exit 0; for (one1; onex*10; one) { for (two1; twox*10/2; two) { for (five1; fivex*10/5; five) { if (onetwo*2five*5 x*10) { printf(可以用%d个1角加%d个2角加%d个5角得到%d元\n, one, two, five, x); exit 1; break; } } if (exit1) break; } if (exit1) break; }每个循环结束之后都检查exit状态内层发现解后设置标记逐层向上退出。另一种更直接的方式是使用gotofor (one1; onex*10; one) { for (two1; twox*10/2; two) { for (five1; fivex*10/5; five) { if (onetwo*2five*5 x*10) { printf(可以用%d个1角加%d个2角加%d个5角得到%d元\n, one, two, five, x); goto out; } } } } out: return 0;goto直接跳转到标签out处省去了三层if检查。很多人听到goto就紧张但在这类多重循环提前退出的场景里goto比标志位方案更清晰、更不容易出错。使用它的原则是只能向下跳、跳到统一的出口处、不在代码间随意跳转。课堂教学不鼓励但在底层驱动和内核代码里这是常规操作。4. 常见问题排查变量定义、scanf返回值和循环控制语句的坑4.1 变量未初始化就参与运算结果是随机值现象程序每次运行输出的结果都不一样有时候是0有时候是一个很大的数换编译器结果还会变。原因局部变量如果没有初始化它内存里的值是之前遗留的随机内容。C语言不会自动帮你清零局部变量C89标准要求在代码块开头集中定义变量C99允许使用前定义但都不做隐式初始化。解决所有变量在第一次使用前必须初始化。讲义原话是「所有的变量在第一次被使用之前应该有过初始化」。读到scanf输入的值之前先给变量赋一个明确的初始值或者声明的同时直接初始化int x 0; int n 0;4.2 scanf返回值被忽略输入不匹配时程序黑匣子化现象用户输入了非数字字符程序不报错但变量取到随机值或者保持原值后续逻辑全部跑偏。原因scanf是一个有返回值的函数它返回成功读入的参数个数。输入3,4给scanf(%d %d, a, b)时第一个%d成功读入3逗号匹配失败读入失败返回值是1而不是2。很多代码从不检查这个返回值导致部分变量处于未定义状态。解决养成检查scanf返回值的习惯int a, b; int result scanf(%d %d, a, b); if (result 2) { // 两个变量都读入成功 } else if (result 0) { // 输入不匹配提示用户重新输入 } else if (result EOF) { // 输入流结束Windows下CTRLZLinux下CTRLD }还有一点需要注意用%d读入整数时scanf会跳过空白字符但用%c读字符不会跳过空白。如果先读整数再读字符换行符会留在缓冲区里被%c读走这一点在字符处理章节里特别值得留意。4.3 浮点数直接比较相等f1 f2永远返回0现象两个float变量做加法后判断是否等于某个预期值结果printf输出看起来相等但if判断永远不成立。原因浮点数在计算机里是二进制近似表示1.345f和1.123f相加的二进制结果和2.468的二进制表示并不相等。讲义里专门写了这个例子float a 1.345f; float b 1.123f; float c a b; if (c 2.468) printf(相等\n); else printf(不相等c%.10f或%f\n, c, c);解决判断两个浮点数是否相等时应该用差的绝对值是否小于一个极小值if (fabs(c - 2.468) 1e-12) { printf(相等\n); }fabs是绝对值函数1e-12是允许的误差范围。涉及货币或精密计算的场景讲义明确指出用整数代替浮点数分和角都用整数存储避免精度问题。4.4 for循环控制变量在循环外访问编译直接报错现象在for循环结束后想读取循环控制变量i的值代码编译失败提示i未定义。原因把变量定义写在for语句里只在C99及之后的标准中有效而且这个变量的作用域仅限于for循环内部。如果在循环外还需要这个值应该把变量定义在for之前。解决int i; // 定义在外部循环结束后仍可访问 for (i 1; i n; i) { fact * i; } printf(i%d\n, i); // 可以访问如果项目还在用ANSI C标准则必须在代码块开头集中定义所有变量。这份资料的代码同时兼容两种风格你自己写时要注意编译环境支持哪个标准。4.5 整数溢出和char类型的取值范围陷阱现象unsigned char c 255; c c 1;结果变成0或者char类型变量赋值为128后打印结果是负数。原因char类型在多数编译器上是signed char范围是-128到127128超出表达范围会溢出。整数类型范围是固定的char占1字节、short占2字节、int通常占4字节、long long占8字节。解决明确知道数据范围再选类型。无符号类型unsigned不是为了扩大范围而是为了做纯二进制运算尤其是移位操作。如果需要表达的数值超过当前类型的范围升级到更大的类型不要做溢出依赖。5. 数据类型进阶整数、浮点和字符的边界处理与工程选型5.1 整数的位宽和字长选择不是越短越快C语言的整数类型有个反直觉的事实在现代64位CPU上用char或short做计算不一定比int快甚至可能更慢。原因是CPU的字长是32位或64位一次内存读写就能拿一个int一次整数运算也是按int设计的。用更短的类型内存带宽不一定节省反而可能需要额外的符号扩展指令。所以讲义给出的结论是没有特殊需要就选择int。只有在明确要表达二进制协议头、控制内存布局时才用int8_t、int16_t这类定长类型。类型选择时还有一个陷阱在scanf和printf的格式化符号上short类型输入要用%hdlong要用%ldlong long要用%lld。printf里任何小于int的类型都会被自动提升为intfloat会被提升为double而scanf不会它需要你传指针格式符必须精确匹配变量的真实类型。这一点是C语言类型系统里最容易翻车的地方。5.2 浮点类型统一用double别被float的字节数吸引讲义对浮点类型的建议非常确定没有特殊需要只使用double。原因有三层现代CPU对double做硬件运算性能和float没什么差别64位机器上double的数据存取速度不比float慢double有15位有效数字float只有7位累积误差更小。算钱的时候用整数计算器不用浮点计算这是工程上的一条硬规矩。浮点数还有两个特殊的边界值INF表示超出浮点数表示范围NAN表示无效浮点数。判断计算结果时先用isfinite()这类函数检查再参与后续运算。5.3 字符处理从ASCII编码出发C语言里的char既是一种整数类型也是字符类型。1的ASCII编码是49所以当char变量等于49时它代表字符1。字符的大小写转换利用的是ASCII表中字母顺序排列的特点char ch a; char upper ch A - a; // 大写 char lower ch a - A; // 小写a-A 得到的是大写和小写之间的固定差值把这个差值加到字符上就能完成转换。转义字符\b、\t、\n、\r、\、\和\\在printf输出和scanf输入里各有含义\t跳到下一个表格位常用于对齐输出\b是回退一格用于简单的进度条刷新。5.4 复合赋值和强制类型转换的优先级边界复合赋值运算符、-、*、/、%的运算规则和平常的写法有些不同total / 12 6等价于total total / (12 6)不是total total / 12 6。复合赋值的右侧始终是一个完整的表达式先算右侧再和左侧做运算。这个规则在代码审查里经常见到有人写错右侧的表达式中如果既有乘除又有加减要确认结合顺序是否符合预期。强制类型转换的优先级高于四则运算这个细节让(int)(a/b)和(int)a/b得出完全不同的结果double a 1.0; double b 2.0; int i (int)(a/b); // 先除法得0.5再转int得0 int j (int)a / b; // 先转int得1再除以b的2.0结果为0.5赋值给int截断为0如果想对整数除法得到浮点结果要把转换写在除法之外int a 5, b 6; double d (double)a / b; // 0.8333(double)(a/b)是先取整除的0再转0.0完全两个世界。从那以后我每次写浮点转换都强迫自己看一眼括号位置这个检查动作值回票价。希望帮到你。本文还有配套的精品资源点击获取