学习笔记
[C指针]指针与结构体:完整源码 C语言 实现 单链表
addHead 源码
void addHead(LinkedList * list, void * data) {
Node * node = (Node *) malloc(sizeof(Node));
node->data = data;
if(list-> head == NULL) {
list->tail = node;
node->next = NULL;
} else {
node->next = list->head;
}
list->head = node;
}
- 注意:链表为空(还没有结点)不代表链表指针变量值是
NULL
,实际上,指针变量现在是什么都有可能,因此不能写list == NULL
,函数initializeList有明明白白地将list-> head初始化成了NULL
void initializeList(LinkedList * list) {
list->head = NULL;
list->tail = NULL;
}
addHead算法过程
先给节点分配内存,然后把传递给函数的数据赋给结构体的data字段。通过把data以void指针的形式传递,链表就能够持有用户想用的任何类型的数据了。接下来,我们检查链表是否为空,如果为空,就把尾指针指向节点,然后把节点的next字段赋值为NULL;如果不为空,那么将节点的next指针指向链表头。无论哪种情况,链表头都指向节点:
addHead 函数本质 内存示意图
addHead 本质.png内存示意图说明
-
首先,main函数内创建的变量,比如
Employee *samuel
、Employee *sally
、Employee *susan
这些在程序结束之前都是一直存在的; -
其次,
addHead
内部创建的局部变量Node * node
,在每次函数执行完毕后就不复再存在了,即每次函数调用时都会重新创建一个全新的Node * node
; -
回想一下,指针的本质的是什么?是一个变量,是一个存着地址的变量,对于
Employee *samuel
而言,它的变量名就是samuel
,它的值就是samuel
,这个值就是它存着的地址; -
再回想一下,C语言的函数是靠什么传递的?是值传递,是传递变量的值,那么
addHead(&linkedList, samuel);
其实就是addHead(&linkedList, 0x100);
-
现在进入到
addHead
内部,每次都需要在内部创建一个局部变量node
,对于Node * node = (Node *) malloc(sizeof(Node));
这条语句一定要分成三部分来看待:
第一部分是左边,创建了一个局部变量node,对应于示意图中绿色部分;
第二部分是右边,动态分类了一个Node类型的内存空间,根据Node类型的定义可以知道,Node类型本质就是两个指针,一个
void * data
,一个Node * next
,指针本身的大小只与机器、编译器有关,而与指向的数据类型无关,因此不管是一个void * data
,还是一个char * ptr
,在同一台机器、同一个编译器下,sizeof(data)
与sizeof(char)
的数值都代表的是指针的大小,在示意图中我假设指针大小是4字节,因此,data
与next
在连续的内存地址之下,表示为0x500、0x504
这种;第三部分,其实就是等于号 =,相当于示意图中的绿色箭头,把动态分配的内存空间首地址赋值给了node变量,即把0x500填到了绿色框内;
node变量是函数局部变量,在函数执行完毕之后就不复存在,但是malloc分配的动态内存是一直存在的
- 继续看源码,接下来是一句
node->data = data;
首先,是语句右边的那个data,这是什么?这是函数的传入参数
void addHead(LinkedList *list, void* data) {}
,前面说明过了,C语言是值传递,因此其实这个右边的data就是0x100,这条代码本质就是node->data = 0x100;
;然后再看,语句左边那个node->data是什么?前面说了,动态分配malloc在
内存 0x500 ~ 0x504 ~ 0x507
这段连续的内存申请了内存空间,作为一个Node类型,换言之,如果你想要得到0x500
这个数值,写的是&node->data
,注意前面这个&
符号;如果是 要修改0x500处的数据,就是用
node->data = 想写入的数据
,注意,我们不是修改0x500,而是修改0x500处的数据,换言之,我们 想将0x100填入到0x500处;
-
之后,就是关于
next
的部分与data
其实异曲同工,参见算法流程 -
最后,还有一个地方,是一定要理解的,那就是,在函数addHead执行完毕后,正如示意图所示,有两个地方存着
0x100
这个数据:
第一个地方,就是在函数addHead调用之前,最初那个
Employee *samuel
,这是一个指向Employee类型的指针,Employee *samuel = (Employee*) malloc(sizeof(Employee));
,这同样是需要分三个部分:右边、左边、中间去看待的语句,并且我们知道真正存着那个有着name以及age的数据的Employee 结构体是由malloc分配的,而0x100
就是那个结构体的起始地址,在0x100
开始那里才是存着真正的结构体;第二个地方,当我们函数addHead的时候,的确有把
0x100
以值传递的形式发送给函数的传入参数的,但是无论是参数还是局部变量在函数执行完后不复存在了,可是在函数执行过程中,我们有明明确确地把0x100写到了0x500处,因此这里也存着一个0x100
;
- 分析内存的变化,真正理解什么是C语言的值传递,真正理解什么是malloc动态分配的内存,所谓的链表结点的连接无非就是内存某处写入一个地址。
网友评论