diff --git a/ex1.md b/ex1.md index 43dffd7..fddf52e 100644 --- a/ex1.md +++ b/ex1.md @@ -6,7 +6,7 @@ 这是你用C写的第一个简单的程序: -``` +```c int main(int argc, char *argv[]) { puts("Hello world."); @@ -17,7 +17,7 @@ int main(int argc, char *argv[]) 把它写进 `ex1.c` 并输入: -``` +```sh $ make ex1 cc ex1.c -o ex1 ``` @@ -28,7 +28,7 @@ cc ex1.c -o ex1 现在你可以运行程序并看到输出。 -``` +```c $ ./ex1 Hello world. ``` @@ -41,7 +41,7 @@ Hello world. 对于这个程序,打开所有编译警告重新构建它: -``` +```sh $ rm ex1 $ CFLAGS="-Wall" make ex1 cc -Wall ex1.c -o ex1 @@ -54,7 +54,7 @@ $ 现在你会得到一个警告,说`puts`函数是隐式声明的。C语言的编译器很智能,它能够理解你想要什么。但是如果可以的话,你应该去除所有编译器警告。把下面一行添加到`ex1.c`文件的最上面,之后重新编译来去除它: -``` +```c #include ``` @@ -64,4 +64,4 @@ $ + 在你的文本编辑器中打开`ex1`文件,随机修改或删除一部分,之后运行它看看发生了什么。 + 再多打印5行文本或者其它比`"Hello world."`更复杂的东西。 -+ 执行`man 3 puts`来阅读这个函数和其它函数的文档。 \ No newline at end of file ++ 执行`man 3 puts`来阅读这个函数和其它函数的文档。 diff --git a/ex10.md b/ex10.md index bc2c4a5..a975763 100644 --- a/ex10.md +++ b/ex10.md @@ -8,7 +8,7 @@ 这一章的有趣之处就是你的程序中已经有一个现成的字符串数组,`main`函数参数中的`char *argv[]`。下面这段代码打印出了所有你传入的命令行参数: -``` +```c include int main(int argc, char *argv[]) @@ -38,7 +38,7 @@ int main(int argc, char *argv[]) `for`循环的格式是这样的: -``` +```c for(INITIALIZER; TEST; INCREMENTER) { CODE; } @@ -90,4 +90,4 @@ for(INITIALIZER; TEST; INCREMENTER) { + 弄清楚在`for`循环的每一部分你都可以放置什么样的代码。 + 查询如何使用`','`(逗号)字符来在`for`循环的每一部分中,`';'`(分号)之间分隔多条语句。 + 查询`NULL`是什么东西,尝试将它用做`states`的一个元素,看看它会打印出什么。 -+ 看看你是否能在打印之前将`states`的一个元素赋值给`argv`中的元素,再试试相反的操作。 \ No newline at end of file ++ 看看你是否能在打印之前将`states`的一个元素赋值给`argv`中的元素,再试试相反的操作。 diff --git a/ex11.md b/ex11.md index 18b0f03..24d55e2 100644 --- a/ex11.md +++ b/ex11.md @@ -10,7 +10,7 @@ 现在用`while`循环来实现和上一个练习相同的函数。这会让你两种循环,看看两种循环是什么关系。 -``` +```c #include int main(int argc, char *argv[]) @@ -42,7 +42,7 @@ int main(int argc, char *argv[]) 你可以看到`while`循环的语法更加简单: -``` +```c while(TEST) { CODE; } @@ -54,7 +54,7 @@ while(TEST) { 输出基本相同,所以我做了一点修改,你可以看到它运行的另一种方式。 -``` +```sh $ make ex11 cc -Wall -g ex11.c -o ex11 $ ./ex11 @@ -88,4 +88,4 @@ $ + 让这些循环倒序执行,通过使用`i--`从`argc`开始递减直到0。你可能需要做一些算数操作让数组的下标正常工作。 + 使用`while`循环将`argv`中的值复制到`states`。 + 让这个复制循环不会执行失败,即使`argv`之中有很多元素也不会全部放进`states`。 -+ 研究你是否真正复制了这些字符串。答案可能会让你感到意外和困惑。 \ No newline at end of file ++ 研究你是否真正复制了这些字符串。答案可能会让你感到意外和困惑。 diff --git a/ex12.md b/ex12.md index 49307df..cf41112 100644 --- a/ex12.md +++ b/ex12.md @@ -6,7 +6,7 @@ `if`语句是每个编程语言中共有的特性,包括C语言。下面是一段代码,使用了`if`语句来确保只传入了一个或两个命令行参数: -``` +```c #include int main(int argc, char *argv[]) @@ -32,7 +32,7 @@ int main(int argc, char *argv[]) `if`语句的格式为: -``` +```c if(TEST) { CODE; } else if(TEST) { @@ -54,17 +54,17 @@ if(TEST) { 这段代码非常易于运行和尝试: -``` +```sh $ make ex12 cc -Wall -g ex12.c -o ex12 $ ./ex12 You only have one argument. You suck. $ ./ex12 one Here's your arguments: -./ex12 one +./ex12 one $ ./ex12 one two Here's your arguments: -./ex12 one two +./ex12 one two $ ./ex12 one two three You have too many arguments. You suck. $ @@ -82,4 +82,4 @@ $ + 我已经向你简短地介绍了`&&`,它执行“与”操作。上网搜索与之不同的“布尔运算符”。 + 为这个程序编写更多的测试用例,看看你会写出什么。 + 回到练习10和11,使用`if`语句使循环提前退出。你需要`break`语句来实现它,搜索它的有关资料。 -+ 第一个判断所输出的话真的正确吗?由于你的“第一个参数”不是用户输入的第一个参数,把它改正。 \ No newline at end of file ++ 第一个判断所输出的话真的正确吗?由于你的“第一个参数”不是用户输入的第一个参数,把它改正。 diff --git a/ex13.md b/ex13.md index 10e0208..eff4223 100644 --- a/ex13.md +++ b/ex13.md @@ -8,7 +8,7 @@ C中的`switch`语句与它们不同,实际上是一个“跳转表”。你只能够放置结果为整数的表达式,而不是一些随机的布尔表达式,这些整数用于计算从`swicth`顶部到匹配部分的跳转。下面有一段代码,我要分解它来让你理解“跳转表”的概念: -``` +```c #include int main(int argc, char *argv[]) @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) 下面是我运行它的一个例子,也演示了传入命令行参数的不同方法: -``` +```sh $ make ex13 cc -Wall -g ex13.c -o ex13 $ ./ex13 @@ -134,4 +134,4 @@ $ + 使用`','`(逗号)在`for`循环中初始化`letter`。 + 使用另一个`for`循环来让它处理你传入的所有命令行参数。 + 将这个`switch`语句转为`if`语句,你更喜欢哪个呢? -+ 在“Y”的例子中,我在`if`代码块外面写了个`break`。这样会产生什么效果?如果把它移进`if`代码块,会发生什么?自己试着解答它,并证明你是正确的。 \ No newline at end of file ++ 在“Y”的例子中,我在`if`代码块外面写了个`break`。这样会产生什么效果?如果把它移进`if`代码块,会发生什么?自己试着解答它,并证明你是正确的。 diff --git a/ex14.md b/ex14.md index 8113a0b..3efa16d 100644 --- a/ex14.md +++ b/ex14.md @@ -7,7 +7,7 @@ 到现在为止,你只使用了作为`stdio.h`头文件一部分的函数。在这个练习中你将要编写并使用自己的函数。 -``` +```c #include #include @@ -80,23 +80,23 @@ ex14.c:38-42 向这个程序传入不同的命令行参数来玩转它,这样会遍历你函数中的所有路径。这里演示了我和它的交互: -``` +```sh $ make ex14 cc -Wall -g ex14.c -o ex14 $ ./ex14 -'e' == 101 'x' == 120 +'e' == 101 'x' == 120 $ ./ex14 hi this is cool -'e' == 101 'x' == 120 -'h' == 104 'i' == 105 -'t' == 116 'h' == 104 'i' == 105 's' == 115 -'i' == 105 's' == 115 -'c' == 99 'o' == 111 'o' == 111 'l' == 108 +'e' == 101 'x' == 120 +'h' == 104 'i' == 105 +'t' == 116 'h' == 104 'i' == 105 's' == 115 +'i' == 105 's' == 115 +'c' == 99 'o' == 111 'o' == 111 'l' == 108 $ ./ex14 "I go 3 spaces" -'e' == 101 'x' == 120 -'I' == 73 ' ' == 32 'g' == 103 'o' == 111 ' ' == 32 ' ' == 32 's' == 115 'p' == 112 'a' == 97 'c' == 99 'e' == 101 's' == 115 +'e' == 101 'x' == 120 +'I' == 73 ' ' == 32 'g' == 103 'o' == 111 ' ' == 32 ' ' == 32 's' == 115 'p' == 112 'a' == 97 'c' == 99 'e' == 101 's' == 115 $ ``` @@ -114,4 +114,4 @@ $ + 重新编写这些函数,使它们的数量减少。比如,你真的需要`can_print_it`吗? + 使用`strlen`函数,让`print_arguments`知道每个字符串参数都有多长,之后将长度传入`print_letters`。然后重写`print_letters`,让它只处理固定的长度,不按照`'\0'`终止符。你需要`#include `来实现它。 + 使用`man`来查询`isalpha`和`isblank`的信息。使用其它相似的函数来只打印出数字或者其它字符。 -+ 上网浏览不同的人喜欢什么样的函数格式。永远不要使用“K&R”语法,因为它过时了,而且容易使人混乱,但是当你碰到一些人使用这种格式时,要理解代码做了什么。 \ No newline at end of file ++ 上网浏览不同的人喜欢什么样的函数格式。永远不要使用“K&R”语法,因为它过时了,而且容易使人混乱,但是当你碰到一些人使用这种格式时,要理解代码做了什么。 diff --git a/ex15.md b/ex15.md index 268796f..a3ac4b9 100644 --- a/ex15.md +++ b/ex15.md @@ -8,7 +8,7 @@ 要想以一种我们可以谈论的方式来讲解指针,我会编写一个无意义的程序,它以三种方式打印了一组人的年龄: -``` +```c #include int main(int argc, char *argv[]) @@ -121,7 +121,7 @@ ex15.c:48-49 在你运行这个程序之后,尝试根据打印出的每一行追溯到代码中产生它们的那一行。在必要情况下,修改`printf`调用来确认你得到了正确的行号: -``` +```shell $ make ex15 cc -Wall -g ex15.c -o ex15 $ ./ex15 @@ -238,4 +238,4 @@ $ + 将获取值和获取地址组合到一起。 + 在程序末尾添加一个`for`循环,打印出这些指针所指向的地址。你需要在`printf`中使用`%p`。 + 对于每一种打印数组的方法,使用函数来重写程序。试着向函数传递指针来处理数据。记住你可以声明接受指针的函数,但是可以像数组那样用它。 -+ 将`for`循环改为`while`循环,并且观察对于每种指针用法哪种循环更方便。 \ No newline at end of file ++ 将`for`循环改为`while`循环,并且观察对于每种指针用法哪种循环更方便。 diff --git a/ex16.md b/ex16.md index c23aa67..82d79e9 100644 --- a/ex16.md +++ b/ex16.md @@ -8,7 +8,7 @@ 像往常一样,下面是我们将要讨论的程序,你应该把它打下来并且使它正常工作: -``` +```c #include #include #include @@ -137,7 +137,7 @@ int main(int argc, char *argv[]) 在你使用描述性注释扩展程序之后,要确保它实际上能够运行,并且产生下面的输出: -``` +```sh $ make ex16 cc -Wall -g ex16.c -o ex16 diff --git a/ex17.md b/ex17.md index 19171a0..08fa3bc 100644 --- a/ex17.md +++ b/ex17.md @@ -8,7 +8,7 @@ 像通常一样,输入下面整个程序,并且使之正常工作,之后我们会进行讨论: -``` +```c #include #include #include @@ -262,7 +262,7 @@ int main(int argc, char *argv[]) 你应该为此花费大量时间,知道你可以测试它能正常工作了。并且你应当用`Valgrind`来确保你在所有地方都正确使用内存。下面是我的测试记录,并且随后使用了`Valgrind`来检查操作: -``` +```sh $ make ex17 cc -Wall -g ex17.c -o ex17 $ ./ex17 db.dat c @@ -334,4 +334,4 @@ $ + 编写一个shell脚本来通过以正确顺序运行命令执行自动化测试。提示:在`bash`顶端使用使用`set -e`,使之在任何命令发生错误时退出。 > 译者注:使用Python编写多行脚本或许更方便一些。 + 尝试重构程序,使用单一的全局变量来储存数据库连接。这个新版本和旧版本比起来如何? -+ 搜索“栈数据结构”,并且在你最喜欢的语言中实现它,然后尝试在C中实现。 \ No newline at end of file ++ 搜索“栈数据结构”,并且在你最喜欢的语言中实现它,然后尝试在C中实现。 diff --git a/ex18.md b/ex18.md index ac367b6..45baf19 100644 --- a/ex18.md +++ b/ex18.md @@ -8,7 +8,7 @@ 函数指针的格式类似这样: -``` +```c int (*POINTER_NAME)(int a, int b) ``` @@ -20,7 +20,7 @@ int (*POINTER_NAME)(int a, int b) 这个方法的关键是,当你完成这些之后,指针的变量名称为`compare_cb`,而你可以将它用作函数。这类似于指向数组的指针可以表示所指向的数组。指向函数的指针也可以用作表示所指向的函数,只不过是不同的名字。 -``` +```c int (*tester)(int a, int b) = sorted_order; printf("TEST: %d is same as %d\n", tester(2, 3), sorted_order(2, 3)); ``` @@ -33,7 +33,7 @@ printf("TEST: %d is same as %d\n", tester(2, 3), sorted_order(2, 3)); 需要解决的下一个问题是使用函数指针向其它函数提供参数比较困难,比如当你打算向其它函数传递回调函数的时候。解决方法是使用`typedef`,它是C的一个关键字,可以给其它更复杂的类型起个新的名字。你需要记住的事情是,将`typedef`添加到相同的指针语法之前,然后你就可以将那个名字用作类型了。我使用下面的代码来演示这一特性: -``` +```c #include #include #include @@ -56,8 +56,8 @@ void die(const char *message) typedef int (*compare_cb)(int a, int b); /** - * A classic bubble sort function that uses the - * compare_cb to do the sorting. + * A classic bubble sort function that uses the + * compare_cb to do the sorting. */ int *bubble_sort(int *numbers, int count, compare_cb cmp) { @@ -102,7 +102,7 @@ int strange_order(int a, int b) } } -/** +/** * Used to test that we are sorting things correctly * by doing the sort and printing it out. */ @@ -217,13 +217,13 @@ ex18.c:109 运行这个程序非常简单,但是你要尝试不同的数字组合,甚至要尝试输入非数字来看看它做了什么: -``` +```sh $ make ex18 cc -Wall -g ex18.c -o ex18 $ ./ex18 4 1 7 3 2 0 8 -0 1 2 3 4 7 8 -8 7 4 3 2 1 0 -3 4 2 7 1 0 8 +0 1 2 3 4 7 8 +8 7 4 3 2 1 0 +3 4 2 7 1 0 8 $ ``` @@ -231,7 +231,7 @@ $ 我打算让你做一些奇怪的事情来使它崩溃,这些函数指针都是类似于其它指针的指针,他们都指向内存的一块区域。C中可以将一种指针的指针转换为另一种,以便以不同方式处理数据。这些通常是不必要的,但是为了想你展示如何侵入你的电脑,我希望你把这段代码添加在`test_sorting`下面: -``` +```c unsigned char *data = (unsigned char *)cmp; for(i = 0; i < 25; i++) { diff --git a/ex19.md b/ex19.md index c610881..1fa4096 100644 --- a/ex19.md +++ b/ex19.md @@ -16,7 +16,7 @@ C预处理器的工作原理是,如果你给它一个文件,比如`.c`文件 一个快速查看预处理器所做事情的方法,是对上个练习中的代码执行下列命令: -``` +```sh cpp ex18.c | less ``` @@ -34,7 +34,7 @@ C编译器与`cpp`的集成十分紧密,这个例子只是向你展示它是 我打算将数据类型和函数声明放在一个单独的头文件中,叫做`object.h`。这个是一个标准的C技巧,可以让你集成二进制库,但其它程序员任然需要编译。在这个文件中,我使用了多个高级的C预处理器技巧,我接下来准备简略地描述它们,并且你会在后续的步骤中看到。 -``` +```c #ifndef _object_h #define _object_h @@ -86,7 +86,7 @@ void *Object_new(size_t size, Object proto, char *description); `object.h`是声明函数和数据类型的地方,它们在`object.c`中被定义(创建),所以接下来: -``` +```c #include #include #include @@ -163,7 +163,7 @@ void *Object_new(size_t size, Object proto, char *description) 上面的代码创建了基本的对象系统,但是你需要编译它和将它链接到`ex19.c`文件,来创建出完整的程序。`object.c`文件本身并没有`main`函数,所以它不可能被编译为完整的程序。下面是一个`Makefile`文件,它基于已经完成的事情来构建程序: -``` +```makefile CFLAGS=-Wall -g all: ex19 @@ -185,7 +185,7 @@ clean: 一旦你编写完成了那些文件,你需要使用对象系统来实现实际的游戏,第一步就是把所有数据类型和函数声明放在`ex19.h`文件中: -``` +```c #ifndef _ex19_h #define _ex19_h @@ -250,7 +250,7 @@ int Map_init(void *self); 编写完函数定义和数据结构之后,我现在就可以实现带有四个房间和一个牛头人的游戏了。 -``` +```c #include #include #include @@ -481,7 +481,7 @@ int main(int argc, char *argv[]) 下面是我自己的游戏的输出: -``` +```shell $ make ex19 cc -Wall -g -c -o object.o object.c cc -Wall -g ex19.c object.o -o ex19 diff --git a/ex2.md b/ex2.md index 70e7a6d..3167493 100644 --- a/ex2.md +++ b/ex2.md @@ -14,7 +14,7 @@ 使用make的第一阶段就是用它已知的方式来构建程序。Make预置了一些知识,来从其它文件构建多种文件。上一个练习中,你已经使用像下面的命令来这样做了: -``` +```sh $ make ex1 # or this one too $ CFLAGS="-Wall" make ex1 @@ -34,7 +34,7 @@ $ CFLAGS="-Wall" make ex1 实际上你可以深入探索使用make的上述方法,但是先让我们来看看`Makefile`,以便让你对make了解得更多一点。首先,创建文件并写入以下内容: -``` +```Makefile CFLAGS=-Wall -g clean: @@ -50,7 +50,7 @@ clean: 确保它和你的`ex1.c`文件在相同的目录中,之后运行以下命令: -``` +```sh $ make clean $ make ex1 ``` @@ -59,7 +59,7 @@ $ make ex1 如果代码能正常工作,你应该看到这些: -``` +```sh $ make clean rm -f ex1 $ make ex1 @@ -81,7 +81,7 @@ $ 上面那些已经足够让你起步了,但是让我们以一种特定的方式来破坏make文件,以便你可以看到发生了什么。找到`rm -f ex1`的那一行并去掉缩进(让它左移),之后你可以看到发生了什么。再次运行`make clean`,你就会得到下面的信息: -``` +```sh $ make clean Makefile:4: *** missing separator. Stop. ``` @@ -94,4 +94,4 @@ Makefile:4: *** missing separator. Stop. + 阅读`man make`来了解关于如何执行它的更多信息。 + 阅读`man cc`来了解关于`-Wall`和`-g`行为的更多信息。 + 在互联网上搜索Makefile文件,看看你是否能改进你的文件。 -+ 在另一个C语言项目中找到`Makefile`文件,并且尝试理解它做了什么。 \ No newline at end of file ++ 在另一个C语言项目中找到`Makefile`文件,并且尝试理解它做了什么。 diff --git a/ex22.md b/ex22.md index 3c83bc2..55e1a9c 100644 --- a/ex22.md +++ b/ex22.md @@ -26,7 +26,7 @@ 你的第一步是创建你自己的`ex22.h`头文件,其中定义了所需的函数和“导出”变量。 -``` +```c #ifndef _ex22_h #define _ex22_h @@ -47,7 +47,7 @@ void print_size(); 最重要的事情是`extern int THE_SIZE`的用法,我将会在你创建完`ex22.c`之后解释它: -``` +```c #include #include "ex22.h" #include "dbg.h" @@ -119,7 +119,7 @@ void print_size() 一旦你写完了上面那些文件,你可以接着编程`main`函数,它会使用所有上面的文件并且演示了一些更多的作用域转换: -``` +```c #include "ex22.h" #include "dbg.h" @@ -241,7 +241,7 @@ ex22_main.c:46-51 这次我想让你手动构建这两个文件,而不是使用你的`Makefile`。于是你可以看到它们实际上如何被编译器放到一起。这是你应该做的事情,并且你应该看到如下输出: -``` +```sh $ cc -Wall -g -DNDEBUG -c -o ex22.o ex22.c $ cc -Wall -g -DNDEBUG ex22_main.c ex22.o -o ex22_main $ ./ex22_main diff --git a/ex23.md b/ex23.md index eb293f9..82b0750 100644 --- a/ex23.md +++ b/ex23.md @@ -12,7 +12,7 @@ 达夫设备由汤姆·达夫“发现”(或创造),它是一个C编译器的小技巧,本来不应该能够正常工作。我并不想告诉你做了什么,因为这是一个谜题,等着你来思考并尝试解决。你需要运行这段代码,之后尝试弄清它做了什么,以及为什么可以这样做。 -``` +```c #include #include #include "dbg.h" @@ -99,7 +99,7 @@ int main(int argc, char *argv[]) memset(to, 'y', 1000); check(valid_copy(to, 1000, 'y'), "Not initialized right."); - // use normal copy to + // use normal copy to rc = normal_copy(from, to, 1000); check(rc == 1000, "Normal copy failed: %d", rc); check(valid_copy(to, 1000, 'x'), "Normal copy failed."); diff --git a/ex24.md b/ex24.md index c587789..3520786 100644 --- a/ex24.md +++ b/ex24.md @@ -6,7 +6,7 @@ 你已经学会了使用`printf`来打印变量,这非常不错,但是还需要学习更多。这个练习中你会用到`fscanf`和`fgets`在结构体重构建关于一个人的信息。在这个关于读取输入的简介之后,你会得到C语言IO函数的完整列表。其中一些你已经见过并且使用过了,所以这个练习也是一个记忆练习。 -``` +```c #include #include "dbg.h" @@ -120,7 +120,7 @@ ex24.c:55-61 当你运行这个程序时,你应该看到你的输入被适当地转换。你应该尝试给它非预期的输入,看看程序是怎么预防它的。 -``` +```sh $ make ex24 cc -Wall -g -DNDEBUG ex24.c -o ex24 $ ./ex24 @@ -186,4 +186,3 @@ Income: 1.234500 + 修改这个程序,使用`scanf`来代替`fscanf`,并观察有什么不同。 + 修改程序,是输入的名字不包含任何换行符和空白字符。 + 使用`scanf`编写函数,按照文件名读取文件内容,每次读取单个字符,但是不要越过(文件和缓冲区的)末尾。使这个函数接受字符串大小来更加通用,并且确保无论什么情况下字符串都以`'\0'`结尾。 - diff --git a/ex25.md b/ex25.md index f7209d2..b7b9f35 100644 --- a/ex25.md +++ b/ex25.md @@ -8,7 +8,7 @@ 理解“变参函数”对于C语言编程并不必要,我在编程生涯中也只有大约20次用到它。但是,理解变参函数如何工作有助于你对它的调试,并且让你更加了解计算机。 -``` +```c /** WARNING: This code is fresh and potentially isn't correct yet. */ #include @@ -161,7 +161,7 @@ error: 当你运行程序时,会得到与下面详细的结果: -``` +```sh $ make ex25 cc -Wall -g -DNDEBUG ex25.c -o ex25 $ ./ex25 diff --git a/ex26.md b/ex26.md index 945aec1..daaa1f6 100644 --- a/ex26.md +++ b/ex26.md @@ -76,7 +76,7 @@ devpkg -B 下面是一个ShellScript,用于安装所需的所有库。你应该手动将它写到一个文件中,之后运行它直到APR安装好并且没有任何错误。 -``` +```sh set -e # go somewhere safe @@ -126,7 +126,7 @@ rm -rf apr-util-1.4.1* apr-1.4.6* 你需要创建一些简单的项目文件来起步。下面是我通常创建一个新项目的方法: -``` +```sh mkdir devpkg cd devpkg touch README Makefile @@ -144,13 +144,13 @@ touch README Makefile > 在一些平台上`bstring.c`文件会出现下列错误: -> ``` +> ```sh > bstrlib.c:2762: error: expected declaration specifiers or '...' before numeric constant > ``` > 这是由于作者使用了一个不好的定义,它在一些平台上不能工作。你需要修改第2759行的`#ifdef __GNUC__`,并把它改成: -> ``` +> ```c > #if defined(__GNUC__) && !defined(__APPLE__) > ``` @@ -162,7 +162,7 @@ touch README Makefile 我们最好从`Makefile`开始,因为它列出了项目如何构建,以及你会创建哪些源文件。 -``` +```Makefile PREFIX?=/usr/local CFLAGS=-g -Wall -I${PREFIX}/apr/include/apr-1 -I${PREFIX}/apr/include/apr-util-1 LDFLAGS=-L${PREFIX}/apr/lib -lapr-1 -pthread -laprutil-1 @@ -236,7 +236,7 @@ clean: 首先,创建`db.h`头文件,以便让你知道需要实现什么。 -``` +```c #ifndef _db_h #define _db_h @@ -254,7 +254,7 @@ int DB_find(const char *url); 之后实现`db.c`中的这些函数,在你编写它的时候,像之前一样使用`make`。 -``` +```c #include #include #include @@ -394,7 +394,7 @@ error: 观察`shell.h`文件来了解我会用到的结构和命令。你可以看到我使用`extern`来表明其他的`.c`文件也能访问到`shell.c`中定义的变量。 -``` +```c #ifndef _shell_h #define _shell_h @@ -430,7 +430,7 @@ extern Shell INSTALL_SH; 确保你已经创建了`shell.h`,并且`extern Shell`变量的名字和数量相同。它们被`Shell_run`和`Shell_exec`函数用于运行命令。我定义了这两个函数,并且在`shell.c`中创建实际变量。 -``` +```c #include "shell.h" #include "dbg.h" #include @@ -570,7 +570,7 @@ Shell INSTALL_SH = { 现在你需要构造正确的命令来完成功能。这些命令会用到APR的函数、`db.h`和`shell.h`来执行下载和构建软件的真正工作。这些文件最为复杂,所以要小心编写它们。你需要首先编写`commands.h`文件,接着在`commands.c`文件中实现它的函数。 -``` +```c #ifndef _commands_h #define _commands_h @@ -607,7 +607,7 @@ int Command_build(apr_pool_t *p, const char *url, const char *configure_opts, `commands.h`中并没有很多之前没见过的东西。你应该看到了一些字符串的定义,它们在任何地方都会用到。真正的代码在`commands.c`中。 -``` +```c #include #include #include @@ -796,7 +796,7 @@ error: `devpkg.c`是最后且最重要的,但是也可能是最简单的文件,其中创建了`main`函数。没有与之配套的`.h`文件,因为这个文件包含其他所有文件。这个文件用于创建`devpkg`可执行程序,同时组装了来自`Makefile`的其它`.o`文件。在文件中输入代码并保证正确。 -``` +```c #include #include #include @@ -920,7 +920,7 @@ error: 你可以执行下列命令来将你的代码与我的对比: -``` +```sh cd .. # get one directory above your current one git clone git://gitorious.org/devpkg/devpkg.git devpkgzed diff -r devpkg devpkgzed diff --git a/ex27.md b/ex27.md index c8fe122..c111397 100644 --- a/ex27.md +++ b/ex27.md @@ -88,7 +88,7 @@ 让我们来看一个坏设计和“更好”的设计的例子。我并不想称之为好设计,因为它可以做得更好。看一看这两个函数,它们都复制字符串,`main`函数用于测试哪个更好。 -``` +```c undef NDEBUG #include "dbg.h" #include diff --git a/ex28.md b/ex28.md index 84da2b0..34cfd23 100644 --- a/ex28.md +++ b/ex28.md @@ -12,7 +12,7 @@ 首先要做的事情是创建一个C的目录框架,并且放置一些多续项目都拥有的,基本的文件和目录。这是我的目录: -``` +```sh $ mkdir c-skeleton $ cd c-skeleton/ $ touch LICENSE README.md Makefile @@ -76,7 +76,7 @@ $ 我要讲到的第一件事情就是Makefile,因为你可以从中了解其它东西的情况。这个练习的Makeile比之前更加详细,所以我会在你输入它之后做详细的分解。 -``` +```makefile CFLAGS=-g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG $(OPTFLAGS) LIBS=-ldl $(OPTLIBS) PREFIX?=/usr/local @@ -134,7 +134,7 @@ BADFUNCS='[^_.>a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)|stpn?cpy|a? check: @echo Files with potentially dangerous functions. @egrep $(BADFUNCS) $(SOURCES) || true - + ``` 要记住你应该使用一致的Tab字符来缩进Makefile。你的编辑器应该知道怎么做,但是如果不是这样你可以换个编辑器。没有程序员会使用一个连如此简单的事情都做不好的编辑器。 @@ -177,7 +177,7 @@ Makefile:11 这就是Makefile的头部了,但是我应该对“让其他人扩展构建”做个解释。你在运行它的时候可以这样做: -``` +```shell # WARNING! Just a demonstration, won't really work right now. # this installs the library into /tmp $ make PREFIX=/tmp install @@ -249,7 +249,7 @@ Makefile:34-35 你需要为单元测试创建一个小型的shell脚本,它知道如何运行程序。我们开始创建这个`tests/runtests.sh`脚本: -``` +```shell echo "Running unit tests:" for i in tests/*_tests @@ -331,10 +331,10 @@ Makefile:53 我在完成这个项目框架目录的构建之前,还设置了两个额外的练习。下面这是我对`Makefile`特性的测试结果: -``` +```sh $ make clean rm -rf build -rm -f tests/tests.log +rm -f tests/tests.log find . -name "*.gc*" -exec rm {} \; rm -rf `find . -name "*.dSYM" -print` $ make check @@ -342,7 +342,7 @@ Files with potentially dangerous functions. ^Cmake: *** [check] Interrupt: 2 $ make -ar rcs build/libYOUR_LIBRARY.a +ar rcs build/libYOUR_LIBRARY.a ar: no archive members specified usage: ar -d [-TLsv] archive file ... ar -m [-TLsv] archive file ... @@ -356,7 +356,7 @@ usage: ar -d [-TLsv] archive file ... make: *** [build/libYOUR_LIBRARY.a] Error 1 $ make valgrind VALGRIND="valgrind --log-file=/tmp/valgrind-%p.log" make -ar rcs build/libYOUR_LIBRARY.a +ar rcs build/libYOUR_LIBRARY.a ar: no archive members specified usage: ar -d [-TLsv] archive file ... ar -m [-TLsv] archive file ... diff --git a/ex29.md b/ex29.md index f453d3e..74135cf 100644 --- a/ex29.md +++ b/ex29.md @@ -30,7 +30,7 @@ C中的库有两种基本类型: 我创建了两个源文件里完成它。一个用于侯建`libex29.so`库,另一个是个叫做`ex29`的程序,它可以加载这个库并运行其中的程序、 -``` +```c #include #include #include "dbg.h" @@ -82,7 +82,7 @@ int fail_on_purpose(const char *msg) 我们打算使用`dlopen`,`dlsym`,和`dlclose`函数来处理上面的函数。 -``` +```c #include #include "dbg.h" #include @@ -150,7 +150,7 @@ ex29.c:26 既然你已经知道这些文件做什么了,下面是我的shell会话,用于构建`libex29.so`和`ex29`并随后运行它。下面的代码中你可以学到如何手动构建: -``` +```shell # compile the lib file and make the .so # you may need -fPIC here on some platforms. add that if you get an error $ cc -c libex29.c -o libex29.o @@ -177,7 +177,7 @@ $ ./ex29 ./libex29.so fail_on_purpose # try calling a function that is not there $ ./ex29 ./libex29.so adfasfasdf asdfadff -[ERROR] (ex29.c:20: errno: None) Did not find adfasfasdf +[ERROR] (ex29.c:20: errno: None) Did not find adfasfasdf function in the library libex29.so: dlsym(0x1076009b0, adfasfasdf): symbol not found # try loading a .so that is not there @@ -209,4 +209,3 @@ $ + 使用项目框架目录,并且为这个练习创建新的项目。将`libex29.c`放入`src/`目录,修改`Makefile`使它能够构建`build/libex29.so`。 + 将`ex29.c`改为`tests/ex29_tests.c`,使它做为单元测试执行。使它能够正常工作,意思是你需要修改它让它加载`build/libex29.so`文件,并且运行上面我手写的测试。 + 阅读`man dlopen`文档,并且查询所有有关函数。尝试`dlopen`的其它选项,比如`RTLD_NOW`。 - diff --git a/ex3.md b/ex3.md index d56e4cd..23fd756 100644 --- a/ex3.md +++ b/ex3.md @@ -8,7 +8,7 @@ 许多编程语言都使用了C风格的格式化输出,所以让我们尝试一下: -``` +```c #include int main() @@ -40,7 +40,7 @@ int main() 当你做完上面的整个步骤,你应该看到这些东西: -``` +```shell $ make ex3 cc -Wall -g ex3.c -o ex3 $ ./ex3 @@ -69,7 +69,7 @@ $ + 运行新的程序,它会崩溃,或者打印出奇怪的年龄。 + 将`printf`恢复原样,并且去掉`age`的初值,将那一行改为`int age;`,之后重新构建并运行。 -``` +```shell # edit ex3.c to break printf $ make ex3 cc -Wall -g ex3.c -o ex3 @@ -95,4 +95,4 @@ $ + 找到尽可能多的方法使`ex3`崩溃。 + 执行`man 3 printf`来阅读其它可用的'%'格式化占位符。如果你在其它语言中使用过它们,应该看着非常熟悉(它们来源于`printf`)。 + 将`ex3`添加到你的`Makefile`的`all`列表中。到目前为止,可以使用`make clean all`来构建你所有的练习。 -+ 将`ex3`添加到你的`Makefile`的`clean`列表中。当你需要的时候使用`make clean`可以删除它。 \ No newline at end of file ++ 将`ex3`添加到你的`Makefile`的`clean`列表中。当你需要的时候使用`make clean`可以删除它。 diff --git a/ex30.md b/ex30.md index fff6623..c283386 100644 --- a/ex30.md +++ b/ex30.md @@ -8,7 +8,7 @@ 我接下来打算使用,并且你会包含进框架目录的框架,叫做“minunit”,它以[Jera Design](http://www.jera.com/techinfo/jtns/jtn002.html)所编写的一小段代码作为开始,之后我扩展了它,就像这样: -``` +```c #undef NDEBUG #ifndef _minunit_h #define _minunit_h @@ -52,7 +52,7 @@ int tests_run; 首先我们需要创建一个简单的空单元测试,命名为`tests/libex29_tests.c`,在里面输入: -``` +```c #include "minunit.h" char *test_dlopen() @@ -129,13 +129,13 @@ libex29_tests.c:38 这就是用于运行测试所有代码了,现在你需要尝试使它运行在项目框架中。下面是我的执行结果: -``` +```shell not printable ``` 我首先执行`make clean`,之后我运行了构建,它将模板改造为`libYOUR_LIBRARY.a`和`libYOUR_LIBRARY.so`文件。要记住你需要在练习29的附加题中完成它。但如果你没有完成的话,下面是我所使用的`Makefile`的文件差异: -``` +```diff diff --git a/code/c-skeleton/Makefile b/code/c-skeleton/Makefile index 135d538..21b92bf 100644 --- a/code/c-skeleton/Makefile @@ -166,7 +166,7 @@ index 135d538..21b92bf 100644 完成这些改变后,你现在应该能够构建任何东西,并且你可以最后补完剩余的单元测试函数: -``` +```c #include "minunit.h" #include diff --git a/ex31.md b/ex31.md index 2986c61..b47864c 100644 --- a/ex31.md +++ b/ex31.md @@ -47,7 +47,7 @@ 我将在这个练习中调试下面这个程序,它只有一个不会正常终止的`while`循环。我在里面放置了一个`usleep`调用,使它循环起来更加有趣。 -``` +```c #include int main(int argc, char *argv[]) @@ -122,7 +122,7 @@ quit 下面是一段会话,我对`ex31`做了上述事情,单步执行它,之后修改`while`循环并使它退出。 -``` +```sh $ ps ax | grep ex31 10026 s000 S+ 0:00.11 ./ex31 10036 s001 R+ 0:00.00 grep ex31 @@ -176,7 +176,7 @@ $2 = 0 8 while(i < 100) { 9 usleep(3000); 10 } -11 +11 12 return 0; (gdb) set var i = 200 diff --git a/ex32.md b/ex32.md index c0db3a3..b41cc5a 100644 --- a/ex32.md +++ b/ex32.md @@ -33,7 +33,7 @@ C中还有其它样式的数据结构,但是这个模式效果很好,并且 你已经实现了`c-skeleton`(项目框架目录),使用它来创建一个`liblcthw`项目: -``` +```sh $ cp -r c-skeleton liblcthw $ cd liblcthw/ $ ls @@ -47,7 +47,7 @@ $ vim tests/minunit.h $ rm src/libex29.* tests/libex29* $ make clean rm -rf build tests/libex29_tests -rm -f tests/tests.log +rm -f tests/tests.log find . -name "*.gc*" -exec rm {} \; rm -rf `find . -name "*.dSYM" -print` $ ls tests/ @@ -79,7 +79,7 @@ $ 正如在这个练习的介绍部分所说,整个过程的第一步,是编程一个头文件,带有正确的C结构定义。 -``` +```c #ifndef lcthw_List_h #define lcthw_List_h @@ -139,7 +139,7 @@ void *List_remove(List *list, ListNode *node); 一旦你理解了它们之后,你很可能理解了双向链表如何工作。它只是带有两个指针的节点,指向链表中前一个和后一个元素。接下来你可以编写`src/lcthw/list.c`中的代码,来理解每个操作如何实现。 -``` +```c #include #include @@ -326,7 +326,7 @@ list.c:List_remove 在你编译它们之前,需要创建测试来确保它们正确执行。 -``` +```c #include "minunit.h" #include #include @@ -397,7 +397,7 @@ char *test_unshift() char *test_remove() { - // we only need to test the middle remove case since push/shift + // we only need to test the middle remove case since push/shift // already tests the other cases char *val = List_remove(list, list->first->next); @@ -448,7 +448,7 @@ RUN_TESTS(all_tests); 如果你正确完成了每件事,当你执行构建并且运行单元测试是,你会看到: -``` +```sh $ make cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -fPIC -c -o src/lcthw/list.o src/lcthw/list.c ar rcs build/liblcthw.a src/lcthw/list.o diff --git a/ex33.md b/ex33.md index 0edb904..10bc2f7 100644 --- a/ex33.md +++ b/ex33.md @@ -29,7 +29,7 @@ 下面是你应该通过的单元测试: -``` +```c #include "minunit.h" #include #include @@ -126,7 +126,7 @@ RUN_TESTS(all_tests); 你作弊了吗?之后的练习中,我只会给你单元测试,并且让自己实现它。对于你来说,不看这段代码知道你自己实现它是一种很好的练习。下面是`list_algos.c`和`list_algos.h`的代码: -``` +```c #ifndef lcthw_List_algos_h #define lcthw_List_algos_h @@ -141,7 +141,7 @@ List *List_merge_sort(List *list, List_compare cmp); #endif ``` -``` +```c #include #include @@ -239,10 +239,10 @@ List *List_merge_sort(List *list, List_compare cmp) 如果一切都正常工作,你会看到这些: -``` +```sh $ make clean all rm -rf build src/lcthw/list.o src/lcthw/list_algos.o tests/list_algos_tests tests/list_tests -rm -f tests/tests.log +rm -f tests/tests.log find . -name "*.gc*" -exec rm {} \; rm -rf `find . -name "*.dSYM" -print` cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -fPIC -c -o src/lcthw/list.o src/lcthw/list.c diff --git a/ex34.md b/ex34.md index 1df5b0c..c152b78 100644 --- a/ex34.md +++ b/ex34.md @@ -10,7 +10,7 @@ 我会给你头文件作为起始,你需要为实现打下它们: -``` +```c #ifndef _DArray_h #define _DArray_h #include @@ -97,7 +97,7 @@ error: 之后我会修改代码,并且让你创建`DArray`的单元测试。 -``` +```c #include "minunit.h" #include @@ -225,7 +225,7 @@ RUN_TESTS(all_tests); 这向你展示了所有操作都如何使用,它会使`DArray`的实现变得容易: -``` +```c #include #include diff --git a/ex35.md b/ex35.md index 572a6b3..5be4f5c 100644 --- a/ex35.md +++ b/ex35.md @@ -8,7 +8,7 @@ 然而,我是一个懒人,大多数C标准库都实现了堆排序、快速排序和归并排序算法,你可以直接使用它们: -``` +```c #include #include @@ -31,7 +31,7 @@ int DArray_mergesort(DArray *array, DArray_compare cmp) 这就是`darray_algos.c`文件的整个实现,它在大多数现代Unix系统上都能运行。它们的每一个都使用`DArray_compare`对`contents`中储存的无类型指针进行排序。我也要向你展示这个头文件: -``` +```c #ifndef darray_algos_h #define darray_algos_h @@ -50,7 +50,7 @@ int DArray_mergesort(DArray *array, DArray_compare cmp); 大小几乎一样,你也应该能预料到。接下来你可以了解单元测试中这三个函数如何使用: -``` +```c #include "minunit.h" #include @@ -140,7 +140,7 @@ RUN_TESTS(all_tests); 下面是为新算法创建的头文件,其中也含有数据结构: -``` +```c #ifndef _radixmap_h #include @@ -186,7 +186,7 @@ int RadixMap_delete(RadixMap *map, RMElement *el); 首先让我向你展示如何使用C联合体构造可变类型: -``` +```c #include typedef enum { @@ -254,7 +254,7 @@ int main(int argc, char *argv[]) 接下来是实际的`RadixMap`对于这些操作的实现: -``` +```c /* * Based on code by Andre Reinald then heavily modified by Zed A. Shaw. */ @@ -397,7 +397,7 @@ error: 像往常一样键入它并使它通过单元测试,之后我会解释它。尤其要注意`radix_sort`函数,我实现它的方法非常特别。 -``` +```c #include "minunit.h" #include #include diff --git a/ex36.md b/ex36.md index 348e093..e33cebf 100644 --- a/ex36.md +++ b/ex36.md @@ -21,7 +21,7 @@ > 译者注:检验C风格字符串是否有效等价于“停机问题”,这是一个非常著名的不可解问题。 -``` +```c void copy(char to[], char from[]) { int i = 0; @@ -66,7 +66,7 @@ int safercopy(int from_len, char *from, int to_len, char *to) 下面是我在`liblcthw`项目目录里所做的事情: -``` +```sh $ mkdir bstrlib $ cd bstrlib/ $ unzip ~/Downloads/bstrlib-05122010.zip @@ -87,7 +87,7 @@ $ ``` 在第14行你可以看到,我编辑了`bstrlib.c`文件,来将它移动到新的位置,并且修复OSX上的bug。下面是差异: -``` +```diff 25c25 < #include "bstrlib.h" --- diff --git a/ex37.md b/ex37.md index ad9fb85..b41754d 100644 --- a/ex37.md +++ b/ex37.md @@ -8,7 +8,7 @@ 下面是哈希表(也叫作字典)的一个使用示例: -``` +```c fruit_weights = {'Apples': 10, 'Oranges': 100, 'Grapes': 1.0} for key, value in fruit_weights.items(): @@ -17,7 +17,7 @@ for key, value in fruit_weights.items(): 几乎所有现代语言都具备这种特性,所以许多人写完代码都不知道它实际上如何工作。通过在C中创建`Hashmap`数据结构,我会向你展示它的工作原理。我会从头文件开始,来谈论整个数据结构。 -``` +```c #ifndef _lcthw_Hashmap_h #define _lcthw_Hashmap_h @@ -91,7 +91,7 @@ void *Hashmap_delete(Hashmap *map, void *key); 有文件的剩余部分没有新的东西,所以我现在可以向你展示`hashmap.c`的实现了: -``` +```c #undef NDEBUG #include #include @@ -103,7 +103,7 @@ static int default_compare(void *a, void *b) return bstrcmp((bstring)a, (bstring)b); } -/** +/** * Simple Bob Jenkins's hash algorithm taken from the * wikipedia description. */ @@ -333,7 +333,7 @@ void *Hashmap_delete(Hashmap *map, void *key) 最后你需要编写单元测试,对于所有这些操作: -``` +```c #include "minunit.h" #include #include diff --git a/ex38.md b/ex38.md index fbd440c..552c4af 100644 --- a/ex38.md +++ b/ex38.md @@ -22,7 +22,7 @@ DJB Hash 头文件非常简单,所以我以它开始: -``` +```c #ifndef hashmap_algos_h #define hashmap_algos_h @@ -39,7 +39,7 @@ uint32_t Hashmap_djb_hash(void *data); 我只是声明了三个函数,我会在`hashmap_algos.c`文件中实现它们: -``` +```c #include #include @@ -99,7 +99,7 @@ uint32_t Hashmap_djb_hash(void *data) 接着我为每个算法编写了单元测试,同时也测试了它们在多个桶中的分布情况。 -``` +```c #include #include #include @@ -260,7 +260,7 @@ RUN_TESTS(all_tests); 下面是一个简略的shell会话,向你展示了我如何运行`1tests/hashmap_algos_test`来获取`test_distribution`产生的表(这里没有展示),之后使用R来观察统计结果: -``` +```sh $ tests/hashmap_algos_tests # copy-paste the table it prints out $ vim hash.txt diff --git a/ex39.md b/ex39.md index 4ad1142..93bc3d5 100644 --- a/ex39.md +++ b/ex39.md @@ -10,7 +10,7 @@ 首先,创建头文件: -``` +```c #ifndef string_algos_h #define string_algos_h @@ -49,7 +49,7 @@ StringScanner_scan 一旦你完成了头文件,下面就是实现了: -``` +```c #include #include @@ -197,7 +197,7 @@ void StringScanner_destroy(StringScanner *scan) 最后,我编写了单元测试来确保算法有效,之后在它的注释部分,我为三个搜索函数运行了简单的性能测试: -``` +```c #include "minunit.h" #include #include @@ -339,20 +339,20 @@ RUN_TESTS(all_tests); 当我在我的笔记本上运行测试时,我得到的数据是这样的: -``` +```sh $ ./tests/string_algos_tests DEBUG tests/string_algos_tests.c:124: ----- RUNNING: ./tests/string_algos_tests ---- RUNNING: ./tests/string_algos_tests -DEBUG tests/string_algos_tests.c:116: +DEBUG tests/string_algos_tests.c:116: ----- test_find_and_scan -DEBUG tests/string_algos_tests.c:117: +DEBUG tests/string_algos_tests.c:117: ----- test_scan_performance DEBUG tests/string_algos_tests.c:105: SCAN COUNT: 110272000, END TIME: 2, OPS: 55136000.000000 -DEBUG tests/string_algos_tests.c:118: +DEBUG tests/string_algos_tests.c:118: ----- test_find_performance DEBUG tests/string_algos_tests.c:76: FIND COUNT: 12710000, END TIME: 2, OPS: 6355000.000000 -DEBUG tests/string_algos_tests.c:119: +DEBUG tests/string_algos_tests.c:119: ----- test_binstr_performance DEBUG tests/string_algos_tests.c:54: BINSTR COUNT: 72736000, END TIME: 2, OPS: 36368000.000000 ALL TESTS PASSED @@ -362,7 +362,7 @@ $ 我看到了它,觉得每轮运行应该超过两秒。并且,我打算多次运行它,并且像之前一样使用R来验证。下面是我获得的10个样例,每个基本上是10秒: -``` +```r scan find binstr 71195200 6353700 37110200 75098000 6358400 37420800 @@ -378,7 +378,7 @@ scan find binstr 我在shell的一点点帮助下获取数据,之后编辑输出: -``` +```shell $ for i in 1 2 3 4 5 6 7 8 9 10; do echo "RUN --- $i" >> times.log; ./tests/string_algos_tests 2>&1 | grep COUNT >> times.log ; done $ less times.log $ vim times.log @@ -386,7 +386,7 @@ $ vim times.log 现在你可以看到`scan`系统要优于另外两个,但是我会在R中打开它并且验证结果: -``` +```rebol > times <- read.table("times.log", header=T) > summary(times) scan find binstr @@ -433,4 +433,3 @@ $ vim times.log + 修改单元测试,使它最开始执行每个函数一小段时间,来消除任何“热身”缓解。这样会修改所运行时长的依赖性吗?每秒可能出现多少次操作? + 使单元测试中的所查找字符串随机化,之后测量你的得到的性能。一种实现它的方式就是使用`bstrlib.h`中的`bsplit`函数在空格处分割`IN_STR`。之后使用你得到的`strList`结构访问它返回的每个字符串。这也教给你如何使用`bstrList`操作进行字符串处理。 + 尝试一些不同顺序的测试,看看能否得到不同的结果。 - diff --git a/ex4.md b/ex4.md index 629938a..b853431 100644 --- a/ex4.md +++ b/ex4.md @@ -26,7 +26,7 @@ 下面是执行以上步骤的脚本,我想让你复制它: -``` +```sh # 1) Download it (use wget if you don't have curl) curl -O http://valgrind.org/downloads/valgrind-3.6.1.tar.bz2 @@ -51,13 +51,13 @@ sudo make install 按照这份脚本,但是如果 `Valgrind` 有新的版本请更新它。如果它不能正常执行,也请试着深入研究原因。 -## 使用 Valgrind +## 使用 Valgrind 使用 `Valgrind` 十分简单,只要执行`valgrind theprogram`,它就会运行你的程序,随后打印出你的程序运行时出现的所有错误。在这个练习中,我们会崩溃在一个错误输出上,然后会修复它。 首先,这里有一个`ex3.c`的故意出错的版本,叫做`ex4.c`。出于练习目的,将它再次输入到文件中: -``` +```c #include /* Warning: This program is wrong on purpose. */ @@ -83,7 +83,7 @@ int main() 现在我们像通常一样构建它,但是不要直接运行,而是使用`Valgrind`来运行它(见源码:"使用Valgrind构建并运行 ex4.c"): -``` +```sh $ make ex4 cc -Wall -g ex4.c -o ex4 ex4.c: In function 'main': @@ -95,38 +95,38 @@ $ valgrind ./ex4 ==3082== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al. ==3082== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info ==3082== Command: ./ex4 -==3082== +==3082== I am -16775432 years old. ==3082== Use of uninitialised value of size 8 ==3082== at 0x4E730EB: _itoa_word (_itoa.c:195) ==3082== by 0x4E743D8: vfprintf (vfprintf.c:1613) ==3082== by 0x4E7E6F9: printf (printf.c:35) ==3082== by 0x40052B: main (ex4.c:11) -==3082== +==3082== ==3082== Conditional jump or move depends on uninitialised value(s) ==3082== at 0x4E730F5: _itoa_word (_itoa.c:195) ==3082== by 0x4E743D8: vfprintf (vfprintf.c:1613) ==3082== by 0x4E7E6F9: printf (printf.c:35) ==3082== by 0x40052B: main (ex4.c:11) -==3082== +==3082== ==3082== Conditional jump or move depends on uninitialised value(s) ==3082== at 0x4E7633B: vfprintf (vfprintf.c:1613) ==3082== by 0x4E7E6F9: printf (printf.c:35) ==3082== by 0x40052B: main (ex4.c:11) -==3082== +==3082== ==3082== Conditional jump or move depends on uninitialised value(s) ==3082== at 0x4E744C6: vfprintf (vfprintf.c:1613) ==3082== by 0x4E7E6F9: printf (printf.c:35) ==3082== by 0x40052B: main (ex4.c:11) -==3082== +==3082== I am 0 inches tall. -==3082== +==3082== ==3082== HEAP SUMMARY: ==3082== in use at exit: 0 bytes in 0 blocks ==3082== total heap usage: 0 allocs, 0 frees, 0 bytes allocated -==3082== +==3082== ==3082== All heap blocks were freed -- no leaks are possible -==3082== +==3082== ==3082== For counts of detected and suppressed errors, rerun with: -v ==3082== Use --track-origins=yes to see where uninitialised values come from ==3082== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 4 from 4) @@ -173,7 +173,7 @@ $ 这段信息读起来会相当多,下面是你的处理方法: -+ 无论什么时候你运行C程序并且使它工作,都应该使用`Valgrind`重新运行它来检查。 ++ 无论什么时候你运行C程序并且使它工作,都应该使用`Valgrind`重新运行它来检查。 + 对于得到的每个错误,找到“源码:行数”提示的位置,然后修复它。你可以上网搜索错误信息,来弄清楚它的意思。 + 一旦你的程序在`Valgrind`下不出现任何错误信息,应该就好了。你可能学会了如何编写代码的一些技巧。 @@ -184,4 +184,4 @@ $ + 按照上面的指导,使用`Valgrind`和编译器修复这个程序。 + 在互联网上查询`Valgrind`相关的资料。 + 下载另一个程序并手动构建它。尝试一些你已经使用,但从来没有手动构建的程序。 -+ 看看`Valgrind`的源码是如何在目录下组织的,并且阅读它的Makefile文件。不要担心,这对我来说没有任何意义。 \ No newline at end of file ++ 看看`Valgrind`的源码是如何在目录下组织的,并且阅读它的Makefile文件。不要担心,这对我来说没有任何意义。 diff --git a/ex40.md b/ex40.md index 53b7b6b..ba7090d 100644 --- a/ex40.md +++ b/ex40.md @@ -10,7 +10,7 @@ 在我真正解释它的工作原理之前,让我向你展示`bstree.h`头文件,便于你看到数据结构,之后我会用它来解释如何构建。 -``` +```c #ifndef _lcthw_BSTree_h #define _lcthw_BSTree_h @@ -68,7 +68,7 @@ void *BSTree_delete(BSTree *map, void *key); 花一些时间在纸上画出一些树并且遍历一些节点来进行查找或设置,你就可以理解它如何工作。之后你要准备好来看一看实现,我在其中解释了删除操作。删除一个节点非常麻烦,因此它最适合逐行的代码分解。 -``` +```c #include #include #include @@ -399,7 +399,7 @@ bstree.c:210 最后,你可以查看它的单元测试以及测试方法: -``` +```c #include "minunit.h" #include #include diff --git a/ex41.md b/ex41.md index fe7b89c..acd14f5 100644 --- a/ex41.md +++ b/ex41.md @@ -12,16 +12,16 @@ 为了运行Callgrind,你需要向`valgrind`传入`--tool=callgrind`选项,之后它会产生`callgrind.out.PID`文件(其中PID为所运行程序的进程PID)。一旦你这样运行了,你就可以使用一个叫做`callgrind_annotate`的工具分析`callgrind.out`文件,它会告诉你哪个函数运行中使用了最多的指令。下面是个例子,我在`bstree_tests`上运行了`callgrind`,之后得到了这个信息: -``` +```sh $ valgrind --dsymutil=yes --tool=callgrind tests/bstree_tests ... $ callgrind_annotate callgrind.out.1232 -------------------------------------------------------------------------------- Profile data file 'callgrind.out.1232' (creator: callgrind-3.7.0.SVN) -------------------------------------------------------------------------------- -I1 cache: -D1 cache: -LL cache: +I1 cache: +D1 cache: +LL cache: Timerange: Basic block 0 - 1098689 Trigger: Program termination Profiled target: tests/bstree_tests (PID 1232, part 1) @@ -34,7 +34,7 @@ User annotated: Auto-annotation: off -------------------------------------------------------------------------------- - Ir + Ir -------------------------------------------------------------------------------- 4,605,808 PROGRAM TOTALS @@ -74,7 +74,7 @@ $ 下一步我使用`callgrind_annotate`输出`bstree.c`文件,并且使用所带有的`Ir`对每一行做注解。你可以通过运行下面的命令来得到注解后的源文件: -``` +```sh $ callgrind_annotate callgrind.out.1232 src/lcthw/bstree.c ... ``` diff --git a/ex42.md b/ex42.md index bcec473..f369e06 100644 --- a/ex42.md +++ b/ex42.md @@ -12,7 +12,7 @@ 我将会向你展示单元测试,你需要实现头文件来让它们正常工作。你不能创建`stack.c` 或 `queue.c`实现文件来通过测试,只能使用`stack.h` 和 `queue.h`来使测试运行。 -``` +```c #include "minunit.h" #include #include @@ -77,7 +77,7 @@ RUN_TESTS(all_tests); 之后是`queue_tests.c`,几乎以相同的方式来使用`Queue`: -``` +```c #include "minunit.h" #include #include @@ -142,19 +142,19 @@ RUN_TESTS(all_tests); 你应该在不修改测试文件的条件下,使单元测试能够运行,并且它应该能够通过`valgrind`而没有任何内存错误。下面是当我直接运行`stack_tests`时它的样子: -``` +```sh $ ./tests/stack_tests DEBUG tests/stack_tests.c:60: ----- RUNNING: ./tests/stack_tests ---- RUNNING: ./tests/stack_tests -DEBUG tests/stack_tests.c:53: +DEBUG tests/stack_tests.c:53: ----- test_create -DEBUG tests/stack_tests.c:54: +DEBUG tests/stack_tests.c:54: ----- test_push_pop DEBUG tests/stack_tests.c:37: VAL: test3 data DEBUG tests/stack_tests.c:37: VAL: test2 data DEBUG tests/stack_tests.c:37: VAL: test1 data -DEBUG tests/stack_tests.c:55: +DEBUG tests/stack_tests.c:55: ----- test_destroy ALL TESTS PASSED Tests run: 3 diff --git a/ex43.md b/ex43.md index 15329ff..dbe563d 100644 --- a/ex43.md +++ b/ex43.md @@ -40,14 +40,14 @@ 我将会使用R来验证这些计算,因为我知道R能够计算正确。 -``` +```r > s <- runif(n=10, max=10) > s [1] 6.1061334 9.6783204 1.2747090 8.2395131 0.3333483 6.9755066 1.0626275 [8] 7.6587523 4.9382973 9.5788115 > summary(s) - Min. 1st Qu. Median Mean 3rd Qu. Max. - 0.3333 2.1910 6.5410 5.5850 8.0940 9.6780 + Min. 1st Qu. Median Mean 3rd Qu. Max. + 0.3333 2.1910 6.5410 5.5850 8.0940 9.6780 > sd(s) [1] 3.547868 > sum(s) @@ -107,7 +107,7 @@ lines 20-21 这就是计算`stddev`的方法,现在我可以编写一些简单的代码来实现这一计算。 -``` +```c #ifndef lcthw_stats_h #define lctwh_stats_h @@ -136,7 +136,7 @@ void Stats_dump(Stats *st); 这里你可以看到我将所需的统计量放入一个struct,并且创建了用于处理样本和获得数值的函数。实现它只是转换数字的一个练习: -``` +```c #include #include #include @@ -226,7 +226,7 @@ Stats_dump 我需要干的最后一件事,就是确保这些运算正确。我打算使用我的样本,以及来自于R会话中的计算结果创建单元测试,来确保我会得到正确的结果。 -``` +```c #include "minunit.h" #include #include diff --git a/ex44.md b/ex44.md index 26ff01b..c6243a5 100644 --- a/ex44.md +++ b/ex44.md @@ -6,7 +6,7 @@ 环形缓冲区在处理异步IO时非常实用。它们可以在一端接收随机长度和区间的数据,在另一端以相同长度和区间提供密致的数据块。它们是`Queue`数据结构的变体,但是它针对于字节块而不是一系列指针。这个练习中我打算向你展示`RingBuffer`的代码,并且之后你需要对它执行完整的单元测试。 -``` +```c #ifndef _lcthw_RingBuffer_h #define _lcthw_RingBuffer_h @@ -64,7 +64,7 @@ bstring RingBuffer_gets(RingBuffer *buffer, int amount); 下面是它的实现,它是对工作原理更好的解释: -``` +```c #undef NDEBUG #include #include @@ -163,16 +163,16 @@ error: 下面是我的`ringbuffer_tests`运行结果: -``` +```sh $ ./tests/ringbuffer_tests DEBUG tests/ringbuffer_tests.c:60: ----- RUNNING: ./tests/ringbuffer_tests ---- RUNNING: ./tests/ringbuffer_tests -DEBUG tests/ringbuffer_tests.c:53: +DEBUG tests/ringbuffer_tests.c:53: ----- test_create -DEBUG tests/ringbuffer_tests.c:54: +DEBUG tests/ringbuffer_tests.c:54: ----- test_read_write -DEBUG tests/ringbuffer_tests.c:55: +DEBUG tests/ringbuffer_tests.c:55: ----- test_destroy ALL TESTS PASSED Tests run: 3 diff --git a/ex45.md b/ex45.md index c1d62dd..1a40da1 100644 --- a/ex45.md +++ b/ex45.md @@ -10,26 +10,26 @@ 首先,为程序添加一些变量,就像单元测试的`TESTS`和`TEST_SRC`变量: -``` +```Makefile PROGRAMS_SRC=$(wildcard bin/*.c) PROGRAMS=$(patsubst %.c,%,$(PROGRAMS_SRC)) ``` 之后你可能想要添加`PROGRAMS`到所有目标中: -``` +```makefile all: $(TARGET) $(SO_TARGET) tests $(PROGRAMS) ``` 之后在`clean`目标中向`rm`那一行添加`PROGRAMS`: -``` +```makefile rm -rf build $(OBJECTS) $(TESTS) $(PROGRAMS) ``` 最后你还需要在最后添加一个目标来构建它们: -``` +```makefile $(PROGRAMS): CFLAGS += $(TARGET) ``` @@ -39,7 +39,7 @@ $(PROGRAMS): CFLAGS += $(TARGET) netclient的代码是这样的: -``` +```c #undef NDEBUG #include #include @@ -203,7 +203,7 @@ error: 如果你完成了所有构建,测试的最快方式就是看看你能否从learncodethehardway.org上得到一个特殊的文件: -``` +```sh $ $ ./bin/netclient learncodethehardway.org 80 GET /ex45.txt HTTP/1.1 diff --git a/ex46.md b/ex46.md index a4baeb5..6b1b414 100644 --- a/ex46.md +++ b/ex46.md @@ -8,7 +8,7 @@ `TSTree`的工作方式是,每个键都是字符串,根据字符串中字符的等性,通过构建或者遍历一棵树来进行插入。首先由根节点开始,观察每个节点的字符,如果小于、等于或大于则去往相应的方向。你可以参考这个头文件: -``` +```c #ifndef _lcthw_TSTree_h #define _lctwh_TSTree_h @@ -82,7 +82,7 @@ traverse `TSTree`的实现非常简单,但是第一次可能难以理解。我会在你读完之后拆分它。 -``` +```c #include #include #include @@ -278,7 +278,7 @@ tstree.c:57-61 最后,我编写了简单的单元测试,来确保我所做的全部东西正确。 -``` +```c #include "minunit.h" #include #include diff --git a/ex47.md b/ex47.md index 272c248..140e417 100644 --- a/ex47.md +++ b/ex47.md @@ -8,7 +8,7 @@ 我打算编程一个小型命令行工具和路由交互,他叫做`urlor`,读取简单的路由文件,之后提示用户输入要检索的URL。 -``` +```c #include #include @@ -138,7 +138,7 @@ error: 一旦你使`urlor`工作,并且创建了路由文件,你可以尝试这样: -``` +```sh $ ./bin/urlor urls.txt URL> / MATCH: / == MainApp diff --git a/ex5.md b/ex5.md index 3d86ca2..fe6ad08 100644 --- a/ex5.md +++ b/ex5.md @@ -6,7 +6,7 @@ 你已经知道了如何使用`printf`,也有了可以随意使用的一些工具,现在让我们逐行分析一个简单的C程序,以便你了解它是如何组织的。在这个程序里你会编写一些不是很熟悉的东西,我会轻松地把它们拆开。之后在后面的几章我们将会处理这些概念。 -``` +```c #include /* This is a comment. */ @@ -27,7 +27,7 @@ int main(int argc, char *argv[]) 这真是一段无聊的输出,但是这个练习的目的是让你分析代码: -``` +```sh $ make ex5 cc -Wall -g ex5.c -o ex5 $ ./ex5 @@ -80,4 +80,4 @@ $ ## 附加题 + 对于每一行,写出你不理解的符号,并且看看是否能猜出它们的意思。在纸上写下你的猜测,你可以在以后检查它,看看是否正确。 -+ 回头去看之前几个练习的源代码,并且像这样分解代码,来看看你是否了解它们。写下你不了解和不能自己解释的东西。 \ No newline at end of file ++ 回头去看之前几个练习的源代码,并且像这样分解代码,来看看你是否了解它们。写下你不了解和不能自己解释的东西。 diff --git a/ex6.md b/ex6.md index e73aa57..466fb8b 100644 --- a/ex6.md +++ b/ex6.md @@ -6,7 +6,7 @@ 你应该掌握了一个简单的C程序的结构,所以让我们执行下一步简单的操作,声明不同类型的变量。 -``` +```c include int main(int argc, char *argv[]) @@ -37,7 +37,7 @@ int main(int argc, char *argv[]) 你的输出应该和我的类似,你可以看到C的格式化字符串相似于Python或其它语言,很长一段时间中都是这样。 -``` +```sh $ make ex6 cc -Wall -g ex6.c -o ex6 $ ./ex6 @@ -79,7 +79,7 @@ $ 你可以通过向`printf`传递错误的参数来轻易使这个程序崩溃。例如,如果你找到打印我的名字的那行,把`initial`放到`first_name`前面,你就制造了一个bug。执行上述修改编译器就会向你报错,之后运行的时候你可能会得到一个“段错误”,就像这样: -``` +```sh $ make ex6 cc -Wall -g ex6.c -o ex6 ex6.c: In function 'main': @@ -103,4 +103,4 @@ $ + 寻找其他通过修改`printf`使这段C代码崩溃的方法。 + 搜索“`printf`格式化”,试着使用一些高级的占位符。 + 研究可以用几种方法打印数字。尝试以八进制或十六进制打印,或者其它你找到的方法。 -+ 试着打印空字符串,即`""`。 \ No newline at end of file ++ 试着打印空字符串,即`""`。 diff --git a/ex7.md b/ex7.md index 4f4f4b2..e65da41 100644 --- a/ex7.md +++ b/ex7.md @@ -6,7 +6,7 @@ 你可以通过声明`int`,`float`,`char`和`double`类型的变量,来对它们做更多的事情,让我们来熟悉它们吧。接下来我们会在各种数学表达式中使用它们,所以我会向你介绍C的基本算术操作。 -``` +```c int main(int argc, char *argv[]) { int bugs = 100; @@ -91,7 +91,7 @@ int main(int argc, char *argv[]) 通常,你应该看到如下输出: -``` +```shell $ make ex7 cc -Wall -g ex7.c -o ex7 $ ./ex7 @@ -113,4 +113,4 @@ $ + 这些巨大的数字实际上打印成了什么? + 将`long`改为`unsigned long`,并试着找到对它来说太大的数字。 + 上网搜索`unsigned`做了什么。 -+ 试着自己解释(在下个练习之前)为什么`char`可以和`int`相乘。 \ No newline at end of file ++ 试着自己解释(在下个练习之前)为什么`char`可以和`int`相乘。 diff --git a/ex8.md b/ex8.md index 34576f7..403415a 100644 --- a/ex8.md +++ b/ex8.md @@ -8,7 +8,7 @@ 在我真正解释其重要性之前,我先要介绍一些概念:`sizeof`和数组。下面是我们将要讨论的一段代码: -``` +```c #include int main(int argc, char *argv[]) @@ -65,7 +65,7 @@ int main(int argc, char *argv[]) ## 你会看到什么 -``` +```sh $ make ex8 cc -Wall -g ex8.c -o ex8 $ ./ex8 diff --git a/ex9.md b/ex9.md index 63ff51c..b2cf249 100644 --- a/ex9.md +++ b/ex9.md @@ -8,7 +8,7 @@ 这个练习向你展示了C只是简单地将字符串储存为字符数组,并且在结尾加上`'\0'`(空字符)。你可能在上个练习中得到了暗示,因为我们手动这样做了。下面我会通过将它与数字数组比较,用另一种方法更清楚地实现它。 -``` +```c #include int main(int argc, char *argv[]) @@ -70,7 +70,7 @@ int main(int argc, char *argv[]) 当你运行这段代码的时候,你应该首先看到所打印的数组的内容初始化为0值,之后打印初始化后的内容: -``` +```sh $ make ex9 cc -Wall -g ex9.c -o ex9 $ ./ex9 @@ -78,7 +78,7 @@ numbers: 0 0 0 0 name each: a name: a numbers: 1 2 3 4 -name each: Z e d +name each: Z e d name: Zed another: Zed another each: Z e d @@ -117,4 +117,4 @@ C中所有bug的大多数来源都是忘了预留出足够的空间,或者忘 + 有多少种其它的方式可以用来打印它? + 如果一个字符数组占四个字节,一个整数也占4个字节,你可以像整数一样使用整个`name`吗?你如何用黑魔法实现它? + 拿出一张纸,将每个数组画成一排方框,之后在纸上画出代码中的操作,看看是否正确。 -+ 将`name`转换成`another`的形式,看看代码是否能正常工作。 \ No newline at end of file ++ 将`name`转换成`another`的形式,看看代码是否能正常工作。