C++ [STL之list的使用]

news2024/11/25 2:46:16

list使用

本文已收录至《C++语言和高级数据结构》专栏!
作者:ARMCSKGT

在这里插入图片描述


前言

vector是一片连续的空间,在数据访问上性能较好,但是任意位置插入删除性能较低,头插头删性能亦是如此;此时在这种需要频繁插入的场景下,显然链表是一种更好的选择,STL中实现了带头双选循环链表,本次我们来介绍该如何使用STL中的链表list!
list


正文

本文理论依据来自于官方文档:STL容器list文档!
首先在使用list前,需要声明头文件 < list > 且声明命名空间std!
list是通过模板实例的泛型容器,需要指定类型进行实例化

默认成员函数


构造函数类

构造函数类

  • 默认构造
    –构造一个空对象,里面没有任何数据(底层上只有一个头节点)
  • 构造n个值为val的链表对象
    –插入n个值实例类型的val值,构造一个对象
  • 迭代器区间构造
    – 通过其他容器迭代器或指针构造一个list对象
  • 列表初始化(C++11)
    – 对于支持C++11的编译器支持像数组一样使用列表{ }初始化
  • 拷贝构造
    – 拷贝另一个list,从而构造一个一模一样的list对象
#include <iostream>
#include <list>
using namespace std;

void Print(const list<int>& obj) //打印函数
{
	for (const auto& x : obj)
	{
		cout << x << " ";
	}
	cout << endl;
}

int main()
{
	list<int> l1; //默认构造

	list<int> l2(5, 1); //构造5个值为1(int类型)的链表对象
	Print(l2);

	int arr[] = { 5,4,3,2,1 };
	list<int> l3(arr, arr + 5); //指针作为迭代器区间构造
	Print(l3);

	vector<int> v = { 6,7,8,9,10 };
	list<int> l4(v.begin(),v.end()); //容器迭代器区间构造
	Print(l4);

	list<int> l5 = {11,12,13,14,15}; //列表初始化构造(C++11)
	Print(l5);

	list<int> l6(l5); //拷贝构造
	Print(l6);

	return 0;
}

演示



赋值重载

赋值重载与拷贝构造类似,只不过赋值的前提是对象已存在,拷贝构造是初始化对象!

void Print(const list<int>& obj)
{
	for (const auto& x : obj)
	{
		cout << x << " ";
	}
	cout << endl;
}

int main()
{
	vector<int> v = { 5,4,3,2,1 };
	list<int> l1(v.begin(),v.end()); //容器迭代器构造

	list<int> l2(5, 1);
	Print(l2);
	l2 = l1;
	Print(l2);
	return 0;
}

赋值重载



析构函数

析构函数在list对象声明周期结束时调用,释放所有节点(包括头节点)!
析构函数


迭代器


list迭代器比较特殊,知道链表的小伙伴应该都懂链表不是一片连续的空间,所以不能像顺序表那样支持迭代器随机访问!(即it+1,it+2…)

同样的,链表的迭代器也分三种(理论上四种):

  • 正向迭代器
  • 反向迭代器
  • const迭代器(正向和反向)
    迭代器
    const迭代器不支持修改当前指向的值,每一种迭代器不能混用,各自使用各自的迭代器类型!
int main()
{
	vector<int> v = { 5,4,3,2,1 };
	list<int> l1(v.begin(),v.end());
	list<int>::iterator it = l1.begin();
	list<int>::reverse_iterator rit = l1.rbegin();
	while (it != l1.end())
	{
		cout << *it << endl;
		++it;		
	}
	cout << endl;
	while (rit != l1.rend())
	{
		cout << *rit << endl;
		++rit;		
	}
	return 0;
}

迭代器演示
虽然list迭代器不支持随机访问,但是也支持通过++和- -遍历链表,甚至可以反向遍历,可见迭代器的强大之处!

迭代器在链表中的位置:
迭代器在链表中的位置


容量操作类


数据查询类

链表没有提前开空间这样的说法,对于数据只能查询以及对现有数据操作!
数据量查询

  • empty:查询链表是否为空,为空返回真
  • size:返回当前节点的个数
  • max_size:返回当前实例对象可容纳最大节点数(这个函数常用来检查申请空间的合法性即配合resize使用,查看申请的空间是否合法)
int main()
{
	list<int> obj(10,5); //初始化10个值为5的节点
	cout << "empty:" << obj.empty() << endl;
	cout << "size:" << obj.size() << endl;
	cout << "max_size:" << obj.max_size() << endl;

	return 0;
}

数据量查询



节点数量修改

list支持resize控制节点个数,resize支持增多节点和减少节点,对于增多的节点设置为val值,val有缺省参数,根据需要可进行设置初始化!
resize

int main()
{
	list<int> obj = { 1,2,3,4,5 };
	cout << "size:" << obj.size() << endl;
	obj.resize(10);
	cout << "size:" << obj.size() << endl;
	obj.resize(3);
	cout << "size:" << obj.size() << endl;
	return 0;
}

resize


数据访问


链表不支持下标访问,提供了额外对头尾节点访问的函数front和back,并且拥有引用和const引用重载函数应对不同的场景!
front和back

int main()
{
	list<int> obj = { 668,778,888,998,1080 };
	cout << "front:" << obj.front() << endl;
	cout << "back:" << obj.back() << endl;
	return 0;
}

front和back

不过list常用的遍历方式还是通过迭代器遍历,而且list因为迭代器所以支持范围for遍历!


增删类


因为链表插入删除效率非常高,所以增删类接口比较多,在这里介绍几种常用的接口!
增删接口



头插头删

头插push_front()头删pop_front() 只需要对头节点next(后继节点)指针操作即可!

int main()
{
	list<int> obj = { 1,2,3,4,5 };
	Print(obj);
	obj.pop_front();
	Print(obj);
	obj.push_front(668);
	Print(obj);

	return 0;
}

push_front和pop_front



尾插尾删

头插push_back()头删pop_back() 也只需要对头节点prev(前驱节点)指针操作即可!

int main()
{
	list<int> obj = { 1,2,3,4,5 };
	Print(obj);
	obj.pop_back();
	Print(obj);
	obj.push_back(668);
	Print(obj);

	return 0;
}

push_back和pop_back



重新分配

assign函数有点类似于 = 赋值重载,区别于赋值的是该函数可以跨容器达到赋值的效果(利用迭代器区间),assign函数会先将list对象清空再重新插入值!

assign函数有两个版本:

  • 分配n个值为val的节点(类似于带参构造函数)
  • 迭代器区间分配
int main()
{
	list<int> obj = { 1,2,3,4,5 };
	Print(obj);
	obj.assign(10, 5); //分配5个值为10的节点
	Print(obj);

	int arr[] = { 668 ,778,888,998,1080 };
	obj.assign(arr,arr+5); //分配arr数组区间
	Print(obj);

	return 0;
}

assign



任意位置插入

insert函数支持在迭代器位置插入节点,有三个版本:
insert

  • 在position迭代器位置下插入值为val的节点(返回新插入节点的迭代器)
  • 在position迭代器位置下插入n个值为val的节点
  • 在position迭代器位置下插入一段区间
int main()
{
	list<int> obj = { 1,2,3,4,5 };
	auto it = ++obj.begin(); //记录2位置迭代器
	Print(obj);

	it = obj.insert(it, 668); //在2位置插入668更新迭代器为668节点
	Print(obj);

	obj.insert(it, 888); //在668位置插入888
	Print(obj);

	int arr[] = { 6,7,8,9,10 };
	obj.insert(obj.begin(), arr, arr + 5); //在头节点1位置插入arr数组区间
	Print(obj);

	return 0;
}

insert演示
虽然insert函数不会导致迭代器失效,但是insert在插入单节点的函数上支持更新迭代器,必要时建议及时更新迭代器!



任意位置删除

erase函数有两种版本,删除函数需要注意迭代器失效问题,因为删除当前迭代器位置的节点后该位置节点内存已经被释放,所以两个版本的删除函数都会返回删除后的下一个节点迭代器
erase

int main()
{
	list<int> l1 = { 1,2,3 };
	list<int> l2 = { 1,2,3,4,5,6,7,8,9,10 };
	Print(l1);
	Print(l2);

	auto it1 = l1.begin();
	auto it2 = l2.begin();
	++it2; ++it2; ++it2;//迭代器挪动到4位置下

	it1 = l1.erase(it1); //删除手节点 - 更新迭代器
	it2 = l2.erase(l2.begin(),it2); //删除1-3区间节点(左闭右开) - 更新迭代器

	Print(l1);
	Print(l2);

	return 0;
}

在这里插入图片描述
迭代器失效情况:
迭代器失效
对于单节点删除,如果不更新迭代器,肯定会造成野指针访问,但对于区间删除,因为是左避右开,所以删不到闭区间的迭代器,所以不更新迭代器影响不大,但是如果删除的区间是全部,则还是需要更新迭代器!
这里可以发现it1如果更新迭代器则指向下一个节点迭代器!


其他操作


清空函数

clear可以将链表清空,也就是逐个释放节点,只留下头节点(区别于析构函数,clear不会释放对象)!

int main()
{
	list<int> obj = { 1,2,3 };
	cout << "size:" << obj.size() << endl;
	obj.clear();
	cout << "size:" << obj.size() << endl;
	return 0;
}

clear



交换函数

list有属于自己的交换函数,同时库中的函数也支持对list进行交换!

int main()
{
	list<int> l1 = { 1,2,3 };
	list<int> l2 = { 4,5,6 };
	cout << "l1:"; Print(l1);
	cout << "l2:"; Print(l2);
	cout << endl;

	l1.swap(l2); //list对象swap函数交换
	cout << "l1:"; Print(l1);
	cout << "l2:"; Print(l2);
	cout << endl;

	std::swap(l1, l2); //库函数交换
	cout << "l1:"; Print(l1);
	cout << "l2:"; Print(l2);
	cout << endl;

	return 0;
}

在这里插入图片描述
list自带的swap和库中的swap都支持容器的交换!



拼接

splice支持将一个list对象中的一个节点或者一段节点从原list对象上截取下来链接在调用对象上!
splice有三个版本:

  • 一个list截取插入到另一个list中的position迭代器位置
  • 将一个list的一个迭代器节点截取插入另一个list中的position迭代器位置
  • 将一个list的迭代器区间截取插入到另一个list中的position迭代器位置
    splice
int main()
{
	list<int> l1 = { 1,2,3 };
	list<int> l2 = { 4,5,6 };

	l1.splice(l1.begin(), l2); //将l2插入l1中
	cout << "l1:"; Print(l1);
	cout << "l2:"; Print(l2);
	cout << endl;

	list<int> l3 = { 1,2,3 };
	list<int> l4 = { 7,8,9 };
	l3.splice(l3.begin(),l4,l4.begin()); //将l4中的头节点插入l3中
	cout << "l3:"; Print(l3);
	cout << "l4:"; Print(l4);
	cout << endl;

	list<int> l5 = { 1,2,3 };
	list<int> l6 = { 448,558,668,778,888 };
	l5.splice(l5.begin(), l6, ++l6.begin(), l6.end()); //将l5的558-888插入l6中
	cout << "l5:"; Print(l5);
	cout << "l6:"; Print(l6);
	cout << endl;

	return 0;
}

splice演示
splice函数每次执行都需要另一个list对象的引用,哪怕只截取一个节点,说明在函数内部需要对节点进行剥离和缝合!

splice函数原理:
出自SLT源码剖析



移除指定值元素

remove类似于find+erase,find先找到这个值返回其迭代器供erase删除,该函数会根据给定的val值找到此值并删除,如果该值不存在,则什么也不做!
remove

int main()
{
	list<int> l1 = { 1,2,3 };
	l1.remove(2); //删除2
	l1.remove(5); //删除非list中的元素则执行无效
	Print(l1);

	return 0;
}

remove示例



排序

list自带排序算法,但效率非常低!

一般如果要对list排序,可以先将数据拷贝到vector,然后使用库函数sort快速排序后再拷贝会list,虽然有两次拷贝,但是排序性能仍然超过直接对list排序!

#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
	list<int> l1;
	list<int> l2;
	for (int i = 1; i < 10000000; ++i) //一千万降序数
	{
		l1.push_back(i);
		l2.push_back(i);
	}
	//排升序	
	time_t t = clock(); //记录开始时间
	l1.sort(); //list排序
	cout << "list sort:" << clock() - t << endl;

	t = clock();
	vector<int> v(l2.begin(),l2.end());
	std::sort(v.begin(), v.end()); //库函数排序
	l2.assign(v.begin(), v.end()); //将排完序的元素分配回list
	cout << "lib sort:" << clock() - t << endl;

	return 0;
}

sort
使用库函数sort需要声明头文件algorithm和std命名空间,此结果是在release模式下运行!
库函数对容器操作一般都是使用迭代器!



逆置

list对象自带reverse逆置函数,库中也有reverse函数,作用是将当前序列反过来!
list自带的reverse直接调用即可,库中的reverse则是通过容器迭代器作为参数进行逆置!

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

int main()
{
	list<int> obj = { 1,2,3,4,5,6,7,8,9,10 };
	Print(obj);
	obj.reverse(); //list自带逆置
	Print(obj);
	std::reverse(obj.begin(), obj.end()); //库函数逆置
	Print(obj);

	return 0;
}

reverse
同样的,使用库函数reverse需要声明头文件algorithm和命名空间std!



去掉重复元素

unique函数支持对元素,但是在去重之前需要进行排序,因为其底层原理是两个相邻的重复元素会删除一个,而需要将所有相同的元素相邻就需要sort排序!

同样的,如果数据量小我们可以直接使用list的排序和去重,如果数据量较大建议拷贝到vector排序然后拷贝会list再去重!

int main()
{
	list<int> obj = { 1,2,5,3,7,1,6,5,9,8,7,2 };
	Print(obj);

	obj.unique(); //不排序直接去重没有任何效果
	Print(obj);

	obj.sort(); //排序再去重才会有效果
	obj.unique();
	Print(obj);

	return 0;
}

unique
算法库algorithm中也有unique,同样的适用于所有容器,因为使用迭代器操作!



合并

merge函数支持将一个list合并(移动)到另一个list中(而并非复制),与splice函数类似,只不过merge是将list中的全部节点按升序或降序的方式链入被插入对象中,该函数有两种形式:
merge

  • 第一种是合并一个list,并按升序插入其中
  • 第二种支持控制升序和降序,我们可以选择将list按升序插入其中,也可以按降序插入其中(使用greater和less控制)
int main()
{
	list<int> l1 = {6,10};
	list<int> l2 = { 1,2,5 };
	Print(l1);

	l1.merge(l2); //将l2按默认规则链入l1中
	Print(l1);

	list<int> l3 = {3,4,9,8,7};
	l1.merge(l3, less<int>()); //将l3按升序规则链入l1中
	Print(l1);

	return 0;
}

merge



关于查找

  • list没有提供查找函数,因为对于链表的查找只能用迭代遍历的方式进行,所以用库中的find足以!
  • 库中的find也是基于迭代器的遍历查找,也适用于所有支持迭代器的容器,find如果查找到了就会返回该元素的迭代器,如果没找到则返回传入的开区间(end())迭代器,如果有多个重复的值则返回正向序列上第一个符号条件的值!
//find的使用
find(迭代器开区间(起始区间),迭代器闭区间(结束区间),查找元素);

find底层原理:

template<class InputIterator, class T>
InputIterator find (InputIterator first, InputIterator last, const T& val)
{
 	while (first!=last) {
    if (*first==val) return first;
    	++first;
	}
 return last;
}

使用展示:

int main()
{
	list<int> obj = { 1,2,5,3,7,1,6,5,9,8,7,2 };
	list<int>::iterator it = std::find(obj.begin(), obj.end(), 2); //找到了就返回迭代器
	cout << *it << endl;
	it = std::find(obj.begin(), obj.end(), 10);
	cout << (it == obj.end()) << endl; //没找到返回开区间
	return 0;
}

find
find属于库函数,需要algorithm算法头文件和是他的命名空间!


最后

list的使用介绍到这里就结束了,相信学会了list在需要频繁增删数据的场景下我们那个轻松应对,对于vector和list两者各有优劣,应对不同的场景我们需要合理应用甚至结合使用;list的底层实现非常值得我们去研究,C++下一节将为大家介绍list模拟实现,揭开迭代器的背后秘密!

本次 <C++ list的使用> 就先介绍到这里啦,希望能够尽可能帮助到大家。

如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!

C-PLUS-PLUS

🌟其他文章阅读推荐🌟
C++ <STL之vector模拟实现> -CSDN博客
C++ <STL之vector的使用> -CSDN博客
C++ <STL之string的使用> -CSDN博客
C++ <STL之string模拟实现> -CSDN博客
🌹欢迎读者多多浏览多多支持!🌹

​​

​​


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/579103.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PCL计算法向量结果不对的问题

一、点云&#xff08;高程渲染。CC计算的法向量&#xff0c;Z分量渲染&#xff09; 高程渲染 法向量Z分量的绝对值渲染 二、PCL计算法向量 下面是PCL计算法向量的代码&#xff08;点类型自己设置的&#xff09; 计算出的法向量有正有负 void pclNormalEstimation(pcl::Point…

【C++】构造函数初始化列表的特性以及注意事项

文章目录 一、1.1构造函数体赋值&#xff1a;二、 初始化列表2.1初始化基本形式&#xff0c;注意&#xff1a;3. 尽量使用初始化列表初始化&#xff0c;因为不管你是否使用初始化列表&#xff0c;对于自定义类型成员变量&#xff0c;一定会先使用初始化列表初始化。4. 成员变量…

JavaScript教程(二)

BOM浏览器对象模型 什么是BOM BOM&#xff08;Browser Object Model&#xff09;即浏览器对象模型&#xff0c;它提供了独立于内容而与浏览器窗口进行交互的对象&#xff0c;其核心对象是 window&#xff1b;BOM由一系列相关的对象构成&#xff0c;并且每个对象都提供了很多方…

deepfloyd/IF

Stable Diffusion团队放大招&#xff01;新绘画模型直出AI海报&#xff0c;实现像素级图像生成AI画文字终于能画对了https://mp.weixin.qq.com/s/_pwBD4-wLA9zNHBpD6WdNgDeepFloyd IF — DeepFloydhttps://deepfloyd.ai/deepfloyd-ifhttps://colab.research.google.com/#scroll…

微服务开发系列 第八篇:Elasticsearch

总概 A、技术栈 开发语言&#xff1a;Java 1.8数据库&#xff1a;MySQL、Redis、MongoDB、Elasticsearch微服务框架&#xff1a;Spring Cloud Alibaba微服务网关&#xff1a;Spring Cloud Gateway服务注册和配置中心&#xff1a;Nacos分布式事务&#xff1a;Seata链路追踪框架…

gradle快速入门

1.Gradle 入门 1.1 Gradle 简介 Gradle 是一款Google 推出的基于JVM、通用灵活的项目构建工具&#xff0c;支持Maven&#xff0c;JCenter 多种第三方仓库;支持传递性依赖管理、废弃了繁杂的xml 文件&#xff0c;转而使用简洁的、支持多种语言(例如&#xff1a;java、groovy 等…

如何成功申请计算机软件著作权【申请流程完整记录】

致谢 &#xff1a;此博文的编写包括软著的申请&#xff0c;均借鉴了大佬【万里守约】的博客&#xff0c;很幸运一次性通过 — 提示&#xff1a;此博文仅适合个人申请&#xff0c;因为我是自己一个人的项目&#xff0c;自己一个人申请软著 文章目录 前言&#xff1a;一、网站注册…

HCIA-RSTP,MSTP

目录 STP的不足 RSTP对STP的改进 1&#xff0c;配置BPDU的处理发生变化&#xff1a; 2&#xff0c;配置BPDU的格式发生变化&#xff0c;充分利用STP的flag字段&#xff0c;明确接口角色。 3&#xff0c;RSTP拓扑处理&#xff1a; 端口角色&#xff1a; MSTP&#xff08;多…

Inline HOOK

一、Inline HOOK介绍 1、内联钩子简介 Inline hook&#xff08;内联钩子&#xff09;是一种在程序运行时修改函数执行流程的技术。它通过修改函数的原始代码&#xff0c;将目标函数的执行路径重定向到自定义的代码段&#xff0c;从而实现对目标函数的拦截和修改。 内联钩子通…

Flume学习笔记

1 简介 (1) Apache Flume是一个分布式、可信任的数据采集、日志收集弹性系统(框架)&#xff0c;用于高效收集、汇聚和移动大规模日志信息从多种不同的数据源到一个集中的数据存储中心(HDFS、Hbase或者本地文件系统) (2) 可信任是指保证消息有效的处理和传递&#xff1a; 如果…

聊聊Go语言的控制语句

在高级编程语言中&#xff0c;控制流语句(control-flow statement)是一类用于控制程序执行流程的语句&#xff0c;以下简称为控制语句。它们可以根据条件或循环执行相应的代码块&#xff0c;或者跳转到指定位置执行代码。 常见的控制语句包括&#xff1a; 条件语句&#xff1a;…

【特征选择】基于二进制粒子群算法的特征选择方法(GRNN广义回归神经网络分类)【Matlab代码#32】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】1. 特征选择问题2. 二进制粒子群算法3. 广义回归神经网络&#xff08;GRNN&#xff09;分类4. 部分代码展示5. 仿真结果展示6. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第6节…

2023年Github学生包认证,提取JetBrains全家桶,认证Copilot 等多种权益

什么是GIthub学生包 1 Github学生包简而言之是可以白嫖100美刀的微软Azure&#xff08;每年&#xff09;&#xff0c;200美刀的DigitalOcean金额&#xff08;一年有效期&#xff09;总计300刀可用来购买云服务器。 2 一年免费的.tech .me .live三种顶级域名&#xff08;可以分…

Kubernetes 云原生 Gateway 网关

一、云原生定义 CNCF 对云原生的定义中提到了几个关键的点&#xff1a; 1、强调应用环境的动态性&#xff0c;像公有云、私有云、混合云等新型的动态环境已成为大多数应用的首选&#xff1b; 2、强调在跨多云部署应用时具备非云平台绑定的属性&#xff1b; 3、还强调了弹性扩展…

Python系列模块之标准库shutil详解

感谢点赞和关注 &#xff0c;每天进步一点点&#xff01;加油&#xff01; 目录 一、shutil介绍 二 、使用详解 2.1 复制函数 2.1.1 shutil.copy 2.1.2 shutil.copy2 2.1.3 shutil.copyfile 2.1.4 shutil.copytree 2.2 移动文件 2.2.1 shutil.move 2.3 删除文件 2.3…

Vue-后台管理项目001---侧边栏

从浏览器上可以看出&#xff0c;他的返回值是promise(pending)&#xff0c;所以我们可以用async&#xff0c;await来简化这个操作 await只能用在被async修饰的方法中&#xff0c;需要把仅挨着await的方法修饰成async 需要把仅挨着await的方法修饰成异步的async现在可以将这个da…

1-1 统计数字问题

题目&#xff1a; 我的答案&#xff1a; 一、信息 二、分析 1.如何选择数据结构&#xff1f; 2.如何选择算法有很多思路&#xff1f; 3.如何用文件实现输入输出&#xff1f; 三、思考 疑问1 我选择了一开始数组选择使用数组是一个不错的选择&#xff0c;尤其在这个问题中…

【网络协议详解】——DHCP系统协议(学习笔记)

目录 &#x1f552; 1. DHCP概述&#x1f552; 2. 工作过程&#x1f552; 3. DHCP的报文格式&#x1f552; 4. DHCP中继代理&#x1f552; 5. 实验&#xff1a;DHCP配置 &#x1f552; 1. DHCP概述 动态主机配置协议DHCP&#xff08;Dynamic Host Configuration Protocol&…

TA-lib第三方库安装问题

因为学习的需要&#xff0c;用到Talib库做写指标分析&#xff0c;但是百度了好久&#xff0c;说是去要某某网站下载对应版本的文件进行本地安装&#xff0c;但是把…404 Not found 然后通过查找&#xff0c;Ta-lib库的安装已经迁移到这里了 https://github.com/TA-Lib/ta-lib-p…

【SpringBoot教程】SpringBoot+MybatisPlus数据库连接测试 用户收货信息接口开发

⛪ 专栏地址 系列教程更新中 &#x1f680; 文章介绍: SpringBootMybatisPlus组合可以大大加快开发效率&#xff0c;紧接上一篇文章的内容&#xff0c;这篇文章进行数据库的连接与查询测试&#xff0c;并配置日志输出调试 &#x1f680; 源码获取: 项目中的资料可以通过文章底部…