C++ 指针操作的艺术与实践

0. 前言

上一章的顺序表底层是数组,封装了增删查改的功能接口成为了顺序表。存储顺序表的空间是连续的,顺序表非常大时,内存可能无法提供这么大的连续空间,由此,链表就被创造了。在一个复杂的系统中,空闲的内存空间散落在内存各处,而链表通过指针很好的链接起了各个分散的内存空间。

如图:

在这里插入图片描述

在这里插入图片描述

内存空间分配并不连续,但是指针称为维系各个空间的桥梁

链表(linked list) 是一种线性数据结构,每个元素是一个节点对象,各个节点通过指针(或引用)相连接,指针(或引用)记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点

1. 链表的分类

链表有多种结构,以下情况组合起来有

23

种:

  1. 单项或双向

在这里插入图片描述

在这里插入图片描述

  1. 带头或不带头

在这里插入图片描述

在这里插入图片描述

  1. 循环或不循环

在这里插入图片描述

在这里插入图片描述

这八种结构种,最常用的是最简单和最复杂的两种结构:

单链表(无头单项不循环链表):结构简单,一般作为其他数据结构的子结构

在这里插入图片描述

在这里插入图片描述

双向链表(带头双向循环链表):结构最复杂,实际使用中基本都是此结构,操作简单

在这里插入图片描述

在这里插入图片描述

注意:我们把这个带头的头称为哨兵位,哨兵位不存储有效数据(有些时候可能存储链表节点个数),位于链表第一个有效节点(头节点) 的前面。

现在我们来实现一下单链表和双向链表

2. 单链表的实现

2.1 链表的基本结构——节点(Node)

链表的最小单元是节点,C++用结构体或类定义,链表的一个节点包含指针域数据域,指针域保存该节点指向下一个结点的地址,数据域保存该节点的有效值。还有一个头指针的概念:头指针是链表的门把手,指向链表头节点的指针变量,是链表的入口

代码语言:javascript

AI代码解释

// 节点结构体,存储数据和指向下一个结点的指针
struct Node {
	T _value; // 数据域
	Node* _next; // 指针域
	// 节点构造函数 确保新节点创建时_next处于安全状态
	Node(const T& val) : _value(val), _next(nullptr) {}
};

然后我们将节点放入类中,加入单链表的基本信息:头指针、节点个数

代码语言:javascript

AI代码解释

template <typename T>
class single_list{
private:
	// 节点结构体,存储数据和指向下一个结点的指针
	struct Node {
		T _value;
		Node* _next;
		// 节点构造函数 确保新节点创建时_next处于安全状态
		Node(const T& val) : _value(val), _next(nullptr) {}
	};
	Node* _head; // 头指针,指向链表的第一个节点
	size_t _count; // 节点数量
};

我们引入类模板,能使链表的数据类型更多样化,需要什么样的类型我们就可以显式实例化什么类型的链表结构

2.2 核心操作详解
2.2.1 构造和析构

代码语言:javascript

AI代码解释

single_list() : _head(nullptr), _count(0) {}

利用初始化列表将链表初始化为空链表,方便后续操作

代码语言:javascript

AI代码解释

~single_list() { clear();}

调用清空函数删除所有节点,该函数在下文实现

2.2.2 插入操作
  1. 头插

代码语言:javascript

AI代码解释

// 头插 O(1)
void push_front(const T& val) {
	// 创建新节点
	Node* new_node = new Node(val);
	new_node->_next = _head;
	_head = new_node;
	++_count;
}

在这里插入图片描述

在这里插入图片描述

创建新的节点,新节点的指针指向原先的头节点,然后更新头指针的指向

  1. 尾插

代码语言:javascript

AI代码解释

// 尾插 O(n)
void push_back(const T& val) {
	Node* new_node = new Node(val);
	// 如果是空链表 new_node就是新的头节点
	if (!_head) {
		_head = new_node;
	}
	else {
		// 创建临时变量开始找尾节点
		Node* cur = _head;
		while (cur->_next != nullptr)  cur = cur->_next;

		cur->_next = new_node;
	}
	++_count;
}

在这里插入图片描述

在这里插入图片描述

空链表要做单独处理,新节点就是头节点了;链表非空,则要创建临时变量遍历整个链表找尾,一定不能用头指针遍历,使用头指针遍历会丢失头节点的数据

  1. 指定位置插入节点

代码语言:javascript

AI代码解释

// 插入指定位置 O(n)
void insert(int pos, const T& val) {
	// 确保pos的合法性
    // pos能取到_count,取到相当于尾插
	assert(pos >= 0 && pos <= _count);

	if (pos == 0) return push_front(val);

	// 创建临时变量找pos位置的节点
	Node* cur = _head;
	// 找到pos的前一个节点 cur最终指向pos-1的位置
	for (size_t i = 0; i < pos - 1; i++)// 循环pos - 1次
	{
		cur = cur->_next;
	}
	Node* new_node = new Node(val);
	new_node->_next = cur->_next;
	cur->_next = new_node;

	++_count;
}

在这里插入图片描述

在这里插入图片描述对于用户输入的pos要做检查,pos的值代表索引,从0开始。pos == 0,可以复用头插法,而pos == _count则代表尾插,在下面的循环逻辑中,是找到pos的前一个节点,所以pos的边界判断都可以取到。然后更新指针指向即可.


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值