【C++】拆分详解 - list

news2025/1/19 11:33:45

文章目录

  • 一、list的介绍
  • 二、list的使用
    • 1. 构造
    • 2. 迭代器
    • 3. 增 删 查 改
    • 4. list 迭代器失效问题
    • 5. list 排序问题
  • 三、list的模拟实现
    • 0. 整体框架
    • 1. 迭代器类
      • 1.1 operator->
      • 1.2 临时对象
      • 1.3 const_iterator
    • 2. list类
      • 2.1 begin / end
      • 2.2 构造 / 析构 / 拷贝构造 / 赋值重载
      • 2.3 增 删 查 改
  • 四、list与vector的对比


一、list的介绍

  • 底层是带头双向链表结构,需要额外空间保存节点信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
  • 在常数范围内支持任意位置的插入和删除,效率通常优于array、vector和deque。
  • 不支持随机访问,必须从头/尾开始找,访问特定元素需要线性时间。

二、list的使用

1. 构造

构造函数声明(constructor)功能说明
list()default】无参构造
list (size_type n, const value_type& val = value_type())fill】构造并初始化填充n个val
list (const list& x)copy】拷贝构造
list (InputIterator first, InputIterator last)range】使用迭代器区间进行初始化构造
void TestList1()
{
    list<int> l1;                         // 构造空的l1
    list<int> l2(4, 100);                 // l2中放4个值为100的元素
    list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3
    list<int> l4(l3);                    // 用l3拷贝构造l4

    // 以数组为迭代器区间构造l5 (数组名的
    int array[] = { 16,2,77,29 };
    list<int> l5(array, array + sizeof(array) / sizeof(int));

    // 列表初始化(initializer list) --> C++11
    // 以下三种写法都是一样的效果
    list<int> l6({ 1,2,3,4,5 });
    list<int> l66{ 1,2,3,4,5 };
    list<int> l666 = { 1,2,3,4,5 };

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

    for (auto& e : l6)
        cout << e << " ";

    cout << endl;
}

2. 迭代器

iterator的使用功能说明
begin + end (重点)获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin + rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

在这里插入图片描述

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
  3. list中的迭代器为双向迭代器,只支持++, – 即只能前后移动一个节点
// 注意:遍历链表只能用迭代器和范围for
void PrintList(const list<int>& l)
{
    // 注意这里l是const对象,调用返回的是const_iterator对象
    for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
    {
        cout << *it << " ";
        // *it = 10; 编译不通过
    }

    cout << endl;
}

void TestList2()
{
    int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    list<int> l(array, array + sizeof(array) / sizeof(array[0]));
    // 使用正向迭代器正向list中的元素
    // list<int>::iterator it = l.begin();   // C++98中语法
    auto it = l.begin();                     // C++11之后推荐写法
    while (it != l.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 使用反向迭代器逆向打印list中的元素
    // list<int>::reverse_iterator rit = l.rbegin();
    auto rit = l.rbegin();
    while (rit != l.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
}

3. 增 删 查 改

接口名称功能说明
size获取有效节点个数
empty判断是否为空
front获取第一个节点的值
back获取最后一个节点的值
reserve改变vector的capacity
push_front头插
push_back尾插
pop_front头删
pop_back尾删
insert指定位插入
erase指定位删除
swap交换两个list的成员
clear删除所有节点
// list插入和删除
// push_back/pop_back/push_front/pop_front
void TestList3()
{
    int array[] = { 1, 2, 3 };
    list<int> L(array, array + sizeof(array) / sizeof(array[0]));

    // 在list的尾部插入4,头部插入0
    L.push_back(4);
    L.push_front(0);
    PrintList(L);

    // 删除list尾部节点和头部节点
    L.pop_back();
    L.pop_front();
    PrintList(L);
}

// insert /erase 
void TestList4()
{
    int array1[] = { 1, 2, 3 };
    list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));

    // 获取链表中第二个节点
    auto pos = ++L.begin();
    cout << *pos << endl;

    // 在pos前插入值为4的元素
    L.insert(pos, 4);
    PrintList(L);

    // 在pos前插入5个值为5的元素
    L.insert(pos, 5, 5);
    PrintList(L);

    // 在pos前插入[v.begin(), v.end)区间中的元素
    vector<int> v{ 7, 8, 9 };
    L.insert(pos, v.begin(), v.end());
    PrintList(L);

    // 删除pos位置上的元素
    L.erase(pos);
    PrintList(L);

    // 删除list中[begin, end)区间中的元素,即删除list中的所有元素
    L.erase(L.begin(), L.end());
    PrintList(L);
}

// resize/swap/clear
void TestList5()
{
    // 用数组来构造list
    int array1[] = { 1, 2, 3 };
    list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
    PrintList(l1);

    // 交换l1和l2中的元素
    list<int> l2;
    l1.swap(l2);
    PrintList(l1);
    PrintList(l2);

    // 将l2中的元素清空
    l2.clear();
    cout << l2.size() << endl;
}

4. list 迭代器失效问题

  • list 不是整块的连续物理空间,不存在扩容 / 缩容的概念,只有删除时会引发迭代器失效。(详细说明参考博主的vector解析中的 二、5.迭代器失效问题)
void TestListIterator()
{
    int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    list<int> l(array, array + sizeof(array) / sizeof(array[0]));
    auto it = l.begin();
    while (it != l.end())
    {
        // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
            l.erase(it);
        // it = l.erase(it); 正确写法
        ++it;
    }
}

5. list 排序问题

结论:

  • 一般不用:效率极其低下,是vector的一半,甚至不如先拷贝给vector,排好序再拷贝回来效率高
// vector与list排序性能比较
void test_op1()
{
    srand(time(0));
    const int N = 10000000;

    list<int> lt1;
    list<int> lt2;

    vector<int> v;

    for (int i = 0; i < N; ++i)
    {
        auto e = rand() + i;
        lt1.push_back(e);
        v.push_back(e);
    }

    int begin1 = clock();
    // 排序
    sort(v.begin(), v.end());
    int end1 = clock();

    int begin2 = clock();
    lt1.sort();
    int end2 = clock();

    printf("vector sort:%d\n", end1 - begin1);
    printf("list sort:%d\n", end2 - begin2);
}

//先将list元素拷贝给vector,在vector中排好序再拷贝回list的性能
void test_op2()
{
    srand(time(0));
    const int N = 10000000;

    list<int> lt1;
    list<int> lt2;

    for (int i = 0; i < N; ++i)
    {
        auto e = rand() + i;
        lt1.push_back(e);
        lt2.push_back(e);
    }

    int begin1 = clock();
    // 拷贝vector

    vector<int> v(lt2.begin(), lt2.end());
    // 排序
    sort(v.begin(), v.end());

    // 拷贝回lt2
    lt2.assign(v.begin(), v.end());

    int end1 = clock();

    int begin2 = clock();
    lt1.sort();
    int end2 = clock();

    printf("list copy vector sort copy list sort:%d\n", end1 - begin1);
    printf("list sort:%d\n", end2 - begin2);
}

在这里插入图片描述

三、list的模拟实现

0. 整体框架

template<class T>
struct ListNode
{
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T _data;

	ListNode(const T& data = T())
		:_next(nullptr)
		,_prev(nullptr)
		,_data(data)
	{}
};

template<class T>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;
	Node* _node; //迭代器本体,唯一成员变量 ---> 指针

	ListIterator(Node* node)
		:_node(node)
	{}
	// ... 各类迭代器行为
};


template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	//typedef Node* iterator;  直接使用原生指针定义迭代器,不符合所需的迭代器行为,需要格外定义一个类进行封装
	typedef ListIterator<T, T&, T*> iterator;
	typedef ListIterator<T, const T&, const T*> const_iterator;

	// ...各类函数接口

private:
	Node* _head;
};
  1. list类封装 有关节点操作的相关接口

  2. ListNode类 封装节点构造相关

  3. ListIterator 封装list的迭代器相关

    • 链表物理空间不连续,不能通过++iterator​ 找下一个节点,必须用一个类封装原生指针然后手动控制其行为(重载运算符)使之符合

      (迭代器的最终效果要像原生指针对于数组一样使用,但是不是全部的功能都要实现,比如list的双向迭代器就不能iterator + 4​,最多只能找前一个和后一个,因为效率太低,设计者没有必要支持)

    • 不需要显式写析构 / 拷贝构造 / 赋值:

      1. 节点的释放由list类控制,迭代器不涉及资源管理,默认生成的析构就够用
      2. ListIterator类唯一的成员变量(本体)就是指针,我们只是规范了它的行为,则拷贝 / 赋值时需要的就是浅拷贝,同一个指针指向同一块空间

1. 迭代器类

//V1 版本:写两个迭代器类,一个是普通迭代器,一个是const 迭代器
template<class T>
struct ListIterator
{
	typedef ListNode<T> Node;
	//typedef ListIterator<T, Ref, Ptr> Self;
	typedef ListIterator<T> Self;
	Node* _node; //迭代器本体,指针

	ListIterator(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;
	}

	T& operator*()
	{
		return _node->_data;
	}

	T* operator->()
	{
		return &_node->_data;
	}

	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}

	bool operator==(const Self& it)
	{
		return _node == it._node;
	}
};

template<class T>
struct ListConstIterator
{
	typedef ListNode<T> Node;
	typedef ListConstIterator<T> Self;
	Node* _node;

	ListConstIterator(Node* node) 
		:_node(node) 
	{}

	//...略,与普通迭代器一模一样

	const T& operator*() 
	{
		return _node->_data;
	}

	const T* operator->()
	{
		return &_node->_data;
	}
	// ...略,同上
};

//V2 版本:使用多参数模板,交给编译器实现const迭代器类
//const 和 非const 模板生成两个类
template<class T, class Ref, class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;
	Node* _node; //迭代器本体,指针

	ListIterator(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& it)
	{
		return _node != it._node;
	}

	bool operator==(const Self& it)
	{
		return _node == it._node;
	}
};

1.1 operator->

专门给没有重载流插入<<运算符的类提供的(对于自定义类型,流插入可以直接使用,而自定义类型需要类设计者自己重载控制),下见示例类 Pos。该类没有重载流插入,我们只能使用最原始的方法,利用指针手动访问该类的成员变量_row, _col

  • cout << (*it)._row​ 该段代码很好理解,先解引用得Pos类对象,再用Pos类对象访问其成员
  • cout << it->_row ​ 功能同上,先获取Pos对象指针再使用Pos类对象指针访问其成员,但是仔细观察一下,是不是少了一个箭头啊?实际上这是编译器的省略行为,该段代码等同于cout << it.operator->()->_row​ 即先调用迭代器类中 重载的-> 获取Pos类对象的指针再用 原生-> 访问其成员
struct Pos
{
	int _row;
	int _col;

	Pos(int row = 0, int col = 0)
		:_row(row)
		, _col(col)
	{}
};

void test_list2()
{
	list<Pos> lt1;
	lt1.push_back(Pos(100, 100));
	lt1.push_back(Pos(200, 200));
	lt1.push_back(Pos(300, 300));

	list<Pos>::iterator it = lt1.begin();
	while (it != lt1.end())
	{
		//cout << (*it)._row << ":" << (*it)._col << endl;
		// 为了可读性,省略了一个->
		cout << it->_row << ":" << it->_col << endl;
		//cout << it->->_row << ":" << it->->_col << endl;
		cout << it.operator->()->_row << ":" << it.operator->()->_col << endl;

		++it;
	}
	cout << endl;
}

1.2 临时对象

begin返回的是临时对象,临时对象具有常性,那为什么返回的迭代器可以++,—呢?

为了使用的便利,编译器经过了特殊处理,实际上临时对象的常性介于const和非const之间,可以调用非静态成员函数,也可以被修改,但是不能被非const对象引用

在这里插入图片描述
在这里插入图片描述

1.3 const_iterator

  1. const迭代器 专给const类对象调用,类似于 const T* ,迭代器本身可以修改,指向的内容不可以通过迭代器修改(const是对迭代器本身的修饰,如果用其他方法修改其指向的内容,迭代器管不着)

    故对迭代器行为中涉及解引用且修改的进行约束:

    • 对迭代器解引用行为要返回const类型值(通过const迭代器获取的指向内容 不可以修改)
  2. 如何写?

    方法1:再创建一个const版本的迭代器类

    方法2:使用多参数模板,交给编译器干(实际上还是会创建两个类)

//方法1:
template<class T>
struct ListConstIterator
{
	typedef ListNode<T> Node;
	typedef ListConstIterator<T> Self;
	//const Node* _node; 为了直接使用多模板参数省事,可以不加const,因为迭代器的行为完全由我们手动控制,
	//只要我们不在其解引用时修改即可。但是如果有人手动调用迭代器内部的指针进行解引用修改是可以的(没大病不会这么干)
	Node* _node;
	ListConstIterator(Node* node) 
		:_node(node) 
	{}
	//...略,与普通迭代器一模一样

	const T& operator*() 
	{
		return _node->_data;
	}

	const T* operator->()
	{
		return &_node->_data;
	}
};

//方法2:
//const 和 非const 模板生成两个类
template<class T, class Ref, class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;
	Node* _node; //迭代器本体,指针

	ListIterator(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& it)
	{
		return _node != it._node;
	}

	bool operator==(const Self& it)
	{
		return _node == it._node;
	}
};

2. list类

2.1 begin / end

iterator begin()
{
	return iterator(_head->_next); //类的匿名对象,下同
}

const_iterator begin() const
{
	return const_iterator(_head->_next);
}

iterator end()
{
	return iterator(_head);
}

const_iterator end() const
{
	return const_iterator(_head);
}

2.2 构造 / 析构 / 拷贝构造 / 赋值重载

//头结点创建(链表为空),由于多个接口都要调用,故单独提出来封装成函数
void empty_init()
{
	_head = new Node();
	_head->_next = _head;
	_head->_prev = _head;
}

list()
{
	empty_init();
}

list(initializer_list<T> il)
{
	empty_init();

	for (const auto& e : il)
		push_back(e);
}

//lt2(lt1)
list(const list<T>& lt)
{
	empty_init();

	for (const auto& e : lt)
		push_back(e);
}

list<T>& operator=(list<T> lt)
{
	swap(_head, lt._head);

	return *this;
}

~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

2.3 增 删 查 改

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

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

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

	return iterator(newnode);
}

iterator erase(iterator pos)
{
	assert(pos != end());

	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;

	prev->_next = cur->_next;
	next->_prev = prev;
	delete cur;

	return iterator(next);
}

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

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

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

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

四、list与vector的对比

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效插入,删除时删除时
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问

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

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

相关文章

谷歌浏览器如何防范恶意网站和广告

在日常使用互联网时&#xff0c;我们经常会遇到一些恶意网站或广告&#xff0c;这些不仅会影响我们的浏览体验&#xff0c;还可能对我们的设备安全造成威胁。为了帮助大家更好地防范这些问题&#xff0c;本文将详细介绍如何在谷歌浏览器中采取有效的措施来防范恶意网站和广告。…

Grafana+ Node+ Prometheus对服务器进行性能监控

目录 一、Grafana 1.把Grafana压缩文件上传到Linux服务器 2.安装服务 3.启动服务 4.访问 二、Node 1.上传node_exporter-1.5.0.linux-amd64.tar到服务器 2.解压 3.启动服务 三、Prometheus 1.上传prometheus-2.43.0.linux-amd64.tar到服务器 2.解压 3.进入prometh…

【Linux】ComfyUI和SD WebUI之PYTHON环境共享,模型共享,LORA等公共资源共享。最大限度节约空间

需求 一般玩AI绘图都会装ComfyUI和SD WebUI。而且这俩的模型、lora等都是一致的。为了避免空间的浪费&#xff0c;一般会采用共享数据的方式。而且共享的数据可以任意指定分区&#xff0c;这让挂载NAS共享空间成为可能&#xff0c;实现多绘画机ComfyUI和SD WebUI共享资源。 实…

SpringCloud学习记录|day4

学习材料 2024最新SpringCloud微服务开发与实战&#xff0c;java黑马商城项目微服务实战开发&#xff08;涵盖MybatisPlus、Docker、MQ、ES、Redis高级等&#xff09; 网关 微服务下&#xff0c;好多不同地址和端口&#xff0c;而前端只知道8080&#xff0c;这怎么解决&…

不起眼的错误参数导致remote-debugging-port不生效

引言 背景 由于有一些小需求需要控制浏览器批量执行请求&#xff0c;最简单的方案是使用 DrissionPage 来实现&#xff0c;遂有了下面的问题。 问题 通过命令行传入 remote-debugging-port9111 参数&#xff0c;但是调试了一天&#xff0c;一直不生效&#xff0c;各种方法都…

【数据结构与算法】线性表链式存储结构

线性表链式存储结构 文章目录 链式存储结构*头结点和头指针一.线性链表&#xff08;单链表&#xff09;1.1定义1.2初始化1.2.1带头结点的初始化1.2.2不带头结点的初始化 1.3插入1.3.1按位序插入1.3.2指定结点的后插入操作1.3.3指定结点的前插入操作 1.4删除1.4.1按位序删除1.4.…

小猿口算安卓端安装包PK一题秒过关。。。

大家好&#xff0c;我是小黄。 近段时间&#xff0c;越来越多的同学都想去小猿口算里面虐小学生&#xff0c;但是发现越来越多的计算机学生带着科技与他们进行对抗&#xff0c;这样非计算机专业的大学生们​苦不堪言。 现在&#xff0c;非计算机大学生们翻身的机会来了&#…

二叉平衡树(AVL树)Java语言实现

一、二叉平衡树 上一章所示的同一组元素按照不同顺序插入到二叉排序树中可能会产生两种形状不同的二叉排序树。 当出现右边的情况时&#xff0c;树的高度过高&#xff0c;如果要查找值为“70”的节点需要查找7次&#xff0c;其查找次数已经接近于链表了&#xff0c;这样会导致…

短短几日连发数案,艺术家Jennifer Le Feuvre插画版权维权

案件基本情况起诉时间&#xff1a;2024-10-7、2024-10-8、2024-10-9案件号&#xff1a;24-cv-09629、24-cv-09636、24-cv-09640、24-cv-09688、24-cv-09697、24-cv-09709、24-cv-09712、24-cv-09757、24-cv-09775、24-cv-09794原告&#xff1a;Jennifer Le Feuvre原告律所&…

【安装教程】Windows10环境下Pytorch(GPU版)的安装与配置

目录 Pytorch的概念安装前要求一、NVIDIA驱动查看二、Anaconda的安装2.1 Anaconda的安装2.2 创建虚拟环境2.3 激活虚拟环境 三、CUDA ToolKit的安装&#xff08;选做&#xff0c;CPU版本可跳过&#xff09;3.1 CUDA安装包的下载&#xff08;以CUDA11.6.0为例&#xff09;3.2 CU…

架构设计笔记-19-大数据架构设计理论与实践

知识要点 案例分析 1.Lambda架构优缺点 2.web架构设计 3.web系统架构设计相关技术 论文

面向对象的继承性

目录 1、继承的概念 2、方法的重写 3、重载和重写的区别 4、super关键字 5、this和super的区别 6、final关键字 7、抽象类 8、接口 1、继承的概念 在程序中&#xff0c;继承描述的是事物之间的所属关系&#xff0c;通过继承可以使很多事物之间形成一种关系体系。 在Java…

51单片机的智能空调【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温湿度传感器继电器按键等模块构成。适用于空调温度控制等相似项目。 可实现功能: 1、LCD1602实时显示室内温湿度、运行模式、设定温度和定时时间 2、首先选择空调的运行模式&#xff0c;如加热、制冷、除湿&…

@RequestMapping对不同参数的接收方式

1、简单参数 1、参数名与形参变量名相同&#xff0c;定义形参即可接收参数&#xff0c;且会自动进行类型转换。 RequestMapping("/simple")public String simpleParam(String name,int age){String username name;int userAge age;System.out.println(username&…

芝法酱学习笔记(0.7)——harbor与SpringBoot容器化docker部署

前言 之前我们主要讲的jar包部署。使用jar包部署可能导致不同服务互相争抢资源&#xff08;隔离性&#xff09;&#xff0c;不同服务可能需要不同的jdk环境&#xff0c;有时也会造成困扰。故在微服务时代&#xff0c;我们通常使用docker部署 一、docker安装 docke相关的知识…

Dev-C++萌新学习福利3

朝鲜球作品原创https://blog.csdn.net/2401_86502594?spm1011.2124.3001.5343 清北互联地址https://www.17ac.cn/#/ 萌新福利 作品成本6999元&#xff01;&#xff01;&#xff01; 清北互联团队编写课程&#xff0c;本人不收费。亏本买卖&#xff0c;良心服务&#xff0c;同嫂…

ASP.NET Core8.0学习笔记(二十)——EFCore导航属性与外键

一、什么是实体间关系 数据库表&#xff08;实体&#xff09;之间的关系&#xff1a;一对一&#xff08;学生-成绩&#xff09;、一对多&#xff08;学生-科目&#xff09;、多对多&#xff08;教师-班级&#xff09;。数据库中&#xff0c;每一个实体可以由主键唯一标识&…

自学1个月拿金奖!北交大学子分享昇腾AI原生创新算子挑战赛金奖之路

近年来在人工智能领域&#xff0c;算子开发的价值日益凸显&#xff0c;算子开发也受到越来越多年轻开发者的青睐。对于高校开发者&#xff0c;如何从零开始学习算子开发&#xff0c;提升软硬结合的AI开发能力&#xff1f;成功已举办两个赛季的昇腾AI原生创新算子挑战赛&#xf…

IDEA中的快捷键大全--超详细

目录 一、通用类型 1.1 图示 1.2 表格化 二、编写速度提升 2.1 图示 2.1.1 表格化 2.2 图示 2.2.1 表格化: 三、类结构,查找和查看源码 3.1 图示 3.2 表格化 四、查找,替换和关闭 4.1图示 4.2 表格化 五、调整格式 5.1 图示 5.2 表格化 六、快捷键的自主定义…

docker login 命令登录harbor镜像仓库(含报错)

作者&#xff1a;程序那点事儿 日期&#xff1a;2024/02/02 14:10 执行登录命令&#xff1a;docker login -uadmin 192.168.43.106:8880 报错&#xff1a; Error response from daemon: Get "https://192.168.43.106:8880/v2/": http: server gave HTTP response t…