二级指针实现链表效果

二级指针实现链表效果

——借由实现一个简单的链表效果讲解一下二级指针,希望这篇文章能给阅读的小伙伴一些帮助吧。

看文章的小伙伴应该对指针有一个大概的印象吧,这里再来讲解一下。说到指针那就不得不提另外一个东西了,那就是内存。指针存在的很大一部分意义就是为了给我们提供便捷的操纵内存的工具。对于我们来说,内存中最重要的东西就是“每个位置独一无二的地址标识符以及内存中每个位置所存的值”。下面看一下内存模型图:

而对于指针来说就是保存图中的那个内存地址编号,并通过*操作符来操纵内存中的数据。这里再补充一点:在我们的高级语言之中,我们平时使用的变量也是一种访问内存的方式。毕竟如果每次修改数据都要记住那些地址未免有点蠢。那么 我们平时声明变量像

int a=1,b=2,c=44,d=12345,e=1111111;

就可以把上图变为

此时如果我们在之前的基础上再声明一个指针的话

int a=1,b=2,c=44,d=12345,e=1111111;
int *p;//声明一个指向int的指针
p = &a;//a的地址赋值给p

在我们的模型图中就是:

这里要明确的一件事是我们的指针p它本身也是一个变量,占用4个字节。既然我们现在知道a所代表的内存地址编号,那么我们便可以通过*号来操纵里面的数据啦!补充了这些,那么接下来就步入我们今天的正题啦——二级指针。

首先我们先看下面一段代码

int a = 3;
int *p = &a;
int **q = &p;

这里的p很容易理解就是一个指向int型的指针,那我们来分析一下这个q,在这里我们一定要明确一件事q它本质上还是一个指针,只不过它保存的是指针的地址,我们来看图。

这下是不是一目了然多了呀!嘿嘿,既然它也是一个指针那么我们就按照分析指针的方法来分析它好了。

int a = 3;
int b = 5;
int *p = &a;
/*
*  对于一级指针来说,首先*p告诉编译器这是一个指针变量,
*  其次int则告诉编译器这个指针所指向的内容是int型的。
*/
int **q = &p;
/*
*  二级指针同理,首先*q告诉编译器这是一个指针变量,
*  其次int*告诉编译器这个指针所指向的内容是一个int型的指针。
*  其实这个声明我们可以把他拆开来成(int *)与(*p)理解,前面是保存的类型后面是变量的类型。
*  多级指针同理。
*/

//接下我们来对指针q进行操作来验证我们的想法

printf("%d\n",*p);//此时对p进行解引用操作,打印a的值3。

*q = &b;//结合上图,我们对q进行解引操作得到的是p的内存的地址,也就是说此时的赋值操作是对p进行的。

printf("%d\n",*p);//此时我们再对p进行解应操作,打印的值变为了b的值5。验证了我们的想法。

**q = 10;//结合上面的分析你肯定知道这步操作的实际操作对象了吧,没错就是b。

printf("%d\n",b);//打印输出10

看过上面介绍的一些内容,我想你已经对二级指针有了个大概的认识吧,其实在实际操作中我们一般都只涉及到一级和二级指针。不过,你学会了二级指针,其他高级指针也是一样分析的,只是多了几个*而已。那么接下来我们就来看一个实际中的例子吧。利用二级指针来实现一个链表,顺带再介绍一下C语言中的接口概念。

首先我们创建一个Link.h包含我们对链表的一些宏定义与对用户暴露出来的接口。

#define LINK_TYPE int//这里我们设定节点内保存的值类型为int
#define FALSE 0
#define TRUE 1
/*
* 这里先定义一个结构体,包含一个值类型,和一个指向下一个节点的指针类型。
*/
typedef struct LINK_NODE {
	LINK_TYPE value;
	struct LINK_NODE *next;
} Node;
/*
* 暴露给用户的函数接口。
*/

int insertList(Node **phead, LINK_TYPE);
/*
*  用于向链表插入数据,这里可以看到我们使用了一个二级指针的形参用来保存传入的节点指针,
*  与一个用来保存插入值的形参。
*/
void printList(Node *phead);
//用于打印链表的函数

接下来是包含头文件中各函数的具体实现的Link.c文件。

#include "Link.h"
//因为要使用头文件中声明的Node结构体所以这里把头文件包含进来
//这里解释一下include后面跟<>与""的区别,<>一般是使用标准库的头文件,而""一般是你自已定义的头文件。
#include<stdio.h>
#include<stdlib.h>

/*
*  这里我定义了一个初始化Node节点的函数也是为指针开辟一块堆内存空间,并且将节点内的next指针赋值为空。
*  并将这个指针返回出来。
*  因为这个函数我不打算对用户暴露,所以可以看到我在头文件中并没有声明这个函数,也即是用户无法直接使用。
*/
Node* init(Node *pnode) {
	pnode = (Node*)malloc(sizeof(Node));
	pnode->next = NULL;
	return pnode;
}

int insertList(Node **phead, LINK_TYPE value) {
	if (*phead == NULL) { 
        //现在我们对这个二级指针进行解引操作,按照上面所讲的内容,这里实际操作的phead内保存的那个指针
        //也就是我们在主函数中声明的那个指针。
		*phead = init(*phead);//如果该指针为空那么我们就对他进行赋值初始化操作。
	}
	Node *next = NULL; //声明一个新的指针来保存我们将要插入的那个节点。
	next = init(next);
	if (next == NULL)
		return FALSE; //如果开辟失败那么返回一个FALSE。
	next->value = value; //将要插入的值赋值给新节点的value属性。
        /*
        *  接下来的操作就很重要了,(*phead)我们在这里就可以理解为传入的指针对象
        *  如果该指针所指的结构体的next指针为空的话,也是说phead现在所指的是该该链表最后一个元素。
        *  那么就结束循环,将我们新创建的节点接到链表最后一个元素后面。
        *  如果为空的话,那么让phead指针指向它后面一个节点,以此类推,知道phead指向最后一个节点。
        */
	while ((*phead)->next)
	{
		*phead = (*phead)->next;
	}
	(*phead)->next = next;
	return TRUE;
}
/*
*  把链表中的元素打印出来。
*/
void printList(Node *phead) {
	while (phead != NULL)
	{
		printf("%d\n", phead->value);
		phead = phead->next;
	}
}

最后则是我们的main.c文件

#include"Link.h"
#include<stdio.h>

int main() {
	Node *pList = NULL;

	insertList(&pList, 1);
	insertList(&pList, 2);
	printList(pList);

	getchar();
	return 0;
}

可以看到结果正常输出。

这里主要是通过这个例子让小伙伴们对二级指针有个更好的了解,如果文章中有什么错误之处也请各位小伙伴们指出来啦!嘿嘿,感谢你看完了这篇文章哦,希望能对你有帮助。我们下期见。哈哈,See you!

发布于 2019-03-13