ysyx预学习:复习c语言(上)

这是本人 笨方法学C的学习记录,仅供参考,如果有问题可以留言。

练习1附加题

1.
问:在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后运行它看看发生了什么。
答:运行不了。二进制代码被改了,机器无法识别
2.
问:再多打印5行文本或其他比Hello world更复杂的东西。
答:

		#include<stdio.h>
  int main(int argc, char *argv[])
  {
      puts("Hello world.");
      puts("Hello world.");
      puts("Hello world.");
      puts("Hello world.");
      puts("Hello world.");
      puts("Hello world.");
      return 0;
}

3.
问:执行man 3 puts来阅读这个函数和其他函数的文档
答:阅读puts的文档,可以发现puts函数输出文本和换行到标准输出

练习2附加题

1.
问:创建目标all:ex1,可以以单个命令make构建ex1。
答:在Makefile文件第一行加入all:ex1,表明目标是ex1。

2.阅读man make来了解关于如何执行它的更多信息。

3.
问:阅读man cc来了解关于-Wall和-g行为的更多信息。
答:
-Wall:显示所有的warning
-g:在生成是二进制文件中记录调试需要用的信息。

4.
问:在互联网上搜索Makefile文件,看看你是否能改进你的文件。
答:仿照第五题

CC = gcc
CFLAGS = -Wall
SOURCES = ex1.c
OBJECTS = $(SOURCES:.c=.o)
EXECUTABLE = ex1

all: $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
	$(CC) $(CFLAGS) $(OBJECTS) -o $@

.c.o:
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJECTS) $(EXECUTABLE)

5.
问:在另一个C语言项目中找到Makefile文件,并且尝试理解它做了什么。
答:

CC = gcc
CFLAGS = -Wall
SOURCES = main.c utils.c
OBJECTS = $(SOURCES:.c=.o)
EXECUTABLE = myprogram

all: $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
	$(CC) $(CFLAGS) $(OBJECTS) -o $@

.c.o:
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJECTS) $(EXECUTABLE)

解释:
CC被赋值为gcc,用以指示编译器名称
CFLAGS被赋值为-Wall,使编译时显示所有warning
SOURCES是源文件,即main.c和utils.c
OBJECTS在赋值时引用了SOURCES变量,所以加上了$()。冒号后面的.c=.o表明要将SOURCES中的.c后缀换成.o后缀后再赋给OBJECTS。
EXECUTABLE被赋值为最后的输出目标。

all:后面跟的是默认的目标

makefile中的目标和依赖通常是这个格式:

目标:依赖 
(TAB) gcc -Wall 依赖 -o 目标

$ @在这代表指明的目标,而不是shell中的所有变量,因为makefile有一套自己的规则。顺带一提,下面的$<指代依赖文件。

.c.o:

是另外一种格式的目标和依赖,其使用规则如下:

依赖 目标:
(TAB)gcc -Wall 依赖 -o 目标

这种情况适用于多文件对应的时候,此时你不能写成目标:依赖 ,因为每个目标都有不同的依赖,而这个格式则会让makefile以为每个目标文件都依赖于所有的依赖文件。想要描述这种情况还可以用静态模式规则,即

%.o:%.c

这表示每个目标文件都依赖于相同名字的依赖文件。

练习3附加题

1.
问:找到尽可能多的方法使ex3崩溃。
答:我把printf里的整型变量删了就已经error了。

2.
问:执行man 3 printf来阅读其它可用的’%'格式化占位符。如果你在其它语言中使用过它们,应该看着非常熟悉(它们来源于printf)。

答:以下是常用的

\n     换行
\s     空格
\r      回车
%d   十进制decimalism
%o    八进制octonary
%f    浮点型(float、double)
%s	字符串(string)

至于里面加数字限制长度和小数点的,我就不赘述了,整型类的和浮点型的区别还是挺大的,大家想了解可以看这篇文章:

3.
问:将ex3添加到你的Makefile的all列表中。到目前为止,可以使用make clean all来构建你所有的练习。
答:参考回答4
4.
问:将ex3添加到你的Makefile的clean列表中。当你需要的时候使用make clean可以删除它。
答:

	CC = gcc
  1 CFLAGS = -Wall
  2 SOURCES = ex1.c ex3.c
  3 OBJECTS = $(SOURCES:.c=.o)
  4 EXECUTABLE = ex1 ex3
  5 
  6 all: $(EXECUTABLE)
  7 
  8 ex1: ex1.o
  9         $(CC) $(CFLAGS) $< -o $@
 10 
 11 ex3: ex3.o
 12         $(CC) $(CFLAGS) $< -o $@
 13 
 14 .c.o:
 15         $(CC) $(CFLAGS) -c $< -o $@
 16 
 17 clean:
 18         rm -f $(OBJECTS) $(EXECUTABLE)

练习4附加题

1.
问:按照上面的指导,使用Valgrind和编译器修复这个程序。
答:就是ex3.c

2.
问:在互联网上查询Valgrind相关的资料。

3.
问:下载另一个程序并手动构建它。尝试一些你已经使用,但从来没有手动构建的程序。
答:这个问题就交给家人们了。

4.
问:看看Valgrind的源码是如何在目录下组织的,并且阅读它的Makefile文件。不要担心,这对我来说没有任何意义。
答:太高估我了,除了一些赋值语句我基本上看不懂

练习5附加题

1.
问:对于每一行,写出你不理解的符号,并且看看是否能猜出它们的意思。在纸上写下你的猜测,你可以在以后检查它,看看是否正确。
答:不懂的就gpt。

2.
问:回头去看之前几个练习的源代码,并且像这样分解代码,来看看你是否了解它们。写下你不了解和不能自己解释的东西。
答:这几个问题都看自己把

练习6附加题

1.
问:寻找其他通过修改printf使这段C代码崩溃的方法。
答:这可就太多了,忘记写括号,printf的数据与设计的不对应;等等等等。让程序崩溃不是我天天干的事吗hh。

2.
问:搜索“printf格式化”,试着使用一些高级的占位符。
答:练习3有个连接,这个链接做补充
可以发现,m.n格式大部分都是左补空格,只有%.nd和%0nd是左补0。

3.
问:研究可以用几种方法打印数字。尝试以八进制或十六进制打印,或者其它你找到的方法。
答:①puts(char * string)用来输出字符串的;

4.
问:试着打印空字符串,即""
答:可以打印空字符串,但是不可以打印空字符,会报错。

练习7附加题

1.
问:把为universe_of_defects赋值的数改为不同的大小,观察编译器的警告
答:
①如果数字太大:

//提示溢出
ex7.c:9:76: warning: integer overflow in expression of type ‘long int’ results in0[-Woverflow]
    9 |     long universe_of_defects = 1024L * 1024L * 1024L * 1024L *1024L *1024L * 1024L * 1024L * 1024L * 1024L * 1024L;
      |                                                                            ^

2.
问:这些巨大的数字实际上打印成了什么?
答:被打印成了0。

3.
问:将long改为unsigned long,并试着找到对它来说太大的数字。
答:多乘几个1024就行了。

4.
问:上网搜索unsigned做了什么。
答:无符号数。

5.
问:试着自己解释(在下个练习之前)为什么char可以和int相乘。
答:char是按ASCII存储的,都是数字没有理由不能乘吧?

练习8

如何使它崩溃

①将full_name最后的’\0’去掉,并重新运行它,在valgrind下再运行一遍。现在将full_name的定义从main函数中移到它的上面,尝试在Valgrind下运行它来看看是否能得到一些新的错误。有些情况下,你会足够幸运,不会得到任何错误。
答:以下是输出结果

The size of an int: 4
The size of areas (int[]): 20
The number of ints in areas: 5
The first area is 10, the 2nd 12.
The size of a char: 1
The size of name (char[]): 4
The number of chars: 4
The size of full_name (char[]): 11
The number of chars: 11
name="Zed" and full_name="Zed A. Shaw"                                                            ^

ps:理论上来说应该是不能正确执行的,因为字符串数组没有以’\0’结尾(长度只有11位),用printf打印输出时因为没有\0而不知道结尾在哪,出现乱码;但这里没报错,可能是当前版本的gcc编译器对其做了处理,也许换个编译器就会报错了。有时间的同学可以在DEV C++试试。当然,我不觉得这是一种幸运,这是编写程序的时候必须注意的规范。

②将areas[0]改为areas[10]并打印,来看看Valgrind会输出什么。

答:

  1  #include <stdio.h>
  1 
  2     char full_name[] = {
  3         'Z', 'e', 'd',
  4          ' ', 'A', '.', ' ',
  5          'S', 'h', 'a', 'w'
  6         };
  7 int main(int argc, char *argv[])
  8 {
  9     int areas[] = {10, 12, 13, 14, 20};
 10     char name[] = "Zed";
 11 
 12     // WARNING: On some systems you may have to change the
 13     // %ld in this code to a %u since it will use unsigned ints
 14     printf("The size of an int: %ld\n", sizeof(int));
 15     printf("The size of areas (int[]): %ld\n",
 16             sizeof(areas));
 17  
 18     printf("The number of ints in areas: %ld\n",
 19             sizeof(areas) / sizeof(int));
 20     printf("The first area is %d, the 2nd %d.\n",
 21             areas[10], areas[1]);
 22 
 23     printf("The size of a char: %ld\n", sizeof(char));
 24     printf("The size of name (char[]): %ld\n",
 25             sizeof(name));
 26     printf("The number of chars: %ld\n",
 27             sizeof(name) / sizeof(char));
 28            sizeof(name) / sizeof(char));
 29 
 30     printf("The size of full_name (char[]): %ld\n",
 31             sizeof(full_name));
 32     printf("The number of chars: %ld\n",
 33             sizeof(full_name) / sizeof(char));
 34 
 35     printf("name=\"%s\" and full_name=\"%s\"\n",
 36             name, full_name);
 37 
 38     return 0;
 39  }

输出结果:

The size of an int: 4
The size of areas (int[]): 20
The number of ints in areas: 5
The first area is -10314352, the 2nd 12.
The size of a char: 1
The size of name (char[]): 4
The number of chars: 4
The size of full_name (char[]): 11
The number of chars: 11
name="Zed" and full_name="Zed A. Shaw"

③尝试上述操作的不同变式,也对name和full_name执行一遍。
答:添加一个printf name

printf("the last number of name: %c\n",name[9]);

输出结果:

mber of areas: 0,0,0,0,0
The size of a char: 1
The size of name (char[]): 10
The number of chars: 10
this is name:Zed
The size of full_name (char[]): 15
The number of chars: 15
name="Zed" and full_name="Zed A. Shaw"
the last number of name:
附加题

1.
问:尝试使用areas[0] = 100;以及相似的操作对areas的元素赋值。
答:数组赋值的方法主要有:

int areas[10] = {10, 12, 13, 14, 20};     //整型数组
char full_name[15] = {                    //字符数组
        'Z', 'e', 'd',
         ' ', 'A', '.', ' ',
         'S', 'h', 'a', 'w'
         };

  char name[10] = "Zed";             //字符数组

③按单个位置赋值

areas[0] = 100;                        //一般放在循环里赋值

2.
问:尝试对name和full_name的元素赋值。
答:同上

3.
问:尝试将areas的一个元素赋值为name中的字符。因为字符是按照ASCII码值来存储数据的,ASCII码值有上限。
答:在 C 语言中,char 类型的大小通常是 1 字节,范围通常是 -128 到 127(对于有符号 char)或者 0 到 255(对于无符号 char)。当你尝试将一个超过 char 类型范围的值(例如 10086)赋给一个 char 变量时,发生的是 溢出,即该值会被截断到 char 能容纳的范围内。
如果我将full_name这样赋值:

char full_name[] = {
    10086, 'e', 'd',
    ' ', 'A', '.', ' ',
    'S', 'h', 'a', 'w'
};
10086 % 256 = 102
f正好对应102
输出的full_name="fed A. Shaw"

4.
问:上网搜索在不同的CPU上整数所占的不同大小
答:32,64位的电脑,整型一般都是32位的,我理解的是32位足以应付多数情况,如果需要64位的,可以使用长整型。对于更小位数的电脑,一般int的位数跟随电脑CPU的位数。

练习9

如何使它崩溃

①删掉name的初始化表达式。
gcc不报错,valgrind报错。

Conditional jump or move depends on uninitialised value(s)

②将数据赋过长的值,gcc报警告,valgrind不报错。(char name[4] = {‘a’,‘a’,‘a’,‘a’,‘a’};)

warning: excess elements in array initializer
附加题

1.
问:将一些字符赋给numbers的元素,之后用printf一次打印一个字符,你会得到什么编译器警告?
答:

#如果是   int numbers[4] = {a,5,6,7};
#这是无效声明,会报错
error: ‘a’ undeclared (first use in this function)


#如果是   int numbers[4] = {'a',5,6,7};
#不会报错,执行后输出97,对应a的ascii码。
numbers: 97 5 6 7

2.
问:对names执行上述的相反操作,把names当成int数组,并一次打印一个int,Valgrind会提示什么?

答:类似练习八附加题第三问,存储的是数字,printf的是对应的ascii码值。
将输出改为%d,输出的就是数字了。因此可以下一个浅显的结论。不论是何种数组,存储的时候都是以数字的形式存储,无非是输出的时候按照你的需要以%d,%c,%s的形式输出,单位置字长不同,字符数组需要以\0结尾来判断长度而已。

3.
问:有多少种其它的方式可以用来打印它?
答:%s肯定不行,识别不到\0。

4.
问:如果一个字符数组占四个字节,一个整数也占4个字节,你可以像整数一样使用整个name吗?你如何用黑魔法实现它?
答:
①第一种方法,把字符指针强制转化为int型指针intPtr,此时*intPtr对应的就是int型数组对应的值’ABCD‘,即0x44434241。

char name[4] = "ABCD";
int *intPtr = (int*)name;
int intValue = *intPtr;

②第二种方法,循环按位或。以下答案出自GPT,经过测试没有问题。

char name[4] = "ABCD";
int intValue = 0;

for (int i = 4; i > 0; i--) {
    intValue = (intValue << 8) | name[i];
}

5.
问:将name转换成another的形式,看看代码是否能正常工作。
答:不行,指针形式的数组赋值只能采用如下形式:

char *name = "abcd";

练习10

如何使它崩溃

1.
问:将i初始化为0看看会发生什么。是否也需要改动argc,不改动的话它能正常工作吗?为什么下标从o开始可以正常工作?
答:可以正常工作,但是printf(“arg %d: %s\n”, i, argv[i]);这句话不会被循环。

2.
问:将num_states改为错误的值使他变大,来看看会发生什么。
答:gcc显示分段错误,valgrind显示无效读取。读取超出数组长度了。

附加题

2.
问:查询如何使用’,‘(逗号)字符来在for循环的每一部分中,’;'(分号)之间分隔多条语句。

答:在for循环语句里,','起划分语句的作用,相当于平常写程序时候的分号。

3.
问:查询NULL是什么东西,尝试将它用做states的一个元素,看看它会打印出什么。

答:NULL是空指针,表示指针不指向任何东西。

尝试打印会输出如下结果:

state 3: (null)

4.
问:看看你是否能在打印之前将states的一个元素赋值给argv中的元素,再试试相反的操作。

答:
①“打印之前将states的一个元素赋值给argv中的元素”
不可以。c是按照顺序执行的,states都还没创造呢怎么给argv赋值。

②“打印之前将argv的一个元素赋值给中states的元素”
可以。理由同①。

练习11

附加题

1.
问:让这些循环倒序执行,通过使用i–从argc开始递减直到0。你可能需要做一些算数操作让数组的下标正常工作。
答:可以正常工作,但是printf(“arg %d: %s\n”, i, argv[i]);这句话不会被循环。

#修改后的程序
 1 #include <stdio.h>
  2 
  3 int main(int argc, char *argv[])
  4 { 
  5     // go through each string in argv
  6     
  7     int i = argc;
  8     while(i > 0) {
  9         i--;
 10         printf("arg %d: %s\n", i, argv[i]);
 11     }
 12     
 13     // let's make our own array of strings
 14     char *states[] = {
 15         "California", "Oregon",
 16         "Washington", "Texas"
 17     };
 18     
 19     int num_states = 4;
 20     i = num_states;  // watch for this
 21     while(i > 0) {
 22         i--;
 23         printf("state %d: %s\n", i, states[i]);
 24     }
 25     
 26     return 0;
 27 }

执行:

./ex11.exe a b

输出:

arg 2: b
arg 1: a
arg 0: ./ex11.exe
state 3: Texas
state 2: Washington
state 1: Oregon
state 0: California

2.
问:使用while循环将argv中的值复制到states。
答:按已学的知识,答案如下应该:

  1 #include <stdio.h>
  2 
  3 int main(int argc, char *argv[])
  4 {
  5     // go through each string in argv
  6     
  7     int i = argc;
  8     while(i > 0) {
  9         i--;
 10         printf("arg %d: %s\n", i, argv[i]);
 11     }   
 12     
 13     // let's make our own array of strings
 14     char *states[10]; 
 15     
 16     int num_states = argc;
 17     i = num_states;  // watch for this
 18     while(i > 0) {
 19         i--;
 20         states[i] = argv[i];
 21         printf("state %d: %s\n", i, states[i]);
 22     }   
 23     
 24     return 0;
 25 } 

但实际上你可以注意到一个问题,states[i]接收到的仅仅只是地址而已,也就是说,则并不能叫复制,只是让states[i]也指向了argv[i]指向的元素。因此在程序上我做如下修改:

 1 #include <stdio.h>
  2 #include <string.h>
  3 #include <stdlib.h>
  4 
  5 int main(int argc, char *argv[])
  6 {   
  7     // go through each string in argv
  8     
  9     int i = argc;
 10     while(i > 0) {
 11         i--;
 12         printf("arg %d: %s\n", i, argv[i]);
 13     }
 14     
 15     // let's make our own array of strings
 16     char *states[argc];
 17 	int malloc_length = 0
 18     for(int j = 0; j < argc; j++){
 19 	malloc_length += (strlen(argv[j]) + 1);
 20	    }
 21 	states[0] = (char *)malloc(malloc_length);
 22 	char *temp_states = states[0];
 23 	
 24     for(int i = 0; i < argc; i++) {
 25     strcpy(temp_states ,argv[i]); 
 26     printf("state %d: %s\n", i, temp_states);
 27     temp_states += strlen(argv[i])+1;
 28     }   
 29		free(states[0]);
 30 	
 29     return 0;    
 30     }

使用一个字符串复制函数strcpy,它存在于string.h头文件中,其功能是将第二个地址上的字符串复制到第一个地址处。
这里使用一个malloc来给字符串数组开辟存储空间,它属于stdlib.h头文件。strlen函数会计算每个元素的字节数,+1是补充\0的长度,因为strlen不会将其计算在内(不能用sizeof,它指代的是变量类型的存储空间大小)。malloc创建的是viod型指针,需要强制类型转换成char *型指针。

3.
问:让这个复制循环不会执行失败,即使argv之中有很多元素也不会全部放进states。

答:程序如下:

 1 #include <stdio.h>
  2 #include <string.h>
  3 #include <stdlib.h>
  4 
  5 int main(int argc, char *argv[])
  6 {   
  7     // go through each string in argv
  8     
  9     int i = argc;
 10     while(i > 0) {
 11         i--;
 12         printf("arg %d: %s\n", i, argv[i]);
 13     }
 14     
 15     // let's make our own array of strings
 		argc = (argc >=4 ? 4 : argc);
 16     char *states[argc];
 17 	int malloc_length = 0
 18     for(int j = 0; j < argc; j++){
 19 	malloc_length += (strlen(argv[j]) + 1);
 20	    }
 21 	states[0] = (char *)malloc(malloc_length);
 22 	char *temp_states = states[0];
 23 	
 24     for(int i = 0; i < argc; i++) {
 25     strcpy(temp_states ,argv[i]); 
 26     printf("state %d: %s\n", i, temp_states);
 27     temp_states += strlen(argv[i])+1;
 28     }   
 29		free(states[0]);
 30 	
 29     return 0;    
 30     }

将states限定四个字符串。(argc >=4 ? 4 : argc)防止没有数据的时候printf空元素报错。

4.
问:研究你是否真正复制了这些字符串。答案可能会让你感到意外和困惑

答:详见第二问。

练习12

附加题

1.
问:我已经向你简短地介绍了 && ,它执行“与”操作。上网搜索与之不同的“布尔运算符”。
答:主要还是 &&、||、&、|这些。

3.
问:回到练习10和11,使用if语句使循环提前退出。你需要break语句来实现它,搜索它的有关资料。

答:练习10程序如下,在printf中加入了if判断break;练习11类似。

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i = 0;

    // go through each string in argv
    // why am I skipping argv[0]?
    for(i = 1; i < argc; i++) {
        printf("arg %d: %s\n", i, argv[i]);
    }

    // let's make our own array of strings
    char *states[] = {
        "California", "Oregon",
        "Washington", "Texas"
    };
    int num_states = 4;

    for(i = 0; i < num_states; i++) {
        printf("state %d: %s\n", i, states[i]);
        if(i == 2){
        	break;
        	}
    }

    return 0;
}

4.
问:第一个判断所输出的话真的正确吗?由于你的“第一个参数”不是用户输入的第一个参数,把它改正。
答:输入的第一个参数就是命令本身。判断时增加1即可。

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i = 0;

    if(argc == 2) {
        printf("You only have one argument. You suck.\n");
    } else if(argc > 2 && argc < 5) {
        printf("Here's your arguments:\n");

        for(i = 1; i < argc; i++) {
            printf("%s ", argv[i]);
        }
        printf("\n");
    } else {
        printf("You have too many arguments. You suck.\n");
    }

    return 0;
}

练习13

附加题

1.
问:编写另一个程序,在字母上做算术运算将它们转换为小写,并且在switch中移除所有额外的大写字母。
答:

#include <stdio.h>

int main(int argc, char *argv[])
{
    if(argc != 2) {
        printf("ERROR: You need one argument.\n");
        // this is how you abort a program
        return 1;
    }

    int i = 0;
    for(i = 0; argv[1][i] != '\0'; i++) {
        char letter = argv[1][i];
        if(letter >= 'A' && letter <= 'Z'){
            letter += 32;
        }

        switch(letter) {
            case 'a':
                printf("%d: 'a'\n", i);
                break;

            case 'e':
                printf("%d: 'e'\n", i);
                break;

            case 'i':
                printf("%d: 'i'\n", i);
                break;

            case 'o':
                printf("%d: 'o'\n", i);
                break;

            case 'u':
                printf("%d: 'u'\n", i);
                break;

            case 'y':
                if(i > 2) {
                    // it's only sometimes Y
                    printf("%d: 'y'\n", i);
                }
                break;

            default:
                printf("%d: %c is not a vowel\n", i, letter);
        }
    }

    return 0;
}

2.
问:使用’,'(逗号)在for循环中初始化letter。
答:程序如下

#include <stdio.h>

int main(int argc, char *argv[])
{
    if(argc != 2) {
        printf("ERROR: You need one argument.\n");
        // this is how you abort a program
        return 1;
    }

    int i;
    char letter;
    for(i = 0, letter = argv[1][0]; letter != '\0'; letter = argv[1][i++]) {
        if(letter >= 'A' && letter <= 'Z'){
            letter += 32;
        }

        switch(letter) {
            case 'a':
                printf("%d: 'a'\n", i);
                break;

            case 'e':
                printf("%d: 'e'\n", i);
                break;

            case 'i':
                printf("%d: 'i'\n", i);
                break;

            case 'o':
                printf("%d: 'o'\n", i);
                break;

            case 'u':
                printf("%d: 'u'\n", i);
                break;

            case 'y':
                if(i > 2) {
                    // it's only sometimes Y
                    printf("%d: 'y'\n", i);
                }
                break;

            default:
                printf("%d: %c is not a vowel\n", i, letter);
        }
    }

    return 0;
}

3.
问:使用另一个for循环来让它处理你传入的所有命令行参数。

答:加入一个j的for循环。

#include <stdio.h>

int main(int argc, char *argv[])
{
    if(argc == 1) {
        printf("ERROR: You need at least one argument.\n");
        // this is how you abort a program
        return 1;
    }

    int i, j;
    char letter;
    for(j = 1; j < argc; j++){
        for(i = 0, letter = argv[j][0]; letter != '\0'; letter = argv[j][i++]) {
            if(letter >= 'A' && letter <= 'Z'){
                letter += 32;
            }

            switch(letter) {
                case 'a':
                    printf("%d: 'a'\n", i);
                    break;

                case 'e':
                    printf("%d: 'e'\n", i);
                    break;

                case 'i':
                    printf("%d: 'i'\n", i);
                    break;

                case 'o':
                    printf("%d: 'o'\n", i);
                    break;

                case 'u':
                    printf("%d: 'u'\n", i);
                    break;

                case 'y':
                    if(i > 2) {
                        // it's only sometimes Y
                        printf("%d: 'y'\n", i);
                    }
                    break;

                default:
                    printf("%d: %c is not a vowel\n", i, letter);
            }
        }

    }
    return 0;
}

4.
问:将这个switch语句转为if语句,你更喜欢哪个呢?
答:改成if后如下,喜欢哪个不用我多说了吧。

if(letter == 'a')
	printf("%d: 'a'\n", i);
else if(letter == 'e')
	printf("%d: 'e'\n", i);
else if(letter == 'i')
	printf("%d: 'i'\n", i);
else if(letter == 'O' )
	printf("%d: 'o'\n", i);
else if(letter == 'u')
	printf("%d: 'u'\n", i);
else if(letter == 'y' || i >2)
	printf("%d: 'y'\n", i);
else
	printf("%d: %c is not a vowel\n", i, letter);

5.
问:在“Y”的例子中,我在if代码块外面写了个break。这样会产生什么效果?如果把它移进if代码块,会发生什么?自己试着解答它,并证明你是正确的。
答:如果放进if里,那只有i > 2的时候才会执行break;会导致在i <=2的时候,如果是y或者Y,没有break语句,会向下执行default中的内容。

练习14

附加题

1.
问:重新编写这些函数,使它们的数量减少。比如,你真的需要can_print_it吗?
答:程序如下:

#include <stdio.h>
#include <ctype.h>

// forward declarations
void print_arguments(int argc, char *argv[])
{
    int i = 0;

    for(i = 0; i < argc; i++) {
    for(int j = 0; argv[i][j] != '\0'; j++) {
        char ch = argv[i][j];

        if(isalpha(ch) || isblank(ch)) {
            printf("'%c' == %d ", ch, ch);
        }
    }

    printf("\n");
    }
}

int main(int argc, char *argv[])
{
    print_arguments(argc, argv);
    return 0;
}

2.
问:使用strlen函数,让print_arguments知道每个字符串参数都有多长,之后将长度传入print_letters。然后重写print_letters,让它只处理固定的长度,不按照’\0’终止符。你需要#include <string.h>来实现它。
:程序如下

#include <stdio.h>
#include <ctype.h>
#include <string.h>

// forward declarations
int can_print_it(char ch);
void print_letters(char arg[]);

void print_arguments(int argc, char *argv[])
{
    int i = 0;

    for(i = 0; i < argc; i++) {
        print_letters(argv[i] ,strlen(argv[i]));
    }
}

void print_letters(char arg[] ,int letter_len)
{
    int i = 0;

    for(i = 0; i < letter_len; i++) {
        char ch = arg[i];

        if(can_print_it(ch)) {
            printf("'%c' == %d ", ch, ch);
        }
    }

    printf("\n");
}

int can_print_it(char ch)
{
    return isalpha(ch) || isblank(ch);
}


int main(int argc, char *argv[])
{
    print_arguments(argc, argv);
    return 0;
}

3.
问:使用man来查询isalpha和isblank的信息。使用其它相似的函数来只打印出数字或者其它字符。
答:isdigit函数检查是不是数字,是数字返回非0,不是返回0。

4.
问:上网浏览不同的人喜欢什么样的函数格式。永远不要使用“K&R”语法,因为它过时了,而且容易使人混乱,但是当你碰到一些人使用这种格式时,要理解代码做了什么。
答:通常都用ANSI C风格的函数格式,也就是我们平常教学使用的。

练习15

大多数情况下,除了sizeof、&操作和声明之外,指针和数组在使用上没有明显区别。

附加题

6.
问:在程序末尾添加一个for循环,打印出这些指针所指向的地址。你需要在printf中使用%p。
答:

#include <stdio.h>

int main(int argc, char *argv[])
{
    // create two arrays we care about
    int ages[] = {23, 43, 12, 89, 2};
    char *names[] = {
        "Alan", "Frank",
        "Mary", "John", "Lisa"
    };

    // safely get the size of ages
    int count = sizeof(ages) / sizeof(int);
    int i = 0;

    // first way using indexing
    for(i = 0; i < count; i++) {
        printf("%s has %d years alive.\n",
                names[i], ages[i]);
    }

    printf("---\n");

    // setup the pointers to the start of the arrays
    int *cur_age = ages;
    char **cur_name = names;

    // second way using pointers
    for(i = 0; i < count; i++) {
        printf("%s is %d years old.\n",
                *(cur_name+i), *(cur_age+i));
    }

    printf("---\n");

    // third way, pointers are just arrays
    for(i = 0; i < count; i++) {
        printf("%s is %d years old again.\n",
                cur_name[i], cur_age[i]);
    }

    printf("---\n");

    // fourth way with pointers in a stupid complex way
    for(cur_name = names, cur_age = ages;
            (cur_age - ages) < count;
            cur_name++, cur_age++)
    {
        printf("%p lived %p years so far.\n",
                *cur_name, *cur_age);
    }

    // print the ptr of the name and the age
    for(i = 0; i < count; i++) {
        printf("%p is %p years old again.\n",
                cur_name[i], &cur_age[i]);
    }
    return 0;
}

7.
问:对于每一种打印数组的方法,使用函数来重写程序。试着向函数传递指针来处理数据。记住你可以声明接受指针的函数,但是可以像数组那样用它。
答:

#include <stdio.h>
void print_age( int *cur_age ,char **cur_name ,int count);

int main(int argc, char *argv[])
{
    // create two arrays we care about
    int ages[] = {23, 43, 12, 89, 2};
    char *names[] = {
        "Alan", "Frank",
        "Mary", "John", "Lisa"
    };

    // safely get the size of ages
    int count = sizeof(ages) / sizeof(int);
    int i = 0;

    // first way using indexing
    for(i = 0; i < count; i++) {
        printf("%s has %d years alive.\n",
                names[i], ages[i]);
    }

    printf("---\n");

   //other way to printf.
    print_age(ages, names, count);

    return 0;
}


    //print_age函数:包含了剩下三种打印方法
void print_age(int *cur_age, char **cur_name ,int count){
	
    int j = 0;
    // second way using pointers
    for(j = 0; j < count; j++) {
        printf("%s is %d years old.\n",
                *(cur_name+j), *(cur_age+j));
    }

    printf("---\n");

    // third way, pointers are just arrays
    for(j = 0; j < count; j++) {
        printf("%s is %d years old again.\n",
                cur_name[j], cur_age[j]);
    }

    printf("---\n");
    char **four_name;
     int *four_age;
     // fourth way with pointers in a stupid complex way
     for(four_name = cur_name,four_age = cur_age;
             (four_age - cur_age) < count;
             four_name++, four_age++)
     {
          printf("%s lived %d years so far.\n",
                 *four_name, *four_age);
     }
}

8.
问:将for循环改为while循环,并且观察对于每种指针用法哪种循环更方便。
答:随便举一个例子,不难发现,for循环可以容纳的初始条件,循环后条件,这都是while循环所没有的。所以一般情况下都用for。至于对指针用法的影响倒是没有发现有什么优势。

	int i = 0;
	while(i < count){
        printf("%s has %d years alive.\n",names[i], ages[i]);
         i++;
     }

练习16

附加题

问:在这个练习的附加题中我想让你尝试一些有难度的东西:将这个程序改为不用指针和malloc的版本。这可能很困难,所以你需要研究下面这些东西:

  • 如何在栈上创建结构体,就像你创建任何其它变量那样。
  • 如何使用x.y而不是x->y来初始化结构体。
  • 如何不使用指针来将结构体传给其它函数。

答:在这里还是使用了strdup在堆上分配,仍需要free。如果想在栈上分配,最好是定义结构体时就定义字符数组而不是指针。

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

struct Person {
    char *name;
    int age;
    int height;
    int weight;
};

struct Person Person_create(char *name, int age, int height, int weight)
{
    struct Person who;

    who.name = strdup(name);
    who.age = age;
    who.height = height;
    who.weight = weight;

    return who;
}

void Person_print(struct Person who)
{
    printf("Name: %s\n", who.name);
    printf("\tAge: %d\n", who.age);
    printf("\tHeight: %d\n", who.height);
    printf("\tWeight: %d\n", who.weight);
}

void Person_destroy(struct Person who)
{

    free(who.name);
}

int main(int argc, char *argv[])
{
    // make two people structures
    struct Person joe = Person_create(
            "Joe Alex", 32, 64, 140);

    struct Person frank = Person_create(
            "Frank Blank", 20, 72, 180);

    // print them out and where they are in memory
    printf("Joe is at memory location %p:\n", &joe);
    Person_print(joe);

    printf("Frank is at memory location %p:\n", frank);
    Person_print(frank);

    // make everyone age 20 years and print them again
    joe.age += 20;
    joe.height -= 2;
    joe.weight += 40;
    Person_print(joe);

    frank.age += 20;
    frank.weight += 20;
    Person_print(frank);

    return 0;
}

练习17

程序注释
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_DATA 512
#define MAX_ROWS 100

struct Address {
    int id;                             //数据id
    int set;                            //数据状态
    char name[MAX_DATA];                //数据1:name
    char email[MAX_DATA];               //数据2:email
};

struct Database {
    struct Address rows[MAX_ROWS];      //最大堆栈深度:100
};

struct Connection {                     //struct Connection这个结构体用于将文件和堆栈结合起来,方便查找
    FILE *file;
    struct Database *db;
};

void die(const char *message)
{
    if(errno) {                                                     //检查一下errno,如果非0则说明有错误,perror输出错误以及message;
        perror(message);
    } else {                                                        //如果errno没有问题,则输出ERROR:message,
        printf("ERROR: %s\n", message);                             
    }

    exit(1);                                                        //一旦进入这个函数,横竖都会输出错误且退出。
}

void Address_print(struct Address *addr)
{
    printf("%d %s %s\n",
            addr->id, addr->name, addr->email);
}

void Database_load(struct Connection *conn)                         //根据file初始化db指针指向的结构体数组
{
    int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
    if(rc != 1) die("Failed to load database.");
}

struct Connection *Database_open(const char *filename, char mode)    //为conn和db两个结构体指针指向的结构体开辟一片存储区域,并根据file文件初始化。
{
    struct Connection *conn = malloc(sizeof(struct Connection));     //开辟区域给结构体指针conn
    if(!conn) die("Memory error");

    conn->db = malloc(sizeof(struct Database));                     //开辟区域给结构体指针db
    if(!conn->db) die("Memory error");

    if(mode == 'c') {
        conn->file = fopen(filename, "w");                          //如果mode是create,检查一下*filename的可写性
    } else {
        conn->file = fopen(filename, "r+");                         //如果mode是其他的,检查一下*filename的读写性

        if(conn->file) {
            Database_load(conn);                        //其他模式下如果文件可读写,则将file文件的部分内容读取至database
        }
    }

    if(!conn->file) die("Failed to open the file");                 //如果file指针是NULL,则输出错误

    return conn;
}

void Database_close(struct Connection *conn)                        //关闭file,释放内存
{
    if(conn) {
        if(conn->file) fclose(conn->file);                      
        if(conn->db) free(conn->db);
        free(conn);
    }
}

void Database_write(struct Connection *conn)
{
    rewind(conn->file);

    int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);         //把db指向的经过修改的结构体成员rows[i]写回conn->file
    if(rc != 1) die("Failed to write database.");

    rc = fflush(conn->file);                                                  //将缓冲区的东西直接写入文件
    if(rc == -1) die("Cannot flush database.");
}

void Database_create(struct Connection *conn)
{
    int i = 0;

    for(i = 0; i < MAX_ROWS; i++) {                 //给Database结构体中的每一个数组元素(也是结构体)赋id和set初值
        // make a prototype to initialize it
        struct Address addr = {.id = i, .set = 0};
        // then just assign it
        conn->db->rows[i] = addr;
    }
}

void Database_set(struct Connection *conn, int id, const char *name, const char *email)  //根据提供的id name email 写入结构体rows[id]
{
    struct Address *addr = &conn->db->rows[id];                     //addr指向结构体rows[id]
    if(addr->set) die("Already set, delete it first");              //set的作用就是判断有无数据

    addr->set = 1;
    // WARNING: bug, read the "How To Break It" and fix this
    char *res = strncpy(addr->name, name, MAX_DATA);                //字符串复制,返回值是addr->name的地址
    // demonstrate the strncpy bug
    if(!res) die("Name copy failed");                               //res为NULL则报错

    res = strncpy(addr->email, email, MAX_DATA);                    //字符串复制,返回值是addr->email的地址
    if(!res) die("Email copy failed");                              //res为NULL则报错
}

void Database_get(struct Connection *conn, int id)                          //打印输出rows[id]
{
    struct Address *addr = &conn->db->rows[id];                             //取rows[id]的地址赋给指针addr

    if(addr->set) {                                                         
        Address_print(addr);                                                //如果rows[id]的set是1,则打印该rows[id]的name id email
    } else {
        die("ID is not set");                                               //如果set为0,说明该rows[id]未设置,退出
    }
}

void Database_delete(struct Connection *conn, int id)                       //根据id清空name、email等信息
{
    struct Address addr = {.id = id, .set = 0};
    conn->db->rows[id] = addr;
}

void Database_list(struct Connection *conn)                         //只要set为1,代表这个rows[id]写入过,就printf信息
{
    int i = 0;
    struct Database *db = conn->db;

    for(i = 0; i < MAX_ROWS; i++) {
        struct Address *cur = &db->rows[i];

        if(cur->set) {
            Address_print(cur);
        }
    }
}

int main(int argc, char *argv[])
{
    if(argc < 3) die("USAGE: ex17 <dbfile> <action> [action params]");   //argc<3则提前终止程序,即:argc至少要为3(至少要这两个参数:<dbfile文件名> <action执行什么操作>)

    char *filename = argv[1];
    char action = argv[2][0];
    struct Connection *conn = Database_open(filename, action);   //Database_open 刨一块区域给结构体指针conn,并根据file文件初始化
    int id = 0;

    if(argc > 3) id = atoi(argv[3]);                            //如果不止3个参数,则取第三个参数为id
    if(id >= MAX_ROWS) die("There's not that many records.");

    switch(action) {
        case 'c':                                               //create,初始化一片区域
            Database_create(conn);                              //给conn->db结构体中的每一个数组元素rows[i](也是结构体)赋id和set = 0;
            Database_write(conn);                               //把db指向的经过修改的结构体成员rows[i]写回conn->file
            break;

        case 'g':                                               //get,用于打印输出
            if(argc != 4) die("Need an id to get");             //这个操作要5个参数,<dbfile> <action>以及id

            Database_get(conn, id);                             //打印输出rows[id]
            break;

        case 's':                                                   //set,设置name和email
            if(argc != 6) die("Need id, name, email to set");       //这个操作要5个参数:<dbfile> <action> id  name  email

            Database_set(conn, id, argv[4], argv[5]);               //根据提供的id name email 写入结构体rows[id]
            Database_write(conn);                                   //把db指向的经过修改的结构体成员rows[i]写回conn->file
            break;

        case 'd':                                                   //delete
            if(argc != 4) die("Need id to delete");                 //delete需要3个参数:<dbfile> <action> id;

            Database_delete(conn, id);                              //delete主要是根据id将set复位,感觉实际上此位置还有数据,但因为set为0,已经是无用数据了
            Database_write(conn);                                   //同步至file
            break;

        case 'l':
            Database_list(conn);                                    //printf已经写入过(set为1)的结构体的信息
            break;
        default:
            die("Invalid action, only: c=create, g=get, s=set, d=del, l=list");
    }

    Database_close(conn);                                           //关闭file,释放内存

    return 0;
}

库函数解释

  1. fopen
FILE *fopen(const char *filename, const char *mode);

答:fopen用于打开一个文件。第一个参数为文件名,第二个参数是打开的模式:表示对文件的操作,可以是 “r”(只读),“w”(写入),“a”(追加),“r+”(读写),“w+”(读写,如果文件存在则截断为零长度),“a+”(读写,如果文件存在则在末尾追加)等。

fopen会在堆上分配一些地址以存储文件相关信息(大约几百k),并返回文件指针。

  1. fread
size_t fread(const void *ptr, size_t size, size_t nmemb, FILE *stream);

答:从文件中读取size*nmemb大小的内容到ptr指针指向的内存块中。

  1. fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

答:从ptr指针指向的内存块中读取size*nmemb大小的内容到文件中。

  1. fflush
int fflush(FILE *stream);

fflush 用于确保数据被及时写入文件,而不是缓冲区。(通常当数据被写入文件时,它通常首先被存储在内存缓冲区中,而不是立即写入磁盘。)

  1. exit
void exit(int status);

答:用于中止程序的执行。非0状态就退出程序。

  1. memset
void *memset(void *s, int c, size_t n);

答:将s指向的位置连续n个存储单元初始化为c。

  1. rewind
void rewind(FILE *stream);

答:刷新文件指针位置(因为fwrite或者fgetc等函数会改变文件指针位置)。

如何使它崩溃

1.
问:这个程序中有个bug,因为strncpy有设计缺陷。查询strncpy的相关资料,然后试着弄清楚如果name或者address超过512个字节会发生什么。可以通过简单把最后一个字符设置成’\0’来修复它,你应该无论如何都这样做(这也是函数原本应该做的)。
答:这个问题是在问这一行:

 char *res = strncpy(addr->name, name, MAX_DATA); 

查询strncpy的定义,会发现它是纯粹的字符复制,如果name数组的位数少于MAX_DATA,addr->name的末尾会被补充MAX_DATA-name个’\0’;如果name的位数大于MAX_DATA,那*addr->name末尾不会有’\0’,这会导致字符串的结构不完整。总之,这个函数需要人为填充’\0’。

修改后的程序如下:

 char *res = strncpy(addr->name, name, MAX_DATA -1);
 *(addr->name +  MAX_DATA -1) = '\0';
附加题

1.
问:die函数需要接收conn变量作为参数,以便执行清理并关闭它。

答:这是修改后的函数。

  void die(const char *message ,struct Connection *conn)
{
    if(errno) {                                                     //检查一下errno,如果非0则说明有错误,perror输出错误以及message;
        perror(message);
    } else {                                                        //如果errno没有问题,则输出ERROR:message,
        printf("ERROR: %s\n", message);                             
    }
    Database_close(conn);

    exit(1);                                                        //一旦进入这个函数,横竖都会输出错误且退出。
}

注意,需要在前面声明 Database_close函数,因为它定义在die函数后面;
其次,所有的die函数引用处都要修改为die(const char *message ,struct Connection *conn)形式;
最后,主函数的第一行应该修改为

if(argc < 3) printf("ERROR: USAGE: ex17 <dbfile> <action> [action params]\n");

2.问:修改代码,使其接收参数作为MAX_DATA和MAX_ROWS,将它们储存在Database结构体中,并且将它们写到文件。这样就可以创建任意大小的数据库。
3.问:向数据库添加更多操作,比如find。
5.问:向Address添加一些字段,使它们可被搜索。

答:经修改的程序如下:
其实有很多bug,比如create,可以对同一个文件名进行操作。这理论上是不允许的。

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>


struct Address {
    int id;                             //数据id
    int set;                            //数据状态
    char *name;                //数据1:name
    char *email;               //数据2:email
    char *address;             //数据3:address
};

struct Database {
    int max_data;
    int max_rows;
    struct Address *rows;      //最大堆栈深度取决于输入的MAX_ROWS
};

struct Connection {                     //struct Connection这个结构体用于将文件和堆栈结合起来,方便查找
    FILE *file;
    struct Database *db;
};

void Database_close(struct Connection *conn);

void die(const char *message ,struct Connection *conn)
{
    if(errno) {                                                     //检查一下errno,如果非0则说明有错误,perror输出错误以及message;
        perror(message);
    } else {                                                        //如果errno没有问题,则输出ERROR:message,
        printf("ERROR: %s\n", message);                             
    }
    Database_close(conn);

    exit(1);                                                        //一旦进入这个函数,横竖都会输出错误且退出。
}

void Address_print(struct Address *addr)
{
    printf("%d %s %s %s\n",
            addr->id, addr->name, addr->email, addr->address);
}

void Database_load(struct Connection *conn)                         //根据file初始化db指针指向的结构体数组
{

    int rc0 = fread(&conn->db->max_data, sizeof(int), 1, conn->file);               //前两个参数是max_data和max_rows
    int rc1 = fread(&conn->db->max_rows, sizeof(int), 1, conn->file);

    if(rc0 != 1 || rc1 != 1) die("Failed to load max_data and max_rows to database." ,conn);

    for(int i =0; i< conn->db->max_rows; i++){
    int rc2 = fread(&conn->db->rows[i].id, sizeof(int), 1, conn->file);
    int rc3 = fread(&conn->db->rows[i].set, sizeof(int), 1, conn->file);                  //把db指向的经过修改的结构体成员rows[i]写回conn->file
    int rc4 = fread(conn->db->rows[i].name, conn->db->max_data, 1, conn->file);
    int rc5 = fread(conn->db->rows[i].email, conn->db->max_data, 1, conn->file);
    int rc6 = fread(conn->db->rows[i].address, conn->db->max_data, 1, conn->file);
    if(rc2 != 1 || rc3 != 1 || rc4 != 1 || rc5 != 1 || rc6 != 1) die("Failed to load database." ,conn);
    }
    
}

struct Connection *Database_open(const char *filename, char mode, int max_data, int max_rows)    //为conn和db两个结构体指针指向的结构体开辟一片存储区域,并根据file文件初始化。
{
    struct Connection *conn = malloc(sizeof(struct Connection));     //开辟区域给结构体指针conn
    if(!conn) die("Memory error" ,conn);

    conn->db = malloc(sizeof(struct Database));                     //开辟区域给结构体指针db
    if(!conn->db) die("Memory error" ,conn);
    conn->db->max_data = max_data;
    conn->db->max_rows = max_rows;

    conn->db->rows = malloc(max_rows * sizeof(struct Address));         //开辟区域给所有rows[i]
    if(!conn->db->rows) die("Memory error" ,conn);

    for(int j = 0; j < max_rows ; j++){
    conn->db->rows[j].name = malloc(max_data);                              //开辟区域给每个rows[i]的name、email以及address
    conn->db->rows[j].email = malloc(max_data);
    conn->db->rows[j].address = malloc(max_data);
    
    if(!conn->db->rows[j].name || !conn->db->rows[j].email || !conn->db->rows[j].address) die("Memory error" ,conn);
    }

    if(mode == 'c') {
        conn->file = fopen(filename, "w");                          //如果mode是create,检查一下*filename的可写性
    } else {
        conn->file = fopen(filename, "r+");                         //如果mode是其他的,检查一下*filename的读写性

        if(conn->file) {
            Database_load(conn);                        //其他模式下如果文件可读写,则将file文件的部分内容读取至database
        }
    }

    if(!conn->file) die("Failed to open the file" ,conn);                 //如果file指针是NULL,则输出错误

    return conn;
}

void Database_close(struct Connection *conn)                        //关闭file,释放内存,先malloc的后释放
{
    if(conn) {
        for(int i = 0; i < conn->db->max_rows ; i++){
            free(conn->db->rows[i].name);
            free(conn->db->rows[i].email); 
            free(conn->db->rows[i].address);
        }

        if(conn->db->rows) free(conn->db->rows);
        if(conn->file) fclose(conn->file);                      
        if(conn->db) free(conn->db);
        
        free(conn);
    }
}

void Database_write(struct Connection *conn)
{
    rewind(conn->file);

    int rc0 = fwrite(&conn->db->max_data, sizeof(int), 1, conn->file);                      //将max_data和max_rows写入文件
    int rc1 = fwrite(&conn->db->max_rows, sizeof(int), 1, conn->file);

    if(rc0 != 1 || rc1 != 1) die("Failed to write max_data and max_rows to database." ,conn);

    for(int i =0; i< conn->db->max_rows; i++){
    int rc2 = fwrite(&conn->db->rows[i].id, sizeof(int), 1, conn->file);
    int rc3 = fwrite(&conn->db->rows[i].set, sizeof(int), 1, conn->file);                  //把需要存储的参数写回conn->file
    int rc4 = fwrite(conn->db->rows[i].name, conn->db->max_data, 1, conn->file);
    int rc5 = fwrite(conn->db->rows[i].email, conn->db->max_data, 1, conn->file);
    int rc6 = fwrite(conn->db->rows[i].address, conn->db->max_data, 1, conn->file);
    if(rc2 != 1 || rc3 != 1 || rc4 != 1 || rc5 != 1 || rc6 != 1) die("Failed to write database." ,conn);
    }

    int rc7 = fflush(conn->file);                                                            //将缓冲区的东西直接写入文件
    if(rc7 == -1) die("Cannot flush database." ,conn);
}

void Database_create(struct Connection *conn)
{
    int i = 0;

    for(i = 0; i < conn->db->max_rows; i++) {                 //初始化Database结构体中的每一个数组元素(也是结构体)
        // make a prototype to initialize it
        conn->db->rows[i].id= i;
        conn->db->rows[i].set = 0;
        memset(conn->db->rows[i].name, 0, conn->db->max_data);
        memset(conn->db->rows[i].email, 0, conn->db->max_data);
        memset(conn->db->rows[i].address, 0, conn->db->max_data);
    }
}

void Database_set(struct Connection *conn, int id, const char *name, const char *email, const char *address)             //根据提供的id name email 写入结构体rows[id]
{
    struct Address *addr = &conn->db->rows[id];                     //addr指向结构体rows[id]
    if(addr->set) die("Already set, delete it first" ,conn);              //set的作用就是判断有无数据

    addr->set = 1;
    // WARNING: bug, read the "How To Break It" and fix this
    char *res = strncpy(addr->name, name, conn->db->max_data - 1);                //字符串复制,返回值是addr->name的地址
     *(addr->name +  conn->db->max_data -1) = '\0';
    // demonstrate the strncpy bug
    if(!res) die("Name copy failed" ,conn);                               //res为NULL则报错

    res = strncpy(addr->email, email, conn->db->max_data - 1);                  
    *(addr->email + conn->db->max_data -1) = '\0';
    if(!res) die("Email copy failed" ,conn);                            

    res = strncpy(addr->address, address, conn->db->max_data - 1);               
    *(addr->address + conn->db->max_data -1) = '\0';
    if(!res) die("Email copy failed" ,conn);                          
}

void Database_get(struct Connection *conn, int id)                          //打印输出rows[id]
{
    struct Address *addr = &conn->db->rows[id];                             //取rows[id]的地址赋给指针addr

    if(addr->set) {                                                         
        Address_print(addr);                                                //如果rows[id]的set是1,则打印该rows[id]的name id email
    } else {
        die("ID is not set" ,conn);                                               //如果set为0,说明该rows[id]未设置,退出
    }
}

void Database_delete(struct Connection *conn, int id)                       //根据id清空name、email等信息
{
        conn->db->rows[id].id= id;
        conn->db->rows[id].set = 0;
}

void Database_list(struct Connection *conn)                         //只要set为1,代表这个rows[id]写入过,就printf信息
{
    int i = 0;
    struct Database *db = conn->db;

    for(i = 0; i < conn->db->max_rows; i++) {
        struct Address *cur = &db->rows[i];

        if(cur->set) {
            Address_print(cur);
        }
    }
}

void Database_find(struct Connection *conn, char *message)
{
    int comp1, comp2, comp3;
    for(int i = 0;i < conn->db->max_rows; i++){
        comp1 = strcmp(message, conn->db->rows[i].name);                    //将要寻找的字段和存储的name、email、address比较,只要有一个函数返回值为0即匹配成功
        comp2 = strcmp(message, conn->db->rows[i].email);
        comp3 = strcmp(message, conn->db->rows[i].address);
        if(!comp1 || !comp2 || !comp3){
            Address_print(&conn->db->rows[i]);
        }
    }
}

int main(int argc, char *argv[])
{
    if(argc < 3) {
        printf("ERROR: USAGE: ex17 <dbfile> <action> [action params]\n");   //argc<4则提前终止程序,即:argc至少要为4(至少要这两个参数:<dbfile文件名> <action执行什么操作>)
        exit(1);
    }

    char *filename, action;
    int MAX_DATA, MAX_ROWS;

    if(argv[2][0] == 'c'){                                                          //c模式下,其命令行格式:可执行文件  c   MAX_DATA   MAX_ROWS
        filename = argv[1];
        action = argv[2][0];
        MAX_DATA = atoi(argv[3]);
        MAX_ROWS = atoi(argv[4]);
    }
    else {
        filename = argv[1];                                                     //其他模式则从file文件中读取MAX_DATA和MAX_ROWS
        action = argv[2][0];

        struct Connection connect;
        connect.db = NULL;
        int temp[2];

        connect.file = fopen(filename, "r+");
        if(connect.file) {
            fread(temp, 2 * sizeof(int), 1, connect.file);
            MAX_DATA = temp[0];
            MAX_ROWS = temp[1];
            fclose(connect.file);
        }
        else
            exit(1);
            
    }
        

    struct Connection *conn = Database_open(filename, action, MAX_DATA, MAX_ROWS);   //Database_open 刨一块区域给结构体指针conn,并根据file文件初始化
    int id = 0;

    if(argc > 3 && action != 'c' && action != 'f') id = atoi(argv[3]);                            //如果不止3个参数,则取第三个参数为id
    if(id >= conn->db->max_rows && action != 'c' && action != 'f') die("There's not that many records." ,conn);

    switch(action) {
        case 'c':                                               //create,初始化一片区域
            if(argc != 5) die("Need filename c max_size max_rows" ,conn);
            Database_create(conn);                              //给conn->db结构体中的每一个数组元素rows[i](也是结构体)赋id和set = 0;
            Database_write(conn);                               //把db指向的经过修改的结构体成员rows[i]写回conn->file
            break;

        case 'g':                                               //get,用于打印输出
            if(argc != 4) die("Need an id to get" ,conn);             //这个操作要5个参数,<dbfile> <action>以及id

            Database_get(conn, id);                             //打印输出rows[id]
            break;

        case 's':                                                   //set,设置name和email
            if(argc != 7) die("Need id, name, email to set" ,conn);       //这个操作要6个参数:<dbfile> <action> id  name  email address

            Database_set(conn, id, argv[4], argv[5], argv[6]);               //根据提供的id name email 写入结构体rows[id]
            Database_write(conn);                                   //把db指向的经过修改的结构体成员rows[i]写回conn->file
            break;

        case 'd':                                                   //delete
            if(argc != 4) die("Need id to delete" ,conn);                 //delete需要3个参数:<dbfile> <action> id;

            Database_delete(conn, id);                              //delete主要是根据id将set复位,感觉实际上此位置还有数据,但因为set为0,已经是无用数据了
            Database_write(conn);                                   //同步至file
            break;

        case 'l':
            Database_list(conn);                                    //printf已经写入过(set为1)的结构体的信息
            break;

        case 'f':
            if(argc != 4) die("Need message to find" ,conn);
            Database_find(conn, argv[3]);                   //取第四个参数为要寻找的字符串
            break;
        default:
            die("Invalid action, only: c=create, g=get, s=set, d=del, l=list" ,conn);
    }

    Database_close(conn);                                           //关闭file,释放内存

    return 0;
}

第二版
答:我第一版写的是按照之前的思维,先在堆中创建好完整的数据库再进行操作。这样会导致Database_open函数需要max_data和max_rows参数,而不进行create操作时,这些数据是封存在文件中的,因此我必须先打开文件一遍,读取两个参数。新的版本将字符数组的堆分配分别放在了Databae_create和Database_load中,避免了这一操作。

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>


struct Address {
    int id;                             //数据id
    int set;                            //数据状态
    char *name;                //数据1:name
    char *email;               //数据2:email
    char *address;             //数据3:address
};

struct Database {
    int max_data;
    int max_rows;
    struct Address *rows;      //最大堆栈深度取决于输入的MAX_ROWS
};

struct Connection {                     //struct Connection这个结构体用于将文件和堆栈结合起来,方便查找
    FILE *file;
    struct Database *db;
};

void Database_close(struct Connection *conn);

void die(const char *message ,struct Connection *conn)
{
    if(errno) {                                                     //检查一下errno,如果非0则说明有错误,perror输出错误以及message;
        perror(message);
    } else {                                                        //如果errno没有问题,则输出ERROR:message,
        printf("ERROR: %s\n", message);                             
    }
    Database_close(conn);

    exit(1);                                                        //一旦进入这个函数,横竖都会输出错误且退出。
}

void Address_print(struct Address *addr)
{
    printf("%d %s %s %s\n",
            addr->id, addr->name, addr->email, addr->address);
}

void Database_load(struct Connection *conn)                         //根据file初始化db指针指向的结构体数组
{
    int rc0 = fread(&conn->db->max_data, sizeof(int), 1, conn->file);               //前两个参数是max_data和max_rows
    int rc1 = fread(&conn->db->max_rows, sizeof(int), 1, conn->file);

    if(rc0 != 1 || rc1 != 1) die("Failed to load max_data and max_rows to database." ,conn);

    conn->db->rows = malloc(conn->db->max_rows * sizeof(struct Address));         //开辟区域给所有rows[i]
    if(!conn->db->rows) die("Memory error" ,conn);

    int j;
    for(j = 0; j < conn->db->max_rows ; j++){
    conn->db->rows[j].name = malloc(sizeof(char)* conn->db->max_data);                              //开辟区域给每个rows[i]的name、email以及address
    conn->db->rows[j].email = malloc(sizeof(char)* conn->db->max_data);
    conn->db->rows[j].address = malloc(sizeof(char)* conn->db->max_data);
    
    if(!conn->db->rows[j].name || !conn->db->rows[j].email || !conn->db->rows[j].address) die("Memory error" ,conn);
    }

    for(int i =0; i< conn->db->max_rows; i++){
    int rc2 = fread(&conn->db->rows[i].id, sizeof(int), 1, conn->file);
    int rc3 = fread(&conn->db->rows[i].set, sizeof(int), 1, conn->file);                  //把db指向的经过修改的结构体成员rows[i]写回conn->file
    int rc4 = fread(conn->db->rows[i].name, conn->db->max_data, 1, conn->file);
    int rc5 = fread(conn->db->rows[i].email, conn->db->max_data, 1, conn->file);
    int rc6 = fread(conn->db->rows[i].address, conn->db->max_data, 1, conn->file);
    if(rc2 != 1 || rc3 != 1 || rc4 != 1 || rc5 != 1 || rc6 != 1) die("Failed to load database." ,conn);
    }
    
}

struct Connection *Database_open(const char *filename, char mode)    //为conn和db两个结构体指针指向的结构体开辟一片存储区域,并根据file文件初始化。
{
    struct Connection *conn = malloc(sizeof(struct Connection));     //开辟区域给结构体指针conn
    if(!conn) die("Memory error" ,conn);

    conn->db = malloc(sizeof(struct Database));                     //开辟区域给结构体指针db
    if(!conn->db) die("Memory error" ,conn);

    if(mode == 'c') {
        conn->file = fopen(filename, "w");                          //如果mode是create,检查一下*filename的可写性
    } else {
        conn->file = fopen(filename, "r+");                         //如果mode是其他的,检查一下*filename的读写性

        if(conn->file) {
            Database_load(conn);                        //其他模式下如果文件可读写,则将file文件的部分内容读取至database
        }
    }

    if(!conn->file) die("Failed to open the file" ,conn);                 //如果file指针是NULL,则输出错误

    return conn;
}

void Database_close(struct Connection *conn)                        //关闭file,释放内存,先malloc的后释放
{
    if(conn) {
        for(int i = 0; i < conn->db->max_rows ; i++){
            free(conn->db->rows[i].name);
            free(conn->db->rows[i].email); 
            free(conn->db->rows[i].address);
        }

        if(conn->db->rows) free(conn->db->rows);
        if(conn->file) fclose(conn->file);                      
        if(conn->db) free(conn->db);
        
        free(conn);
    }
}

void Database_write(struct Connection *conn)
{
    rewind(conn->file);

    int rc0 = fwrite(&conn->db->max_data, sizeof(int), 1, conn->file);                      //将max_data和max_rows写入文件
    int rc1 = fwrite(&conn->db->max_rows, sizeof(int), 1, conn->file);

    if(rc0 != 1 || rc1 != 1) die("Failed to write max_data and max_rows to database." ,conn);

    for(int i =0; i< conn->db->max_rows; i++){
    int rc2 = fwrite(&conn->db->rows[i].id, sizeof(int), 1, conn->file);
    int rc3 = fwrite(&conn->db->rows[i].set, sizeof(int), 1, conn->file);                  //把需要存储的参数写回conn->file
    int rc4 = fwrite(conn->db->rows[i].name, conn->db->max_data, 1, conn->file);
    int rc5 = fwrite(conn->db->rows[i].email, conn->db->max_data, 1, conn->file);
    int rc6 = fwrite(conn->db->rows[i].address, conn->db->max_data, 1, conn->file);
    if(rc2 != 1 || rc3 != 1 || rc4 != 1 || rc5 != 1 || rc6 != 1) die("Failed to write database." ,conn);
    }

    int rc7 = fflush(conn->file);                                                            //将缓冲区的东西直接写入文件
    if(rc7 == -1) die("Cannot flush database." ,conn);
}

void Database_create(struct Connection *conn, int max_data, int max_rows)
{
    conn->db->max_data = max_data;
    conn->db->max_rows = max_rows;

    conn->db->rows = malloc(max_rows * sizeof(struct Address));         //开辟区域给所有rows[i]
    if(!conn->db->rows) die("Memory error" ,conn);

    int j;
    for(j = 0; j < max_rows ; j++){
    conn->db->rows[j].name = malloc(sizeof(char)* max_data);                              //开辟区域给每个rows[i]的name、email以及address
    conn->db->rows[j].email = malloc(sizeof(char)* max_data);
    conn->db->rows[j].address = malloc(sizeof(char)* max_data);
    
    if(!conn->db->rows[j].name || !conn->db->rows[j].email || !conn->db->rows[j].address) die("Memory error" ,conn);
    }

    int i = 0;
    for(i = 0; i < conn->db->max_rows; i++) {                 //初始化Database结构体中的每一个数组元素(也是结构体)
        // make a prototype to initialize it
        conn->db->rows[i].id= i;
        conn->db->rows[i].set = 0;
        memset(conn->db->rows[i].name, 0, max_data);
        memset(conn->db->rows[i].email, 0, max_data);
        memset(conn->db->rows[i].address, 0, max_data);
    }
}

void Database_set(struct Connection *conn, int id, const char *name, const char *email, const char *address)             //根据提供的id name email 写入结构体rows[id]
{
    struct Address *addr = &conn->db->rows[id];                     //addr指向结构体rows[id]
    if(addr->set) die("Already set, delete it first" ,conn);              //set的作用就是判断有无数据

    addr->set = 1;
    char *res = strncpy(addr->name, name, conn->db->max_data - 1);                //字符串复制,返回值是addr->name的地址
     *(addr->name + conn->db->max_data -1) = '\0';
    // demonstrate the strncpy bug
    if(!res) die("Name copy failed" ,conn);                               //res为NULL则报错

    res = strncpy(addr->email, email, conn->db->max_data - 1);                  
    *(addr->email + conn->db->max_data -1) = '\0';
    if(!res) die("Email copy failed" ,conn);                            

    res = strncpy(addr->address, address, conn->db->max_data - 1);               
    *(addr->address + conn->db->max_data -1) = '\0';
    if(!res) die("Address copy failed" ,conn);                          
}

void Database_get(struct Connection *conn, int id)                          //打印输出rows[id]
{
    struct Address *addr = &conn->db->rows[id];                             //取rows[id]的地址赋给指针addr

    if(addr->set) {                                                         
        Address_print(addr);                                                //如果rows[id]的set是1,则打印该rows[id]的name id email
    } else {
        die("ID is not set" ,conn);                                               //如果set为0,说明该rows[id]未设置,退出
    }
}

void Database_delete(struct Connection *conn, int id)                       //根据id清空name、email等信息
{
    struct Address *addr = &conn->db->rows[id];
    addr->id= id;
    addr->set = 0;
    memset(addr->name, 0, conn->db->max_data);
    memset(addr->email, 0, conn->db->max_data);
    memset(addr->address, 0, conn->db->max_data);
}

void Database_list(struct Connection *conn)                         //只要set为1,代表这个rows[id]写入过,就printf信息
{
    int i = 0;
    struct Database *db = conn->db;

    for(i = 0; i < conn->db->max_rows; i++) {
        struct Address *cur = &db->rows[i];

        if(cur->set) {
            Address_print(cur);
        }
    }
}

void Database_find(struct Connection *conn, char *message)
{
    int comp1, comp2, comp3;
    for(int i = 0;i < conn->db->max_rows; i++){
        struct Address *cur = &conn->db->rows[i];
        comp1 = strcmp(message, cur->name);                    //将要寻找的字段和存储的name、email、address比较,只要有一个函数返回值为0即匹配成功
        comp2 = strcmp(message, cur->email);
        comp3 = strcmp(message, cur->address);
        if(cur->set){
            if(!comp1 || !comp2 || !comp3){
                Address_print(&conn->db->rows[i]);
            }
        }
    }
}

int main(int argc, char *argv[])
{
    if(argc < 3) {
        printf("ERROR: USAGE: ex17 <dbfile> <action> [action params]\n");   //argc<4则提前终止程序,即:argc至少要为4(至少要这两个参数:<dbfile文件名> <action执行什么操作>)
        exit(1);
    }

    char *filename = argv[1];
    char action = argv[2][0];

    struct Connection *conn = Database_open(filename, action);   //Database_open 刨一块区域给结构体指针conn,并根据file文件初始化
    int id = 0;

    if(argc > 3 && action != 'c' && action != 'f') id = atoi(argv[3]);                            //如果不止3个参数,则取第三个参数为id
    
    if(action != 'c' && action != 'f') {
        if(id >= conn->db->max_rows){
        die("There is not that many records." ,conn);
        }
    }
    switch(action) {
        case 'c':                                               //create,初始化一片区域
            if(argc != 5) die("Need parameter: filename c max_size max_rows" ,conn);
            int MAX_DATA = atoi(argv[3]);
            int MAX_ROWS = atoi(argv[4]);
            Database_create(conn, MAX_DATA, MAX_ROWS);                              //给conn->db结构体中的每一个数组元素rows[i](也是结构体)赋id和set = 0;
            Database_write(conn);                               //把db指向的经过修改的结构体成员rows[i]写回conn->file
            break;

        case 'g':                                               //get,用于打印输出
            if(argc != 4) die("Need parameter: id" ,conn);             //这个操作要5个参数,<dbfile> <action>以及id

            Database_get(conn, id);                             //打印输出rows[id]
            break;

        case 's':                                                   //set,设置name和email
            if(argc != 7) die("Need parameter:id, name, email" ,conn);       //这个操作要6个参数:<dbfile> <action> id  name  email address

            Database_set(conn, id, argv[4], argv[5], argv[6]);               //根据提供的id name email address写入结构体rows[id]
            Database_write(conn);                                   //把db指向的经过修改的结构体成员rows[i]写回conn->file
            break;

        case 'd':                                                   //delete
            if(argc != 4) die("Need parameter: id" ,conn);                 //delete需要3个参数:<dbfile> <action> id;

            Database_delete(conn, id);                              //delete主要是根据id将set复位,感觉实际上此位置还有数据,但因为set为0,已经是无用数据了
            Database_write(conn);                                   //同步至file
            break;

        case 'l':
            Database_list(conn);                                    //printf已经写入过(set为1)的结构体的信息
            break;

        case 'f':
            if(argc != 4) die("Need message to find" ,conn);
            Database_find(conn, argv[3]);                   //取第四个参数为要寻找的字符串
            break;
        default:
            die("Invalid action, only: c=create, g=get, s=set, d=del, l=list f=find" ,conn);
    }

    Database_close(conn);                                           //关闭file,释放内存

    return 0;
}

4.
问:查询C如何打包结构体,并且试着弄清楚为什么你的文件是相应的大小。看看你是否可以计算出结构体添加一些字段之后的新大小。

答:在liunux上,结构体是以4字节模式对齐的。
我尝试将数据库的大小定义为如下大小:

#define MAX_DATA 61
#define MAX_ROWS 64

得到的文件为6448kb。

实际上的Address结构体为:

struct Address {
    int id;        /* 4 bytes */
    int set;       /* 4 byte */
    char name[61]; /* 61 bytes */
    char email[61];/* 61 bytes */
    char pad[3];   /* 3 bytes */
};

Tips:剩下的几道题就交给大家了,没时间做了┭┮﹏┭┮。

练习18

附加题

2.
问:在你的十六进制编辑器中找到更多随机出现的东西并修改它们。重新运行你的程序看看发生了什么。字符串是你最容易修改的东西。
答:不太懂题意,意思是让我修改十六进制的函数?

./ex18.exe  5 4 8 9 3
3 4 5 8 9 
f3:0f:1e:fa:55:48:89:e5:89:7d:fc:89:75:f8:***8b***:45:fc:2b:45:f8:5d:c3:f3:0f:1e:
9 8 5 4 3 
f3:0f:1e:fa:55:48:89:e5:89:7d:fc:89:75:f8:8b:45:f8:2b:45:fc:5d:c3:f3:0f:1e:
5 8 4 9 3 
f3:0f:1e:fa:55:48:89:e5:89:7d:fc:89:75:f8:83:7d:fc:00:74:06:83:7d:f8:00:75:

将十六进制的第一个函数8b修改为8d,输出如下:

./ex18.exe  5 4 8 9 3
5 4 8 9 3 
f3:0f:1e:fa:55:48:89:e5:89:7d:fc:89:75:f8:8d:45:fc:2b:45:f8:5d:c3:f3:0f:1e:
9 8 5 4 3 
f3:0f:1e:fa:55:48:89:e5:89:7d:fc:89:75:f8:8b:45:f8:2b:45:fc:5d:c3:f3:0f:1e:
5 8 4 9 3 
f3:0f:1e:fa:55:48:89:e5:89:7d:fc:89:75:f8:83:7d:fc:00:74:06:83:7d:f8:00:75:

显然第一个函数的排序失去了作用,但暂时不影响执行。

3.
问:将错误的函数传给compare_cb,并看看C编辑器会报告什么错误。
答:程序如下,修改了第一个函数的形参。

test_sorting(numbers, count, die);

输出如下:

Segmentation fault (core dumped)

一般是内存访问不对或者越界。

4.
问:将NULL传给它,看看程序中会发生什么。然后运行Valgrind来看看它会报告什么。

答:输出如下,传递了无效参数,导致内存泄漏。

 ==7949== Memcheck, a memory error detector
==7949== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==7949== Using Valgrind-3.21.0 and LibVEX; rerun with -h for copyright info
==7949== Command: ./ex18.exe 5 4 8 9 3
==7949== 
==7949== Jump to the invalid address stated on the next line
==7949==    at 0x0: ???
==7949==    by 0x10947E: test_sorting (ex18.c:76)
==7949==    by 0x109612: main (ex18.c:113)
==7949==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==7949== 
==7949== 
==7949== Process terminating with default action of signal 11 (SIGSEGV)
==7949==  Bad permissions for mapped region at address 0x0
==7949==    at 0x0: ???
==7949==    by 0x10947E: test_sorting (ex18.c:76)
==7949==    by 0x109612: main (ex18.c:113)
==7949== 
==7949== HEAP SUMMARY:
==7949==     in use at exit: 40 bytes in 2 blocks
==7949==   total heap usage: 2 allocs, 0 frees, 40 bytes allocated
==7949== 
==7949== LEAK SUMMARY:
==7949==    definitely lost: 0 bytes in 0 blocks
==7949==    indirectly lost: 0 bytes in 0 blocks
==7949==      possibly lost: 0 bytes in 0 blocks
==7949==    still reachable: 40 bytes in 2 blocks
==7949==         suppressed: 0 bytes in 0 blocks
==7949== Rerun with --leak-check=full to see details of leaked memory
==7949== 
==7949== For lists of detected and suppressed errors, rerun with: -s
==7949== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

5.
问:编写另一个排序算法,修改test_sorting使它接收任意的排序函数和排序函数的比较回调。并使用它来测试两种排序算法。
答:写了一个乱七八糟算法,将就看一看吧。重要的是新定义了一个回调函数,使我能在两种外算法x三种内算法中自由切换了。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

/** Our old friend die from ex17. */
void die(const char *message)
{
    if(errno) {
        perror(message);
    } else {
        printf("ERROR: %s\n", message);
    }

    exit(1);
}

// a typedef creates a fake type, in this
// case for a function pointer
typedef int (*compare_cb)(int a, int b);
typedef int *(*algo_cb)(int *number, int count, compare_cb cmp);

/**
 * A classic bubble sort function that uses the
 * compare_cb to do the sorting.
 */
int *bubble_sort(int *numbers, int count, compare_cb cmp)
{
    int temp = 0;
    int i = 0;
    int j = 0;
    int *target = malloc(count * sizeof(int));

    if(!target) die("Memory error.");

    memcpy(target, numbers, count * sizeof(int));

    for(i = 0; i < count; i++) {
        for(j = 0; j < count - 1; j++) {
            if(cmp(target[j], target[j+1]) > 0) {
                temp = target[j+1];
                target[j+1] = target[j];
                target[j] = temp;
            }
        }
    }

    return target;
}

int *add_num(int *numbers, int count, compare_cb cmp)
{
    int *target = malloc(sizeof(int) * count);
    int temp = 0;

    memcpy(target, numbers, count * sizeof(int));
    
    for(int i =0; i < count; i++) {
    temp += cmp(target[0], target[i]);
    target[i] = temp;
    }

    return target;
}

int sorted_order(int a, int b)
{
    return a - b;
}

int reverse_order(int a, int b)
{
    return b - a;
}

int strange_order(int a, int b)
{
    if(a == 0 || b == 0) {
        return 0;
    } else {
        return a % b;
    }
}

/**
 * Used to test that we are sorting things correctly
 * by doing the sort and printing it out.
 */
void test_sorting(int *numbers, int count, compare_cb cmp, algo_cb algorithm)
{
    int i = 0;
    int *sorted = algorithm(numbers, count, cmp);

    if(!sorted) die("Failed to algorium as requested.");

    for(i = 0; i < count; i++) {
        printf("%d ", sorted[i]);
    }
    printf("\n");

    free(sorted);
}


int main(int argc, char *argv[])
{
    if(argc < 2) die("USAGE: ex18 4 3 1 5 6");

    int count = argc - 1;
    int i = 0;
    char **inputs = argv + 1;

    int *numbers = malloc(count * sizeof(int));
    if(!numbers) die("Memory error.");

    for(i = 0; i < count; i++) {
        numbers[i] = atoi(inputs[i]);
    }

    test_sorting(numbers, count, sorted_order, bubble_sort);
    test_sorting(numbers, count, reverse_order, add_num);
    test_sorting(numbers, count, strange_order, add_num);

    free(numbers);

    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值