接可变数组
但如果我们可以使用BLOCK,将其都拼接在一起,并不是用上面的方法复制粘贴。每一个BLOCK会有一个单元指向的是下一个BLOCK的地址,这样就不会有上述的问题了
所以对于一个单元,它里面应该分成两部分:
1.数据
2.下一个单元的地址(指针)
这样指向的下一个数据结构也应是如此。
直到最后一个单元,保存的是数据,地址用一个标记标志它已经没有指向下一个地方了
当然最开始,还需要一个指针指向第一个单元的地址,俗称head
这样的东西就叫做链表(linklist),每一个单元叫做节点
定义:
typedef struct _node{
int value;
struct _node *next;
} Node;
对于往链表内加入数据,代码实现如下:
#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
// int value;
// struct _node *next;
//} Node;
int main(int argc, char** argv) {
Node *head = NULL;//最开始head指向的是null
int number;
do{
cin>>number;
if(number != -1){
//add to linked-list,这是新的一个p,它是要放到最后去的
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
//find the last,找到目前链表中的最后一个节点
//该节点的next还是null,我们要让其指向p
Node *last = head;
if( last ){//最初始的时候last=head,head=null,所以此时的last->next是无效的,为了防止这个bug发生,添加if保险
while(last->next){//直到循环结束,那么last->next就是最后一个null了
last = last->next;
}
//找到目前链表的末尾,让其指向p
last->next = p;
} else {
head = p;//如果last是null,那么head就是p,这个else只会在初次赋值的时候用到
}
}
} while(number != -1);
return 0;
}
2.链表的函数
在上述do-while函数内的代码,实质上就是一个对链表进行赋值的功能,那么我们可以将其封装成一个函数,这样更易使用,如:
#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
// int value;
// struct _node *next;
//} Node;
void add(Node* head, int number);
int main(int argc, char** argv) {
Node *head = NULL;//最开始head指向的是null
int number;
do{
cin>>number;
if(number != -1){
add(head,number)
}
} while(number != -1);
return 0;
}
void add(Node* head,int number){
Node *p = (Node*)malloc(sizeof(Node));
//add to linked-list,这是新的一个p,它是要放到最后去的
p->value = number;
p->next = NULL;
//find the last,找到目前链表中的最后一个节点
//该节点的next还是null,我们要让其指向p
Node *last = head;
if( last ){//最初始的时候last=head,head=null,所以此时的last->next是无效的,为了防止这个bug发生,添加if保险
while(last->next){//直到循环结束,那么last->next就是最后一个null了
last = last->next;
}
//找到目前链表的末尾,让其指向p
last->next = p;
} else {
head = p;//如果last是null,那么head就是p,这个else只会在初次赋值的时候用到
}
}
但这样有非常严重的问题,在add函数中,我的head是一个传参,在函数中的head = p;
这个操作实质上并没有任何用,add函数用完后的传参被释放,head永远是NULL
解决方法
1.将Node *head设为全局变量
缺陷:全局变量是有害的,是一次性的,这个head只对一个链表起作用,如果一个程序有多个链表,全设为全局变量很危险
2.将add函数设置一个返回值head,执行完毕后将head的值返回回去
head = add(head, number);
这方法看似不错,但是在使用的时候感觉会很奇怪(尤其是给他人使用这个方法的时候)——只不过想add值进去,为什么要返回值呢?要返回也是返回add成功与否吧?
3.传head的指针回去
那这样,函数的应该为
void add(Node **pHead, int number);
但是用到**的时候就要注意,指针的指针是一个非常复杂的东西,一不小心会产生难以想象的错误,且指向指针的指针代码也不易于阅读
4.创建一个结构体list,里面存的是Node *head,再对其进行使用
函数调用的时候是调用list的地址,实质上其实是和3是一样的,但是换了一个更易阅读的方式。也就是通过自己定义的数据结构来代表整个list,代码:
#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
// int value;
// struct _node *next;
//} Node;
typedef struct _list{
Node *head;
} List;
void add(List *plist, int number);
int main(int argc, char** argv) {
// Node *head = NULL;//最开始head指向的是null
List list;
list.head = NULL;
int number;
do{
cin>>number;
if(number != -1){
add(&list, number);
}
} while(number != -1);
return 0;
}
void add(List *plist, int number){
Node *p = (Node*)malloc(sizeof(Node));
//add to linked-list,这是新的一个p,它是要放到最后去的
p->value = number;
p->next = NULL;
//find the last,找到目前链表中的最后一个节点
//该节点的next还是null,我们要让其指向p
Node *last = plist->head;
if( last ){//最初始的时候last=head,head=null,所以此时的last->next是无效的,为了防止这个bug发生,添加if保险
while(last->next){//直到循环结束,那么last->next就是最后一个null了
last = last->next;
}
//找到目前链表的末尾,让其指向p
last->next = p;
} else {
plist->head = p;//如果last是null,那么head就是p,这个else只会在初次赋值的时候用到
}
}
思考:每次加入新东西的时候都要用last从head开始从头寻找最后一个单元再做后续的动作,“每次都要遍历”这个操作非常有重复性并且浪费时间,那么我们能否就定义一个东西,让它一直指向的就是我们的最后一个单元呢?(视频内没讲了,提示:在list里再加一个Node *tail)
链表的搜索
遍历:
一个指针,拿到head,输出value
变到下一个地址
当没有下一个地址(最后指向的是null)循环结束
可以用for,也可以用while,for循环更加易懂:
Node *p;//开始遍历
for( p = list.head; p; p = p->next){
cout<<p->value<<' ';
}
cout<<endl;
同样的,可以封装成函数,取名为print,我们需要的只是head
void print(List *plist){
Node *p;//开始遍历
for( p = plist->head; p; p = p->next){
cout<<p->value<<' ';
}
cout<<endl;
}
既然能遍历了,那么我想寻找链表中某一个特定的数那也很方便了
cin>>number;
Node *p;
int isfound = 0;
for(p = list.head; p; p->next){
if(p->value == number){
cout<<"find it"<<endl;
isfound = 1;
break;
}
}
if(!isfound){
cout<<"don't find it"<<endl;
}
那么找到这个之后,我们想删除这块
单元,该如何考虑
1.该单元的前面一个单元所指的位置就不应该是待删除的这块了,而是下一块
2.free掉这个待删除的单元
1.1 如何找到上一个单元?
再创建一个指针q,那个指针指向它前面那个单元,这样只要让q.next = p.next
就可以安心free(p)
了
Node *q;
for(q = NULL,p = list.head; p; q=p,p = p->next){//先让q=p,p再向后走,这样的q就是上一个p了
if(p->value == number){
q->next = p->next;
free(p);
break;
}
}
但是这样有一个问题,q初始值是null,对于如果第一个单元就是要去掉的,此时我们要修改的是head所指的地方,而不是对q有什么操作。
在如此情况下,上述代码中的q->next是非法、无意义的
所以我们需要通过语句将其保护起来(让代码更加严谨)
代码实现:
Node *q;
for(q = NULL,p = list.head; p; q=p,p = p->next){//先让q=p,p再向后走,这样的q就是上一个p了
if(p->value == number){
if(q){
q->next = p->next;
} else {
list.head = p->next;
}
free(p);
break;
}
}
链表的清除
每次遍历到一个,先有一个临时变量存储next,然后free(),接着到下一个节点,继续free,直到p=NULL结束
for( p = list.head; p; p=q){
q = p->next;
free(p);
}
程序设计入门-链表到此为止,至此将此课的代码全部奉上:
node.h:
#ifndef _NODE_H_
#define _NODE_H_
typedef struct _node{
int value;
struct _node *next;
} Node;
#endif
main.cpp:
#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
// int value;
// struct _node *next;
//} Node;
typedef struct _list{
Node *head;
} List;
void add(List *plist, int number);
void print(List *plist);
int main(int argc, char** argv) {
// Node *head = NULL;//最开始head指向的是null
List list;
list.head = NULL;
int number;
do{
cin>>number;
if(number != -1){
add(&list, number);
}
} while(number != -1);
// print(&list);
cin>>number;//寻找值
Node *p;
int isfound = 0;
for(p = list.head; p; p->next){
if(p->value == number){
cout<<"find it"<<endl;
isfound = 1;
break;
}
}
if(!isfound){
cout<<"don't find it"<<endl;
}
Node *q;//删除指定值
for(q = NULL,p = list.head; p; q=p,p = p->next){//先让q=p,p再向后走,这样的q就是上一个p了
if(p->value == number){
if(q){
q->next = p->next;
} else {
list.head = p->next;
}
free(p);
break;
}
}
for( p = list.head; p; p=q){//链表的清除
q = p->next;
free(p);
}
return 0;
}
void add(List *plist, int number){
Node *p = (Node*)malloc(sizeof(Node));
//add to linked-list,这是新的一个p,它是要放到最后去的
p->value = number;
p->next = NULL;
//find the last,找到目前链表中的最后一个节点
//该节点的next还是null,我们要让其指向p
Node *last = plist->head;
if( last ){//最初始的时候last=head,head=null,所以此时的last->next是无效的,为了防止这个bug发生,添加if保险
while(last->next){//直到循环结束,那么last->next就是最后一个null了
last = last->next;
}
//找到目前链表的末尾,让其指向p
last->next = p;
} else {
plist->head = p;//如果last是null,那么head就是p,这个else只会在初次赋值的时候用到
}
}
void print(List *plist){
Node *p;//开始遍历
for( p = plist->head; p; p = p->next){
cout<<p->value<<' ';
}
cout<<endl;
}