跳转至

CS50 Week 2 Array

程序是如何运行的。

编译

我们把make hello.c叫做编译,但实际上make包含了以下四个步骤:

预处理

Preprocessing 的过程就是找到头文件,再替换成对应的代码。

例如,下面是预处理前的代码:

C
#include <cs50.h>
#include <stdio.h>

int main(void)
{
    string name = get_string("What's your name? ");
    printf("hello, %s\n", name);
}

经过预处理后,代码会变成:

C
...
string get_string(string prompt);
...
int printf(string format, ...);
...

int main(void)
{
    string name = get_string("Name: ");
    printf("hello, %s\n", name);
}

编译

Compiling 并不会将预处理后的代码直接转为二进制的文件,而是会转为汇编语言(Assembly language)。汇编语言里面的内容是最基础、最底层的指令。

Text Only
...
main:                         # @main
    .cfi_startproc
# BB#0:
    pushq    %rbp
.Ltmp0:
    .cfi_def_cfa_offset 16
.Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
.Ltmp2:
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    xorl    %eax, %eax
    movl    %eax, %edi
    movabsq    $.L.str, %rsi
    movb    $0, %al
    callq    get_string
    movabsq    $.L.str.1, %rdi
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rsi
    movb    $0, %al
    callq    printf
    ...

汇编

Assembling 就是将上述汇编语言转为机器语言(由 0 和 1 组成)。

链接

Linking 就是把hello.c的机器语言和头文件(也就是cs50.cstdio.c)中形成的机器语言链接起来,形成一个单独的二进制文件。默认会输出a.out这个文件。

调试(Debugging)

Bug 就是让程序不能按照预期运行的错误。

Debug 的方法:

  • printf将变量的值打印出来,检查变量的值是否符合预期。

  • Debugger,可以设置断点,让程序逐步运行。

  • 小黄鸭调试法。一个可爱的名字,实际上就是程序员自己将程序一步一步地描述出来,来找到发现问题的灵感。

Insights are often found by simply describing the problem aloud.

内存

RAM (Random-Access Memory)

  • bool:1 byte

实际上,bool可以用 1 个比特(1 bit)来表示,因为只需要一个 0 或者一个 1。但为了简便,我们仍然用 1 byte(8 bits)来表示。

  • char:1 byte

ASCII 一共用 256 个不同的字符,\(2^8=256\),因此需要用 8 bits 来表示,也就是 1 byte。

  • float:4 bytes(也就是 32 位二进制)

  • double:8 bytes

  • int:4 bytes

  • long:8 bytes

  • string, ? bytes。取决于字符串的长度。

数组

数组可以储存多个相同类型的量。

C
#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int scores[3];

    scores[0] = 72;
    scores[1] = 73;
    scores[2] = 33;

    printf("Average: %f\n", (scores[0] + scores[1] + scores[2]) / 3.0);
}
  • 数组的下标从 0 开始:第 1 个元素的下标是 0,第 2 个元素的下标是 1。
  • 数组并不能节约内存,但可以避免太多的变量名。

字符

如果将字符以%i的形式输出,则会得到对应的 ASCII 值。

例如,下面的代码:

C
#include <stdio.h>

int main(void)
{
    char c1 = 'H';
    char c2 = 'I';
    char c3 = '!';
    // 以%i的形式输出字符。
    printf("%i %i %i\n", c1, c2, c3);
}

会得到:

Bash
$ make hi
$ ./hi
72 73 33

字符串

字符串的本质是一个数组,它的每一个元素是一个字符。

  • 字符串的最后一个元素是一个特殊的字符串:\0

有些库文件可以计算字符串的长度,就不用自己重复编写相同功能的代码了。

C
#include <cs50.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
    string name = get_string("Name: ");
    int length = strlen(name);
    printf("%i\n", length);
}

可以到 CS50 课程的手册上找到常用的库。

命令行

允许在命令行中输入main函数的参数

可以修改main函数的写法,使得用户可以在命令行直接输入主函数的参数。

C
#include <cs50.h>
#include <stdio.h>

int main(int argc, string argv[])
{
    if (argc == 2)
    {
        printf("hello, %s\n", argv[1]);
    }
    else
    {
        printf("hello, world\n");
    }
}

其中,argc是用户输入单词的个数,argv是用户输入单词的数组(存放了所有单词)。

Bash
$ make argv

//输入一个单词,也就是没有输入名字
$ ./argv
hello, world

//输入了两个单词,可以作为参数,存到argv[1]$ ./argv David
hello, David

//输入了三个单词,程序不能支持两个单词的名字
$ ./argv David Malan
hello, world

退出状态(Exit Status)

main函数的默认返回值是0,代表一切正常。如果想在命令行输出其他的推出状态,可以设定main函数的返回值。

C
#include <cs50.h>
#include <stdio.h>

int main(int argc, string argv[])
{
    if (argc != 2)
    {
        printf("missing command-line argument\n");
        return 1;
    }
    printf("hello, %s\n", argv[1]);
    return 0;
}

这也是为什么,很多程序异常报错时会出现错误代码:XXX

评论