【C++】list类的模拟实现
🏖️作者:@malloc不出对象
⛺专栏:C++的学习之路
👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈
目录
- 前言
- 一、list类的模拟实现
- 1.1 list的主体框架
- 1.2 无参构造函数
- 1.3 push_back
- 1.4 正向迭代器
- 1.5 反向迭代器
- 1.6 insert
- 1.7 erase
- 1.8 clear
- 1.9 析构函数
- 1.10 构造函数
- 1.11 赋值运算符重载
- 1.12 empty
- 1.13 front && back
- 1.14 完整代码
- 二、vector与list的对比
前言
本篇文章我们要来模拟实现的是list类,它的底层是用带头结点的双向循环链表实现的。
一、list类的模拟实现
1.1 list的主体框架
既然我们是用双向循环链表实现的,那么每个结点肯定都存储着next、prev与data信息,那么接下来我们就来定义一个类对它的结点进行初始化操作。
template<class T> // 模板参数T
struct list_node
{list_node<T>* _next; //list_node<T>* 是类型list_node<T>* _prev;T _data;list_node(const T& val = T()) // 匿名对象初始化: _next(nullptr), _prev(nullptr), _data(val){}
};
我们把节点定义好之后,我们就来定义list类了,list类的成员变量只需要一个哨兵位的头结点就可以了。
template <class T>
class list
{typedef list_node<T> node;
private:node* _head; // 哨兵位头节点
};
1.2 无参构造函数
list()
{_head = new node; // 申请一个节点_head->_next = _head; // _head->_next指向自己_head->_prev = _head; // _head->_prev也指向自己
}
1.3 push_back
双向链表的插入和删除都是非常好实现的,因为每个结点都有上一个节点和下一个节点的信息。这里我们要想实现尾插,我们要找到尾结点再改变它的指向就行了,非常的简单这里我就不做过多的赘述了。另外后续在我们实现insert和erase之后全都可以进行复用,这里只是先给大家打个样。
void push_back(const T& x)
{node* tail = _head->_prev;node* newnode = new node(x);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
}
1.4 正向迭代器
有了尾插之后我们可以往链表里面插入数据,下面我们想遍历一下链表,我们知道list不支持[]下标访问,原因是因为它是不连续的空间,所以我们必须使用迭代器对它进行遍历。
我们知道在实现vector类(SGI版本)时,我们的迭代器是作为一个原生指针来使用的,而在vector类(P.J.版本)中我们的迭代器是自定义类型对原生指针的封装,但本质上它们都是在模拟指针的行为!!!那么在list类中迭代器到底充当什么角色呢?我们知道迭代器支持++ - -操作这是为了找到后一个数据和前一个数据的位置,对于list而言它是双向链表它的空间是不连续的,假设迭代器是一个原生指针的话,指针++ - -一步取决于指针所指向的类型,对于不连续的空间来说++ - -能否刚好指向下一个位置或者上一个位置一切都是未知数,因此我们的迭代器在list中是对自定义类型原生指针的封装!!!
我们先来看看SGI版本下对正向迭代器的封装源码:
好了,也许我们有些地方可能有些不太懂,而且标准库的源码采用了非常多的命名替换,这是命名规范的问题,接下来我们模拟实现的时候不采用标准库这种方式,我们尽量的实现简洁易懂些。
最原始的代码
template<class T>
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T> self;node* _node;__list_iterator(node* x) // 初始化结点: _node(x){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){__list_iterator tmp = *this;_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){__list_iterator tmp = *this;_node = _node->_prev;return tmp;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}
};
这是我们最原始的代码,那么大家知道为什么源码为什么会多出两个模板参数吗?这里我们实现的是正向迭代器,那么我们要实现const正向迭代器版本呢?难道要再去写一个__list_const_iterator类吗?
显然这样出现了大量的代码重复,我们是极其不支持这种实现方式的,所以我们必须想办法让他们之间可以进行复用,我们只需要改变一下返回值类型、参数类型就能实现iterator和const_iterator版本,这里模板参数的作用就体现出来了,我们可以添加一个模板参数,到时候我们可以实例化一份iterator和const_iterator。至于第三个模板参数是为了重载
->
运算符函数的,它同样的有T*
版本和const T*
版本。
为什么要重载->运算符?
struct AA
{int _a1;int _a2;AA(int a1, int a2): _a1(a1), _a2(a2){}
};void test()
{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; ++it;}cout << endl;
}
我们可以看到上诉代码对于一个自定义类型要想访问它的成员变量就必须得写成(*it)._a1、(*it)._a2,先*it得到AA对象,再访问它的成员变量,这种写法是不是未免有些麻烦了?我们平常可以直接使用->去访问它的成员变量,就像这段代码我们可以写成it->_a1、it->_a2,但是我们此时未重载->运算符,所以为了方便使用这里我们还需要重载一下->运算符。
对于__list_iterator类我们可以重载->写出下面的代码:
T* operator->()
{return &_node->_data;
}
但你有没有发现一些奇怪之处??
好了,关于为什么要重载->运算符这里我们已经讲清楚了,那么为什么这跟添加第三个模板参数有什么关系呢?原因很简单,一个T*版,一个const T*版,添加第三个模板参数Ptr也是为了复用T*版本。
所以最终我们的__list_iterator可以写成这种版本:
template<class T, class Ref, class Ptr>
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> self;node* _node;__list_iterator(node* x) // 初始化结点: _node(x){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const self& s){return _node == s._node;}bool operator!=(const self& s){return _node != s._node;}};
我们在list类中就可以实例化iterator和const_iterator这两种版本的迭代器,list类中迭代器的定义如下:
typedef list_node<T> node;
public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_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);}
我们来进行测试一下:
我们可以看到对应的过程,当list<int>显式声明模板类时,此时我们的类模板就根据类型实例化出一个具体的类。
1.5 反向迭代器
我们知道C++追求极致的性能,既然能复用绝不会写出两份差不多的代码,,所以我们实现反向迭代器并不会像正向迭代器那样倒着来,而是去复用正向迭代器!!!
反向迭代器其实也是一种适配器,它可以适配出各种容器的反向迭代器,其中最重要的就是将正向迭代器作为底层结构来封装反向迭代器,反向迭代器 ++ 就复用正向迭代器的 - -,反向迭代器 - - 就复用正向迭代器的 ++。
我们的反向迭代器既然是作为适配器去使用,那么我们就把它封装到单独的一个类中对它进行模拟实现,并且正向迭代作为它的模板参数进行复用它的功能!!
反向迭代器的模拟实现
// iterator.h
namespace curry
{template<class Iterator, class Ref, class Ptr>struct ReverseIterator{typedef ReverseIterator<Iterator, Ref, Ptr> Self;Iterator _cur; // _cur就是一个正向迭代器ReverseIterator(Iterator it) : _cur(it){}Ref operator*(){Iterator tmp = _cur;--tmp;return *tmp;}Self& operator++(){--_cur;return *this;}Self operator++(int){Self tmp = *this;--_cur;return tmp;}Self& operator--(){++_cur;return *this;}Self operator--(int){Self tmp = *this;++_cur;return tmp;}// 返回当前对象的地址Ptr operator->(){return &(operator*());}bool operator!=(const Self& s){return _cur != s._cur;}bool operator==(const Self& s){return _cur == s._cur;}};
}
只要知道了反向迭代器与正向迭代器的特性,我们就能够很容易的通过复用正向迭代器的成员函数来实现反向迭代器的成员函数!!同时反向迭代器其实解决了所有的双向迭代器的问题,因为只要将对应容器的正向迭代器作为反向迭代器的模板参数我们就能够对反向迭代器进行复用,所以我们之前的vector类的反向迭代器也能够直接使用它的正向迭代器复用实现!!这是一种非常巧妙的思想!!
1.6 insert
void 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;
}
实现了insert接口函数,那么我们的push_back与push_front都是可以复用的。
push_back(int x)
void push_back(const T& x)
{insert(end(), x);
}
push_front(int x)
void push_back(const T& x)
{insert(begin(), x);
}
list类与vector类的insert不同之处在于list类insert不会导致迭代器失效,因为它的空间的不连续的,并且没有挪动数据造成迭代器失效,所以我们也可以看到它的返回值为void,并不需要放回插入位置的迭代器。
1.7 erase
iterator erase(iterator pos)
{assert(pos != end());node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);
}
迭代器失效即迭代器所指向的节点的无效,即该节点被删除了,所以对于list类的erase会导致指向删除节点的迭代器失效,其他迭代器不会受到影响而vector类进行erase会导致当前位置或者后续迭代器失效,所以正确的解决办法是给迭代器重新赋值!!
实现了erase函数接口,pop_back()以及pop_front()就可以进行复用了。
pop_back()
void pop_back()
{erase(--end());
}
pop_front()
void pop_front()
{erase(begin());
}
1.8 clear
void clear()
{iterator it = begin();while (it != end()){it = erase(it); // erase返回下一个位置的迭代器}
}
clear释放链表中的结点,_head哨兵位头结点除外。
1.9 析构函数
~list()
{clear();delete _head;_head = nullptr;
}
析构函数的作用是释放所有结点,我们可以先调用clear依次释放链表中的结点,最后再释放头结点。
1.10 构造函数
传统写法
void empty_init()
{// 创建并初始化哨兵位头节点_head = new node;_head->_prev = _head;_head->_next = _head;
}// 拷贝构造传统写法 lt2(lt1)
list(const list<T>& lt)
{empty_init();for (auto& e : lt) // 加引用避免自定义类型的拷贝构造{push_back(e);}
}
现代写法
template <class Iterator> // 双向迭代器类型构造
list(Iterator first, Iterator last)
{empty_init();while (first != last){push_back(*first);++first;}
}void swap(list<T>& tmp)
{std::swap(_head, tmp._head); // 交换哨兵位的头节点
}// 拷贝构造现代写法 lt2(lt1)
list(const list<T>& lt)
{empty_init(); list<T> tmp(lt.begin(), lt.end()); // 迭代器区间初始化swap(tmp);
}
1.11 赋值运算符重载
传统写法
list<T>& operator=(const list<T>& lt)
{if (this != <) // 防止自己给自己赋值{clear(); // 清理数据for (auto& e : lt){push_back(e);}}return *this;
}
现代写法
list<T>& operator=(list<T> lt)
{swap(lt);return *this;
}
一些常用的函数接口就讲到这里了,还有一些简单的函数接口读者下来也可以自己去尝试实现一下。
1.12 empty
bool empty()
{return _head->_next == _head &&_head->_prev == _head;
}
1.13 front && back
T& front()
{assert(!empty());return *begin();
}const T& front() const
{assert(!empty());return *begin();
}T& back()
{assert(!empty());return *(--end());
}const T& back() const
{assert(!empty());return *(--end());
}
1.14 完整代码
// list.h#include "iterator.h"namespace curry
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _data;list_node(const T& val = T()): _next(nullptr), _prev(nullptr), _data(val){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> self;node* _node;__list_iterator(node* x) // 初始化结点: _node(x){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const self& s){return _node == s._node;}bool operator!=(const self& s){return _node != s._node;}};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;typedef ReverseIterator<iterator, T&, T*> reverse_iterator;typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;iterator begin(){return iterator(_head->_next);}reverse_iterator rbegin(){return reverse_iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_reverse_iterator rbegin() const{return const_reverse_iterator(_head);}iterator end(){return iterator(_head);}reverse_iterator rend(){return reverse_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}const_reverse_iterator rend() const{return const_reverse_iterator(_head->_next);}list(){empty_init();}// 现代写法list(const list<T>& lt){empty_init();list<T> tmp(lt.begin(), lt.end());swap(tmp);}template<class Iterator>list(Iterator first, Iterator last){empty_init();while (first != last){push_back(*first);++first;}}list<T>& operator=(list<T> lt){swap(lt);return *this;}// 释放所有结点~list(){clear();delete _head;_head = nullptr;}void swap(list<T>& tmp){std::swap(_head, tmp._head);}// 释放结点,但是_head头结点不处理void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void empty_init(){_head = new node;_head->_next = _head;_head->_prev = _head;}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void 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;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator erase(iterator pos){assert(pos != end());node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);}T& front(){assert(!empty());return *begin();}const T& front() const{assert(!empty());return *begin();}T& back(){assert(!empty());return *(--end());}const T& back() const{assert(!empty());return *(--end());}bool empty(){return _head->_next == _head &&_head->_prev == _head;}private:node* _head;};
}
二、vector与list的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:
vector | list | |
---|---|---|
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
以上就是本文的所有内容了,如有错处或者疑问欢迎大家在评论区相互交流orz~🙈🙈
相关文章:

【C++】list类的模拟实现
🏖️作者:malloc不出对象 ⛺专栏:C的学习之路 👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈 目录 前言一、list类的模拟实现1.1 list的…...

机械臂+2d相机实现复合机器人定位抓取
硬件参数 机械臂:艾利特 相机:海康相机 2d识别库:lindmod,github可以搜到 光源:磐鑫光源 软件参数 系统:windows / Linux 开发平台:Qt 开发语言:C 开发视觉库:OpenCV …...

网络编程 http 相关基础概念
文章目录 表单是什么http请求是什么http请求的结构和说明关于http方法 GET和POST区别http常见状态码http响应http 请求是无状态的含义html是什么 (前端内容,了解即可)html 常见标签 (前端内容,了解即可)关于…...

LatexEasy公式渲染教程
LatexEasy使用简单的URL渲染公式为图片 https://r.latexeasy.com/image.svg?1-sin^2(x) 使用单个HTML图像标签将公式添加到任何现有网站 <img src"https://r.latexeasy.com/image.svg?1-sin^2(x)" />...

十年测试工程师叙述自动化测试学习思路
自动化测试介绍 自动化测试(Automated Testing),是指把以人为驱动的测试行为转化为机器执行的过程。实际上自动化测试往往通过一些测试工具或框架,编写自动化测试用例,来模拟手工测试过程。比如说,在项目迭代过程中,持…...

SpringAOP详解(下)
proxyFactory代理对象创建方式和代理对象调用方法过程: springaop创建动态代理对象和代理对象调用方法过程: 一、TargetSource的使用 Lazy注解,当加在属性上时,会产生一个代理对象赋值给这个属性,产生代理对象的代码为…...

主流软件漏洞跟踪 Apache RocketMQ NameServer 远程代码执行漏洞(CVE-2023-37582)
主流软件漏洞跟踪 Apache RocketMQ NameServer 远程代码执行漏洞(CVE-2023-37582) 漏洞描述影响版本安全版本如何修复可供参考的资料主流软件漏洞跟踪 Apache RocketMQ NameServer 远程代码执行漏洞(CVE-2023-37582) CVE编号 : CVE-2023-37582 利用情况 : EXP 已公开 …...

Element table根据字段合并表格(可多字段合并),附带拖拽列动态合并
效果如图,姓名 数值1 字段进行自动合并 封装合并列js - tableMerge.js // 获取列合并的行数 // params // tableData: 表格数据 // mergeId: 合并的列的字段名 export const tagRowSpan (tableData, mergeId) >{const tagArr [];let pos 0;tableData.map((i…...

C++标准库STL容器详解
目录 C标准模板库STL容器容器分类容器通用接口 顺序容器vectorlistdeque 容器适配器queuestackpriority_queue 关联容器:红黑树setmultisetmapmultimap 关联容器:哈希表unordered_set和unordered_multisetunordered_map和unordered_multimap 附1…...

ParNew垃圾收集器(Serial+多线程)是干什么用的?
在Java中,ParNew垃圾收集器是一种垃圾收集算法,它是Serial垃圾收集器的多线程版本。它主要用于新生代(Young Generation)的垃圾收集。新生代是Java堆内存的一部分,主要用于存放新创建的对象。 ParNew垃圾收集器的设计目标是在多核CPU上并行地…...

【Android】AES解密抛出异常Cipher functions:OPENSSL_internal:WRONG_FINAL_BLOCK_LENGTH
Java使用AES加密的时候没得问题,但是在解密的时候就出错了,一起来找找原因吧。 首先,Java运行的代码如下,使用AES加解密 Cipher cipher Cipher.getInstance("AES/CBC/NOPadding"); //...主要问题 可调试运行控制台抛…...

菜鸟教程《Python 3 教程》笔记(2):数据类型转换
菜鸟教程《Python 3 教程》笔记(2) 2 数据类型转换2.1 隐式类型转换2.2 显式类型转换2.2.1 int() 函数2.2.2 repr() 函数2.2.3 frozenset ()函数 2 数据类型转换 出处:菜鸟教程 - Python3 数据类型转换 Python 数据类型转换可以分为2种&…...

JVM运行时参数查看
常用命令查找文档站点:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/index.html -XX:PrintFlagsInitial 输出所有参数的名称和默认值,默认不包括Diagnostic和Experimental的参数。可以配合 -XX:UnlockDiagnosticVMOptions和-XX:UnlockEx…...

每日一题:leetcode 1267 统计参与通信的服务器
这里有一幅服务器分布图,服务器的位置标识在 m * n 的整数矩阵网格 grid 中,1 表示单元格上有服务器,0 表示没有。 如果两台服务器位于同一行或者同一列,我们就认为它们之间可以进行通信。 请你统计并返回能够与至少一台其他服务…...

Unity打包Windows程序,概率性出现无法全屏或分辨率不匹配
排除代码和Resolution and Presentation面板设置问题 如果程序还是不能按照预期的分辨率运行,应该是系统注册表记录了对应的设置。 解决方案: 打开注册表,使用快捷键“Win” "R"组合快捷键。在打开后面键入命令:Rege…...

消息中间件 介绍
MQ简介 MQ,Message queue,消息队列,就是指保存消息的一个容器。具体的定义这里就不类似于数据库、缓存等,用来保存数据的。当然,与数据库、缓存等产品比较,也有自己一些特点,具体的特点后文会做详细的介绍。 现在常用…...

JAVA-字符串长度
给定一行长度不超过 100 的非空字符串,请你求出它的具体长度。 输入格式 输入一行,表示一个字符串。注意字符串中可能包含空格。 输出格式 输出一个整数,表示它的长度。 数据范围 1≤字符串长度≤100 字符串末尾无回车 输入样例: …...

[oneAPI] 基于BERT预训练模型的SWAG问答任务
[oneAPI] 基于BERT预训练模型的SWAG问答任务 基于Intel DevCloud for oneAPI下的Intel Optimization for PyTorch基于BERT预训练模型的SWAG问答任务数据集下载和描述数据集构建问答选择模型训练 结果参考资料 比赛:https://marketing.csdn.net/p/f3e44fbfe46c465f4d…...

如何为winform控件注册事件
有很多winform的初学者不知道如何为winform注册的事件代码,本篇博文就是以button控件为例子,为winform注册单击事件,如下: 1、新建一个winform 以visual studio 2019 社区版为例子,新建一个winform程序,如下: 关于visual studio 2019 社区版下载方式点击这里:手把手教…...

【LeetCode-面试经典150题-day15】
目录 104.二叉树的最大深度 100.相同的树 226.翻转二叉树 101.对称二叉树 105.从前序与中序遍历序列构造二叉树 106.从中序与后序遍历序列构造二叉树 117.填充每个节点的下一个右侧节点指针Ⅱ 104.二叉树的最大深度 题意: 给定一个二叉树 root ,返回其…...

git查看和修改项目远程仓库地址
git查看和修改项目远程仓库地址 一、背景 项目代码仓库迁移,需要本地更新远程仓库地址,进行代码同步与提交。 二、查看项目的远程仓库地址 # 查看远程地址 git remote -v # 查看远程仓库信息(分支、地址等) git remote show origin三、修…...

JavaWeb 速通JSON
目录 一、JSON快速入门 1.基本介绍 : 2.定义格式 : 3.入门案例 : 二、JSON对象和字符串的相互转换 1.常用方法 : 2.应用实例 : 3.使用细节 : 三、JSON在Java中的使用 1.基本说明 : 2.应用场景 : 2.1 JSON <---> JavaBean 2.2 JSON <---> List 2.3 JSON …...

20 MySQL(下)
文章目录 视图视图是什么定义视图查看视图删除视图视图的作用 事务事务的使用 索引查询索引创建索引删除索引聚集索引和非聚集索引影响 账户管理(了解非DBA)授予权限 与 账户的相关操作 MySQL的主从配置 视图 视图是什么 通俗的讲,视图就是…...

测试圈的网红工具:Jmeter到底难在哪里?!
雨果的公司最近推出了一款在线购物应用,吸引了大量用户。然而随着用户数量的增加,应用的性能开始出现问题。用户抱怨说购物过程中页面加载缓慢,甚至有时候无法完成订单,小欧作为负责人员迫切需要找到解决方案。 在学习JMeter之前…...

深度学习10:Attention 机制
目录 Attention 的本质是什么 Attention 的3大优点 Attention 的原理 Attention 的 N 种类型 Attention 的本质是什么 Attention(注意力)机制如果浅层的理解,跟他的名字非常匹配。他的核心逻辑就是「从关注全部到关注重点」。 Attention…...

简单着色器编写(中下)
这篇我们来介绍另一部分函数。 static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader) {unsigned int program glCreateProgram();unsigned int vs CompileShader(GL_VERTEX_SHADER,vertexShader);unsigned int f…...

matlab使用教程(24)—常微分方程(ODE)求解器
1.常微分方程 常微分方程 (ODE) 包含与一个自变量 t(通常称为时间)相关的因变量 y 的一个或多个导数。此处用于表示 y 关于 t 的导数的表示法对于一阶导数为 y ′ ,对于二阶导数为 y ′′,依此类推。ODE 的阶数等于 y 在方程中…...

企业级数据共享规模化模式
数据共享正在成为企业数据战略的重要元素。对于公司而言,Amazon Data Exchange 这样的亚马逊云科技服务提供了与其他公司共享增值数据或从这些数据获利的途径。一些企业希望有一个数据共享平台,他们可以在该平台上建立协作和战略方法,在封闭、…...

Web服务器-Tomcat详细原理与实现
Tomcat 安装与使用 :MAC 安装配置使用Tomcat - 掘金 安装后本计算机就相当于一台服务器了!!! 方式一:使用本地安装的Tomcat 1、将项目文件移动到Tomcat的webapps目录下。 2、启动Tomcat 3、在浏览器输入想要加载的…...

ARM处理器核心概述
一、基于ARM处理器的嵌入式系统 ARM核深度嵌入SOC中,通过JTAG口进行外部调试。计通常既有外部内存又有内部内存,从而支持不通的内存宽度、速度和大小。一般会包含一个中断控制器。可能包含一些Primece外设,需要从ARM公司取得授权。总线使用A…...