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

【C++闯关笔记】STL:list 的学习和使用

系列文章目录

第零篇:从C到C++入门:C++有而C语言没有的基础知识总结-CSDN博客

第一篇:【C++闯关笔记】封装①:类与对象-CSDN博客

第二篇:【C++闯关笔记】封装②:友元与模板-CSDN博客

第三篇:【C++闯关笔记】STL:string的学习和使用(万字精讲)-CSDN博客

第四篇:【C++闯关笔记】STL:vector的学习与使用-CSDN博客

第五篇:【C++闯关笔记】STL:list 的学习和使用-CSDN博客


文章目录

目录

系列文章目录

文章目录

前言

一、list 是什么?

1.list 引入

2.list 概要介绍

二、熟悉 list 的使用

list 的常用操作

添加元素

访问元素

删除元素

大小、判断空和交换

迭代器的使用

什么是迭代器失效?

三、list 的模拟实现示例

1.list 类成员变量

2.构造函数

3.析构函数

4.模拟实现迭代器

正向迭代器

反向迭代器

模拟实现迭代器相关函数

5.添加元素模拟实现

push_front

 push_back

insert

6.访问元素模拟实现

front

back

7.删除元素模拟实现

erase

pop_front

pop_back

clear

remove

8.大小、判断空和交换模拟实现

三、完整 list 模拟代码展示

总结



前言

为什么要引入list?——“你是否曾因为需要在数组中间插入1000个新数据,而导致程序运行缓慢?”

list 的引入,目的就是解决某些情况下由于vector的局限性:中间插入/删除效率低(O(n)),所导致的效率低下问题。


一、list 是什么?

1.list 引入

什么是 std::list?—— 一个直观的比喻:

将 std::list 比作一列火车或一条珍珠项链

解释:每节车厢(或每颗珍珠)就是一个“节点”,里面装着你的数据。车厢之间通过“挂钩”(指针)连接起来。不像数组那样所有车厢都挤在一条固定的轨道上,火车可以在任何地方轻松地加上或去掉一节车厢,完全不影响其他车厢。

所以std::list 是一个双向链表容器。

如果你学习过数据结构——链表,那么list对你而言就是个“老熟人”了,因为list本质就是一个“封装,添加了新元素的”链表。

2.list 概要介绍

① list 是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代;

② list 的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素;

③ 与其他的序列式容器相比(vector,deque),list 通常在任意位置进行插入、移除元素的执行效率更好。 

二、熟悉 list 的使用

1.包含头文件与命名空间

#include <list>
using namespace std; // 或者在具体代码中使用 std::list

2.声明和初始化

std::list<int> myList;              // 一个空的int链表
std::list<int> myList2(5, 100);     // 包含5个值为100的节点
std::list<int> myList3 = {1, 2, 3}; // C++11 列表初始化

list 的常用操作

以下为list中一些常见的重要接口。

添加元素

①push_back:在尾部添加新数据对应链表的append操作。

②push_front:在头部添加新数据。

③insert( itertor pos, value ):在迭代器指向的位置之前插入新元素。这是与链表最大的不同,也是需要重点理解的地方!

std::list<int>myList;
myList.push_back(1);
myList.push_front(2);
mylist.insert(myList.begin(),3);

访问元素

①front:访问第一个元素。

②back:访问最后一个元素。

注意:相较于vector,list没有[]运算符! 因为它不是连续存储,随机访问效率极低(O(n)复杂度)。

std::list<int>myList;
myList.push_back(1);
myList.push_front(2);int first = myList.front();
int end = myList.back();

删除元素

①pop_back:删除尾部元素。

②pop_front:删除头部元素。

③erase:删除迭代器指向的元素。

④remove:删除所有值等于value的元素(这是一个很方便的成员函数)。

⑤clear:清空所有元素。

std::list<int>myList(5,5);
myList.pop_back();
myList.pop_front(2);
mylist.erase(myList.begin());
myList.remove(5);
myList.clear();

大小、判断空和交换

①size:返回 list 中元素个数。

②empty:检测 list 是否为空,是返回true,不是返回false。

③swap:交换两个 list 中的元素。

std::list<int>myList;
myList.push_back(1);
myList.push_front(2);size_t size = myList.size();
bool jug = myList.empty();std::list<int>myList2={1,2,3};
myList.swap(myList2);

迭代器的使用

可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。掌握迭代器的用法,理解它为何是STL容器通用的“指针”。

①begin+end:返回第一个元素的迭代器iterator + 返回最后一个元素下一个位置的迭代器iterator。

②rbegin+rend:返回第一个元素的reverse_iterator,即end位置 + 返回最后一个元素下一个位置的 reverse_iterator,即begin位置。

rbegin是begin的相反,rend是end的相反。

注意:list 迭代器是一个双向迭代器,支持++--操作,但不支持 +n(随机访问)。

迭代器的一个重要用途就是遍历:

std::list<int>myList;
myList.push_back(1);
myList.push_front(2);
mylist.insert(myList.begin(),3);//正向遍历
list<int>::iterator  it = myList.begin();
while(it!=myList.end())
{std::cout<<*it<<' ';it++;
}//逆向遍历
list<int>::reverse_iterator rit =myList.rbegin();
while(rit!=myList.rend())
{std::cout<<*rit<<' ';rit++;
}

什么是迭代器失效?

  • 场景: 当你对容器进行插入(insert) 或删除(erase) 操作后,指向容器元素的迭代器可能会变得“无效”(即不能再使用它)。

  • 对于std::list

    • 插入操作: 所有迭代器不会失效。因为你只是在两个现有节点之间新增了一个节点,不影响其他节点的地址。

    • 删除操作: 只有指向被删除节点的那个迭代器会失效,其他迭代器仍然有效。

错误使用示范

std::list<int> myList = {1, 2, 3, 4, 5};
auto it = myList.begin();
++it; // 现在 it 指向 2
myList.erase(it); // 删除 it 指向的元素(2)
std::cout << *it; // 错误!it 已经失效,行为未定义(程序可能崩溃)!

正确使用示范

std::list<int> myList = {1, 2, 3, 4, 5};
auto it = myList.begin();
++it; // 指向 2
it = myList.erase(it); // 删除2,it现在指向3,是安全的
std::cout << *it; // 输出 3

erase函数会返回一个指向被删除元素的下一个元素的迭代器,应该用它来更新你的迭代器。(insert函数同理)

三、list 的模拟实现示例

在知道如何使用 list 后,可以考虑自己模拟实现 list ,这有两个好处:

一是可以熟练 list 各种接口的使用;二是可以深度理解 list 的特性,在之后使用 list 时扬长避短,提高程序运行效率。

1.list 类成员变量

正如前文所说,list 是封装过后的链表(带头双向循环链表) ,结合链表的特性,我们可以推测 list 类大致为:

class list
{private:node*_head;size_t _size;
};

其中的节点node,本身就是一个封装过后的结构体或者类,所以推测完整的 list 类应如:

template<typename T>
typedef struct ListNode
{T _val;struct ListNode* _next;struct ListNode* _prev;Init_List(T& x):_val(x) _next(nullptr), _prev(nullptr){}}node;template<typename T>
class list
{private:node*_head;size_t _size;
};

2.构造函数

在数据结构链表中,头节点一般是不存储数据的,list 同样如此。

①默认构造函数

void Init_List()
{_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;
}list()
{Init_List();
}

②重载构造函数

初始化形如:std::list<T> 对象名(n, x);

list(const size_t n, T x)
{Init_List(); node*cur = _head;   for(size_t i = 0;i < n;++i){T*newNode=new node(T(x));cur->_next = newNode;newNode->_prev = cur;cur = newNode;}cur->_next = _head;_head->_prev = cur;_size = n;
}

通过传入另一个 list 对象的迭代器构造:

//通过模板,实现两种迭代器构造
template<typename Iterator>
list(const Iterator begin, const Iterator end)
{Iterator first = begin;while(first!=end){Init_List();push_back(*first);first++;_size++;}}

这其中逻辑与push_back如出一辙,所以直接使用了push_back,push_back的实现在下文。

④拷贝构造函数

list(const list<T>& l)
{Init_List(); list<T> temp(l.begin(),l.end());swap(temp);
}

解释:

在函数中临时创建一个对象,用赋值拷贝将目标对象的值赋值给这个临时对象,然后将该临时对象与已经初始化的本对象swap。当该函数运行完毕后,临时对象被析构,目标对象的值则被交换至本对象。

swap的实现在下文。

3.析构函数

在list ,析构函数的主要作用是释放空间。

~list()
{if (_head){node* cur = _head->_next;while (cur != _head){node* next = cur->_next;delete cur;cur = next;}delete _head;_head = cur = nullptr;_size = 0;}free(_head);_head=nullptr;
}

或者

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

clear的实现在下文。

4.模拟实现迭代器

迭代器最根本的设计理念是:它要表现得像一个指针。指针最核心的两个操作是什么?

①解引用:通过* ptr来获取它指向的对象。

②成员访问:通过ptr->member来访问所指向对象的成员。

所以,模拟实现迭代器实际上就是模拟实现上面两个功能。

正向迭代器

	template<typename T,typename Ref,typename Ptr>struct _list_iterator{typedef struct listNode<T> node;typedef struct _list_iterator<T, Ref , Ptr> Self;node* _pnode;_list_iterator(node* pnode):_pnode(pnode){}Ref operator*(){return _pnode->_data;}Ptr operator ->(){return &_pnode->_data;}//没有参数的是前置运算符,有int参数的是后置运算符。Self& operator++(){_pnode = _pnode->_next;return *this;}Self& operator--(){_pnode = _pnode->_prev;return *this;}Self operator++(T){Self temp(*this);_pnode = _pnode->_next;return temp;}Self operator--(T){Self temp(*this);_pnode = _pnode->_prev;return temp;}bool operator!=(const Self& n){return _pnode != n._pnode;}bool operator==(const Self& n){return _pnode == n._pnode;}};

解释:template<typename T,typename Ref,typename Ptr>

①T:即链表节点值_val的类型;Ref:实际传入的是T&,用于重载 ‘*’ ;Ptr:实际传入的是T *,用于重载 ‘->’;

②通过使用模板,以达到一套代码同时实现普通iterator迭代器和const iterator迭代器的目的;

反向迭代器

反向迭代器的设计遵循一个重要的对称原则:反向迭代器的位置与对应的正向迭代器位置是错位的。

所以我们可以利用已经设计好的正向迭代器:

	template<typename _list_iterator>struct Reverse_Iterator{_list_iterator _it;typedef typename _list_iterator::Ref Ref;typedef typename _list_iterator::T T;typedef typename _list_iterator::Ptr Ptr;typedef Reverse_Iterator<_list_iterator> Self;Reverse_Iterator(_list_iterator it):_it(it){}Ref operator*(){_list_iterator temp(_it);--temp;return *temp;}Ptr operator ->(){_list_iterator temp(_it);--temp;//return temp;是错误的,要的是temp存储的变量的地址//_list_iterator的变量成员是个node*指针,这里temp相当于二级指针。return &(*temp);}//没有参数的是前置运算符,有参数的是后置运算符。Self& operator++(){--_it;return *this;}Self& operator--(){++_it;return *this;}Self operator++(T){Self temp(*this);--_it;return temp;}Self operator--(T){Self temp(*this);++_it;return temp;}bool operator !=(const Self& s){return _it != s._it;}bool operator ==(const Self& s){return _it == s._it;}};

解释:为什么在 ‘*’ 与 ‘->’ 中需要--temp?

让我们通过一个例子来理解:

假设有一个包含元素的链表:[A, B, C, D]

  • 正向迭代器:

    • begin() 指向 A

    • end() 指向 D 之后的位置

  • 反向迭代器:

    • rbegin() 内部存储的迭代器指向 end()(即 D 之后)

    • 但对 rbegin() 解引用时,我们希望得到 D

    • rend() 内部存储的迭代器指向 begin()(即 A

    • 但对 rend() 解引用时,我们希望得到 A 之前的位置(实际上不应该解引用 rend()

所以,当反向迭代器内部存储的迭代器 _it 指向某个位置时,解引用操作应该返回的是 _it 前一个位置的元素。

将封装好的迭代器加入到上述 list 类中,如下:

	template<typename T>class list{typedef listNode<T> node;typedef _list_iterator<T, T&,T*> iterator;typedef _list_iterator<T, const T& ,const T*> const_iterator;typedef Reverse_Iterator<iterator> reverse_iterator;typedef Reverse_Iterator<const_iterator> const_reverse_iterator;private:node*_head;size_t _size;};

模拟实现迭代器相关函数

包括begin、end,rbegin、rend,以及它们的const类型重载。

		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);}reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rbegin()const{return reverse_iterator(end());}const_reverse_iterator rend()const{return reverse_iterator(begin());}

5.添加元素模拟实现

push_front

在 list 链表头头节点后插入新元素。

push_front可以用与push_back同理的方式实现。

但实际上push_front与push_back有更简单的方式实现:

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

 push_back

在 list 链表末尾插入新元素。

		void push_back(const T& x){node* end = _head->_prev;node* newNode = new node(T(x));end->_next = newNode;newNode->_prev = end;newNode->_next = _head;_head->_prev = newNode;_size++;}

insert

在传入迭代器之后插入一个新元素,并返回新的迭代器。

		iterator insert(iterator w, const T& x){node* newNode = new node(T(x));node* cur = w._pnode;node* prev = cur->_prev;prev->_next = newNode;newNode->_prev = prev;newNode->_next = cur;cur->_prev = newNode;_size++;return iterator(newNode);}

6.访问元素模拟实现

front

返回 list 首元素的值;

		T& front(){if (!empty())return (_head->_next)->_data;}

back

返回 list 末尾元素的值;

		T& back(){if (!empty())return (_head->_prev)->_data;}

7.删除元素模拟实现

erase

删除迭代器指向的元素,并返回变化后的迭代器。

		iterator erase(iterator pos){node* cur = pos._pnode;node* prev = cur->_prev;node* next = cur->_next;delete cur;prev->_next = next;next->_prev = prev;_size--;return iterator(next);}

pop_front

删除链表非头节点的第一位元素。

与push_front/back可以利用insert同理,pop_front/back依旧可以利用erase。

		void pop_front(){/*erase(begin());*/if (!empty()){node* first = _head->_next;node* newFirst = first->_next;delete first;_head->_next = newFirst;newFirst->_prev = _head;}}

pop_back

删除链表的最后一位元素。

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

clear

清空链表。这里使用了迭代器遍历

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

remove

批量删除链表中,节点值与传入参数相同的节点。

		void remove(const T& x){iterator it = begin();while (it != end()){if(*it == x)it = erase(it);}}

8.大小、判断空和交换模拟实现

包括,size 返回链表有效数据个数,empty 判断链表是否为空,swap 交换两个链表中的数据。

size_t size(){return _size;}bool empty(){return _size == 0;}void swap(list<T>& x){std::swap(x._head, _head);std::swap(x._size, _size);}

swap函数不必挨个交换两个链表中节点的数据,只需要将头节点与_size交换即可。

三、完整 list 模拟代码展示

将上面的各个函数和数据结构的代码合起来,就组成了一个简易的 list 类。

为了防止模拟实现的 list 代码与std::list发生冲突,下面将代码放在一个命名空间里:

#pragma once
#include<iostream>using std::cout;
using std::endl;
namespace karsen
{template<typename T>struct listNode{T _data;listNode* _next;listNode* _prev;listNode(const T& x):_data(x), _next(nullptr), _prev(nullptr){}};template<typename T,typename Ref,typename Ptr>struct _list_iterator{typedef struct listNode<T> node;typedef struct _list_iterator<T, Ref , Ptr> Self;//下面三个声明用于反向迭代器中typedef T T;typedef Ref  Ref;typedef Ptr Ptr;node* _pnode;_list_iterator(node* pnode):_pnode(pnode){}Ref operator*(){return _pnode->_data;}Ptr operator ->(){return &_pnode->_data;}//没有参数的是前置运算符,有int参数的是后置运算符。Self& operator++(){_pnode = _pnode->_next;return *this;}Self& operator--(){_pnode = _pnode->_prev;return *this;}Self operator++(T){Self temp(*this);_pnode = _pnode->_next;return temp;}Self operator--(T){Self temp(*this);_pnode = _pnode->_prev;return temp;}//参数类型错误:迭代器的比较运算符 != 和 ==,//其参数应该是另一个迭代器 (const Self&),而不是一个节点 (const node&)。bool operator!=(const Self& n){return _pnode != n._pnode;}bool operator==(const Self& n){return _pnode == n._pnode;}};//反向迭代器的设计遵循一个重要的对称原则:反向迭代器的位置与对应的正向迭代器位置是错位的。template<typename _list_iterator>struct Reverse_Iterator{_list_iterator _it;typedef typename _list_iterator::Ref Ref;typedef typename _list_iterator::T T;typedef typename _list_iterator::Ptr Ptr;typedef Reverse_Iterator<_list_iterator> Self;Reverse_Iterator(_list_iterator it):_it(it){}Ref operator*(){_list_iterator temp(_it);--temp;return *temp;}Ptr operator ->(){_list_iterator temp(_it);--temp;//return temp;是错误的,要的是temp存储的变量的地址//_list_iterator的变量成员是个node*指针,这里temp相当于二级指针。return &(*temp);}//没有参数的是前置运算符,有参数的是后置运算符。Self& operator++(){--_it;return *this;}Self& operator--(){++_it;return *this;}Self operator++(T){Self temp(*this);--_it;return temp;}Self operator--(T){Self temp(*this);++_it;return temp;}bool operator !=(const Self& s){return _it != s._it;}bool operator ==(const Self& s){return _it == s._it;}};template<typename T>class list{typedef listNode<T> node;public:typedef _list_iterator<T, T&, T*> iterator;typedef _list_iterator<T, const T&, const T*> const_iterator;typedef Reverse_Iterator<iterator> reverse_iterator;typedef Reverse_Iterator<const_iterator> const_reverse_iterator;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);}reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rbegin()const{return reverse_iterator(end());}const_reverse_iterator rend()const{return reverse_iterator(begin());}void empty_InitList(){_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_InitList();}list(int n, const T& x){if (n < 0)return;empty_InitList();node* cur = _head;for (int i = 0; i < n; i++){node* temp = new node(T(x));temp->_prev = cur;cur->_next = temp;cur = cur->_next;}cur->_next = _head;_head->_prev = cur;_size = (size_t)n;}//用以区分是否是const迭代器template<typename InputIterator>list(InputIterator first, InputIterator last){empty_InitList();while (first != last){push_back(*first);first++;_size++;}}list(const list<T>& l){empty_InitList();list<T>temp(l.begin(), l.end());swap(temp);}list<T>& operator=(list<T> other){swap(other);return *this;}~list(){/*if (_head){node* cur = _head->_next;while (cur != _head){node* next = cur->_next;delete cur;cur = next;}delete _head;_head = cur = nullptr;_size = 0;}*/clear();delete _head;_head = nullptr;}void swap(list<T>& x){std::swap(x._head, _head);std::swap(x._size, _size);}size_t size(){return _size;}bool empty(){return _size == 0;}T& front(){if (!empty())return (_head->_next)->_data;}T& back(){if (!empty())return (_head->_prev)->_data;}iterator insert(iterator w, const T& x){node* newNode = new node(T(x));node* cur = w._pnode;node* prev = cur->_prev;prev->_next = newNode;newNode->_prev = prev;newNode->_next = cur;cur->_prev = newNode;_size++;return iterator(newNode);}void push_front(const T& x){insert(begin(), x);}void push_back(const T& x){//node* end = _head->_prev;//node* newNode = new node(T(x));//end->_next = newNode;//newNode->_prev = end;//newNode->_next = _head;//_head->_prev = newNode;//_size++;insert(end(), x);}iterator erase(iterator pos){node* cur = pos._pnode;node* prev = cur->_prev;node* next = cur->_next;delete cur;prev->_next = next;next->_prev = prev;_size--;return iterator(next);}void pop_front(){/*erase(begin());*/if (!empty()){node* first = _head->_next;node* newFirst = first->_next;delete first;_head->_next = newFirst;newFirst->_prev = _head;}}void pop_back(){erase(end());}void clear(){iterator it = begin();while (it != end()){it=erase(it);}}void remove(const T& x){iterator it = begin();while (it != end()){if(*it == x)it = erase(it);}}private:node* _head;size_t _size;};
}

以上就是 list 有关介绍的全部内容了。


总结

本文先是详细介绍了什么是 list ,紧接着是如何使用 list ,最后尝试模拟实现list 。

本文是【C++闯关笔记】系列之一,若对本系列感兴趣,欢迎关注博主。一起学习,共同进步。

整理不易,希望对你有所帮助。

读完点赞,手留余香~


文章转载自:

http://vxYG5VNJ.rzczL.cn
http://MWkvBakN.rzczL.cn
http://tfvovYXe.rzczL.cn
http://Xaq2ZLeV.rzczL.cn
http://wvNphq4y.rzczL.cn
http://2Nf2C0zi.rzczL.cn
http://PsFOrXyq.rzczL.cn
http://6bHiy7o6.rzczL.cn
http://WGqRm9Je.rzczL.cn
http://D6omPL1R.rzczL.cn
http://IQ7T2D26.rzczL.cn
http://gNETpGXL.rzczL.cn
http://YLBqgziA.rzczL.cn
http://J0gflHgC.rzczL.cn
http://1akYcS4q.rzczL.cn
http://rmU0kPHR.rzczL.cn
http://cIukSTI0.rzczL.cn
http://mrwNEYll.rzczL.cn
http://4lfn9jEX.rzczL.cn
http://toHES5jD.rzczL.cn
http://3RmQDmJD.rzczL.cn
http://2ns1C0Q8.rzczL.cn
http://II1G7JCH.rzczL.cn
http://6hG67dtD.rzczL.cn
http://k5cW8Fwt.rzczL.cn
http://2X3u811p.rzczL.cn
http://R3TQfMu0.rzczL.cn
http://1LtJZBjs.rzczL.cn
http://BKrfDfip.rzczL.cn
http://KbatqawQ.rzczL.cn
http://www.dtcms.com/a/366192.html

相关文章:

  • 解密大语言模型推理:Prompt Processing 的内存管理与计算优化
  • Redis vs Memcached vs MongoDB:深入对比与选型指南
  • C# 修改基类List中某一元素的子类类型
  • 如何在 iPhone 或 iPad 上删除文件
  • MongoDB 高可用部署:Replica Set 搭建与故障转移测试
  • MacOS微信双开,亲测有效
  • MySQL事务的四大特性(ACID)
  • 数说故事 | 2025年运动相机数据报告,深挖主流品牌运营策略及行业趋势​
  • K8S容器POD内存快照导出分析处理方案
  • 【面试题】Prompt是如何生成的,优化目标是什么,任务是什么?
  • Elasticsearch 备份和恢复
  • 软考中级习题与解答——第二章_程序语言与语言处理程序(2)
  • RTC实时时钟RX8025SA国产替代FRTC8025S
  • git基础命令 git基础操作
  • 2025市面上比较实用的财会行业证书,最值得考的8个职业证书推荐
  • 开源与定制化直播电商系统源码对比:如何选择开发方案?
  • Spring 事务提交成功后执行额外逻辑
  • Attention-Based Map Encoding for Learning Generalized Legged Locomotion
  • MMD动画(二)动作制作
  • Hoppscotch:开源轻量API测试工具,秒启动高效解决临时接口测试需求
  • 【机器学习】HanLP+Weka+Java算法模型
  • 算法随笔(一)
  • Electron 执行python脚本
  • Dubbo(分布式RPC调用和分布式文件储存)
  • 如何简单理解状态机、流程图和时序图
  • 成为一个年薪30W+的FPGA工程师是一种什么体验?
  • 进程与线程详解, IPC通信与RPC通信对比,Linux前台与后台作业
  • 在国企干了 5 年 Java,居然不知道 RPC?这正常吗?
  • VU9P板卡设计方案:基于VU9P的32@ SFP28+4@ QSFP28路光纤交换板卡
  • Zynq开发实践(FPGA之uart发送)