WEEK 2 Arrays
1. 编译
To those new to programming, this seems fairly simple. A naive compiler might read in every source file, translate everything into machine code, and write out an executable. This could work, but has two serious problems. First, for a large project, the computer may not have enough memory to read all of the source code at once. Second, if you make a change to a single source file, you would rather not have to recompile the entire application.
To deal with these problems, compilers break their job down into steps; for each source file (each .c file), the compiler reads the file, reads the files it references with #include, and translates it to machine code. The result of this is an ”object file” (.o
). Once every object file is made, a ”linker” collects all of the object files and writes the actual program. This way, if you change one source file, only that file needs to be recompiled and then the application needs to be re-linked.
编译的流程:
Preprocessing:加载头文件(
#include
里的文件)(的所需要的Prototype,让接下来出现的函数/变量能够通过编译)。Compiling:将源代码编译成汇编语言(ASM),汇编语言更细,对计算内存和CPU做出了更加细致的指令,可以理解为将一行代码转换成了具体的对于CPU和内存的操作。
不同的CPU会有不同的可识别的一系列操作指令。
Notice that we see some recognisable strings, like
main
,get_string
, andprintf
. But the other lines are instructions for basic operations on memory and values, closer to the binary instructions that a computer’s processor can directly understand.Assembling:将汇编语言翻译成机器语言(0/1)。
It's important to note after discussing the basics that compilation is a ”one way street”. That is, compiling a C source file into machine code is easy, but ”decompiling” (turning machine code into the C source that creates it) is not. Decompilers for C do exist, but the code they create is hard to understand and only useful for reverse engineering
Linking:一个程序的源代码可能包含多个文件,需要讲不同文件的编译结果链接在一起。
The last step is linking, where previously compiled versions of libraries that we included earlier, like
cs50.c
, are actually combined with the compiled binary of our program. In other words, linking is the process of combining all the machine code forhello.c
,cs50.c
, andstdio.c
into our one binary file,a.out
orhello
.
C语言常用的编译器有clang
。
常见参数有:
-o
: 制定输出文件名。默认文件名为xxx.out
。-lxxx
:连接库。一般直接接库名,例如-lcs50
,-lm
(Math)。
Automation: For large C projects, many programmers choose to automate compilation, both in order to reduce user interaction requirements and to speed up the process by only recompiling modified files. On UNIX-like systems, make
and Makefiles
are often used to accomplish the same. make is traditional and flexible and is available as one of the standard developer tools on most Unix and GNU distributions.
2. 内存
之前讲数据类型已经讲了各种数据类型的字节大小。
注意:A Boolean value can technically be represented with just a single bit, but for simplicity our computers use an entire byte.
Inside our computers, we have chips called RAM, random-access memory, that stores zeroes and ones. We can think of bytes stored in RAM as though they were in a grid, one after the other.
In reality, there are millions or billions of bytes per chip.
3. 数组
3.1 数组的定义
为什么要数组?数组里的每个元素在物理内存中是连续存在的,因此可以使用一个表示符就代表整个数组。
并且数组里每一个元素占据相同的物理内存大小,可以使用Indexing的方式找到相应位置的元素。
定义方式:
通常使用循环遍历数组。
特别的,字符数组可以直接通过字符串来定义:
这也意味着,在C里面的字符串本质上都是字符数组,可以通过Indexing来便利每个字符。
注意:在C里面,数组变量和数组对象是绑定的,一旦定义,不能够更换(指针是固定的),单纯的指针变量可以储存的地址。
3.2 多维数组
C 语言支持多维数组。多维数组声明的一般形式如下:
多维数组最简单的形式是二维数组。一个二维数组,在本质上,是一个一维数组的列表。声明一个x
行y
列的二维整型数组,形式如下:
多维数组可以通过在括号内为每行指定值来进行初始化。下面是一个带有 3 行 4 列的数组。
内部嵌套的括号是可选的,下面的初始化与上面是等同的:
二维数组中的元素是通过使用下标(即数组的行索引和列索引)来访问的。例如:
外层的数组其实储存的是指针。
4. 字符
4.1 字符的表示形式。
char
类型即可以是数字形式也可以是字符形式:
运行结果:
默认的字符是\0
,也就是8个0比特。
4.2 大小写转换
可以使用ASCII编码进行转换。
运行结果:
4.3 ctype.h
ctype.h
C 标准库的ctype.h
头文件提供了一些函数,可用于测试和映射字符。
这些函数接受int
作为参数,它的值必须是 EOF 或表示为一个无符号字符。
如果参数c
满足描述的条件,则这些函数返回非零(True)。如果参数c
不满足描述的条件,则这些函数返回零。
除了使用大小于符号,还可以使用ctype.h
库里的函数:
isalnum
- check whether a character is alphanumericisalpha
- check whether a character is alphabeticalisdigit
- check whether a character is a digitislower
- check whether a character is lowercaseisspace
- check whether a character is whitespaceisupper
- check whether a character is uppercasetolower
- convert achar
to lowercasetoupper
- convert achar
to uppercase
5. 字符串
5.1 表示
字符串本质上就是char
的数组,因此cs50.h
里的String可以使用[]
来遍历。
可以使用字符数组来表示字符串。
运行结果:
实际中,每一个String字面量都会以\0
结束(空字符(Null character),又称结束符,缩写NUL),因此长度为n
的字符串,有n + 1
个字符。其他类型数组则没有这个特性。当然,如果显示指定字符数组,则也没有这个特性。
执行结果:
之所以会以\0
结束,是因为字符串的长度是动态的,系统并不知道一个字符串什么时候结束,因此需要使用\0
。
5.2 比较
字符串的比较不能使用 ==
,需要包含string.h
头文件,然后使用函数:
int strcmp(const char *str1, const char *str2)
:把str1
所指向的字符串和str2
所指向的字符串进行比较。
This function returns:
an int less than 0 if s1 comes before s2,
0 if s1 is the same as s2,
an int greater than 0 if s1 comes after s2.
The strings are compared using "ASCIIbetical" order, based on the ASCII values of their characters. For instance, "AAA" would come before "BBB", and "AAA" would also come before "aaa".
执行结果:
5.3 遍历
方式1:使用定位符\0
。
运行结果:
也可以通过此方法获得字符串的长度。
方法2:使用string.h
库里面的strlen()
方法。
运行结果:
5.4 string.h
string.h
string.h
头文件定义了一个变量类型、一个宏和各种操作字符数组的函数。
变量:
size_t
:这是无符号整数类型,它是sizeof
关键字的结果。
宏:
NULL
:这个宏是一个空指针常量的值。
函数:
1
strcpy(s1, s2);
复制字符串 s2 到字符串 s1。
2
strcat(s1, s2);
连接字符串 s2 到字符串 s1 的末尾。
3
strlen(s1);
返回字符串 s1 的长度。
4
strcmp(s1, s2);
如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5
strchr(s1, ch);
返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6
strstr(s1, s2);
返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
7
strcasestr(s1, s2);
返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置,大小写不敏感。
6. 命令行参数
改变main
函数的参数即可。
一般是使用int main(int argc, char* argv[])
,int main(int argc, char ** argv)
,int main(int argc, string argv[])
也可以。
argc
:参数数量。argv
:具体参数。
结果:
实际上argv
的长度并不代表实际长度,如果错误指定Index并不会报错。因此最好在使用argv
先对argc
做检查。
7. Exist Status
我们注意到main
函数会返回一个int
值。
通常0
代表正常(默认),1
代表错误。
不同的应用可以有不同的对于Exist Status的解释。
在命令行查看上次运行结果:
Last updated