我有一些代码可以根据指向节点的指针创建一个简单的树,并且希望将此树(及其数据)写入文件,然后将其从文件读回内存。我怎样才能做到这一点 ?这是我创建简单树的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Node
{
void *data;
struct Node *left;
struct Node *right;
};
int main(void)
{
unsigned int_size = sizeof(int);
int data;
/* Tree root node */
struct Node *start = (struct Node*)malloc(sizeof(struct Node));
data = 5;
start->data = malloc(int_size);
memcpy(start->data, &data, int_size);
/* Left node of root */
start->left = (struct Node*)malloc(sizeof(struct Node));
data = 4;
start->left->data = malloc(int_size);
memcpy(start->left->data, &data, int_size);
start->left->left = NULL;
start->left->right = NULL;
/* Right node of root */
start->right = (struct Node*)malloc(sizeof(struct Node));
data = 3;
start->right->data = malloc(int_size);
memcpy(start->right->data, &data, int_size);
start->right->left = NULL;
start->right->right = NULL;
/* Print data */
printf("%d\n",*(int*)(start->data));
printf("%d\n",*(int*)(start->left->data));
printf("%d\n",*(int*)(start->right->data));
return 0;
}
答案 0 :(得分:1)
这里有个建议,我使用预处理器宏TYPE来更改类型,并使用分离的函数来读写该类型的元素,我还使用函数 mk 轻松创建节点,而不是像对每个节点一样复制代码。
对于序列化,我使用一种非常简单的方法
我还添加了 pr 来轻松打印树作为调试功能。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TYPE int
struct Node
{
TYPE data;
struct Node *left;
struct Node *right;
};
/* make a new Node */
struct Node * mk(TYPE v, struct Node * l, struct Node * r)
{
struct Node * n = malloc(sizeof(struct Node));
if (n == NULL) {
fprintf(stderr, "not enough memory\n");
exit(-1);
}
n->data = v;
n->left = l;
n->right = r;
return n;
}
/* write/read data */
void wrData(TYPE v, FILE * fp)
{
fprintf(fp, "%d", v); /* dedicated to int */
}
TYPE rdData(FILE * fp)
{
TYPE v;
fscanf(fp, "%d", &v); /* dedicated to int */
return v;
}
/* serialize a tree */
void wr(struct Node * n, FILE * fp)
{
if (n == NULL)
fputc('e', fp);
else {
fputc('n', fp);
wrData(n->data, fp);
wr(n->left, fp);
wr(n->right, fp);
}
}
/* unserialize a tree */
struct Node * rd(FILE * fp)
{
switch (fgetc(fp)) {
case 'e':
return NULL;
case 'n':
{
TYPE v = rdData(fp);
struct Node * l = rd(fp);
return mk(v, l, rd(fp));
}
default:
fprintf(stderr, "invalid file");
exit(-1);
}
}
/* for debug, show tree */
void pr(struct Node * t)
{
if (t == NULL)
printf("()");
else {
putchar('(');
pr(t->left);
putchar(' ');
wrData(t->data, stdout);
putchar(' ');
pr(t->right);
putchar(')');
}
}
/* free a tree */
void del(struct Node * t)
{
if (t != NULL) {
del(t->left);
del(t->right);
free(t);
}
}
int main()
{
/* Tree root node */
struct Node *start = mk(5, mk(4, NULL, NULL), mk(3, NULL, NULL));
/* show it */
pr(start);
putchar('\n');
/* serialize */
FILE * fp;
if ((fp = fopen("/tmp/t", "w")) == 0) {
fprintf(stderr, " cannot open /tmp/t to write");
exit(-1);
}
wr(start, fp);
fclose(fp);
/* free tree */
del(start);
/* unserialize */
if ((fp = fopen("/tmp/t", "r")) == 0) {
fprintf(stderr, " cannot open /tmp/t to read");
exit(-1);
}
start = rd(fp);
fclose(fp);
/* show it */
pr(start);
putchar('\n');
/* free it */
del(start);
return 0;
}
编译和执行:
/tmp % gcc -pedantic -Wextra -Wall t.c
/tmp % ./a.out
((() 4 ()) 5 (() 3 ()))
((() 4 ()) 5 (() 3 ()))
/tmp % cat t ; echo
n5n4een3ee
在 valgrind 下执行:
/tmp % valgrind ./a.out
==19907== Memcheck, a memory error detector
==19907== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==19907== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==19907== Command: ./a.out
==19907==
((() 4 ()) 5 (() 3 ()))
((() 4 ()) 5 (() 3 ()))
==19907==
==19907== HEAP SUMMARY:
==19907== in use at exit: 0 bytes in 0 blocks
==19907== total heap usage: 8 allocs, 8 frees, 1,280 bytes allocated
==19907==
==19907== All heap blocks were freed -- no leaks are possible
==19907==
==19907== For counts of detected and suppressed errors, rerun with: -v
==19907== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 6)
答案 1 :(得分:1)
有两种常见的方式来序列化在其他对象上具有指针的对象。第一个是用文件中的偏移量替换指针,第二个是使用简单索引。实际上,偏移方式主要用于直接从文件(例如数据库文件)中使用数据时,而索引方式则用于简单存储。
一种简单的方法是将节点以left_node-right_node-main_node的形式递归存储到文件中。这样,当您存储节点时,您已经知道该节点的左右子节点的索引。然后可能的实现是:
static size_t do_save(FILE *fd, struct Node *node, unsigned data_size, int curr) {
size_t left=0, right=0;
if (node->left != NULL) { // first left sub_hierarchy
left = do_save(fd, node->left, data_size, curr);
curr = left;
}
if (node->left != NULL) { // then right one
right = do_save(fd, node->right, data_size, curr);
curr = right;
}
fwrite(&left, sizeof(left), 1, fd); // store index of left child
fwrite(&right, sizeof(right), 1, fd); // then index of right child
fwrite(node->data, data_size, 1, fd); // then the data
return curr + 1; // and return current index
}
size_t save(FILE *fd, struct Node *root, unsigned data_size) {
size_t nb = do_save(fd, root, data_size, 0);
return nb;
}
这里,索引为0表示空指针,而非null索引是文件中基于1的索引
要反序列化,因为我假设您希望分别分配每个节点,我将使用一个临时指针数组来保留分配节点的实际地址:
struct Node* load(FILE *fd, unsigned data_size) {
size_t nb_elts, left, right;
fseek(fd, 0, SEEK_END); // computer number of nodes in the file
nb_elts = ftell(fd) / (data_size + 2*sizeof(size_t));
struct Node** ptx = malloc(nb_elts * sizeof(*ptx)); // allocate array of pointers
fseek(fd, 0, SEEK_SET);
for(size_t i=0; i<nb_elts; i++) { // loop reading nodes
ptx[i] = malloc(sizeof(struct Node)); // allocate node and data
ptx[i]->data = malloc(data_size);
fread(&left, sizeof(size_t), 1, fd); // extract child indices
fread(&right, sizeof(size_t), 1, fd);
fread(ptx[i]->data, data_size, 1, fd); // read data
ptx[i]->left = (left == 0) ? NULL : ptx[left - 1]; // convert indices to pointers
ptx[i]->right = (right == 0) ? NULL : ptx[right - 1];
}
struct Node *last = ptx[nb_elts - 1];
free(ptx); // free the temporary array
return last; // and return the root node
}