C++:list增删查改模拟实现

这篇具有很好参考价值的文章主要介绍了C++:list增删查改模拟实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本篇博客采用SGI版本,同时考虑到看到此篇博客大部分为初学者,为此博主仅仅给出有用片段。C++:list增删查改模拟实现就是用C++复写双链表,非常简单。难点主要在迭代器实现

一、list底层双链表验证、节点构造

1.1 list底层数据结构

list底层使用什么数据结构来实现的呢?我们先来看看SGI库中list的成员函数和初始化吧。
C++:list增删查改模拟实现,C++经典收录,c++,list,windows,笔记,学习方法

我们发现list实现中,只有node一个成员变量。构造函数构造出一个头尾相连的哨兵位。同时验证了list底层是一个带哨兵位的双链表。

1. 2 节点构造

节点和双链表一样有三个成员:节点数据、指向前一个节点(prev)、指向后一个节点(next)。

//节点
template<class T>
struct List_node
{
	T _data;
	List_node<T>* _prev;
	List_node<T>* _next;

	List_node(const T& x = T())
		:_data(x)
		,_prev(nullptr)
		,_next(nullptr)
	{}
};

小tips:

  1. 我们这里类名和库中一样(List_node),后续在其他类中使用时在typedef。
  2. 这里类名使用struct而不是class原因在于struct默认访问权限为公有,后续其他类只都需要大量使用。如果使用class需要使用大量友元类,过于繁琐。

二、迭代器封装实现(重点、难点)

2.1 前置说明

  1. 我们知道迭代器的最大用途便是遍历数据。但何时停在,迭代器指向空间存储数据时是什么…导致我们需要使用!=、++、*等操作。但自定义类型是无法支持使用这些操作符的。为此给出的解决办法是:封装加运算符重载
  2. 迭代器使用上分为普通迭代器和const迭代器(还分为单向迭代器、双向迭代器和随机访问迭代器)。其中一种最简单的实现方式就是实现两个类。但。。。我们知道两个迭代器的不同之处在于const迭代器无法修改数据,只是j相关借口(这里有*、->)不同而已便实现两个类未免过于“小题大做”。
    所以接下来我们来看看库中是如何巧妙的解决这个问题吧!
    C++:list增删查改模拟实现,C++经典收录,c++,list,windows,笔记,学习方法

2.2 迭代器实现

前置说明中以及解决了如何实现一个类达到目的。接下来的实现过于简单就不单独说明了。

//迭代器封装
template<class T, class Ref, class Ptr>
struct __list_iterator
{
	typedef List_node<T> Node;//节点类名重命名
	//由于迭代器实现中,如++、--等重载函数返回值类型为__list_iterator,名字过长,这里我们重命名self意味自身
	typedef __list_iterator<T,Ref, Ptr> self;
	Node* _node;//成员变量

	__list_iterator(Node* node)//构造出一个节点
		:_node(node)
	{}

	//前置++
	self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	//前置--
	self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	
	//后置++
	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}
	//后置--
	self operator--(int)
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}
	
	//解引用操作符重载
	Ref operator*()
	{
		return _node->_data;
	}
	
	//用于访问迭代器指向对象中存储的是自定义类型
	Ptr operator->()
	{
		return &_node->_data;
	}

	bool operator!=(const self& s)
	{
		return _node != s._node;
	}

	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

三、list实现

3.1 基本框架

list模拟中,我们和库中一样,给出一个头节点_head、_size两个成员变量。同时我们将节点、迭代器进行重命名。迭代器重命名不是单纯图方便,更重要的是提供统一接口(例如sting、vector、set等接口都一样),屏蔽底层的变量和成员函数属性,实现过程和差异。

//list模拟实现
template<class T>
class List
{
	typedef List_node<T> Node;
public:
	//迭代器重命名,提供统一的接口,屏蔽底层实现细节和差异
	typedef __list_iterator<T, T&, T*> iterator;
	typedef __list_iterator<T, const T&, const T*> const_iterator;
private:
	Node* _head;//头节点
	int _size;
};

3.2 迭代器和const迭代器

下面是begin()、end()指向一个有效双线表的位置。
C++:list增删查改模拟实现,C++经典收录,c++,list,windows,笔记,学习方法

实现:

const_iterator begin()const
{
	//return const_iterator(_head->_next);或
	return _head->_next;//单参数类型支持隐式类型转换
}

const_iterator end()const
{
	return _head;
}

iterator begin()
{
	return _head->_next;
}

iterator end()
{
	return _head;
}

3.2 构造函数、析构函数、拷贝构造、赋值重载

3.2.1 构造函数

构造函数的实现和开头中看到的一样:构造函数中调用一个函数(empty_Init)来是实现。empty_Init()用来完成初始化。(empty_Init()函数后续其他接口也要调用)

//初始化
void empty_Init()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;

	_size = 0;
}

//无参构造
List()
{
	empty_Init();
}

3.2.2 析构函数

析构函数我们调用一个clear函数来将数据全部清空,在将_head变量释放。

//析构函数
~List()
{
	clear();
	delete _head;
	_head = nullptr;
}

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
}

3.2.3 拷贝构造

拷贝构造时首先要初始化出一个节点,然后将需要拷贝的数据依次尾插即可(尾插接口后续给出实现)

//拷贝构造
List(const List<T>& It)
{
	empty_Init();
	for (auto e : It)
	{
		push_back(e);
	}
}

3.2.4 赋值重载

赋值重载有很多方式。比较简单的参数我们直接传值,然后将待赋值的变量和传值传参省生成的临时变量的数据进行交换即可。

void swap(const List<T>& It)
{
	std::swap(_head, It._head);
}

//赋值重载
List<T>& operator=(const List<T> It)
{
	swap(It);
	return *this;
}

3.3 任意位置插入、任意位置删除、尾插、尾删、头插、头删

3.3.1任意位置插入

首先new出待插入节点newnode,然后将pos前一个节点prev、newnode、pos相连。最后++_size即可。
C++:list增删查改模拟实现,C++经典收录,c++,list,windows,笔记,学习方法

iterator insert(iterator pos, const T& x)
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(x);

	prev->_next = newnode;
	newnode->_prev = prev;

	newnode->_next = cur;
	cur->_prev = newnode;
	_size++;
	return newnode;
}

3.3.2任意位置插入删除

将待删除数据的前后节点先保存起来,然后将删除pos处节点,再将前后节点连接起来。最后–_size即可。

iterator erase(iterator pos)
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;
	delete cur;
	prev->_next = next;
	next->_prev = prev;

	_size--;
	return next;
}

3.3.3 尾插、尾删、头插、头删

尾插、尾删、头插、头删都是复用任意位置插入、任意位置删除接口。

void push_back(const T& x)
{
	insert(end(), x);
}

void push_front(const T& x)
{
	insert(begin(), x);
}

void pop_back()
{
	erase(--end());
}

void pop_front()
{
	erase(begin());
}

四、list功能完善

4.1 迭代器operator->()

我们先来看看下面这段代码对吗?

struct AA
{
	AA(int a1 = 0, int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{}

	int _a1;
	int _a2;
};

void test_list3()
{
	list<AA> lt;
	lt.push_back(AA(1, 1));
	lt.push_back(AA(2, 2));
	lt.push_back(AA(3, 3));

	list<AA>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout<<*it<<" ";
		++it;
	}
	cout << endl;
}

由于list没有重载<<,所以对存储的是自定义类型之间访问会编译报错。
那我们重载下<<运算符不就行了吗?很不幸的是C++库在list中不支持<<,很大原因也在于编译器不知到我们如何取数据

所以对于自定义类型我们可以先解引用在去访问成员,也可以在迭代器中重载operator->()函数。但需要注意的是编译器隐藏了一个->,具体原生行为如下:

struct AA
{
	AA(int a1 = 0, int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{}

	int _a1;
	int _a2;
};

void test_list3()
{
	list<AA> lt;
	lt.push_back(AA(1, 1));
	lt.push_back(AA(2, 2));
	lt.push_back(AA(3, 3));

	list<AA>::iterator it = lt.begin();
	while (it != lt.end())
	{
		//cout << (*it)._a1<<" "<<(*it)._a2 << endl;
		cout << it->_a1 << " " << it->_a2 << endl;
		//上面编译器访问成员变量的真正行为如下:
		//cout << it.operator->()->_a1 << " " << it.operator->()->_a1 << endl;
		++it;
	}
	cout << endl;
}

4.2 打印数据

//大多数情况模板是class还是typename是一样的,但当有未实例化模板时,必须使用typename
//template<typename T>
void print_list(const list<T>& lt)
{
	// list<T>未实例化的类模板,编译器不能去他里面去找
	// 编译器就无法list<T>::const_iterator是内嵌类型,还是静态成员变量
	// 前面加一个typename就是告诉编译器,这里是一个类型,等list<T>实例化
	// 再去类里面去取
	typename list<T>::const_iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;  
}

优化:上面打印数据是针对list,下面是针对容器的。

// 模板(泛型编程)本质,本来应该由我们干的活交给编译器
template<typename Container>
void print_container(const Container& con)
{
	typename Container::const_iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

五·、所有代码以及测试用例

giteeC++:list增删查改模拟实现代码以及测试用例
推荐:giteeC++:list增删查改模拟实现代码(最终版本、感觉版本!!!)文章来源地址https://www.toymoban.com/news/detail-762275.html

到了这里,关于C++:list增删查改模拟实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++:stack、queue、priority_queue增删查改模拟实现、deque底层原理

    我们先来看看 stack的相关接口有哪些: 从栈的接口,我们可以知道栈的接口是一种特殊的vector,所以我们完全可以使用vector来模拟实现stack。 因此我们可以将底层容器定义成模板,然后将容器类变量作为成员变量进行封装。在实现satck的各种接口时,通过成员变量来调用底层

    2024年02月03日
    浏览(33)
  • 【C++】STL——string的模拟实现、常用构造函数、迭代器、运算符重载、扩容函数、增删查改

    string使用文章   这里我们 实现常用的第四个string(const char* s)和析构函数     拷贝构造函数实现:   在堆上使用new为当前对象的成员变量_str分配内存空间,大小为s._capacity + 1字节,即字符串的容量加上一个结束符\\0的空间。   我们使用深拷贝而不是浅拷贝,

    2024年02月15日
    浏览(34)
  • C++_String增删查改模拟实现

    本篇博客仅仅实现存储字符的string。同时由于C++string库设计的不合理,博主仅实现一些最常见的增删查改接口! 接下来给出的接口都是基于以下框架: 博主在这仅仅提供如 无参和带参默认构造 接口: 小tips: C++string标准库中,无参构造并不是空间为0,直接置为空指针。而

    2024年02月04日
    浏览(33)
  • 【C++】STL——list的介绍和使用、list增删查改函数的介绍和使用、push_back、pop_back

    list构造函数的介绍和使用      push_front()函数用于将一个新的元素插入到链表的开头位置。 通过调用push_front()函数并将待插入的元素作为参数传递给该函数,即可实现在链表开头插入新元素的操作。   和链表的插入一样,push_front()函数的时间复杂度为O(1),因为在双向链

    2024年02月15日
    浏览(43)
  • 【C++庖丁解牛】实现string容器的增删查改 | string容器的基本接口使用

    🍁你好,我是 RO-BERRY 📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 函数名称 功能说明 push_back 在字符串后尾插字符c append 在字符串后追加一个字符串 operator+= (重点) 在字符

    2024年03月14日
    浏览(55)
  • 【c++】vector的增删查改

    为了防止和库里面发生冲突,定义一个命名空间,将类对象放在命名空间 里面 1.返回指向顺序表开始的迭代器函数 2.返回指向顺序表结尾的迭代器函数 3.返回容量大小的函数 4.返回顺序表元素多少 5.判断顺序表为空地的函数 5.一个运算符重载的函数(返回给定下标下的值)

    2024年02月20日
    浏览(33)
  • 认识Mybatis并实现增删查改

    目录 一.Mybatis特性 二.常见持久层技术的比较 三.搭建Mybaits环境 四.使用Mybatis  五.通过Mybatis实现增删改  六.实现数据库的查询操作 定制化SQL:MyBatis允许开发人员编写、优化和管理自定义的SQL语句,可以满足复杂查询和存储过程等高级操作的需求。 避免JDBC代码:MyBatis抽象了

    2024年02月12日
    浏览(33)
  • 带头 + 双向 + 循环链表增删查改实现

    目录 源码: List.c文件: List.h文件: 简单的测试: 很简单,没什么好说的,直接上源码。

    2024年01月23日
    浏览(32)
  • C语言—实现循序表的增删查改

    嗨嗨嗨!大家好!今天我为大家分享的是数据结构知识——顺序表。废话不多数,让我们开始今天的知识分享吧。 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由那部分构

    2024年04月15日
    浏览(47)
  • Elasticsearch基础学习(Java API 实现增删查改)

    ElasticSearch,简称为ES, ES是一个开源的高扩展的分布式全文搜索引擎。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。 物理设计: ElasticSearch 在后台把每个索引划分成多个分片,每份分片可以在集群中的不同服务器间迁移

    2024年02月01日
    浏览(71)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包