0%

C 迷你系列(五)不透明指针

不透明指针

C 不支持面向对象编程,不过,借助不透明指针,我们也可以使用 C 封装数据以及支持某种程度的多态行为。我们可以隐藏数据结构的实现和支持函数,用户没有必要知道数据结构的实现细节,减少这些实现细节就可以降低应用程序的复杂度。此外,这样也不会引诱用户使用数据结构的内部细节,如果用户使用了,之后数据结构的实现发生变化后会导致问题。

说的直白一点就是,你不能通过该指针访问任何元素。

创建和使用不透明指针

不透明指针用来在 C 中实现数据封装。一种方法是在头文件中声明不包含任何实现细节的结构体,然后在实现文件中定义与数据结构的特定实现配合使用的函数。数据结构的用户可以看到声明和函数原型,但是实现会被隐藏(在 .c/.obj 文件中)。只有使用数据结构所需的信息会对用户可见,如果太多的内部信息可见,用户可能会使用这些信息,从而产生依赖。一旦内部结构发生变化,用户的代码可能就会失效。我们创建一个简单的链表来演示不透明指针的用法。

link.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef TEST_C_LINK_H
#define TEST_C_LINK_H

typedef void *Data;
typedef struct _linkedList LinkedList;

LinkedList* getLinkedListInstance();
void removeLinkedListInstance();
void addNode(LinkedList*, Data);
Data removeNode(LinkedList*);

#endif // TEST_C_LINK_H

Data 声明为 void 指针,这样允许实现处理任何类型的数据。LinkedList 的类型定义用了名为 _linkedList 的结构体,这个结构体的定义在实现文件中,对用户隐藏。我们提供来四种方法来使用链表。getLinkedListInstance() 函数获取 LinkedList 实例,一旦不再需要链表就应该调用 removeLinkedListInstance() 函数来释放内存。通过传递链表指针可以让函数处理一个或多个链表。

要将数据添加到链表,需要用 addNode() 函数,我们给它传递链表和要添加到链表的数据指针。removeNode() 函数会返回链表头部的数据。我们把链表的实现在名为 link.c 的独立文件中。

link.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdlib.h>
#include "link.h"

typedef struct _node
{
Data *data;
struct _node *next;
} Node;

struct _linkedList
{
Node *head;
};

LinkedList *getLinkedListInstance()
{
LinkedList *list = (LinkedList *) malloc(sizeof(LinkedList));
list->head = NULL;
return list;
}

void removeLinkedListInstance(LinkedList *list)
{
Node *tmp = list->head;
while (tmp != NULL)
{
free(tmp->data);
Node *current = tmp;
tmp = tmp->next;
free(current);
}
free(list);
}

void addNode(LinkedList *list, Data data)
{
Node *node = (Node *) malloc(sizeof(Node));
node->data = data;
if (list->head == NULL)
{
list->head = node;
node->next = NULL;
} else
{
node->next = list->head;
list->head = node;
}
}

Data removeNode(LinkedList *list)
{
if (list->head == NULL)
{
return NULL;
} else
{
Node *tmp = list->head;
Data *data;
list->head = list->head->next;
data = tmp->data;
free(tmp);
return data;
}
}

一个简单的链表就实现好了,那么我们怎么使用呢?

test.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <stdlib.h>


#include "link.h"

typedef struct {
char *username;
int age;
}Person;

void displayPerson(Person*);

int main(){
LinkedList *list = getLinkedListInstance();

Person *person = (Person *)malloc(sizeof(Person));
person->username = "jack";
person->age = 12;

Person *person2 = (Person *)malloc(sizeof(Person));
person2->username = "jobs";
person2->age = 15;

addNode(list, person);
addNode(list, person2);

person = removeNode(list);
displayPerson(person);

person = removeNode(list);
displayPerson(person);

removeLinkedListInstance(list);

return 0;
}

void displayPerson(Person *person){
printf("username is : %s, age is : %d\n", person->username, person->age);
}

LinkedList 就是一个不透明指针,LinkedList 中的成员对使用者隐藏,只提供必要的方法给使用者,隐藏了实现细节。这个就类似于面向对象中的封装。


参考

  • 【深入理解 C 指针】