视频封面

视频元数据

  • 视频标题:哈佛大学【中英⚡计算机科学导论(2025)|CS50x Introduction to Computer Science】 p03 P3 第1讲 - C
  • 时长:约1小时(CS50x 2025第1讲)

内容概览

本讲介绍CS50x 2025中的C编程语言。内容涵盖从Scratch到C的过渡、VS Code云编程环境、编写和编译你的第一个C程序、变量、数据类型、条件、循环、自定义函数,以及整数溢出和浮点数不精确性等现实世界中的bug。


1. 引言:从Scratch到C [00:00]

  • 教授从Scratch(可视化编程)过渡到C(基于文本的编程)。
  • 核心思想:编程是关于输入变成输出的;无论使用何种语言,解决问题始终是核心范式。
  • 源代码是程序员编写的内容——它看起来像英语,但并不是英语;它必须精确且无歧义。

考点:源代码与机器码 — 人类使用源代码(可读文本)。计算机使用机器码(0和1)。一个编译器 (compiler) 将源代码转换为机器码。


2. 编程环境:VS Code 与 CS50.dev [04:57]

  • CS50.dev 是CS50的云端编程环境;它运行着 Visual Studio Code (VS Code),这是一款行业标准工具。
  • 需要一个免费的 GitHub账户 来登录。
  • VS Code的功能:
    • 右上角:代码标签页(类似于浏览器标签页),你在这里编写代码;它会自动为语法着色。
    • 底部:终端窗口——一个命令行界面 (CLI),你在这里输入文本命令而不是点击图标。
    • 左上角:文件资源管理器(GUI),以可视化方式显示文件和文件夹。
    • 活动栏:功能图标,包括 CS50的鸭子——一个内置的问答聊天机器人。

理解:There are two ways to interact with the computer: GUI (graphical user interface) via clicks, and CLI (command line interface) via typed commands. Programmers often prefer CLI for power and efficiency, though VS Code offers both. (理解:与计算机交互有两种方式:通过点击的GUI(图形用户界面),以及通过键入命令的CLI(命令行界面)。尽管VS Code两者都提供,但程序员通常更偏好CLI,因为它更强大、更高效。)


3. 在C语言中编写和运行 Hello World [09:00]

  • 创建C文件的步骤:
    1. 在终端中输入 code hello.c 来创建一个新文件。
    2. 在编辑器标签页中编写程序代码。
  • 文件命名:约定是 something.c;使用全小写,无空格。
  • 经典的 hello.c 程序:
#include <stdio.h>

int main(void)
{
    printf("hello, world\n");
}
  • 理解:int main(void) 目前可以“暂时不深究”——就像Scratch中的“当绿旗被点击”积木一样。

4. 编译和运行:make 与 ./hello [12:00]

  • 要编译程序:输入 make hello(make很智能——它会自动找到 hello.c;不要输入 make hello.c)。
  • 要运行程序:输入 ./hello(点斜杠表示“在当前文件夹中”)。
  • C程序的三个步骤:
    1. 创建文件(code hello.c)
    2. 编译它(make hello)——将源代码转换为机器码
    3. 运行它(./hello)

易错点:./hello 和 hello — 如果你只输入 hello,终端会显示“找不到命令”,因为计算机不知道去哪里找它。./ 明确告诉计算机“在此文件夹中运行hello”。

  • ls 命令:列出当前文件夹中的文件;是“list”的缩写。绿色表示可执行文件。

5. 理解 printf 和转义序列 [15:30]

  • printf 是C语言中对应Scratch的说 (say) 积木。
  • Scratch到C的映射:
    • 函数名 = 函数名(例如,printf)
    • 白色椭圆 = 圆括号 ( )
    • 输入/参数 = 圆括号内的值,用逗号分隔
    • 双引号 " " 是包裹字符串所必需的
  • 反斜杠 \n 是一个转义序列,表示“换行”(在屏幕上按回车)。
    • 如果没有 \n,终端提示符会紧挨着你的输出出现。

易错点:换行不是自动的 — 你必须使用 \n 明确告诉计算机换行。如果你想要两个空行,就使用两个 \n,以此类推。

  • 其他转义序列:
    • \r = 回车(将光标移动到行首,就像老式打字机一样)
    • \" = 打印一个字面的双引号(因为引号在其他情况下是特殊字符)
    • \\ = 打印一个字面的反斜杠
  • 思考:就像数学中的 f(x) 记号一样,C语言中的函数接收输入(参数)并产生输出。printf 的输出被称为副作用 (side effect)——即发生在屏幕上的事情。

6. 头文件和库:stdio.h 与 cs50.h [20:00]

  • #include <stdio.h> 是一个头文件(任何以 .h 结尾的文件)。
    • 包含C语言本身附带的功能。
    • printf 函数在此声明;如果没有这个包含,编译器会报错“未声明的库函数 printf”。

易错点:stdio.h 而非 studio.h — 每年都有一些学生会犯这个拼写错误。是 STDIO(标准输入/输出)。

  • 库 (Library) = 别人为你编写的代码集合(MIT、C语言作者、CS50团队)。通过包含正确的头文件,你就可以使用那些函数。
  • CS50的库 (cs50.h) 提供了更友好的用户输入函数:
    • get_string("提示") — 从用户获取文本
    • get_int("提示") — 从用户获取一个整数
    • get_char("提示") — 获取一个字符
    • get_float("提示") — 获取一个带小数的实数
  • 理解:在原生C语言中获取用户输入出奇地困难。CS50库是“辅助轮”,几周后将被移除。

  • 手册页:现实世界中的C文档晦涩难懂;CS50在 manual.cs50.io 上提供了一个更友好的版本供参考。


7. 变量、数据类型和获取用户输入 [25:00]

  • 变量 (Variable):存储值的容器,使用赋值运算符 =(从右到左:右边的值被复制到左边的变量中)。
  • C语言要求你声明每个变量的类型:
    • string — 文本(CS50类型;非原生C语言)
    • int — 整数(例如,1, -5, 0)
    • char — 单个字符(例如,'y','n')
    • float / double — 带小数点的实数
    • long — 更大的整数(64位)
  • 声明和赋值的示例:
    string answer = get_string("你叫什么名字? ");
    

易错点:分号 — 在C语言中,每条语句必须以 ;(分号)结束,这与Scratch中积木自成一体不同。忘记分号是最常见的初学者错误之一。


8. 格式代码和占位符:%s, %i, %f [28:00]

  • printf 使用占位符(格式代码)将值插入字符串:
    • %s — 字符串占位符
    • %i — 整数占位符
    • %f — 浮点数占位符
    • %li — 长整数占位符
  • 与Scratch的 join 积木拼接字符串不同,C语言使用 printf 留下一个带占位符的“模板”:
    printf("hello, %s\n", answer);
    
  • 函数的多个输入用逗号分隔。
  • 理解:这里有一个微妙的区别——引号内部的逗号是用户看到的字面英语逗号;引号外部的逗号是C语言中函数参数之间的分隔符。

9. 条件语句:if, else if, else [32:00]

  • 布尔表达式:答案为真或假的问题,例如,x < y,x > y,x == y。
  • 运算符:<,>,<=,>=,==(相等),!=(不等)。
    • ==(双等号)是相等性测试;单个 = 是赋值。这是一个经典的初学者陷阱。

易错点:== 与 = — 在C语言中,= 是赋值(例如,x = 5),而 == 是测试两个值是否相等(例如,if (x == 5))。在 if 语句中使用单个 = 会导致bug。

  • 条件结构:
    if (x < y)
    {
        printf("x 小于 y\n");
    }
    else if (x > y)
    {
        printf("x 大于 y\n");
    }
    else
    {
        printf("x 等于 y\n");
    }
    
  • 思考(设计):在这个三路分支中,第三种情况 x == y 是不必要的——如果x不小于y且不大于y,逻辑上它必然等于y。良好的设计意味着不让计算机做不必要的工作。

  • 逻辑运算符:

    • || = 或(两个竖线)
    • && = 且(两个与号)
    • ! = 非(感叹号)
  • 不区分大小写输入的改进示例 if (c == 'y' || c == 'Y') — 避免重复自己(DRY原则:不要重复自己)。

10. 循环:while, for 和 do-while [40:00]

  • 为什么需要循环? 复制粘贴代码是一种设计缺陷。如果你发现自己重复编写代码,使用循环通常会更好。
  • while 循环:
    int i = 3;
    while (i > 0)
    {
        printf("meow\n");
        i--;
    }
    
  • for 循环(更常规):
    for (int i = 0; i < 3; i++)
    {
        printf("meow\n");
    }
    
  • 计数约定:计算机科学家从 0 开始计数,并数到“小于但不包括”目标值:i < 3。
  • 无限循环:while (true) { printf("meow\n"); } — 将永远运行下去。
    • 逃生技巧:按 Ctrl+C 中断失控的程序。

易错点:for 循环语法 — 在for循环中使用分号(;)而不是逗号来分隔各部分。不要在 for(...) 后面加分号。

  • do-while 循环 — 至少执行一次循环体,然后检查条件:
    int n;
    do
    {
        n = get_int("数字:");
    }
    while (n < 1);
    
  • 理解:为什么用do-while而不用while?因为我们想至少提示用户一次;普通的while循环会在 n 有值之前就检查 n < 1。

11. 自定义函数和作用域 [47:00]

  • 在C语言中定义你自己的函数(例如,一个 meow 函数):
    void meow(void)
    {
        printf("meow\n");
    }
    
    • 第一个 void = 无返回值(无输出)
    • 第二个 void = 无参数(无输入)
  • 带参数的函数:void meow(int n) — 接收一个整数输入。
  • 带返回值的函数:int get_positive_int(void) — 使用 return 关键字返回一个整数。
  • 函数原型 (Function prototype):要使用在文件后面定义的函数,请在顶部(main 之前)声明它的第一行,以便编译器知道它存在:
    void meow(int n);  // 顶部的原型
    

易错点:函数顺序 — C语言自上而下读取代码。如果你在定义 meow() 之前调用它,编译器会报错“调用未声明的函数”。解决方案:将函数的第一行(其原型)复制到文件顶部。

  • 作用域 (Scope):变量只存在于定义它们的花括号 { } 内。
    • 在 main 中定义的 n 在 meow 内部不存在;你必须将它作为参数传递。
    • 理解:这就是为什么输入和输出很重要——它们是函数在不“共享”变量的情况下进行通信的方式。

12. Mario问题:嵌套循环和抽象 [52:00]

  • 受超级马里奥兄弟启发的编程问题——使用 printf 打印砖块/问号。
  • 水平行,共4个问号:
    for (int i = 0; i < 4; i++)
    {
        printf("?");
    }
    printf("\n");  // 循环之后换行
    
  • 垂直列,共3个砖块:在循环内部使用 printf("#\n")。
  • 网格(3x3砖块):嵌套循环——外层循环用于行,内层循环用于列:
    for (int row = 0; row < 3; row++)
    {
        for (int col = 0; col < 3; col++)
        {
            printf("#");
        }
        printf("\n");  // 每行之后换行
    }
    

易错点:换行的位置 — 对于一行,将 \n 放在内层循环之后(每行一次),而不是在内层循环内部(否则会变成垂直的一列)。

  • 抽象策略:分解问题——先编写一个 print_row(int n) 函数,然后在 main 的循环中调用它。
  • 常量:const int n = 3; — 一个不能被改变的变量(防止你自己或同事意外修改它)。
  • 魔法数字 (Magic numbers):在多个地方重复的硬编码数字是不良设计;使用 const 变量或向用户询问。

13. 算术、整数溢出和浮点数不精确性 [59:00]

基本算术运算符

  • + 加法,- 减法,* 乘法,/ 除法,% 取余(模运算)

整数溢出

  • 问题:计算机以固定位数的二进制存储整数(int 通常为32位)。
    • 32位可以表示从0到约40亿(无符号),或大约-20亿到+20亿(有符号)。
  • 如果你不断将一个数翻倍(指数增长),它最终会超出这个限制 → 数字会回绕为负数,然后变为零。
  • 推导:如果所有32位都是1,再加上1,没有第33位来“进位”——所有位都重置为0,得到0。

考点:现实世界的例子——波音787 — 一个使用32位整数以十分之一秒为单位计数的软件计数器在持续通电248天后溢出,导致电力中断。解决方案:定期重启飞机!

截断和类型转换

  • 整数除法会截断:1 / 3 = 0(而不是0.333...),3 / 2 = 1(而不是1.5)。
  • 修复:使用浮点数或强制转换值:(float) x / y — 告诉计算机将这个整数视为浮点数。

浮点数不精确性

  • 浮点数也使用有限位数(32),双精度浮点数使用64位——两者都是有限的。
  • 实数有无穷多个,所以计算机无法完美地表示它们全部。
  • 示例:用50位数字打印 1/3 得到 0.3333333333334326744407... — 超过某一位后就不准确了!

2038年问题

  • 计算机一直使用32位整数来统计自1970年1月1日以来的秒数(Unix纪元)。
  • 在2038年1月19日,这个计数器将溢出,可能导致“世界末日”——时钟重置到1901年或变为负数。
  • 思考:与Y2K问题相同的基本问题——有限的二进制位无法表示无限的量。

14. 总结:正确性、设计和风格 [1:10:00]

  • 评估代码质量的三个维度:
    • 正确性 (Correctness) — 代码是否做了它应该做的事?(由 check50 检查)
    • 设计 (Design) — 代码写得有多好?是否高效、逻辑清晰、没有做不必要的工作?(由 design50 鸭子聊天机器人检查)
    • 风格 (Style) — 代码是否美观可读、缩进正确?(由 style50 按钮检查)
  • VS Code中的工具:check50, style50, design50 内置于CS50的环境中。
  • 理解:这些工具不仅仅用于评分——在现实世界中,工程师也以类似的方式编写测试并审查代码质量。

关键词/标签

C语言 CS50 哈佛 编程 源代码 编译器 VS Code 终端 CLI printf 转义序列 数据类型 变量 条件语句 循环 函数 作用域 整数溢出 浮点数不精确 Mario问题 解决问题


生成时间:2026-09-11 15:18:30 生成平台:VideoNote www.xygy.top