您的位置:首页 > 编程语言 > C语言/C++

【C++基础复习01】结构体和链表

2017-12-16 14:16 435 查看

结构体

结构体是一个可以包含不同数据类型的一个结构,使用修饰符
struct
进行定义。结构体可以在一个结构中声明不同的数据类型,并且只有结构相同的结构体变量可以相互赋值。

先来看例子,如下构造一个结构体:

struct student
{
int id;
char name[9];
int Chinese,math,English;
double average;
};


以上就是由个人定义的一个结构体student,其中的属性(id,Chinese,average)等被称之为结构体成员,并且有自己的数据类型。需要注意的是,在{}结尾处,要加上一个分号 ; 否则会导致语法错误。另外,描述的结构体并不占用内存空间,只有当用这种新的数据类型定义变量,数组,申请变量或申请堆数组时候才要分配空间。

接下来我们利用这个结构体来创建一些变量并来访问,代码片段如下:

student s;        //建立一个student对象
student *ps=&s;     //定义一个指针变量指向s
student &rs=s;      //定义一个引用(不占用空间)
s.id=1;
strcpy(s.name,"李诗珺"); //利用字符串复制对s.name赋值,不能用s.name="XX"
ps->Chinese=95;    //用指针变量访问目标对象成员
(*ps).math=90;      //等价于 ps->math=90
s.English=92;
rs.average=(s.Chinese+rs.English+ps->math)/3.0;
//引用访问“绑定”的目标对象成员
cout<<s.name<<"的平均成绩为"<<s.average<<endl;


需要注意的是,优先级为2的圆点运算符“.”用于结构体对象访问其成员;箭头运算符“->”用于结构体指针访问目标对象成员。这样一来,基本就掌握了结构体的基本用法了。

单向链表

在理解了结构体的基础上,再来认识链表就比较容易了。能够单方向联络的连接方式被称为单向链表,构成单向链表的元素被称为结点。结点应该具有承载数据及指向下一个结点的能力。在链式连接结构中,删除某个结点只需要将该节点保存的下一个结点地址“交给”上一个结点即可。由于结点个数以及各个结点在内存中的地址都很容易得到,所以将单向链表传递给函数时,只需要传递链首地址。

先来试着定义一个结点:

struct memory    //定义内存空间结点
{
int address;      //分区首地址
int space;          //空间大小
bool status;       //状态位
memory *next;   //同类指针,指向下一个内存空间的地址
};


以上便定义了一种型的数据类型,先来创建一条空链表:

memory *head=NULL;


创建完毕之后,就要对这条单向链表进行操作了。首先得加入元素,也就是在首节点处再插入结点,插入函数如下:

void InsertBeforeHead(memory *&head,int space,bool status,int address)
{
memory *p=new memory ;  //创建一个新结点
p->address=address;
p->space=space;   //赋值操作
p->status=status;
p->next=head;    //新结点将原链首结点当成下一个结点
head=p;  //head记录新结点的地址,成为新的链首地址
//最先定义的head到最后会成为链尾
}


在链表创建完成之后(一般创建空链表),然后利用上述函数添加一个新结点到链首前成为新的链首。当插入完成之后,就完成了一条单链表的创建。接下来附上一些常用的链表操作函数。

删除链首结点的函数:

void DeleteHead(memory *&head)
//这里需要使用引用,因为要直接修改head的指向
{
memory *p=head;   //记录当前链首结点地址
if(head!=NULL)  //判断是否为空链表
{
head=head->next;   //下一个结点成为新的链首结点,相当于删除了当前结点
delete p;  //释放原链首结点
}
}


遍历函数的运用:

memory *p;
int n;  //统计结点个数
for(p=head;p!=NULL;p=p->next)
{   n++;   //统计计算
cout<<p->space<<endl;   //遍历输出数据

}


释放所有结点:

while(head!=NULL)
{
p=head;
head=head->next; //使head指向的下一个结点成为头结点
delete p; //释放空间
}


插入一个结点:

void Insert(int i,int e,int n,memory *&head)
//i表示插入位置,e表示插入的数据,n表示结点个数
{
memory *q=new memory ;  //创建一个新结点,即为要插入的结点
memory *p;      //用于遍历结点
q->space=e;        //赋值
q->status=0;
q->address=0;
if(i>n||i<0)  //判断是否越界
return ;
if(i==1)  //即插入位置为头结点
{
q->next=head; //成为链首
head=q;
}
for(p=head;i!=0,p!=NULL;p=p->next,i--);  //找到插入的位置
if(i==n)  //即插入位置为链尾
{
p->next=q; //直接在链尾插入
}
else
{
q->next=p->next;   //q代替本来p的位置,指向p的下一个元素
p->next=q;        //p的下一个结点指向q
}
}


删除一个结点:

void DeleteHead2(int e,memory *&head) //e用来确定删除哪个结点
{
memory *p,*q;
p=head; //前哨结点
q=head->next; //用于定位需要删除的结点
while(q!=NULL)
{
if(q->space==e) //找到该结点
{
p->next=q->next;  //使待删结点脱链
delete q;
break;
}
p=p->next;  //若未找到,则往前推进一个结点
q=q->next;
}
}


以上是单向链表的一些基础操作函数,掌握了这些就基本能够对单向链表进行增删查改的操作了。

双向链表

从上文的单向链表中可以看出,单向链表只有指向下一个结点的指针,并不能访问其前驱元素,所以在访问单链表的时候,只能顺序访问,但在实际操作中,可能是一件比较麻烦的事情,所以接下来要来学习一下双向链表。

说来双向链表其实很简单,无非是在之前的单向链表之前加入一个前驱指针pre,后继指针next不变。这样,无论是顺序遍历还是逆序遍历都很方便,可以快速找到上一个结点的位置。

首先来看一下如何定义双向链表的结点:

struct node
{
int data;
node* prev;  //前驱指针
node* next;  //后继指针
};


那这样看来,双向链表的头结点前驱指针为空,尾结点的后继指针为空,也非常好判断。

结合单向链表实现的代码,双向链表也非常容易实现,而比较容易犯错的地方就是添加和删除结点,其他代码省略了,接下来附上几段伪代码,实现添加和删除结点。

添加元素:

我们假设要插入的结点为node,插入位置的结点为current,插入位置的下一个结点为next。

current->next=node;
node->prev=current;
node->next=next;
next->prev=node;


代码并不难,但要理解。要求是将node插入current和next结点之间,那么要修改的指针就是:current的后继,next的前驱,以及node的前驱和后继。我们要让current的后继指向node,让next的前驱指向node,这样就能找到node的位置了。接下来修改node的前驱后继指针,分别指向current和next,就完整了。

删除结点:

我们假设要删除的结点为node,前驱结点为current,后继结点为next.

current->next=next;
next->prev=current;
delete node;


删除结点相对于插入结点更简单,只需要修改要删除结点的前驱结点和后继结点的指针即可。将前驱结点的后继指向后继结点,然后将后继结点的前驱指向前驱结点,最后别忘记释放node的空间。这样就完成了删除结点的操作。

其他的查询修改操作也比较容易实现,这里笔者就不再赘述了。能够了解上述的函数操作,那在单链表以及双向链表的操作方面,应该都不会有问题了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 数据 结构