本篇主要记载链表如何初始化和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
| #include <iostream> using namespace std; typedef struct list_node ListNode; struct list_node { ListNode* next; int value; }; void InitList(ListNode*& head,int* array,int n) { head=NULL; ListNode* tmp; ListNode* record; for (int i = 0; i < n; i++) { tmp=new ListNode; tmp->value=array[i]; tmp->next=NULL; if(head==NULL) { head=tmp; record=head; } else { record->next=tmp; record=tmp; } } } void print_list(ListNode* list) { ListNode* tmp=list; while(tmp!=NULL) { cout<<tmp->value<<" "; tmp=tmp->next; } cout<<endl; } int main() { int array[]={1,2,3,4,5,6,7,8,9,10}; ListNode* list; InitList(list,array,10); print_list(list); return 0; }
|
接下来主要说一下各个节点怎么连接起来。假设,有一个只有一个节点的链表,在链表尾部加入一个节点。显然需要两个指针才能完成任务,一个指针(tmp)指向新开辟的节点,另一个指针(record)节点指向当前链表的这个节点,那么我们只要令 1 2
| record->next=tmp; record=tmp;
|
就可以把两个节点连起来,而且让record指向链表尾部。不过初始化链表这个过程,最开始应该是不存在链表的,那该怎么办了?其实我们只要把最开始的这个节点想象成空节点就好了。初始化第一个节点使用下面代码就可以了:
链表连接过程的大概思路就是这样子的,不过这并不代表你就能写出完整的代码了。
C++指针和引用
这里还是先分析一下每一行代码吧,毕竟C++指针和引用的东西也太多了。 1
| typedef struct list_node ListNode;
|
这行代码只要是认真学了c语言的人应该能明白吧,要是还不明白我在举一个例子
这不就是把数据类型重新换了一个名字啦,以后就可以用它来代替原来的数据类型了,那么struct list_node又是说明数据类型了?
其实它就是下面我们自己定义的数据类型: 1 2 3 4 5 6 7
| struct list_node { ListNode* next; int value; };
|
说白了就是定义了一个表示链表节点的数据类型。
InitList函数的主体思想我前面已经讲解了,这里就不再累赘了。不过有没有觉得参数
很奇怪,这里怎么有一个地址符号(&)了?接下来就来揭晓奥秘吧。
第一眼是不是觉得应该去掉地址符号,直接传一个链表的首地址就好了。如果去掉&符号那么将得到如下的错误:Segmentation Faults(完整的错误我没有写出来),通过google,发现这是新手C+ + 程序员会遇到的问题(你要是没有一眼看出其中的门道,欢迎你加入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
| #include <iostream> using namespace std; typedef struct list_node ListNode; struct list_node { ListNode* next; int value; }; void InitList(ListNode* head,int* array,int n) { cout<<"在InitList函数内,链表初始化之前,head的值:"<<head<<endl; cout<<"在InitList函数内,链表初始化之前,head的地址:"<<&head<<endl; cout<<endl; head=NULL; ListNode* tmp; ListNode* record; for (int i = 0; i < n; i++) { tmp=new ListNode; tmp->value=array[i]; tmp->next=NULL; if(head==NULL) { head=tmp; record=head; } else { record->next=tmp; record=tmp; } } cout<<"在InitList函数内,链表初始化之后,head的值:"<<head<<endl; cout<<"在InitList函数内,链表初始化之后,head的地址:"<<&head<<endl; cout<<endl; } void print_list(ListNode* list) { ListNode* tmp=list; while(tmp!=NULL) { cout<<tmp->value<<" "; tmp=tmp->next; } cout<<endl; } int main() { int array[]={1,2,3,4,5,6,7,8,9,10}; ListNode* list; cout<<"在初始化链表之前,list的值:"<<list<<endl; cout<<"在初始化链表之前,list的地址:"<<&list<<endl; cout<<endl; InitList(list,array,10); cout<<"在初始化链表之后,list的值:"<<list<<endl; cout<<"在初始化链表之后,list的地址:"<<&list<<endl; // print_list(list); return 0; }
|
运行的结果:
在初始化链表之前,list的值:0x400c7a
在初始化链表之前,list的地址:0x7fffa8805708
在InitList函数内,链表初始化之前,head的值:0x400c7a
在InitList函数内,链表初始化之前,head的地址:0x7fffa88056b8
在InitList函数内,链表初始化之后,head的值:0x12bd780
在InitList函数内,链表初始化之后,head的地址:0x7fffa88056b8
在初始化链表之后,list的值:0x400c7a
在初始化链表之后,list的地址:0x7fffa8805708
居然发现,list的值在初始化前后既然没有变化。在初始化过程中首地址开辟新地址空间,应该变了才对啊。接着,发现head的值在初始化前后是变化了的,这是为什么了?难道是在InitList中开辟的是临时变量,让人不经想起这段代码: 1 2 3 4 5 6 7 8 9 10 11 12
| void fun(int a) { a+=1; cout<<a<<endl; } int main() { int a=1; fun(a); cout<<a<<endl; return 0; }
|
不过真的就是同一个原因,就是因为在函数中开辟了临时变量。值得注意的是list和head指针指向的地址的值同样也是一个地址,而不是一个数值。
首先来看一下list的变化过程:
接着来看一下head的变化过程:
通过比较head和list最后指向的地址不一样,就是因为临时变量作用的结果(方框顶上的地址是不一样的哦,表示list传到head地址是变了的,也就是有临时变量)。如果我们加上&符号,地址就不会变了(其中的原因看一下下面推荐的博客吧).
总结一下,所有的问题都是因为被表面现象所迷惑,以为传一个指针进去就是把地址传进去了,归根到底是对指针和引用理解不深,因此我也找了一篇博客C++指针和引用的详解