C语言指针(*和&)—单链表实例详解

前言

在笔者学习《大话数据结构》一书,编写C语言代码实现相应的数据结构、完成对数据的增删改查等功能时,对文章中C语言中指针(*和&符号)的使用缺少透彻的认识和理解。因此笔者在本篇文章中,将单链表的实现过程作为案例,对单链表实现过程中C语言指针的使用进行详细的讲解和分析,以此加深对C语言中指针使用的实践理解。

一、 声明单链表存储结构

在对单链表进行初始化前,需要对单链表的存储结构给出定义,已知单链表由两部分组成:存储数据元素信息的数据域+存储直接后继位置的指针域组成。线性表的单链表存储结构代码定义如下:

typedef int ElemType;

typedef struct Node
{
	ElemType data;
	struct Node *next;
}Node;
typedef struct Node *LinkList;

在C语言中,指针是一种特殊的变量,指针存储变量的地址而不是变量的值。
而一元运算操作符*是间接的、非关联的操作运算符。可以使用*来声明变量的指针类型。
当在指针变量前使用*时,可以得到指针变量所指向的变量的值。

示例代码中:*LinkList即申明了LinkList是指针类型的变量,其存储的变量的数据结构是Node类型,Node的数据结构即为单链表的结点结构由数据域和指针域组成。
申明指针变量时,以下三种申明方式都能达到相同的效果:

typedef struct Node* LinkList;

typedef struct Node *LinkList;

typedef struct Node * LinkList;

三、单链表初始化

申明好单链表的存储结构后,对单链表进行初始化操作。单链表初始化代码如下:

int InitLinkList(LinkList *L){
    *L = (LinkList)malloc(sizeof(Node));
    if (!(*L)) {
        return  0;
    }
    (*L)->next = NULL;
    return -1;
}

主程序调用初始化操作如下:

int main(int argc, const char * argv[]) {
	LinkList L;
	ret = InitLinkList(&L);
}

使用一元操作符&能够返回对象在存储空间的存储地址

示例代码中:

&L实际上是将指针变量LinkList的存储地址(注意:这里传入的是指针在内存空间的地址,而不是其存储的变量在存储空间的地址)

传入InitLinkList函数后,使用*L可通过指针的地址,访问到指针变量存储的结点Node的地址。

由于指针变量LinkList只进行了变量声明,未指向任何Node数据结构的变量即其存储的变量地址为空。

因此需要在初始化函数中,使用malloc函数,从内存空间中划分了一段Node大小的存储空间,并将该存储空间的地址赋值给指针。使其指向一个Node类型的变量。

malloc()函数能够为指针变量保留指定字节数的内存块,并返回指定指针类型的指针。而指针变量则保留分配内存中的第一个字节的地址。

二、单链表的插入

单链表的插入操作,如图所示:只需要:

  1. p的后继结点改成s的后继结点
  2. 再把结点s变成p的后继结点

LinkListInsert

《大话数据结构》一书中,单链表的插入代码如下:

int LinkListInsert(LinkList *L, int i, ElemType e)
{
    int j;
    LinkList p,s;
    p = *L;
    j = 1;
    while (p && j < i) {    /*遍历寻找第i-1个结点*/
        p = p->next;
        ++j;
    }
    if (!p || j > i) {
        return  0;  /*第i个结点不存在*/
    }
    s = (LinkList)malloc(sizeof(Node)); /*生成新结点(C标准函数)*/
    s->data = e;
    s->next = p->next;  /*将p的后继结点赋值给s的后继*/
    p->next = s;    /*将s赋值给p的后继*/
    return  1;
}

主程序调用初始化操作如下:

int main(int argc, const char * argv[]) {
	LinkList L;
	int e;
    e = 77;
    // 在线性表第一个位置插入元素
    i = 1;
    ret = LinkListInsert(&L, i, e);
}

在插入代码中:
语句LinkList p, s申明了指针p和指针s,让p等于单链表的头结点,再给指针s指向的变量分配一个节点的内存空间,即s为新的需要插入的结点。已知结点由:数据域+指针域组成,s->data即结点的数据域,s->next即结点的指针域,用来保存下一个结点在存储空间中的地址。

三、单链表的读取

获取链表第i个数据的算法思路:

  1. 声明一个指针p指向链表第一个结点,初始化j从1开始
  2. 当j<i时,遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
  3. 若到链表末尾p为空,则说明第i个结点不存在
  4. 否则查找成功,返回结点p的数据

《大话数据结构》一书中,单链表的查找代码如下:

int GetLinkListElem(LinkList L, int i, ElemType *e){
    int j;
    LinkList p;     /*声明一指针p*/
    p = L->next;    /*让p指向链表的头结点*/
    j = 1;          /*j为计数器*/
    while (p && j < i) {
        p = p->next;
        ++j;
    }
    if (!p || j>i) {
        return 0;   /*第i个结点不存在*/
    }
    *e = p->data;   /*取第i个结点的数据*/
    return  1;
}

这一段代码中,在对C语言指针的了解不是很透彻的时候,让笔者感到困惑的是,在函数LinkListInsert代码中传入函数的是&L,而在GetLinkListElem只传入了L,这两种指针的用法的区别是什么?

两者用法实际上能够实现相同的效果:
当完成InitLinkList后,此时指针L已经指向了存在在内存空间中的结点,即指针变量L的值为结点在内存空间的地址,通过结点地址可获取结点的数据域与指针域
*&L也能实现相同的效果(获取结点的地址):

  1. 通过&L得到的是指针在内存空间的地址
  2. 使用*&L可获取指针存储的其他变量的地址,即结点在内存空间的地址

而在实际的应用中,当需要改变链表的结构如增加结点、删除结点时,可以将指针的地址传入函数中,加以应用。
而当只需查询链表信息时不会改变链表结构时,可只将指针指向的变量地址传入函数中,进行查询操作,从而避免代码发生错误时导致指针地址变化,丢失原有的指针信息。

四、单链表的删除

删除单链表结点算法思路:

  1. 将p的后继结点指向q的后继结点
    LinkListDelete
int LinkListDelete(LinkList *L, int i, ElemType *e)
{
    int j;
    LinkList p, q;
    p = *L;
    j = 1;
    while (p->next && j < i) {  /*遍历寻找第i-1个结点*/
        p = p->next;
        j++;
    }
    if (!(p->next) || j > i) {
        return 0;           /*第i个结点不存在*/
    }
    q = p->next;
    p->next = q->next;  /*将q的后继赋值给p的后继*/
    *e = q->data;       /*将q结点中的数据给e*/
    free(q);            /*让系统回收此结点, 释放内存*/
    return 1;
}

单链表的删除中与指针相关与上文重复的知识内容,笔者在这里就不再赘述,而其中值得注意的语句free(q)实现了释放指针指向的内存地址即结点的分配空间,即回收了结点在内存空间的地址,释放了内存。

free()函数释放指针指向的内存中分配的空间。

总结

本文仅供大家参考,有不对的地方还请读者指出,一起学习共同进步

参考文献

大话数据结构
Programiz:Learn C Programming.
The C Programming Language

  • 10
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值