目录
使用malloc函数或new运算符为链表结点分配内存空间
malloc函数
malloc函数是c语言中stdlib.h头文件下用于申请动态内存的函数,其返回类型是申请的同变量类型的指针,其基本用法如下:
typename* p=(typename*)malloc(sizeof(typename));
以申请一个int型变量和一个node型结构体变量为例:
int* p=(int*)malloc(sizeof(int));
node* p=(node*)malloc(sizeof(node));
这个写法的逻辑是:以需要申请的内存空间大小(即sizeof(node))为malloc函数的参数,这样malloc函数就会向内存申请一块大小为sizeof(node)的空间,并且返回指向这块空间的指针。但是此时这个指针是一个未确定类型的指针void*,因此需要把它强制转换为node*型的指针,因此在malloc之前加上(node*),这样等号右边就得到了一个node*型的指针,并通过赋值等号把这个指针赋给node*型的指针变量p,就成功申请了一块node类型大小的内存空间,即一个node型的结构体变量,并通过指针p来访问它。如果申请失败,则会返回空指针NULL。
其实一般来说,如果只是申请一个链表结点的话是不会失败的,失败一般发生在使用malloc申请了较大的动态数组,即
int* p=(int*)malloc(1000000*sizeof(int));
这种情况下malloc会返回空指针NULL并赋值给p,因此只要是正常分配一个结点的空间,是不会失败的,当然发生死循环导致无限申请的情况除外。
new运算符
new是C++中用来申请动态空间的运算符,其返回类型同样是申请的同变量类型的指针,其基本用法如下:
typename* p=new typename;
同样以申请一个int型变量和一个node型结构体变量为例:
int* p=new int;
node* p=new node;
可以看到,new的写法比malloc要简洁许多,只需要“new+类型名"即可分配一块该类型的空间,并返回一个对应类型的指针。如果申请失败,则会启动C++异常机制处理而不是返回空指针NULL。和malloc同理,如果是使用new申请了较大的动态数组,即:
int* p=new int[1000000];
这时会发生异常,并在没有特殊处理的情况下直接退出程序。不过,只要是正常分配一个结点的空间,也是不会失败的。
内存泄漏
内存泄漏是指使用malloc与new开辟出来的内存空间在使用过后没有释放,导致其在程序结束之前始终占据该内存空间。这在一些较大的程序中很容易导致内存消耗过快以致最后无内存可分配。在使用完malloc与new开辟出来的空间后必须将其释放,否则会造成内存泄漏。下面详细叙说如何释放malloc与new开辟出来的空间。
free函数
free函数是对应malloc函数的,同样在stdlib.h头文件下。其使用方法非常简单,只需要在free的参数中填写需要释放的内存空间的指针变量(不妨设为p)即可:
free(p);
free(p)函数主要实现了两个效果:释放指针变量p所指向的内存空间:将指针变量p指向空地址NULL。由此可以知道,在free函数执行之后,指针变量p本身并没有消失,只不过让它指向了空地址NULL,但是它原指向的内存是确实被释放了的。
需要注意的是,malloc函数与free函数必须成对出现,否则容易产生内存泄漏。
delete运算符
delete运算符是对应new运算符的,其使用方法和实现效果均与free相同。使用delete只需要在delete的参数中填写需要释放的内存空间的指针变量(不妨设为p)即可:
delete p;
和free函数一样,new运算符与delete运算符必须成对出现。否则会容易产生内存泄漏。
链表的基本操作
创建链表
现在已经可以通过malloc函数或者new函数来获得若干个零散的结点了,那么接下来要做的就是把这些零散的结点连接起来。方法只要把每个结点的next指针指向下一个结点的地址即可。下面的代码用最直观的写法是实现了链表的建立:
node* node1=new node;
node* node2=new node;
node* node3=new node;
node* node4=new node;
node* node5=new node;
node1->data=5;
node1->next=node2;
node2->data=3;
node2->next=node3;
node3->data=6;
node3->next=node4;
node4->data=1;
node4->next=node5;
node5->data=2;
node5->next=NULL;
上面的写法似乎显得有些冗长,并且在结点个数不确定的情况下更是无法确定其写法的,因此一般使用for循环来确立需要的链表:
#include<stdio.h>
#include<stdlib.h>
struct node{
int data;
node* next;
};
node* create(int array[]){
node *p,*pre,*head;
head=new node;
head->next=NULL;
pre=head;
for(int i=0;i<5;i++){
p=new node;
p->data=array[i];
p->next=NULL;
pre->next=p;
pre=p;
}
return head;
}
int main(){
int array[5]={5,3,6,1,2};
node* l=create(array);
l=l->next;
while(l!=NULL){
printf("%d",l->data);
l=l->next;
}
return 0;
}
查找元素
只需要从第一个结点开始,不断判断当前结点的数据域是否等于x,如果等于,那么就给计数器count加1。这样当到达链表结尾时,count的值就是链表中元素x的个数。
int serach(node* head,int x){
int count=0;
node* p=head->next;
while(p!=NULL){
if(p->data==x){
count++;
}
p=p->next;
}
return count;
}
插入元素
对链表来说,插入元素是指在链表给定位置的地方插入一个结点。
void insert(node* head,int pos,int x){
node* p=head;
for(int i=0;i<pos-1;i++){
p=p->next;
}
node* q=new node;
q->data=x;
q->next=p->next;
p->next=q;
}
删除元素
对链表来说,删除元素是指删除链表上所有值为给定的数x。
删除操作是这样进行的:
(1)由指针变量p枚举结点,另一个指针变量pre表示p所指向结点的前驱结点。
(2)当p所指结点的数据域恰好为x时,进行下面三个操作。
1.令pre所指结点的指针域next指向p所指结点的下一个结点。
2.释放p所指结点的内存空间。
3.令p指向pre所指结点的下一个结点。
void del(node* head,int x){
node* p=head->next;
node* pre=head;
while(p!=NULL){
if(p->data==x){
pre->next=p->next;
delete(p);
p=pre->next;
}
else{
pre=p;
p=p->next;
}
}
}
静态链表
前面说的都是动态链表,即需要指针来建立结点之间的连接关系。而对有些问题来说,结点的地址是比较小的数,这样就没必要去建立动态链表,而应使用方便得多的静态链表。
静态链表的实现原理是hash,即通过建立一个结构体数组,并令数组的下标直接表示结点的地址,来达到直接访问数组中的元素就能访问结点的效果。另外,由于结点的访问非常方便,因此静态链表是不需要头结点的。静态链表结点定义的方法如下:
struct Node{
typename data;
int next;
}node[size];
在上面的定义中,next是一个int型的整数,用以存放下一个结点的地址(事实上就是数组下标)。例如,如果初始结点的地址为1111,第二个结点的地址是22222,第三个结点的地址是33333,且第三个结点为链表末尾,那么整个静态链表的结点就可以通过下面的写法连接起来:
node[11111]=22222;
node[22222]=33333;
node[33333]=-1;