前言
本章继续拓展前面所学的指针概念,包括结构体的数组表示法、结构体的内存分配、结构体内存管理技术以及函数指针的用法。
介绍
结构体的声明:
struct _person {
char* firstName;
char* lastName;
char* title;
unsigned int age;
}
使用typedef关键字可以简化使用:
struct struct_person {
char* firstName;
char* lastName;
char* title;
unsigned int age;
} Person;
Person person;
声明Person结构体指针并为它分配内存:
Person *ptrPerson;
ptrPerson = (Person*) malloc(sizeof(Person));
注:为结构体分配内存需要考虑到内存对齐。
结构体释放问题
在为结构体分配内存时,运行时系统不会自动为结构体内部的指针分配内存。类似地,当结构体消失时,运行时系统也不会自动释放结构体内部的指针指向的内存。
typedef struct_person {
char* firstName;
char* lastName;
char* title;
uint age;
} Person;
当我们声明这个类型的变量或者为这个类型动态分配内存时,三个指针会包含垃圾数据。
void processPerson() {
Person person;
...
}
对结构体的字段进行初始化赋值:
void initializePerson (Person *person. const char* fn.
const char* ln,const char* title, uint age) {
person->firstName = (char*) malloc(strlen(fn) + 1);
strcpy(person->firstName, fn);
person->lastName = (char*) malloc(strlen(ln) + 1);
strcpy(person->lastName, ln);
person->title = (char*) malloc(strlen(title)+ 1);
strcpy(person->title, title);
person->age = age;
}
void processPerson() {
Person person;
initializePerson(&person, "Peter", "Underwood", "Manager", 36);
...
}
int main() {
processPerson();
...
}
因为这个声明是函数的一部分,函数返回后person的内存会消失。不过,动态分配的内存不会被释放,仍然保存在堆上。不幸的是,我们丢失了它们的地址,因此无法将其释放,从而导致了内存泄漏。
我们需要在processPerson函数结束前释放内存:
void deallocatePerson(Person *person) {
free(person->firstName);
free(person->lastName);
free(person->title);
}
void processPerson( ) {
Person person;
initializePerson(&person, "Peter", "Underwood", "Manager",36);
...
deallocatePerson(&person);
避免malloc/free开销
重复分配然后释放结构体会产生开销,可能导致巨大的性能瓶颈。
解决办法(池化
):为分配的结构体单独维护一个表。当用户不再需要某个结构体实例时,将其返回结构体池中。当我们需要某个实例时,从结构体池中获取一个对象。如果池中没有可用的元素,我们就动态分配一个实例。这种方法高效地维护一个结构体池,能按需使用和重复使用内存。
注:这种技术被广泛应用于诸多领域。
Person *list[LIST_SIZE];
void initializeList() {
for(int i = 0; i < LIST_SIZE; i++) {
list[i] = NULL;
}
}
//如果存在可用的结构体,这个函数从表中获取一个。将数组的元素跟NULL比较,返回第一个非空的元素,然后将它在list中的位置赋值为NULL。如果没有可用的结构体,那就创建并返回一个新的Person实例。
Person *getPerson() {
for(int i = 0; i < LIST_SIZE; i++) {
if(list[i] != NULL) {
Person *ptr = list[i];
list[i] = NULL;
return ptr;
}
}
Person *person = (Person*)malloc(sizeof(Person));
return person;
}
//这个函数要么将结构体返回表,要么把结构体释放掉。我们会检查数组元素看看有没有NULL值,有的话就将person添加到那个位置,然后返回指针。如果表满了,就用deallocatePerson 函数释放person内的指针,然后释放person,最后返回NULL。
Person *returnPerson (Person *person) {
for(int i=0; i<LIST_SIZE; i++) {
if (list[i] == NULL) {
list[i] = person;
return person;
}
}
deallocatePerson(person);
free(person);
return NULL;
}
//初始化,以及添加到表
initializeList();
Person *ptrPerson;
ptrPerson = getPerson();
initializePerson(ptrPerson,"Ralph", "Fitsgerald", "Mr.", 35);
displayPerson(*ptrPerson);
returnPerson(ptrPerson);
用指针支持数据结构
注:这部分内容主要介绍用指针实现常见的数据结构,考虑到之前已进行过充分的学习,因此此处略去大部分的方法介绍。
单链表
typedef struct _node {
void *data;
struct _node *next;
} Node;
typedf struct _linkedList {
Node *head;
Node *tail;
Node *current;
} LinkedList;
用指针支持队列
typedef LinkedList Queue;
用指针支持栈
typedef LinkedList Stack;
用指针支持树
typedef struct _tree {
void *data;
struct _tree *left;
struct _tree *right;
} TreeeNode;
小结
这部分内容很早之前就进行过设计,尤其与指针与结构体之间组成常见的数据结构。