
1. 项目概述从“计算”开始真正走进C语言的世界很多朋友初学C语言拿到教材或者打开视频看到“第二周计算”这个标题可能会觉得有点平淡甚至有点“小儿科”——不就是加减乘除吗谁不会啊如果你也这么想那可能就错过了C语言入门阶段最关键的“地基”搭建环节。我当年自学时也犯过这个错误以为快速扫一眼语法就能进入更“酷”的循环和数组结果在后面写稍微复杂一点的表达式时各种优先级错误、类型转换的诡异结果层出不穷调试起来苦不堪言。浙大翁恺老师将“计算”作为独立且靠前的核心章节其深意正在于此它不仅仅是教你怎么写11而是为你构建一套关于程序如何“思考”和“运作”的底层心智模型。这一周的内容实际上是为你后续所有的编程学习铺设轨道。它涵盖了程序最基本的交互输入输出、数据在计算机中的存在形式变量与数据类型、以及对这些数据进行处理的核心规则运算符与表达式。这就像学做菜你以为第二周是教你切土豆丝但实际上它是在教你认识不同的刀具数据类型、掌握握刀的姿势运算符优先级、理解食材下锅的顺序表达式求值顺序。只有这些基础扎实了后面无论是炒“数组”这盘大菜还是炖“函数”这锅高汤你才能得心应手不会因为基础不牢而手忙脚乱。所以无论你是零基础的纯小白还是学过其他语言想快速掌握C语法的朋友我都强烈建议你沉下心来把“计算”这个主题吃透。接下来我会结合我多年的开发和教学经验为你拆解这一周的所有核心知识点并补充大量官方教程里可能不会细讲但在实际编码中一定会遇到的“坑”和技巧。2. 核心基石变量、数据类型与赋值程序的核心是处理数据而数据在程序中的“家”就是变量。理解变量和数据类型是理解一切计算的前提。2.1 变量数据的容器与标签你可以把变量想象成一个有名字的盒子。在C语言中使用变量前必须先声明告诉编译器“我要一个盒子盒子叫什么名字以及这个盒子专门用来装什么类型的东西”。int price; // 声明了一个名字叫price的变量类型是int整数这里的int是数据类型price是变量名。声明之后这个叫price的“盒子”就在内存中被分配了一个空间但里面装的值是随机的我们称之为“垃圾值”。所以声明后尽快给它一个确定的值是一个好习惯。赋值就是往盒子里放东西的操作用等号表示。price 100; // 将整数100放入price这个盒子声明和赋值可以合并称为初始化int price 100; // 声明一个整数变量price并初始化为100注意是赋值运算符不是数学中的等号。它表示将右边的值“送入”左边的变量。a b和b a是完全不同的两件事。2.2 基本数据类型认清你的“武器”C语言提供了几种基本数据类型它们决定了盒子的大小占多少内存字节和能存放的东西值的范围。整数类型用来存储没有小数部分的数字。int最常用的整数类型。在大多数现代系统上占4个字节32位取值范围约为-21亿到21亿。short短整数通常占2字节。long长整数通常占4或8字节。long long更长的整数通常占8字节。以上类型都可以加上unsigned前缀表示“无符号”即只存放非负数这样正数的范围可以扩大一倍。例如unsigned int。浮点数类型用来存储带有小数部分的数字。float单精度浮点数通常占4字节。有效数字约6-7位。double双精度浮点数通常占8字节。有效数字约15-16位。精度更高是更常用的浮点类型。例如double pi 3.1415926535;字符类型用来存储单个字符。char通常占1个字节。它实际上存储的是字符的ASCII码一个整数。所以char c A;本质上是在变量c里存储了整数65。为什么区分类型这么重要首先不同类型占用的内存空间不同选择合适的类型可以节省内存尤其在处理大量数据时。其次也是更关键的一点类型决定了能对该数据执行的操作以及操作的含义。整数除法和浮点数除法就是经典例子我们会在运算符部分详细讲。实操心得关于数据类型的选择对于初学者我的建议是在明确知道数值范围不大且一定是整数时比如计数器、年龄、索引使用int但凡涉及到可能需要小数计算的情况比如价格、平均值、物理计算毫不犹豫地使用double。虽然float内存占用小但double更高的精度能避免很多因精度不足导致的细微错误在当今内存充裕的环境下优先用double是更稳妥的选择。char类型除了存字符有时也用来处理很小的整数。3. 程序的眼睛与嘴巴标准输入与输出程序如果无法与外界交互就是一个死循环。printf和scanf是C语言标准库中用于格式化输出和输入的两个核心函数它们就像是程序的嘴巴和眼睛。3.1 格式化输出printfprintf的功能是将数据按照指定的格式“打印”到标准输出通常是屏幕。#include stdio.h // 使用printf必须包含这个头文件 int main() { int a 10; double b 3.14; char c X; printf(a %d, b %.2f, c %c\n, a, b, c); return 0; }%d以十进制整数格式输出int类型。%f输出float或double类型。%.2f表示保留两位小数。%c输出char类型。\n换行符非常重要。没有它所有输出可能会挤在一行。格式控制符是printf的灵魂它告诉函数如何解读后面提供的变量。类型不匹配是常见错误比如用%d去输出一个double结果会是乱码。3.2 格式化输入scanfscanf的功能是从标准输入通常是键盘读取数据并按照格式存入变量。#include stdio.h int main() { int age; double height; printf(请输入您的年龄和身高米); scanf(%d %lf, age, height); // 注意 符号和 %lf printf(年龄%d身高%.2f米\n, age, height); return 0; }符号这是取地址运算符。scanf需要知道将读取的数据存放到哪个内存地址变量名就是获取该变量的地址。忘记写是初学者最常犯的错误之一会导致程序运行时崩溃或行为异常。%lf用于读取double类型。注意在printf中输出double用%f但在scanf中读取double必须用%lfl是long的意思。这是一个经典的“坑点”。输入时数据之间可以用空格、Tab或回车分隔。常见问题与排查技巧实录scanf读取后残留换行符如果你在scanf后使用gets()或%c读取字符可能会直接读到上次输入留下的换行符\n导致看起来“跳过了”输入。解决方法是在%c前加一个空格如scanf(” %c”, ch);这个空格会消耗掉之前所有的空白字符。输入格式不匹配如果要求输入整数却输入了字母scanf会读取失败变量值保持不变且后续的输入也会混乱。编写健壮的程序需要检查scanf的返回值它返回成功读取的变量个数。printf与scanf的格式字符串它们本质上是两个不同的函数格式控制符的某些细节如double对应的%lf和%f有区别务必区分记忆。4. 计算的灵魂运算符与表达式有了数据和输入输出接下来就是如何操作数据。运算符就是我们对数据进行运算的符号。4.1 算术运算符这是最直观的运算符加、-减、*乘、/除、%取模求余数。重点在于/除法运算符的行为它完全取决于操作数的类型。整数除法当两个操作数都是整数时/执行的是整数除法结果会舍弃小数部分只保留整数商。int a 5, b 2; double c a / b; // 注意a/b是整数除法结果是2。然后将整数2转换为double 2.0赋值给c。 printf(“%f”, c); // 输出 2.000000 而不是 2.5浮点数除法只要有一个操作数是浮点数/执行的就是浮点数除法结果也是浮点数。double d 5.0 / 2; // 5.0是double2会被提升为double结果是2.5想要得到整数除法的浮点数结果需要进行类型转换double c (double)a / b; // 将a转换为doubleb自动提升结果为2.5%取模运算符只能用于整数求的是整数除法后的余数。5 % 2的结果是1。它常用于判断整除、循环队列、哈希计算等场景。4.2 赋值与复合赋值运算符基本的赋值是。复合赋值运算符结合了算术运算和赋值能使代码更简洁。a 3;等价于a a 3;同理还有-、*、/、%。4.3 递增与递减运算符和--分别表示将变量的值加1或减1。它们有前缀和后缀之分区别在于表达式的值。前缀式i先自增/减然后取新值参与表达式运算。int i 5; int j i; // i先变成6然后将6赋值给j。结果i6, j6后缀式i先取当前值参与表达式运算然后再自增/减。int i 5; int j i; // 先将i的当前值5赋值给j然后i再变成6。结果i6, j5实操心得避免复杂的自增表达式在初学阶段我强烈建议你不要在同一个表达式中对同一个变量进行多次自增/自减运算例如int k i i;。这种代码的行为在C语言标准中可能是“未定义”的不同编译器会产生不同结果代码可读性也极差。好的习惯是将自增/自减作为独立的语句使用或者只在简单的、意义明确的场景下使用如array[index]。4.4 关系、逻辑与条件运算符这些运算符的计算结果都是“真”或“假”在C语言中用整数1表示真0表示假。关系运算符用于比较大小。、、、、等于、!不等于。注意判断相等的和赋值的是两回事if (a 5)会把5赋给a并且表达式a5的值是5非零视为真这几乎永远不是你想要的结果。这是一个极其常见的逻辑错误。有些编程风格建议将常量写在左边如if (5 a)这样如果误写成if (5 a)编译器会报错。逻辑运算符用于组合多个条件。逻辑与两者都为真才真、||逻辑或至少一个为真就真、!逻辑非取反。if (age 18 age 60) { // 年龄在18到60之间包含 printf(“是适龄劳动力\n”); }短路求值对于如果左边表达式为假右边将不再计算。对于||如果左边为真右边将不再计算。这可以用来安全地编写某些代码例如if (p ! NULL p-data 0)如果p是NULL就不会去访问p-data避免了程序崩溃。条件运算符三目运算符? :。它是if-else的简洁形式。int max (a b) ? a : b; // 如果ab成立maxa否则maxb4.5 运算符的优先级与结合性当表达式中有多个运算符时谁先算这就是优先级。如果优先级相同是从左往右算还是从右往左算这就是结合性。例如a b * c因为*优先级高于所以先算b*c再与a相加。 再如a b c是右结合所以先算b c再算a (bc的结果)。我的建议是不要死记硬背完整的优先级表。对于初学者记住以下几点并善用括号()即可括号()的优先级最高用它来明确你的计算意图。算术运算符 (*/%) (-)。关系运算符 (等) 相等性运算符 (!)。逻辑与 逻辑或||。赋值运算符的优先级几乎是最低的。当你不能确定时或者为了让代码更清晰易读毫不犹豫地使用括号。(a b) (c d)比a b c d的意图更一目了然即使不加括号运算顺序也一样。5. 类型转换数据世界的“翻译官”在表达式中不同类型的数据混在一起运算时编译器会自动进行类型转换或者我们需要手动强制转换。5.1 自动类型转换隐式转换遵循“向更宽、更精确的类型看齐”的原则主要是为了不丢失信息。整数提升char、short等小于int的类型在参与运算时会先自动转换为int或unsigned int。寻常算术转换当不同类型的操作数混合时较低等级的类型会转换为较高等级的类型。一个常见的等级顺序是int-unsigned int-long-unsigned long-long long-unsigned long long-float-double-long double。例如int和double相加int会被先转换为double然后进行双精度浮点加法。5.2 强制类型转换显式转换当你需要主动改变一个值的类型时使用强制类型转换。语法是(目标类型)表达式。int total 10, count 4; double average; average total / count; // 错误整数除法average 2.0 average (double)total / count; // 正确将total转为double结果为2.5注意事项强制转换只是产生了一个指定类型的临时值并不会改变原变量本身的类型和值。从大范围类型向小范围类型转换如double转intlong转short可能会丢失精度或发生溢出编译器可能会给出警告需要程序员自己确保安全。6. 综合实战一个简单的计算器程序让我们把上面所有知识点串联起来写一个能进行加、减、乘、除、取模运算的简单计算器。#include stdio.h int main() { double num1, num2, result; char op; int valid_input 1; // 标志位用于判断输入是否有效 printf(“简易计算器\n”); printf(“请输入算式 (例如12.5 3.7)”); // 注意%lf读取double%c前有空格用于消耗可能的空白符 if (scanf(“%lf %c %lf”, num1, op, num2) ! 3) { printf(“错误输入格式不正确\n”); return 1; // 非正常退出 } switch (op) { case ‘’: result num1 num2; break; case ‘-’: result num1 - num2; break; case ‘*’: result num1 * num2; break; case ‘/’: if (num2 0) { printf(“错误除数不能为0\n”); valid_input 0; } else { result num1 / num2; } break; case ‘%’: // 取模运算要求操作数是整数这里进行强制转换和判断 if ((int)num2 0) { printf(“错误取模运算的除数不能为0\n”); valid_input 0; } else { // 注意对double进行%运算是错误的需先转为int result (int)num1 % (int)num2; // 此操作会丢失小数部分 printf(“注意已对操作数取整后进行取模运算。\n”); } break; default: printf(“错误不支持的操作符 ‘%c’\n”, op); valid_input 0; } if (valid_input) { printf(“结果%.2f %c %.2f %.2f\n”, num1, op, num2, result); } return 0; }这个程序涵盖的要点和技巧健壮的输入检查scanf的返回值被用来判断是否成功读取了三个数据项。这是防止用户乱输入导致程序崩溃的好习惯。除法除零保护这是程序中必须处理的边界情况。直接除以零会导致程序运行时错误浮点数除零可能得到无穷大inf但整数除零会导致程序崩溃。取模运算的特殊处理%只能用于整数。我们对double类型的操作数进行了强制转换。这里有一个取舍直接截断小数部分。在实际应用中你可能需要根据业务逻辑决定是四舍五入还是向上/向下取整。同时也做了除零保护。switch-case语句用于多分支选择比一连串的if-else if更清晰。注意每个case后面的break不能丢否则会“穿透”执行后面的case。标志位的使用valid_input这个变量用来记录整个计算过程是否有效。只有在所有检查都通过后才输出最终结果。这是一种清晰的控制流程的方式。7. 常见问题与排查技巧实录在学习和练习“计算”这部分内容时你几乎一定会遇到下面这些问题。我把它们整理成表方便你快速对照排查。问题现象可能原因解决方案与排查思路程序运行后一闪而过看不到输出程序正常结束控制台窗口自动关闭。在return 0;前加上getchar();或system(“pause”);需#include stdlib.h来暂停程序。或者在集成开发环境IDE中直接运行而不是双击.exe文件。使用scanf读取数字后后面的gets或%c读取被跳过scanf读取数字后输入缓冲区残留了换行符\n。1. 在%c前加空格scanf(” %c”, ch);。2. 使用getchar()手动“吃掉”换行符。printf输出浮点数时显示一堆无意义数字或inf1. 格式控制符用错如用%d输出double。2. 浮点数计算中出现了除零。1. 检查格式控制符float/double用%fscanf读double用%lf。2. 检查除数是否可能为零并添加判断。两个整数相除结果总是整数即使我用了double变量接收整数除法发生在赋值之前。确保至少有一个操作数是浮点数。使用强制转换(double)a / b。if (a 5)条件永远为真误用了赋值运算符而不是相等运算符。将if (a 5)改为if (a 5)。养成习惯比较常量时可以写成if (5 a)这样写错成if (5 a)时编译器会报错。复杂的表达式如涉及结果和预期不符表达式中对同一变量的修改和访问顺序存在“未定义行为”或理解歧义。避免在复杂表达式中对同一变量使用多个自增/自减。拆分成多条简单的语句。scanf读取double时程序行为异常在scanf中用了%f而不是%lf。scanf读取double必须使用%lf。输出时用%f即可。数值计算的结果有微小的误差如 0.10.2 ! 0.3浮点数在计算机中以二进制近似存储存在精度误差。这是浮点数的固有特性。比较浮点数是否相等时不要用而应判断两者差的绝对值是否小于一个很小的数如1e-9fabs(a - b) 1e-9。我个人在实际操作中的体会是“计算”这一章的内容其深度远超表面上的加减乘除。它训练的是你对程序执行流程的精确把握能力。每一个运算符的优先级、每一个类型转换的时机、每一个输入输出函数的细节都像是一块块积木。你现在练习的就是如何把这些积木严丝合缝地拼接在一起。初期多犯错误是好事每一个奇怪的运行结果背后都藏着一个你需要理解的语言特性或底层原理。把这一章的练习做扎实养成声明变量时思考类型、写表达式时注意优先级和类型转换、使用输入输出函数时检查边界条件的习惯你后续的学习道路会顺畅得多。当你对“计算”了如指掌后你会发现后面复杂的控制流、数组、函数本质上都是在组织和管理更大型的“计算”而已。