| 关于   英文版   «  1.1. 第 0 周:Scratch   ::   目录   ::   3.1. 第 2 周:数组  »

2.1. 第 1 周:C

2.1.1. 欢迎!

  • 在上一节课中,我们学习了 Scratch,一种可视化编程语言。

  • 学习计算机科学概念可能相当具有挑战性。确实,这可能会让你感觉像是在对着一根消防水管喝水。请记住:最终重要的是,通过你在本课程中的努力工作和学习,你在未来几周和几个月内所获得的进步。

  • 确实,Scratch 中呈现的所有基本编程概念都将在你学习如何为任何编程语言进行编程时得到运用。Scratch 中的函数、条件语句、循环和变量是你在任何编程语言中都会发现的基本构建块。

2.1.2. 源代码

  • 回想一下,机器只理解二进制。人类编写的是 源代码 ,即计算机可读的指令列表,而机器只理解我们现在所称的 机器码 。这种机器码是由 0 和 1 组成的模式,能够产生预期的效果。

  • 事实证明,我们可以使用一种非常特殊的软件——编译器 ,将 源代码 转换为机器码。今天,我们将向你介绍一款编译器,它允许你将用 C 语言编写的源代码转换为机器码。

    flowchart
  • 今天,除了学习如何编程之外,你还将学习如何编写优质的代码。

2.1.3. 面向 CS50 的 Visual Studio Code

  • 本课程使用的文本编辑器是 Visual Studio Code ,简称 VS Code ,亲切地称为 cs50.dev ,可通过同一 URL 访问。

  • 我们使用 VS Code 的最重要原因之一是它已预装了课程所需的所有软件。本课程及其中的说明都是围绕 VS Code 设计的。

  • 在自己的计算机上手动安装课程所需的软件是一项繁琐且令人头疼的任务。最好始终使用 VS Code 来完成本课程的作业。

  • 你可以在 cs50.dev 打开 VS Code。

  • 该集成开发环境可分为多个区域:

    IDE 请注意,左侧有一个 文件资源管理器,你可以在其中找到你的文件。此外,请注意中间有一个称为 文本编辑器 的区域,你可以在其中编辑你的程序。最后,还有一个 command line interface ,称为 CLI、命令行 或 终端窗口,我们可以在此向云端计算机发送命令。

  • 你还会在左侧栏的图形用户界面(GUI)中看到各种工具和文件浏览器。

  • 由于此集成开发环境已预配置了所有必要软件,你应使用它来完成本课程的所有作业。

2.1.4. Hello World

  • 我们将使用三条命令来编写、编译并运行我们的第一个程序:

    code hello.c
    
    make hello
    
    ./hello
    

    第一个命令 code hello.c 创建一个文件,并允许我们为该程序输入指令。第二个命令 make hello 将我们用 C 语言编写的指令 编译 成名为 hello 的可执行文件。最后一个命令 ./hello 运行名为 hello 的程序。

  • 我们可以通过在终端窗口中输入 code hello.c 来构建你的第一个 C 程序。请注意,我们特意将整个文件名小写,并包含了 .c 扩展名。然后,在出现的文本编辑器中,编写如下代码:

    // A program that says hello to the world
    
    #include <stdio.h>
    
    int main(void)
    {
        printf("hello, world\n");
    }
    

    请注意,上述每个字符都有其特定用途。如果输入错误,程序将无法运行。 printf 是一个可以输出一行文本的函数。请注意引号和分号的位置。此外,请注意 \n 会在单词 hello, world 之后创建一个新行。

  • 在终端窗口中点击返回,你可以通过执行 make hello 来编译代码。请注意,我们省略了 .c 。 make 是一个构建工具,它将编译我们的 hello.c 文件并将其转换为名为 hello 的程序。如果执行此命令没有产生任何错误,你可以继续操作;否则,请仔细检查你的代码以确保其与上述内容一致。

  • 现在,输入 ./hello ,你的程序将执行并输出 hello, world 。

  • 现在,打开左侧的文件浏览器。你会注意到现在既有一个名为 hello.c 的文件,也有另一个名为 hello 的文件。 hello.c 包含可供人类和编译器阅读的源代码。 hello 是一个可执行文件,其中包含计算机可以直接运行的机器码。

2.1.5. 从零开始到 C

  • 在 Scratch 中,我们利用 say 积木在屏幕上显示任何文本。确实,在 C 语言中,我们有一个名为 printf 的函数可以完全实现此功能。

  • 请注意,我们的代码已经调用了这个函数:

    printf("hello, world\n");
    

    请注意,调用了 printf 函数。传递给 printf 的参数是 hello, world\n ,两侧带有双引号。该代码语句以 ; 结束。

  • 代码中的错误很常见,尤其是在分号和引号等语法方面。请按以下方式修改你的代码:

    // \n is missing
    
    #include <stdio.h>
    
    int main(void)
    {
        printf("hello, world");
    }
    

    注意 \n 现在已经消失。

  • 在你的终端窗口中,运行 make hello 。由于你更改了程序,因此必须重新编译程序。

  • 在终端窗口中输入 ./hello ,你的程序发生了怎样的变化?这个 \ 字符称为 转义字符 ,它告诉编译器 \n 是一条用于创建换行的特殊指令。

  • 你还可以使用其他转义字符:

    \n  create a new line
    \r  return to the start of a line
    \"  print a double quote
    \'  print a single quote
    \  print a backslash
    
  • 将你的程序恢复为以下内容:

    // A program that says hello to the world
    
    #include <stdio.h>
    
    int main(void)
    {
        printf("hello, world\n");
    }
    

    注意分号和 \n 已被恢复。

2.1.6. 头文件与 CS50 手册页

  • 代码开头的语句 #include <stdio.h> 是一个非常特殊的命令,它告诉编译器你想要使用一个名为 stdio.h 的 库 的功能,这是一个 头文件 。这使你能够(除其他许多功能外)使用 printf 函数。请注意,它不叫 studio :而是 stdio.h 。

  • 库 是由某人创建的一系列代码。库是其他人过去编写的预写代码和函数的集合,我们可以在自己的代码中使用它们。

  • 你可以在 Manual Pages 上阅读关于该库所有功能的介绍。手册页提供了一种更好地理解各种命令的作用及其工作方式的方法。

  • 事实证明,CS50 拥有自己的一个名为 cs50.h 的库。其中包含众多函数,在你开始学习 C 语言时提供 辅助轮 :

    get_char
    get_double
    get_float
    get_int
    get_long
    get_string
    
  • 这些库已为你预安装在 cs50.dev 。如果你尝试在自己的计算机上使用这些库,则可能需要安装它们。这就是为什么你在本课程中应使用 cs50.dev ,因为它已为你安装了所有必要的软件。

  • 让我们在你的程序中使用这个库。

2.1.7. Hello, You

  • 回想一下,在 Scratch 中我们可以询问用户“你叫什么名字?”,然后说出带有该名字的“你好”。

  • 在 C 语言中,我们也能实现相同的功能。请按以下方式修改你的代码:

    // get_string and printf with incorrect placeholder
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string answer = get_string("What's your name? ");
        printf("hello, answer\n");
    }
    

    get_string 函数用于从用户获取字符串。然后,变量 answer 被传递给 printf 函数。

  • 在终端窗口中再次运行 make hello ,注意会出现大量错误。

  • 查看这些错误,编译器无法识别 string 和 get_string 。我们需要通过添加一个名为 cs50.h 的库来向编译器提供这些定义。此外,我们注意到 answer 并未按预期提供。请按以下方式修改你的代码:

    // get_string and printf with %s
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string answer = get_string("What's your name? ");
        printf("hello, %s\n", answer);
    }
    

    get_string 函数用于从用户获取字符串。然后,变量 answer 被传递给 printf 函数。 %s 指示 printf 函数准备接收一个 string 。

  • 现在,在终端窗口中再次运行 make hello ,你可以通过输入 ./hello 来运行你的程序。该程序现在会询问你的姓名,然后按预期向你问好并附上你的姓名。

  • answer 是一个特殊的存储位置,我们称之为 变量 。 answer 的类型为 string ,可以容纳任何字符串。有许多 数据类型 ,例如 int 、 bool 、 char 以及许多其他类型。

  • %s 是一个称为 格式代码 的占位符,它告诉 printf 函数准备接收一个 string 。 answer 是传递给 %s 的 string 。

2.1.8. Linux

  • 我们一直使用接口来 make 并运行我们的程序。

  • 对于执行命令和处理文件,接口通常比图形用户界面更有用。

  • 在终端窗口中,接口的一些常用命令包括:

    • cd ,用于更改当前目录(文件夹)

    • cp ,用于复制文件和目录

    • ls ,用于列出目录中的文件

    • mkdir ,用于创建目录

    • mv ,用于移动(重命名)文件和目录

    • rm ,用于删除文件

    • rmdir ,用于删除目录

  • 最常用的是 ls ,它将列出当前目录中的所有文件。请在终端窗口中输入 ls 并按下 enter 。你将看到当前文件夹中的所有文件。

2.1.9. 条件语句

  • 你在 Scratch 中使用的另一个构建块是 条件语句 。例如,如果 x 大于 y,你可能想执行一个操作;如果该条件不满足,你可能想执行另一个操作。

  • 我们来看几个来自 Scratch 的示例。

  • 在 C 语言中,你可以按如下方式比较两个值:

    // Conditionals that are mutually exclusive
    
    if (x < y)
    {
        printf("x is less than y\n");
    }
    else
    {
        printf("x is not less than y\n");
    }
    

    注意,如果 x < y ,则发生一种结果。如果 x 不小于 y ,则发生另一种结果。

  • 同样,我们可以为三种可能的结果制定计划:

    // Conditional that isn't necessary
    
    if (x < y)
    {
        printf("x is less than y\n");
    }
    else if (x > y)
    {
        printf("x is greater than y\n");
    }
    else if (x == y)
    {
        printf("x is equal to y\n");
    }
    

    请注意,并非所有这些代码行都是必需的。我们如何消除上述不必要的计算?

  • 你可能已经猜到,我们可以按如下方式改进此代码:

    // Compare integers
    
    if (x < y)
    {
        printf("x is less than y\n");
    }
    else if (x > y)
    {
        printf("x is greater than y\n");
    }
    else
    {
        printf("x is equal to y\n");
    }
    

    注意最终语句如何被替换为 else 。

2.1.10. 类型

  • C 语言中提供了许多数据类型:

    bool
    char
    float
    int
    long
    string
    ...
    

2.1.11. 格式代码

  • 之前,你可能记得我们在 printf 中为字符串使用了占位符 %s 。此占位符称为 格式代码 。

  • printf 支持多种格式代码。以下是本课程中可能用到的一些非详尽列表:

    %c
    %f
    %i
    %li
    %s
    

    %c 用于 char (字符)变量。 %f 用于 float (浮点)变量。 %i 用于 int 或整型变量。 %li 用于 long 整型变量。 %s 用于 string 变量。你可以在 Manual Pages 上了解更多相关信息。

  • 本课程将广泛使用 C 语言提供的多种数据类型。

2.1.12. 变量

  • 在 C 语言中,你可以按如下方式为 int 或整数赋值:

    int counter = 0;
    

    注意,一个名为 counter 且类型为 int 的变量被赋值为 0 。

  • C 也可以编程为将 counter 加一,如下所示:

    counter = counter + 1;
    

    注意 1 是如何被加到 counter 的值上的。

  • 这也可以表示为:

    counter += 1;
    
  • 这可以进一步简化为:

    counter++;
    

    注意 ++ 如何用于加 1。

  • 你也可以从 counter 中减去 1,方法如下:

    counter--;
    

    请注意,在此语法中, 1 从 counter 的值中被移除。

2.1.13. compare.c

  • 利用关于如何为变量赋值的这些新知识,你可以编写第一个条件语句。

  • 在终端窗口中,输入 code compare.c 并按如下方式编写代码:

    // Conditional, Boolean expression, relational operator
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for integers
        int x = get_int("What's x? ");
        int y = get_int("What's y? ");
    
        // Compare integers
        if (x < y)
        {
            printf("x is less than y\n");
        }
    }
    

    请注意,我们创建了两个变量:一个名为 x 的 int 或整数,以及另一个名为 y 的变量。这些变量的值是通过 get_int 函数填充的。

  • 你可以在终端窗口中执行 make compare ,然后执行 ./compare 来运行你的代码。如果出现任何错误消息,请检查代码中的错误。

  • 我们可以通过如下编码方式来改进你的程序:

    // Conditionals
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for integers
        int x = get_int("What's x? ");
        int y = get_int("What's y? ");
    
        // Compare integers
        if (x < y)
        {
            printf("x is less than y\n");
        }
        else if (x > y)
        {
            printf("x is greater than y\n");
        }
        else
        {
            printf("x is equal to y\n");
        }
    }
    

    请注意,现在所有可能的结果都已涵盖。

  • 你可以重新制作并重新运行你的程序,然后对其进行测试。

  • 通过各种流程图检查这些程序,你可以看到代码设计决策的效率。几乎任何代码块都可以转换为可视化形式。

2.1.14. agree.c

  • 考虑另一种称为 char 的数据类型,我们可以在终端窗口中输入 code agree.c 来启动新程序。

  • 如果 string 是一系列字符,那么 char 就是单个字符。

  • 在文本编辑器中,按如下方式编写代码:

    // Comparing against lowercase char
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user to agree
        char c = get_char("Do you agree? ");
    
        // Check whether agreed
        if (c == 'y')
        {
            printf("Agreed.\n");
        }
        else if (c == 'n')
        {
            printf("Not agreed.\n");
        }
    }
    

    请注意,单引号用于单个字符( char 类型),而双引号用于字符串。此外,请注意 == 确保某物 等于 另一物,而在 C 语言中,单个等号的功能则截然不同。

  • 你可以通过在终端窗口中输入 make agree ,然后输入 ./agree 来测试你的代码。

  • 我们还可以允许输入大写和小写字符:

    // Comparing against lowercase and uppercase char
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user to agree
        char c = get_char("Do you agree? ");
    
        // Check whether agreed
        if (c == 'y')
        {
            printf("Agreed.\n");
        }
        else if (c == 'Y')
        {
            printf("Agreed.\n");
        }
        else
        {
            printf("Not agreed.\n");
        }
    }
    

    请注意,这里提供了额外的选项。然而,这不是高效的代码。

  • 我们可以按如下方式改进此代码:

    // Logical operators
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user to agree
        char c = get_char("Do you agree? ");
    
        // Check whether agreed
        if (c == 'Y' || c == 'y')
        {
            printf("Agreed.\n");
        }
        else
        {
            printf("Not agreed.\n");
        }
    }
    

    注意 || 实际上就是 或 (or)的意思。

2.1.15. 循环与 meow.c

  • 我们也可以在 C 程序中使用 Scratch 的循环构建块。

  • 在终端窗口中,输入 code meow.c 并编写如下代码:

    // Opportunity for better design
    
    #include <stdio.h>
    
    int main(void)
    {
        printf("meow\n");
        printf("meow\n");
        printf("meow\n");
    }
    

    注意,这确实按预期工作,但存在改进设计的机会。代码被一遍又一遍地重复。

  • 我们可以通过如下修改代码来改进程序:

    // Better design
    
    #include <stdio.h>
    
    int main(void)
    {
        int i = 3;
        while (i > 0)
        {
            printf("meow\n");
            i--;
        }
    }
    

    请注意,我们创建了一个 int 名为 i 并将其赋值为 3 。然后,我们创建一个 while 循环,只要 i > 0 就会运行。接着,循环开始执行。每次都会通过 i-- 语句从 i 中减去 1 。

  • 同样地,我们可以通过如下修改代码来实现某种形式的递增计数:

    // Print values of i
    
    #include <stdio.h>
    
    int main(void)
    {
        int i = 1;
        while (i <= 3)
        {
            printf("meow\n");
            i++;
        }
    }
    

    注意我们的计数器 i 是从 1 开始的。每次循环运行时,它会将计数器增加 1 。一旦计数器大于 3 ,循环就会停止。

  • 通常,在计算机科学中,我们从零开始计数。最好按如下方式修改你的代码:

    // Better design
    
    #include <stdio.h>
    
    int main(void)
    {
        int i = 0;
        while (i < 3)
        {
            printf("meow\n");
            i++;
        }
    }
    

    请注意,我们现在从零开始计数。

  • 我们工具箱中用于循环的另一个工具是 for 循环。

  • 你可以使用 for 循环进一步优化 meow.c 程序的设计。请按以下方式修改你的代码:

    // Better design
    
    #include <stdio.h>
    
    int main(void)
    {
        for (int i = 0; i < 3; i++)
        {
            printf("meow\n");
        }
    }
    

    请注意, for 循环包含三个参数。第一个参数 int i = 0 将计数器初始化为零。第二个参数 i < 3 是要检查的条件。最后,参数 i++ 指示循环每次运行时递增一。

  • 我们甚至可以使用以下代码无限循环:

    // Infinite loop
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        while (true)
        {
            printf("meow\n");
        }
    }
    

    请注意, true 将始终成立。因此,代码将始终运行。运行此代码会导致你失去对终端窗口的控制。你可以通过在键盘上按下 control-C 来中断无限循环(这会发送一个 SIGINT 信号以终止程序)。

  • 我们可以通过如下修改代码来询问用户需要喵喵叫多少次:

    // Prompts user for n.
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        int n = get_int("What's n? ");
    
        for (int i = 0; i < n; i++)
        {
            printf("meow\n");
        }
    }
    

    请注意, n 由用户定义。随后,存在 n 次喵叫。

  • 如果用户输入小于零的值会发生什么?按如下方式修改你的代码:

    // Prompts user again if need be. (Poor design.)
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        int n = get_int("What's n? ");
        if (n < 0)
        {
            n = get_int("What's n? ");
        }
    
        for (int i = 0; i < n; i++)
        {
            printf("meow\n");
        }
    }
    

    请注意,这可能会阻止用户输入一次小于零的数字。但如果他们多次这样做会发生什么?

  • 按以下方式改进你的代码:

    // Uses a loop with continue/break.
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        int n;
        while (true)
        {
            n = get_int("What's n? ");
            if (n < 0)
            {
                continue;
            }
            else
            {
                break;
            }
        }
    
        for (int i = 0; i < n; i++)
        {
            printf("meow\n");
        }
    }
    

    请注意, while 循环将无限运行,直到 n 大于或等于零。

  • 尽管如此,这段代码还可以进一步改进:

    // Uses a loop with just break.
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        int n;
        while (true)
        {
            n = get_int("What's n? ");
            if (n >= 0)
            {
                break;
            }
        }
    
        for (int i = 0; i < n; i++)
        {
            printf("meow\n");
        }
    }
    

    注意我们的先前代码是如何被简化的,去除了不必要的代码行。

  • 类似于 while 循环,我们可以使用 do-while 循环来实现此代码:

    // Uses a do-while loop instead.
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        int n;
        do
        {
            n = get_int("What's n? ");
        }
        while (n < 0);
    
        for (int i = 0; i < n; i++)
        {
            printf("meow\n");
        }
    }
    

    请注意, do 将至少运行一次。该部分代码会在 n 小于零时循环执行。

  • 敏锐的观察者可能会发现,我们可以 抽象掉 代码中发出喵声的部分。

2.1.16. 函数

  • 虽然我们将在后面提供更多指导,但你可以按以下方式在 C 语言中创建自己的函数:

    void meow(void)
    {
        printf("meow\n");
    }
    

    初始的 void 表示该函数不返回任何值。 (void) 表示没有向该函数提供任何值。

  • 此函数可在主函数中按如下方式使用:

    // Abstraction
    
    #include <stdio.h>
    
    void meow(void);
    
    int main(void)
    {
        for (int i = 0; i < 3; i++)
        {
            meow();
        }
    }
    
    // Meow once
    void meow(void)
    {
        printf("meow\n");
    }
    

    请注意 meow 函数是如何通过 meow() 指令被调用的。这是可能的,因为 meow 函数定义在代码底部,而该函数的 原型 作为 void meow(void) 提供在代码顶部。

  • 你的 meow 函数可以进一步修改以接受输入:

    // Abstraction with parameterization
    
    #include <stdio.h>
    
    void meow(int n);
    
    int main(void)
    {
        meow(3);
    }
    
    // Meow some number of times
    void meow(int n)
    {
        for (int i = 0; i < n; i++)
        {
            printf("meow\n");
        }
    }
    

    请注意,原型已更改为 void meow(int n) ,以表明 meow 接受 int 作为其输入。

  • 在处理变量和函数时,理解变量的 作用域 至关重要。请看以下代码:

    // Demonstrates scope
    
    #include <stdio.h>
    
    void meow(int n);
    
    int main(void)
    {
        int n = 3;
        meow(n);
    }
    
    // Meow some number of times
    void meow(int n)
    {
        for (int i = 0; i < n; i++)
        {
            printf("meow\n");
        }
    }
    

    注意 n 是如何在 main 函数中定义的。因此, n 仅在 main 函数的作用域内。 meow 函数能够使用 n 的唯一方式是将 n 的副本传递给 meow 函数。 meow 并未使用来自 main 函数的原始 n 。相反,它使用的是自己的 n 副本。

  • 通过对代码进行一些修改,我们可以获取用户输入:

    // User input
    
    #include <cs50.h>
    #include <stdio.h>
    
    void meow(int n);
    
    int main(void)
    {
        int n;
        do
        {
            n = get_int("Number: ");
        }
        while (n < 1);
        meow(n);
    }
    
    // Meow some number of times
    void meow(int n)
    {
        for (int i = 0; i < n; i++)
        {
            printf("meow\n");
        }
    }
    

    请注意, get_int 用于从用户处获取一个数字。 n 被传递给 meow 。

  • 我们甚至可以测试以确保用户提供的输入是正确的:

    // Return value
    
    #include <cs50.h>
    #include <stdio.h>
    
    int get_positive_int(void);
    void meow(int n);
    
    int main(void)
    {
        int n = get_positive_int();
        meow(n);
    }
    
    // Get number of meows
    int get_positive_int(void)
    {
        int n;
        do
        {
            n = get_int("Number: ");
        }
        while (n < 1);
        return n;
    }
    
    // Meow some number of times
    void meow(int n)
    {
        for (int i = 0; i < n; i++)
        {
            printf("meow\n");
        }
    }
    

    请注意,一个名为 get_positive_int 的新函数会向用户请求一个整数,而 n < 1 则不会。在获得一个正整数后,此函数将 return n 返回到 main 函数。

2.1.17. 正确性、设计、风格

  • 代码可以从三个维度进行评估。

  • 首先,正确性 指的是“代码是否按预期运行?”你可以使用 check50 检查代码的正确性。

  • 其次,设计 指的是“代码的设计质量如何?”你可以使用 design50 评估代码的设计。

  • 最后,风格 指的是“代码的美观性和一致性如何?”你可以使用 style50 评估代码的风格。

2.1.18. 马里奥

  • 我们今天讨论的所有内容都聚焦于你作为新兴计算机科学家工作的各种构建模块。

  • 以下内容将帮助你定位本课程问题集的一般性工作方向:人们应如何着手解决计算机科学相关问题?

  • 假设我们想要模拟《超级马里奥兄弟》游戏的视觉效果。考虑到图中所示的四个问号方块,我们该如何编写代码来大致表示这四个水平排列的方块?

    Mario Question Marks

  • 在终端窗口中,输入 code mario.c 并按如下方式编写代码:

    // Prints a row of 4 question marks
    
    #include <stdio.h>
    
    int main(void)
    {
        printf("????\n");
    }
    

    请注意,输出了四个问号。

  • 使用循环,我们可以更高效地打印问号:

    // Prints a row of 4 question marks with a loop
    
    #include <stdio.h>
    
    int main(void)
    {
        for (int i = 0; i < 4; i++)
        {
            printf("?");
        }
        printf("\n");
    }
    

    注意这里如何使用循环打印四个问号。

  • 同样,我们可以应用相同的逻辑来创建三个垂直块。

    Mario Blocks

  • 为实现此目标,请按以下方式修改你的代码:

    // Prints a column of 3 bricks with a loop
    
    #include <stdio.h>
    
    int main(void)
    {
        for (int i = 0; i < 3; i++)
        {
            printf("#\n");
        }
    }
    

    请注意如何使用循环打印三块垂直砖块。

  • 如果我们想结合这些想法来创建一个三乘三的方块组,该怎么办?

    Mario Grid

  • 我们可以遵循上述逻辑,结合相同的思路。按如下方式修改你的代码:

    // Prints a 3-by-3 grid of bricks with nested loops
    
    #include <stdio.h>
    
    int main(void)
    {
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                printf("#");
            }
            printf("\n");
        }
    }
    

    请注意,一个循环位于另一个循环内部。第一个循环定义正在打印的垂直行。对于每一行,会打印三列。在每行之后,会打印一个新行。

  • 如果我们希望确保块的数量是 常数 ,即不可更改,该怎么办?请按如下方式修改你的代码:

    // Prints a 3-by-3 grid of bricks with nested loops using a constant
    
    #include <stdio.h>
    
    int main(void)
    {
        const int n = 3;
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < n; j++)
            {
                printf("#");
            }
            printf("\n");
        }
    }
    

    注意 n 现在是一个常量。它永远无法被更改。

  • 正如本讲座前面所阐述的,我们可以将功能 抽象 到函数中。考虑以下代码:

    // Helper function
    
    #include <stdio.h>
    
    void print_row(int width);
    
    int main(void)
    {
        const int n = 3;
        for (int i = 0; i < n; i++)
        {
            print_row(n);
        }
    }
    
    void print_row(int width)
    {
        for (int i = 0; i < width; i++)
        {
            printf("#");
        }
        printf("\n");
    }
    

    请注意,打印一行是通过一个新函数完成的。

2.1.19. 运算符

  • 运算符 指的是编译器支持的数学运算。在 C 语言中,这些数学运算符包括:

    • + 用于加法

    • - 用于减法

    • * 用于乘法

    • / 用于除法

    • % 用于取余

  • 在本课程中,我们将使用所有这些运算符。

  • 让我们通过在终端中输入 code calculator.c 并按如下方式修改代码来实现我们自己的计算器:

    // Addition with int
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for x
        int x = get_int("What's x? ");
    
        // Prompt user for y
        int y = get_int("What's y? ");
    
        // Add numbers
        int z = x + y;
    
        // Perform addition
        printf("%i\n", z);
    }
    

    注意我们如何创建第三个变量 z 来存储 x 和 y 的和,然后使用 %i (整数的格式说明符)打印结果。

  • 我们可以编写更高效的代码,如下所示:

    // Addition with int, without third variable
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for x
        int x = get_int("What's x? ");
    
        // Prompt user for y
        int y = get_int("What's y? ");
    
        // Perform addition
        printf("%i\n", x + y);
    }
    

    请注意,我们通过在 printf 语句中直接执行加法操作,消除了对第三个变量 z 的需求,从而使代码更加简洁。

  • 我们可以使用乘法:

    // Doubles a number
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for x
        int x = get_int("What's x? ");
    
        // Double it
        printf("%i\n", x * 2);
    }
    

    注意我们如何使用乘法运算符 * 将输入值加倍,从而演示了加法之外的另一种算术运算。

  • C 中的整数只能计数到某个上限。考虑以下情况:

    // Overflow
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        int dollars = 1;
        while (true)
        {
            char c = get_char("Here's $%i. Double it and give to next person? ", dollars);
            if (c == 'y')
            {
                dollars *= 2;
            }
            else
            {
                break;
            }
        }
        printf("Here's $%i.\n", dollars);
    }
    

    注意程序如何反复将美元金额翻倍。最终,整数将超过其最大值并“溢出”,回绕为负数或零。

  • 整数溢出是指计算产生的值超出了数据类型的最大存储容量,导致该值不可预测地回绕。

  • C 语言的挑战之一在于,虽然它提供了对内存使用方式的极大控制权,但程序员必须非常清楚内存管理的潜在陷阱。

  • 类型指的是变量中可以存储的可能数据。例如, char 旨在容纳单个字符,如 a 或 2 。

  • 类型非常重要,因为每种类型都有特定的限制。例如,由于内存限制,有符号 int 的最大值通常为 2147483647 ,而无符号 int 可以达到 4294967295 。如果你尝试计数一个超过其最大值的 int ,将导致 整数溢出 ,从而在此变量中存储错误的值。

  • 位数决定了我们可以表示的数值范围。

  • 这可能会产生灾难性的现实影响。

  • 我们可以通过使用 long 变量类型来解决此问题:

    // long
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        long dollars = 1;
        while (true)
        {
            char c = get_char("Here's $%li. Double it and give to next person? ", dollars);
            if (c == 'y')
            {
                dollars *= 2;
            }
            else
            {
                break;
            }
        }
        printf("Here's $%li.\n", dollars);
    }
    

    请注意,我们在格式字符串中将 int 改为 long ,并使用 %li 代替 %i 。 long 可以存储比 int 大得多的值,从而延缓(但无法消除)溢出问题。

  • 你可能知道整数和浮点变量有一个显著区别:表示小于 1 的数字的能力。考虑以下情况:

    // Division with ints, demonstrating truncation
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for x
        int x = get_int("What's x? ");
    
        // Prompt user for y
        int y = get_int("What's y? ");
    
        // Divide x by y
        printf("%i\n", x / y);
    }
    

    请注意,当两个整数相除时,C 执行整数除法并截断(丢弃)任何小数部分。例如,7 / 2 的结果是 3,而不是 3.5。

  • 浮点不精确性 说明了计算机在计算数字时存在精度限制。

  • 在编写代码时,请特别注意所使用的变量类型,以避免代码中出现错误。

  • 我们考察了一些由类型相关错误引发的灾难性案例。

  • 同样地,我们可以将 cast 视为一个 float 。考虑以下情况:

    // Casting
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for x
        int x = get_int("What's x? ");
    
        // Prompt user for y
        int y = get_int("What's y? ");
    
        // Divide x by y
        printf("%f\n", (float) x / y);
    }
    

    注意我们在使用 (float) x 进行除法之前,如何将 x 强制转换为 float 。这会将整数转换为浮点数,使除法能够产生小数结果而不是截断。

  • 我们可以在整个过程中使用 float s:

    // Floats
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for x
        float x = get_float("What's x? ");
    
        // Prompt user for y
        float y = get_float("What's y? ");
    
        // Divide x by y
        printf("%.50f\n", x / y);
    }
    

    请注意,我们使用 get_float 作为输入,使用 %.50f 显示最多 50 位小数,从而揭示浮点精度的局限性,因为由于二进制表示的限制,结果可能会显示出意外的数字。

2.1.20. 总结

在本课中,你学习了如何将你在 Scratch 中学到的构建块应用到 C 编程语言中。你学到了……

  • 如何在 C 语言中创建你的第一个程序。

  • 如何使用接口。

  • 关于 C 语言原生提供的预定义函数。

  • 如何使用变量、条件语句和循环。

  • 如何创建你自己的函数以简化和改进代码。

  • 如何从三个维度评估你的代码:正确性、设计和风格。

  • 如何在代码中集成注释。

  • 如何使用类型和运算符,以及你的选择所带来的影响。

下次见!

   «  1.1. 第 0 周:Scratch   ::   目录   ::   3.1. 第 2 周:数组  »

关闭窗口