CS50 Week 2 Array¶
程序是如何运行的。
编译¶
我们把make hello.c
叫做编译,但实际上make
包含了以下四个步骤:
预处理¶
Preprocessing 的过程就是找到头文件,再替换成对应的代码。
例如,下面是预处理前的代码:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string name = get_string("What's your name? ");
printf("hello, %s\n", name);
}
经过预处理后,代码会变成:
...
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)。汇编语言里面的内容是最基础、最底层的指令。
...
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.c
和stdio.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。取决于字符串的长度。
数组¶
数组可以储存多个相同类型的量。
#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 值。
例如,下面的代码:
#include <stdio.h>
int main(void)
{
char c1 = 'H';
char c2 = 'I';
char c3 = '!';
// 以%i的形式输出字符。
printf("%i %i %i\n", c1, c2, c3);
}
会得到:
字符串¶
字符串的本质是一个数组,它的每一个元素是一个字符。
- 字符串的最后一个元素是一个特殊的字符串:
\0
。
有些库文件可以计算字符串的长度,就不用自己重复编写相同功能的代码了。
#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
函数的写法,使得用户可以在命令行直接输入主函数的参数。
#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
是用户输入单词的数组(存放了所有单词)。
$ make argv
//输入一个单词,也就是没有输入名字
$ ./argv
hello, world
//输入了两个单词,可以作为参数,存到argv[1]中
$ ./argv David
hello, David
//输入了三个单词,程序不能支持两个单词的名字
$ ./argv David Malan
hello, world
退出状态(Exit Status)¶
main
函数的默认返回值是0
,代表一切正常。如果想在命令行输出其他的推出状态,可以设定main
函数的返回值。
#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
。