*程序的基本功能是实现一个树,层数等于所包含的节点数,每个节点中存放一个链表。
typedef struct node{
int data;
struct node *next;
}LNode; 树中包含的链表节点
typedef struct treenod{
LNode *listnode;
struct treenod* *tree;
//注意此处书写格式,是一个treenod*型的指针, 写成**容易让人感觉像是二维数组,很容易搞迷惑自己
}TreeNode; 树的节点
*递归创建树
void create_tree(TreeNode **treenode, int max_data, int num)
{
int i;
LNode new_node;
int test_data;
printf("star : num = %d\n", num);
if(max_data > 1)
{
*treenode = (TreeNode *)malloc(sizeof(TreeNode));
if(*treenode == NULL)
{
printf("the first malloc failed\n");
}
(*treenode)->listnode = (LNode *) malloc(sizeof(LNode) * num);
if(((*treenode)->listnode) == NULL)
{
printf("the second malloc failed\n");
}
(*treenode)->tree = (TreeNode* *)malloc(sizeof(TreeNode*) * (num+1) );
//(*treenode)->tree = (TreeNode *)malloc(sizeof(TreeNode) * (num+1) );
scanf("%d", &test_data);
(*treenode)->listnode->data = test_data;
for(i=0; i<num+1; i++)
{
create_tree(( &(*treenode)->tree[i] ), max_data-1, num+1);
}
}
else if(max_data == 1)
{
*treenode = (TreeNode *)malloc(sizeof(TreeNode));
(*treenode)->listnode = (LNode *) malloc(sizeof(LNode));
(*treenode)->tree = NULL;
scanf("%d", &test_data);
(*treenode)->listnode->data = test_data;
}
}
我们注意第一处标记。
(*treenode)->tree = (TreeNode* )malloc(sizeof(TreeNode) * (num+1) );这是开始时错误的写法。程序在编译时发出警告,此处不可能是空指针,
因为后面刚赋值过。我们返回头看所定义树节点的数据结构中指向下一节点指针的那部分。
由于这棵树的节点中“树枝”数量可能是任意的,所以我们必须动态分配,即创建一个一维数组,数组中每个元素的类型都是一个指针,这些指针用来
指向下一个节点。指针的数量就是此节点下面的“树枝”(或者说“树杈”)的个数。 struct treenod* *tree 由这个可以看出,(*treenode)->tree的类型是
什么。到底是什么呢?于是开始的错误就发生了。直接把类型当成节点类型,编译器提出警告,然后人为忽略,然后运行,然后程序崩溃。再让我们仔细理解一下,
struct treenod* *tree所代表的是一个数组,而数组中每个元素都存放一个"treenod*"型指针。所以,为数组分配空间时,空间大小不是树节点的大小,而是
"指向树节点指针"的大小。所以“(*treenode)->tree = (TreeNode* *)malloc(sizeof(TreeNode*) * (num+1) );”所分配的空间才是我们真正需要的,也是符合
程序原意的。违背了程序的原意,编译器自然会警告你。这也告诉我们,有时后遇到指针警告,可能是“空指针”,但是也有可能是类型不对。
迷惑很多时候是由于我们对概念的掌握不扎实,所以我们进一步讨论使我们容易迷惑的原因。treenod* *tree定义一般有两种解释,一个是指针数组,即
如上讨论,一个数组中存放着一系列指针。另外一种情况就是二维数组,treenod **tree,treenod *tree[], treenod tree[][],都可以表示一个二维数组,现在,
让我们从语法角度分析一下他们的意思。
treenod **tree,无论把指针空格写到哪里,本意是不变的,这说明其实指针数组和二维数组本质是一样的。 下面我们看一段示例程序:
#include<stdio.h>
int array[2][2] = {0,1,2,3};
int main()
{
printf("array's value is %p\n", array);
printf("array+1's value is %p\n", array+1);
printf("array+2's value is %p\n", array+2);
printf("array+3's value is %p\n", array+3);
printf("&array[0]'s value is %p\n",&array[0]);
printf("&(**array)'s value is %p\n", &(**array));
printf("*array's value is %p\n", *array);
}
运行结果
array's value is 0x804a014
array+1's value is 0x804a01c
array+2's value is 0x804a024
array+3's value is 0x804a02c
&array[0]'s value is 0x804a014
&(**array)'s value is 0x804a014
*array's value is 0x804a014
出于好奇心,我们试验一下,发现 &(*array) 的值也是0x804a014。写到这里,我有点凌乱了......
可见,最后三个值和数组首元素相同。*array 就是 **array中的元素,也就是说,在**array指向的一连串存储空间中,每个空间首地址为*array。
当我们使用二维数组的时候,这些地址就是每个一维数组的首地址,由于在二维数组中我们需要在第二个[]中指定个数,所以每个一维数组的大小也是确定的。
为什么*array 和 &(*array)的值是一样的呢?*array代表了数组元素array[][]中第一行元素的首地址,我猜测这个东西应该不是存储在内存中的,而是只是编译器认识*array,
在内存中只有地址的概念。
同样,当我们把它当做指针数组时,每个*array也是一个指针。对于开始我们给出的创建树,我们为指针分配了一段连续空间,用以存放指针。而这一段连续空间
的首地址,我们赋值给了这个**的值。
总的来说,在关于指针数组或二维数组的问题中,我们可以将**这种东西理解为一个头地址,指向一片内存空间。如果在此空间中存放数组,那么这就是二维数组,
如果存放了指针,那么就是一个指针数组。无论如何,只要我们把握住它们的首地址是**所定义的变量,就可对其进行操作。
最后,关于**的第三种我们也在程序中见到了,见程序的参数TreeNode **treenode。大多数数据结构的书中,在创建树的时候都喜欢使用这种方式,开始有点接受
不了,就要*来定义,在使用过程中,发现这个在初始化树的第一个节点和初始化后面的节点使用的方法不太一样。这种用法你的头结点是创建好的实体,填入的参数加&,由于
在创建树时我们常常使用递归的方式,也就是说每创建一个节点使用的方式相同,而我们在初始化时只能为第一个节点定义实体,后面的节点就需要在程序中定义。这就导致了
创建时需要分情况。为了保证我们程序的规范,我们使用**,输入参数时直接填入指针变量,让第一个节点也在创建程序中得到内存。这样,我们直接定义一个结构体指针的指针变量,
然后为它所指向的指针分配空间和进行下一步操作。