当前位置: 首页 > news >正文

C++_list

目录

1.引言:

2.list的介绍与使用

2.1.list的介绍

2.2.list的使用

构造

析构

赋值运算符重载

迭代器

容量相关接口

元素访问

对象修改

专属接口

splice

remove

remove_if

unique

merge

sort

拓展:仿函数

reverse

3.list模拟实现

3.1.list源码观察

3.2.list模拟实现

迭代器初步实现

代码重构

insert

push_back和push_front

析构

erase

pop_back和pop_front

->运算符重载

const迭代器初步实现

迭代器优化

迭代器失效

clear

赋值运算符重载

源码

拓展

5.结语


1.引言:

这篇讲list的使用和模拟实现,在学list前,需要掌握这些内容

C++_类和对象(上)-CSDN博客

C++_类和对象(中)-CSDN博客

C++_类和对象(下)-CSDN博客

C++_模板初阶-CSDN博客

C++_内存管理_c++内存管理要如何做-CSDN博客

那么话不多说直接进入正文——————>


2.list的介绍与使用

2.1.list的介绍

list容器和我们先前讲的vector容器一样,都是模板,list底层涉及的数据结构就是链表,我们还是照着这个文档进行讲解,顺序依旧是能用,明理,能扩展list - C++ Reference

在讲list之前,我先讲下list结构,因为链表有很多种,list底层的链表是带哨兵位的双向循环链表,这个我会在看list底层的时候也讲

2.2.list的使用

经过string和vector的讲解和模拟实现,其实现在看list可以发现很多接口基本都知道是实现什么功能的了,所以这边我就一个个快速过下去了

构造

这构造就和vector一样的四种构造,第一个是默认构造,第二个是构造同时插入n个数据为val的对象,第三个就是迭代器构造,构造同时插入[first,last)这些位置的对象,第四个就是拷贝构造,没什么好说的

析构

赋值运算符重载

迭代器

这些迭代器就正向反向const,跟之前的也都一样,过一眼就好

注意:这里需要注意的是,从list开始,遍历就不能用【】+下标的遍历方式了,因为从list往后的那些容器全都不是顺序表了,那么,这个时候如果用[]+下标遍历的话,成本会很高,就比如说从begin()到end()的所有数据输出一遍,vector和string的时间复杂度是O(N),但是如果这种非顺序表的话时间复杂度就会到O(N^2)了,因为比如链表想找第10个数据,那就要从头往后跳10次,,因为成本太高了,所以从list就没有operator[]重载,我们看如下图,他是这么跳的

这里的迭代器其实就和我们先前讲的vector,string的迭代器不一样了,因为这不是连续的空间,所以list的迭代器就肯定不是原生指针了,迭代器其实有三种,我们先前所讲的vector和string的迭代器是属于随机迭代器,而list是双向迭代器

单向迭代器:forward_list、unordered_map、unordered_set 支持++

双向迭代器: list、map、set... 支持++,--

随机迭代器:vector、string、deque.. 支持++,--,+,-

迭代器的性质是由底层的结构决定的,我们可以在这里看容器的迭代器是哪个类型的

容量相关接口

因为链表是一个个插入,想插入时候就创建个新对象,然后更新下指向就好了,所以链表和vector,string这种不一样,没有扩容的概念,所以这里接口不多,empty()就是判空,size()就是返回有几个数据,max_size返回可行最大容量,这也快速过了

元素访问

list的元素直接访问就只能获取到头和尾,也就是这俩个接口,这俩个返回的是引用,也就是可以修改

对象修改

这些接口全在前面讲过了,这里就看一眼过就行

拓展:这里讲下emplace相关接口的用法

拿emplace_back举例,这个和push_back相比在某些场景下会更高效,就如下面代码

#include <iostream> #include <list> using namespace std; struct A { A(int a1 = 1, int a2 = 1) :_a1(a1) , _a2(a2) { cout << "A(int a1 = 1, int a2 = 1)" << endl; } A(const A& x) { _a1 = x._a1; _a2 = x._a2; cout << "A(const A& x)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a1; int _a2; }; int main() { list<A> lt; A aa1(1, 1); lt.push_back(aa1); lt.push_back(A(2,2)); lt.push_back(2, 2); lt.emplace_back(aa1); lt.emplace_back(A(2,2)); lt.emplace_back(2, 2); }

我们可以发现,如果用push_back,传2,2是会报错的,但是emplace_back不会,如下图

emplace_back可以拿值直接传过去构造,就会少一次拷贝构造,这就是emplace_back的优势,如下图

如果我们要传A类型的2,2的话,用emplace_back就不用像push_back一样传匿名对象了,直接传值就行了,传匿名对象会有构造和拷贝构造,但是传值的话他会只有一次构造,就提高了很多效率

这里只要了解就行,具体会在C++11部分讲

专属接口

splice

这个接口的作用是转移数据

第一条——把x内的所有节点全部转移到pos位置之前

第二条——把x内迭代器 i 所指向位置的单个节点转移到pos位置之前

第三条——把x的迭代器指向从[first,last)的所有元素全部转移到pos位置之前

注意:转移之后原先那些就全消除了,就比如第一条转移完,x就为空了

这个接口还可以调整自己链表的位置顺序

remove

这个接口就是用来删除数据

找val删,找到就删除,找不到也不会报错

注意:他是从头遍历到尾,所以如果有多个val,他会全部删除

remove_if

这个接口就是配合一个条件进行删除(这个得传仿函数,全局函数,静态函数或者匿名lambda),现在了解就行,不用会用

unique

这个接口的功能是去重,去重的前提也是链表要有序

merge

这个接口的功能是将俩个链表进行合并,链表合并的前提是俩个链表必须是有序的,这个合并的底层原理其实就是取小的尾插

需要注意的是,比如lt1.merge(lt2),这就是lt2合并到lt1去了,这个时候lt2其实就空了,这里需要注意

sort

算法库里也是有sort的,但是算法库的sort底层是快排,堆排,三路划分的融合,这些都要基于顺序表才可以实现,所以算法库的sort只能接收随机迭代器,所以list无法用算法库的sort实现排序功能,我们可以看下算法库的sort

这个类型名是RandomAccess,也就是随机,即代表随机迭代器,那自然是不支持双向迭代器的

所以,list自己实现了一个sort,这个sort和算法库实现的sort效果一样,默认就是升序排序,但是 list的sort接口的底层是通过归并排序实现的(数据量大的情况下不要用list的排序,效率很低,可以先拷贝给vector排完序再拷贝回来)

拓展:仿函数

那么如果想要降序排序的话就要用到仿函数(仿函数就是重载了括号运算符-operator()的类的实例化),这里就教怎么用,首先有俩个类模板,less和greater,less就是用来排升序的,greater就是用来排降序的,样例如下

#include <iostream> #include <list> #include <algorithm> using namespace std; void test3() { list<int> lt; lt.push_back(2); lt.push_back(1); lt.push_back(3); lt.push_back(0); lt.push_back(5); less<int> ls; greater<int> lg; lt.sort(ls);//升序 for (auto x : lt) cout << x << " "; cout << endl; lt.sort(lg);//降序 for (auto x : lt) cout << x << " "; cout << endl; } int main() { test3(); }

那既然是类,自然就可以写匿名对象了,比如这样

lt.sort(greater<int>());//降序
reverse

这个reverse的效果就和算法库(algorithm)里的那个reverse实现效果一样,就是逆置,我们看算法库的reverse,如下图

算法库中的reverse是个函数模板,Bidirectional就是双向,也就是说,我们可以通过类型名来推断他支持什么,这个明显就是支持双向迭代器,既然支持双向迭代器,自然也就支持随机迭代器。那么list的迭代器是双向迭代器,调用这个就可以完成list的逆置了,我们可以俩个reverse都测一下

#include <iostream> #include <list> #include <algorithm> using namespace std; void test2() { list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); for (auto x : lt) cout << x << " "; cout << endl; lt.reverse(); for (auto x : lt) cout << x << " "; cout << endl; reverse(lt.begin(), lt.end()); for (auto x : lt) cout << x << " "; cout << endl; } int main() { test2(); }

运行可以发现都是没问题的

注意:虽然这俩个reverse实现的效果一样,但是底层效率而言是完全不一样的,算法库里的reverse是交换俩个迭代器的数据,而list里自己实现的reverse是直接交换next和prew指针(指向前一个和后一个的指针)。这么一看其实效率就天差地别了,所以其实这个reverse设计不冗余


3.list模拟实现

3.1.list源码观察

这边扒源码的方式就和vector部分看源码的方式一样,也是在那个文件夹里

我们先找到list打开

核心的毋庸置疑就是stl_list.h,我们找到这个文件去打开

我们先找到list类

我们可以发现,这个类内的成员变量就一个,那就是node

然后这个link_type是什么类型呢,我们跳转过去看,我们可以发现是list_node*类型

接下来我们再去看list_node是什么类型,可以发现是__list_node<T>类型的

接下来我们接着跳转

所以,node其实就是指向__list_node<T>对象的指针,我们可以通过图来理解这几个命名关系

我们观察__list_node类模板,可以发现,里面有三个成员,data存数据,next指向下一个节点,prev指向上一个节点

接下来,我们来看下list的构造

因为是默认构造,此时list内部还没有结构,所以node->next和node->prev都指向自己,我们再看get_node()内部

这其实就是向空间配置器申请一块内存,用来新建节点(其实就和我们上次实现vector的时候用裸内存定位new的方式中裸内存一样,就是给你一块裸空间,不创建对象)

这个节点的data成员并没有赋值,所以我们可以猜想这个可能会作为哨兵位,接下来,我们来看下push_back

首先push_back会调用insert

随后iinsert一开始进去会新创建一个节点,这个节点是通过create_node得到的,create_node就是像空间配置器申请一块空间后,调用construct函数来给p->data初始化(construct先前vector部分我们讲过,里面就是定位new初始化)

然后初始化完后就返回p给tmp接收,随后在更新prev和next的指向

其实从这里,我们就能看出list底层实现的时候是带有哨兵位的,我们通过图来更清晰的看

那么知道list的成员变量,知道list的链表是带哨兵位的双向循环链表后,我们就可以开始模拟实现list了

3.2.list模拟实现

这里我们依旧创建一个头文件——list.h

接下来,我们先搭个简单的结构之后再修改润色,这边我为了方便多加了个_size来统计节点个数

#pragma once #include <iostream> namespace qiu { template<class T> struct list_node { T data; list_node<T>* next; list_node<T>* prev; }; template<class T> class list { public: typedef list_node<T>* iterator; typedef const list_node<T>* const_iterator; void push_back(const T& x); list() :_size(0) { node = (iterator)::operator new(sizeof(list_node<T>)); node->next = node; node->prev = node; } size_t size() const { return _size; } bool empty() const { return size() == 0; } iterator begin() { return node->next; } iterator end() { return node; } const_iterator begin() const { return node->next; } const_iterator end() const { return node; } private: iterator node; size_t _size; }; template<class T> void list<T>::push_back(const T& x) { iterator new_node = (iterator)::operator new(sizeof(list_node<T>)); new_node->data = x; iterator old_node = node->prev; new_node->prev = old_node; new_node->next = node; old_node->next = new_node; node->prev = new_node; ++_size; } }

接下来,我们测试一下,没有什么大问题

#define _CRT_SECURE_NO_WARNINGS #include "list.h" int main() { qiu::list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); lt.push_back(5); qiu::list<int>::iterator it = lt.begin(); while (it != lt.end()) { std::cout << it->data << " "; it = it->next; } return 0; }

在上面,我们虽然用迭代器成功遍历了list,但是list是有++,解引用重载这些的,但是list内部的成员变量存的是我们这个链表的哨兵位,这是不能改的,那么,我们就需要为了迭代器,在外部实现一个类,如下

迭代器初步实现

template<class T> struct list_iterator { typedef list_node<T>* Node; typedef list_iterator<T> iterator; list_iterator(Node _node) :node(_node) { } iterator& operator++() { node = node->next; return *this; } iterator operator++(int) { iterator _node(node); node = node->next; return _node; } iterator& operator--() { node = node->prev; return *this; } iterator operator--(int) { iterator _node(node); node = node->prev; return _node; } T& operator*() { return node->data; } bool operator!=(const iterator& it) const { return node != it.node; } bool operator==(const iterator& it) const { return node == it.node; } Node node; };

那么,我们上面所写的简单的结构就可以重构变成如下代码

代码重构

#pragma once #include <iostream> namespace qiu { template<class T> struct list_node { T data; list_node<T>* next; list_node<T>* prev; }; template<class T> struct list_iterator { typedef list_node<T>* Node; typedef list_iterator<T> iterator; list_iterator(Node _node) :node(_node) { } iterator& operator++() { node = node->next; return *this; } iterator operator++(int) { iterator _node(node); node = node->next; return _node; } iterator& operator--() { node = node->prev; return *this; } iterator operator--(int) { iterator _node(node); node = node->prev; return _node; } T& operator*() { return node->data; } bool operator!=(const iterator& it) const { return node != it.node; } bool operator==(const iterator& it) const { return node == it.node; } Node node; }; template<class T> class list { public: typedef list_node<T>* Node; typedef list_iterator<T> iterator; void push_back(const T& x); list() :_size(0) { node = (Node)::operator new(sizeof(list_node<T>)); node->next = node; node->prev = node; } size_t size() const { return _size; } bool empty() const { return size() == 0; } iterator begin() { return iterator(node->next); } iterator end() { return iterator(node); } private: Node node; size_t _size; }; template<class T> void list<T>::push_back(const T& x) { Node new_node = (Node)::operator new(sizeof(list_node<T>)); new (&new_node->data) T(x); Node old_node = node->prev; new_node->next = node; new_node->prev = old_node; old_node->next = new_node; node->prev = new_node; ++_size; } }

insert

template<class T> typename list<T>::iterator list<T>::insert(iterator pos, const T & x) { Node new_node = (Node)::operator new(sizeof(list_node<T>)); new (&new_node->data) T(x); new_node->next = pos.node; new_node->prev = pos.node->prev; new_node->next->prev = new_node; new_node->prev->next = new_node; ++_size; return new_node; }

insert实现后,我们其实push_back和push_front就可以复用这块代码了,能减少很多重复代码

push_back和push_front

template<class T> void list<T>::push_back(const T& x) { insert(end(), x); } template<class T> void list<T>::push_front(const T& x) { insert(begin(), x); }

析构

因为我们采用的方式是operator new加定位new,只构造了data对象,所以我们只需要自己手动销毁data的那块数据即可,所以在节点层面,我们不用写析构函数来销毁节点数据,只需要在list地方写个析构函数来销毁节点即可,代码如下

~list() { while (!empty()) pop_back(); ::operator delete(node); }

erase

template<class T> typename list<T>::iterator list<T>::erase(iterator pos) { assert(pos != end()); iterator it = pos.node->next; pos.node->prev->next = pos.node->next; pos.node->next->prev = pos.node->prev; pos.node->data.~T(); ::operator delete(pos.node); --_size; return it; }

erase实现好了后,尾删头删也就没问题了

pop_back和pop_front

template<class T> void list<T>::pop_back() { erase(node->prev); } template<class T> void list<T>::pop_front() { erase(node->next); }

->运算符重载

T* operator->() { return &node->data; }

直接看这个可能看不懂,我们先从我们之前表层运用来看,就比如,a1是一个结构体指针,我们想要访问里面其中一个成员变量,就会这样访问,a1->a;然后a1就是我们这里list里的data成员,所以我们正常实现是&node->data,这样就可以得到结构体的指针,然后想要访问结构体内部的成员变量,在&node->data的基础上->a1。

这里主要难理解是因为,我们调用的时候直接就是 it->a;但其实这里不止一个->,完整的其实是这样的,it.operator->()->a;也就和我们上面的思路一样,即(&node->data)->a;只是为了可读性,省略了一个->,省略的->是后面的那个->

拓展:

operator->拥有递归消解机制:

  • 如果operator->返回的还是一个具备operator->的对象,编译器会继续调用它的operator->()
  • 直到返回原生指针,才停止递归,使用原生指针的->访问成员。

const迭代器初步实现

首先,const迭代器意味就是指向的内容不能修改,那么,我们来找找迭代器里,哪些部分的操作可能会影响到指向的空间

可以发现就这俩个,所以我们只需要在实现const迭代器的时候给这俩个 加上限制,其余拷贝过去即可

template<class T> struct list_const_iterator { typedef list_node<T>* Node; typedef list_const_iterator<T> const_iterator; list_const_iterator(Node _node) :node(_node) { } const_iterator& operator++() { node = node->next; return *this; } const_iterator operator++(int) { const_iterator _node(node); node = node->next; return _node; } const_iterator& operator--() { node = node->prev; return *this; } const_iterator operator--(int) { const_iterator _node(node); node = node->prev; return _node; } const T& operator*() const { return node->data; } const T* operator->() const { return &node->data; } bool operator!=(const const_iterator& it) const { return node != it.node; } bool operator==(const const_iterator& it) const { return node == it.node; } Node node; };

迭代器优化

但是,这么实现的话太冗余了,因为这俩个相似度太高了,就*运算符重载和->运算符重载不一样

这个时候我们可以给模板多几个参数,然后优化合并到一起,如下

template<class T,class Ptr,class Re> struct list_iterator { typedef list_node<T>* Node; typedef list_iterator<T, Ptr, Re> Self; list_iterator(Node _node) :node(_node) { } Self& operator++() { node = node->next; return *this; } Self operator++(int) { Self _node(node); node = node->next; return _node; } Self& operator--() { node = node->prev; return *this; } Self operator--(int) { Self _node(node); node = node->prev; return _node; } Re operator*() { return node->data; } Ptr operator->() { return &node->data; } Re operator*() const { return node->data; } Ptr operator->() const { return &node->data; } bool operator!=(const Self& it) const { return node != it.node; } bool operator==(const Self& it) const { return node == it.node; } Node node; };

所以,之后如果遇到高度相似仅有类型不同的,可以想方设法变为同一个类模板

迭代器失效

接下来,我们来看下迭代器失效问题

首先是insert,insert不会有迭代器失效问题,因为list内部元素是一个一个节点,就算是用户使用一个迭代器,插入之后,那个迭代器所指向的空间里的数据也是不会变的

其次是erase,这个会有迭代器失效问题,因为erase会销毁pos位置的节点,如果用户有迭代器指向pos位置,那么就会有迭代器失效问题

clear

template<class T> void list<T>::clear() { iterator it = begin(); while (it != end()) it = erase(it); }

这个clear就可以复用到析构上

~list() { clear(); ::operator delete(node); }

赋值运算符重载

template<class T> void list<T>::swap(list<T>& x) { std::swap(node, x.node); std::swap(_size, x._size); } template<class T> list<T>& list<T>::operator=(list<T> x) { swap(x); return *this; }

至此,list主要的一些接口就实现完成了,接下来赋源码顺便拓展点东西

源码

#pragma once #include <iostream> #include <cassert> namespace qiu { template<class T> struct list_node { T data; list_node<T>* next; list_node<T>* prev; }; template<class T,class Ptr,class Re> struct list_iterator { typedef list_node<T>* Node; typedef list_iterator<T, Ptr, Re> Self; list_iterator(Node _node) :node(_node) { } Self& operator++() { node = node->next; return *this; } Self operator++(int) { Self _node(node); node = node->next; return _node; } Self& operator--() { node = node->prev; return *this; } Self operator--(int) { Self _node(node); node = node->prev; return _node; } Re operator*() { return node->data; } Ptr operator->() { return &node->data; } Re operator*() const { return node->data; } Ptr operator->() const { return &node->data; } bool operator!=(const Self& it) const { return node != it.node; } bool operator==(const Self& it) const { return node == it.node; } Node node; }; template<class T> class list { public: typedef list_node<T>* Node; typedef list_iterator<T, T*, T&> iterator; typedef list_iterator<T, const T*, const T&> const_iterator; void push_back(const T& x); void push_front(const T& x); iterator insert(iterator pos, const T& x); iterator erase(iterator pos); void pop_back(); void pop_front(); void clear(); void swap(list<T>& x); list<T>& operator=(list<T> x); list() :_size(0) { node = (Node)::operator new(sizeof(list_node<T>)); node->next = node; node->prev = node; } list(const list<T>& lt) :_size(0) { node = (Node)::operator new(sizeof(list_node<T>)); node->next = node; node->prev = node; const_iterator it = lt.begin(); while (it != lt.end()) { push_back(*it); ++it; } } ~list() { clear(); ::operator delete(node); } size_t size() const { return _size; } bool empty() const { return size() == 0; } iterator begin() { return iterator(node->next); } iterator end() { return iterator(node); } const_iterator begin() const { return const_iterator(node->next); } const_iterator end() const { return const_iterator(node); } private: Node node; size_t _size; }; template<class T> void list<T>::push_back(const T& x) { insert(end(), x); } template<class T> void list<T>::push_front(const T& x) { insert(begin(), x); } template<class T> typename list<T>::iterator list<T>::insert(iterator pos, const T & x) { Node new_node = (Node)::operator new(sizeof(list_node<T>)); new (&new_node->data) T(x); new_node->next = pos.node; new_node->prev = pos.node->prev; new_node->next->prev = new_node; new_node->prev->next = new_node; ++_size; return new_node; } template<class T> typename list<T>::iterator list<T>::erase(iterator pos) { assert(pos != end()); iterator it = pos.node->next; pos.node->prev->next = pos.node->next; pos.node->next->prev = pos.node->prev; pos.node->data.~T(); ::operator delete(pos.node); --_size; return it; } template<class T> void list<T>::pop_back() { erase(node->prev); } template<class T> void list<T>::pop_front() { erase(node->next); } template<class Container> void Print_Contaiiner(const Container& v) { for (auto x : v) std::cout << x << " "; std::cout << std::endl; } template<class T> void list<T>::clear() { iterator it = begin(); while (it != end()) it = erase(it); } template<class T> void list<T>::swap(list<T>& x) { std::swap(node, x.node); std::swap(_size, x._size); } template<class T> list<T>& list<T>::operator=(list<T> x) { swap(x); return *this; } }

拓展

我们在使用STL的时候,还有这种写法

list<int> lt = {1,2,3,4,5};

这种是C++11才开始有的,如下图

C++11开始就可以把{}括起来的类型认为是initializer_list,然后这个其实就是一个类模板,我们看下他的接口,可以发现,他也是支持迭代器的

然后这个东西的底层其实就是俩个指针,一个指向数据开头,一个指向数据结尾的下一个位置,我们可以用sizeof看下是不是

64位在,俩个指针16字节没问题

那么,既然这个类也支持迭代器,自然支持范围for,我们实现这个就很简单了,如下

list(std::initializer_list<T> il) { init(); for (auto& x : il) push_back(x); }

所以其实,使用这个构造是有三种写法的

qiu::list<int> lt0({ 1,2,32,34,32,4,324,23,43242,423,432 }); qiu::list<int> lt = { 1,2,32,34,32,4,324,23,43242,423,432 }; const qiu::list<int>& lt1 = { 1,2,32,34,32,4,324,23,43242,423,432 };

像第三种就是引用临时对象


5.结语

那么,C++STL部分list的内容就全部讲解完毕啦,希望以上内容对你有所帮助,感谢观看,若觉得写的还可以,可以分享给朋友一起来看哦,毕竟一起进步更有动力嘛,当然能关注一下就更好啦。

http://www.jsqmd.com/news/1272764/

相关文章:

  • DeepSeek LeetCode 3739. 统计主要元素子数组数目 II Java实现
  • Unity URP卡通渲染实战:基于Custom Render Feature实现法线外描边与色阶切分
  • 免费视频转文字工具推荐:先排除三类不合适的再入选 - 软件小管家
  • DM6467硬件设计核心:仿真控制、电源时钟与上拉电阻实战解析
  • 江门精选口碑瓷砖空鼓维修公司推荐2026卫生间墙砖起翘修复 - 北京优选
  • 【WebFlux】第二篇 —— Project Reactor 核心数据类型与doOnXXX介绍
  • MySQL SQL执行全链路解析:从Parser到Executor的完整生命周期
  • python数据可视化技巧的100个练习 -- 48. 分类数据的分面网格图
  • 微信小程序健康管理工具开发实战:PHP+MySQL全开源方案
  • 华为MetaERP 以同样的大卡车生产BOM和价格数据,用 Oracle EBS(R12) 的三种成本方法重新走一遍全链路。Oracle EBS 的成本管理逻辑与 SAP 核心思想相通,但模块名称、事
  • JTAG接口原理与ARM Cortex-M4调试实战:从TAP状态机到CoreSight架构
  • Seraphine:基于LCU API的智能游戏数据交互平台
  • 鸿蒙多功能工具箱开发实战(三十二)-多设备适配与响应式布局
  • 企业AI平台用户活跃度提升策略与实践
  • 7.20-7.26学习笔记
  • 智能写作辅助系统:课程论文写作的AI解决方案
  • 情感计算与VR技术在教育中的创新应用
  • SRIO外设复位与电源管理:从全局复位到逻辑块控制的嵌入式实践
  • AO3镜像站完整指南:如何轻松访问全球最大同人创作平台
  • 体验家 XMPlus 跨系统数据同步与一致性保障机制:CEM 与 CRM/ERP/BI 的双向集成架构
  • 大疆感知融合面试,ICP算法手推这关太硬核了
  • MySQL 分页有什么性能问题?怎么优化?
  • Claude Sonnet 4.5:AI全能智能体的性能与应用解析
  • Golang学习-冒泡排序(Bubble Sort)
  • C++竞赛必考:深度解析“必然事件”逻辑题陷阱与通解
  • Linux网络排查利器:ss命令核心用法与实战场景详解
  • (二十六)BLSG方案
  • AMD Instinct MI455X AI加速器:突破大模型训练内存墙与能效瓶颈
  • C# + 卷积神经网络:工业图像分类任务的底层原理与工程落地
  • 2026实测教程:微信保存的表情包怎么发到抖音?亲测免费方法 - 图片处理研究员