特征(5):怎么组织信息


总结卡片
用变量表达信息是基本的方式,但一大堆变量会显得没有层次,缺乏封装。
C提供结构体,结构体是信息封装的一种常用的有效手段。
结构体是一个数据结构,它是一个集合,可存放所有类型的变量。一般来说,结构体内的变量(或信息)都是逻辑相关的,有某个原因才会放在一起。
结构体用起来同样很灵活,比如struct {int x; int y;} pt,pt就是一个结构体变量,里面有两个int变量,再比如,把“人”的信息封装在一个结构体中:

struct {char name[128]; int sex; int age;} person = {"free", 1, 28};
printf("name:%s, sex=%d, age=%d\n", person.name, person.sex, person.age);

结构体传递给函数时,如果传递结构体变量,则意味着结构体的所有成员都要拷贝到局部变量中,如果传递结构体的地址(存放在指针变量中),那就没有结构体成员的拷贝开销,但要注意指针意味着共享内存,被调用函数也可直接修改调用函数持有的结构体。
结构体内如果使用指针,指针又指向结构体本身,那就是递归定义,这种做法很常见。比如定义一个二叉查找树:

#include 
#include 
#include 
struct Node {
	char* content;
	int times;
	struct Node* left;
	struct Node* right;
};
struct Node* addItem(char* content, struct Node* root) {
	if(!root) {
		struct Node* node = (struct Node*)malloc(sizeof(struct Node));
		node->content = (char*)malloc(strlen(content)+1);
		strcpy(node->content,content);
		node->content[strlen(content)] = 0;
		node->times = 1;
		node->left = node->right = NULL;
		return node;
	}
	int cmp = strcmp(content, root->content);
	if(cmp == 0) {
		root->times ++;
	}
	else if (cmp < 0) {
		root->left = addItem(content, root->left);
	}
	else {
		root->right = addItem(content, root->right);
	} 
	return root;
}
void printTree(struct Node* root) {
	if(!root) return;
	printTree(root->left);
	printf("%s:%d\t", root->content, root->times);
	printTree(root->right);
}
struct Node* search(char* content, struct Node* root) {
	if(!root) return root;
	int cmp = strcmp(content, root->content);
	if(cmp==0) return root;
	else if(cmp < 0) return search(content, root->left);
	else return search(content, root->right);
}
int main() {
	char* arr[] = {"hello", "world", "123", "12a", "1234", "21ab", "21", "2abc", "2022.2.15", "hello","123", "12a", "hello"};
	int cnt=sizeof arr/sizeof *arr;
	struct Node* root=NULL;
	for(int i=0;itimes);
	else printf("no \'%s\' in tree\n", target);
	target="free";
	tnode=search(target, root);
	if(tnode) printf("got \'%s\':%d\n", target, tnode->times);
	else printf("no \'%s\' in tree\n", target);
	addItem(target,root);
	addItem(target,root);
	addItem(target,root);
	addItem(target,root);
	tnode=search(target, root);
	if(tnode) printf("got \'%s\':%d\n", target, tnode->times);
	else printf("no \'%s\' in tree\n", target);
	printf("\n");
	return 0;
}

创建二叉查找树-执行效果
以上的实现未考虑内存释放或内存不足的情况。
以前说函数调用自己是因为函数正好实现了某个功能,而结构体“指向”自己,则是因为自己刚好定义了这个结构,但要注意指针指向“自己”这个类型,实际指向的是另一个同结构类型的对象。使用递归要大胆一点。
递归函数实现,一要考虑结束条件,二要考虑怎么调用下一层,并且只需要考虑当前层与下一层。
再举一个例子,用数组来实现一个散列(hash)表,数组的每一个元素都是一个结构体的指针,这个结构体又指向结构体的另一个对象,即数组的元素就是一个单链表。结构体包括一个字符串,用简单的hash函数把这个字符串转化成某个小范围内的非负数的值,这个值作为下标直接定位到数组的元素,实现与测试代码如下:

#include 
#include 
#include 
struct Node {
	char* content;
	int times;
	struct Node* next;
};
#define TABLE_SIZE 101
static struct Node* hashTable[TABLE_SIZE];
unsigned hash(char* s) {
	unsigned ret;
	for(ret=0;*s!='\0';s++) ret = *s+16*ret;
	return ret%TABLE_SIZE;
}
struct Node* search(char* s) {
	struct Node* pn;
	for(pn=hashTable[hash(s)]; pn; pn=pn->next) 
		if(strcmp(pn->content,s) == 0) 
			return pn;
	return NULL;
}
void addItem(char* s) {
	struct Node* pn=search(s);
	if(pn) 
		pn->times++;
	else {
		struct Node* n=(struct Node*)malloc(sizeof *pn);
		n->content=(char*)malloc(sizeof(strlen(s)+1));
		strcpy(n->content,s);
		n->content[strlen(s)]=0;
		n->times=1;
		n->next=hashTable[hash(s)];
		hashTable[hash(s)]=n;
	}
}
void printHashTable() {
	for(int i=0;inext) printf("%s:%d  ",pn->content, pn->times);
			printf("\n");
		}
	}
}
int main() {
	char* arr[] = {"hello", "world", "123", "12a", "1234", "21ab", "21", "2abc", "2022.2.15", "hello","123", "12a", "hello"};
	int cnt=sizeof arr/sizeof *arr;
	for(int i=0;itimes);
	else printf("no \'%s\' in tree\n", target);
	target="freeself0363.2022.2.15";
	n=search(target);
	if(n) printf("got \'%s\':%d\n", target, n->times);
	else printf("no \'%s\' in tree\n", target);
	addItem(target);
	addItem(target);
	addItem(target);
	addItem(target);
	addItem(target);
	n=search(target);
	if(n) printf("got \'%s\':%d\n", target, n->times);
	else printf("no \'%s\' in tree\n", target);
	printf("\n");
	return 0;
}

hash表执行效果