Head First C学习日志 第六章 最高机密 二叉树和valgrind工具

依然范特西╮ 提交于 2019-12-04 07:40:58

程序会从根节点开始提问,其左右子树为疑犯名字或另外一个问题。先看数据结构:

typedef struct node {
	char *question;
	struct node *no;
	struct node *yes;
} node;

一个递归结构,内容很简单,一个char*指针,两个node指针。

节点的创建函数:

node *create(char *question) {
	node *n = malloc(sizeof(node));
	n->question = strdup(question);
	n->no = NULL;
	n->yes = NULL;
	return n;
}

先分配空间,然后拷贝question字符串常量,两个node指针置空。

节点的销毁函数:

void release(node *n) {
	if (n) {
		if (n->no)
			release(n->no);
		if (n->yes)
			release(n->yes);
		if (n->question)
			free(n->question);
		free(n);
	}
}

从输入节点开始遍历,递归销毁其子树,释放n->question字符串,最后释放n。

一个通用的判断输入y/n的函数:

int yes_no(char *question) {
	char answer[3];
	printf("%s? (y/n):", question);
	fgets(answer, 3, stdin);
	if (answer[sizeof(answer) - 1] == '\n')
		answer[sizeof(answer) - 1] = 0;
	return answer[0] == 'y';
}

如果输入y返回1,输入其他字符返回0。

以上代码比较简单,二叉树的创建主要在主要在主函数里。

这段代码比较长,分开贴:

char question[80];
	char suspect[20];
	node *start_node = create("Does suspect have a mustache");
	start_node->no = create("Loretta Barnsworth");
	start_node->yes = create("Vinny the Spoon");

	node *current;

创建接收用户输入的question和suspect字符串。

创建根节点,并初始化一个问题,“疑犯有胡子么”。

将根节点的两个子树初始化为“Loretta Barnworth”和“Vinny the Spoon”。

定义一个current指针,用做活动指针,连接二叉树。

外层循环:

do {
}while(yes_no("Run again"));

至少执行一次,如果用户在yes_no函数输入y,则重复执行。

循环体:

current = start_node;
		while (1) {
		......
		}

将current指向起始节点,并执行while(1)循环的内容,一个if else条件判断,第一个if:

if (yes_no(current->question)) {
				if (current->yes)
					current = current->yes;
				else {
					printf("SUSPECT IDENTIFIED\n");
					break;
				}
			}

yes_no打印问题(或疑犯名)并要求用户输入,如果用户选择y,则进入子条件判断:当前节点current->yes是否存在,如果存在,将current=current->next,会进入下一次循环,如果不存在current->yes,则表示当前节点就是疑犯节点(疑犯节点没有子树),输出“SUSPECT IDENTIFIED”,跳出本层循环

下一个条件,如果用户在yes_no时输入了n(本文中不输入y一律认为输入n)

else if (current->no) {
				current = current->no;
			}

判断该节点是否存在no子树,如果存在,将current=current->no,进入下一次循环。

最后一个条件,在yes_no()时输入了n,但是又没有no子树,表示我们已存在的数据中,并没有我们想定位的疑犯信息,那么会进入新建步骤:

else {
				/*Make the yes-node the new suspect name*/
				printf("Who's the suspect? ");
				fgets(suspect, 20, stdin);
				if (suspect[sizeof(suspect) - 1] == '\n')
					suspect[sizeof(suspect) - 1] = 0;
				node *yes_node = create(suspect);
				current->yes = yes_node;
				/*Make the no-node a copy of this question*/
				node *no_node = create(current->question);
				current->no = no_node;
				/*The replace this question with the new question*/
				printf("Give me a question that is TRUE for %s but not for %s ", suspect, current->question);
				fgets(question, 80, stdin);
				if (question[sizeof(question) - 1] == '\n')
					question[sizeof(question) - 1] = 0;
				/*while replacing,the old block for 'question' must be freed*/
				current->question = strdup(question);
				break;
			}

程序输出:“Who's the suspect?”

输入疑犯名字,然后进行以下几步操作:

  1. 创建一个新节点(疑犯节点)yes_node

  2. 将当前节点(疑犯节点)current->yes=yes_node

  3. 创建一个新节点(疑犯节点),与当前节点问题(实际上是疑犯名)相同,no_node,并将当前节点的current->no=no_node;注意,此时有两个节点的question(疑犯名)相同,当前节点和以当前节点疑犯名命名的新节点。

  4. 输入一个新问题,对你给出的新疑犯是true,对当前节点的老疑犯为false

  5. 将当前节点的current->question(疑犯名)指向你新输入的question的拷贝,strdup(question)。

程序最后release(start_node),销毁整个二叉树。

第一部分,二叉树的动态创建到此为止,接下来是用valgrind检查内存泄漏。


用valgrind检查内存泄漏:

安装valgrind:brew install valgrind 就这么简单,强烈安利homebrew。

使用valgrind检查:输入命令 valgrind --leak-check=full ./spies

➜  spies valgrind --leak-check=full ./spies
==1165== Memcheck, a memory error detector
==1165== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1165== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==1165== Command: ./spies
==1165==
Does suspect have a mustache? (y/n):n
Loretta Barnsworth? (y/n):n
Who's the suspect? Johnny
==1165== Conditional jump or move depends on uninitialised value(s)
==1165==    at 0x100000D62: main (in ./spies)
==1165==
Give me a question that is TRUE for Johnny
 but not for Loretta Barnsworth He burns
==1165== Conditional jump or move depends on uninitialised value(s)
==1165==    at 0x100000E01: main (in ./spies)
==1165==
Run again? (y/n):n

注意一定要对初始化后的子树做一些改动才能复现。

结果:

==1165==
==1165== HEAP SUMMARY:
==1165==     in use at exit: 42,915 bytes in 420 blocks
==1165==   total heap usage: 530 allocs, 110 frees, 50,093 bytes allocated
==1165==
==1165== 19 bytes in 1 blocks are definitely lost in loss record 8 of 82
==1165==    at 0x100008EBB: malloc (in /usr/local/Cellar/valgrind/3.11.0/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==1165==    by 0x1001ECDDE: strdup (in /usr/lib/system/libsystem_c.dylib)
==1165==    by 0x100000B57: create (in ./spies)
==1165==    by 0x100000C61: main (in ./spies)
==1165==
==1165== LEAK SUMMARY:
==1165==    definitely lost: 19 bytes in 1 blocks
==1165==    indirectly lost: 0 bytes in 0 blocks
==1165==      possibly lost: 0 bytes in 0 blocks
==1165==    still reachable: 4,096 bytes in 1 blocks
==1165==         suppressed: 38,800 bytes in 418 blocks
==1165== Reachable blocks (those to which a pointer was found) are not shown.
==1165== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==1165==
==1165== For counts of detected and suppressed errors, rerun with: -v
==1165== Use --track-origins=yes to see where uninitialised values come from
==1165== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 16 from 16)

导致内存泄漏的函数给我们列举出来了,书中给的例子里结果中有行号,可是用的版本没有,只有检查这些函数在程序中的调用及其上下文

按照书中的解释,第一次不对二叉树作改动的情况下,并没有出现内存泄漏,因此create函数没有问题,这也是一种定位方法,控制可变量。

问题出在current->question = strdup(question);因为在这步之前,current->question是不为空的,它已经指向了某块堆内存,现在给它新赋值,那块内存就找不到了。所以我们要在current->question = strdup(question)之前,先free(current->question)。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!