【C++跬步积累】—— list模拟实现(含源代码,超详细)

🌏博客主页:PH_modest的博客主页
🚩当前专栏:C++跬步积累
💌其他专栏:
🔴 每日一题
🟡 Linux跬步积累
🟢 C语言跬步积累
🌈座右铭:广积粮,缓称王!

一、总揽(三个类和相关接口)

节点类(list_node)

template<calss T>
class list_node
{//成员对象list_node<T>* _pre;list_node<T>* _next;T _val;//成员函数//默认构造list_node(const T& val=T());
};

迭代器(list_iterator)

template<class T, class Ref, class Ptr>
class list_iterator
{
public://重命名typedef list_node<T> node;typedef list_iterator<T, Ref, Ptr> iterator;//成员变量node* _node;//构造函数list_iterator(node* x);//运算符重载iterator& operator++();iterator operator++(int);iterator& operator--();iterator operator--(int);bool operator==(const iterator& lt) const;bool operator!=(const iterator& lt) const;Ref operator*();Ptr operator->();
};

list类

//list类
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;//默认成员函数list();list(const list<T>& lt);~list();list<T>& operator=(const list<T>& lt);//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//访问容器相关函数T& front();T& back();const T& front() const ;const T& back() const ;//插入、删除函数void push_back(const T& x);void push_front(const T& x);void pop_back();void pop_front();iterator erase(iterator& it);iterator insert(iterator pos, const T& x);//其他函数void clear();void swap(list<T>& lt);private:node* _head;//指向链表头结点的指针
};

二、节点类的模拟实现

list底层的实现就是一个带头双向循环链表
在这里插入图片描述
因此在实现list类之前需要先实现节点类。一个节点类需要存储三个信息:数据、前一个节点的地址、后一个节点的地址。所以成员函数就知道了:数据(_val)、前驱指针(_pre)、后继指针(_next)。

对于节点类的成员函数来说,我们只需要实现一个构造函数即可,因为这个节点类的作用就是根据数据来创建一个节点即可,而节点的释放则由list的析构函数来完成。

构造函数

list_node(const T& x = T()):_pre(nullptr), _next(nullptr), _val(x)
{}

构造函数主要是存储数据,当没有传递参数时,会调用所存储类型的默认构造所构造出来的值作为参数。
例如:数据类型为int时,会调用int类型的默认构造,会将_val初始化为0;

节点类总结

template<class T>
struct list_node
{//成员对象list_node<T>* _pre;list_node<T>* _next;T _val;//成员函数list_node(const T& x = T()):_pre(nullptr), _next(nullptr), _val(x){}
};

三、迭代器类的模拟实现

迭代器类的意义

与vector和string不同,他们可以直接使用原生指针,因为他们的数据是存储在一块连续的内存空间,我们可以直接通过指针进行自增、自减、解引用等操作来进行相关操作。
在这里插入图片描述

而对于list,各个节点在内存中的位置是随机的,不是连续的,所以我们不能直接通过节点指针的自增、自减。解引用等操作对相应节点的数据进行操作。
在这里插入图片描述
迭代器的意义: 让使用者可以不用关心容器的底层实现,可以用简单统一的方式对容器内部的数据进行访问,只需要将迭代器进行相应的封装即可。

迭代器的本质就是指针,既然这个原生指针不能满足我们的要求,那么我们就可以自己封装一个,对相关的操作符进行重载,让其满足相关的操作。(例如:当你使用list迭代器进行自增时,其实执行了ptr = ptr -> next;指向了下一个节点,也就是我们所希望的自增)

如果还觉得抽象,我再举个例子,原生指针就可以看成老虎,他们吃东西的时候是直接吃生肉;而迭代器封装就可以看成是人,我们吃东西的时候,需要将食物煮熟了再吃。老虎想要吃肉就可以直接吃生肉(原生指针进行相关操作时可以直接进行++、- -等操作),而我们人类吃东西时,需要将食物进行相关处理(list迭代器的相关操作符需要进行封装,以此来达到对应的操作)

总结: list迭代器类,实际上就是对节点指针进行了封装,对相关运算符进行了重载,使得节点指针的各种行为看起来和普通指针一样。

迭代器模板参数说明

我们这里使用了三个模版参数,这是为什么呢?

template<class T, class Ref, class Ptr>

在list的模拟实现中,需要有两种迭代器:普通迭代器和const迭代器。

typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;

不难看出,迭代器类的模版参数列表中的Ref和Ptr分别表示引用类型和指针类型。

通过下图对比一下不难看出,const和非const的类型只有三个不同,因此通过设置三个模版参数就可以将两种状态包含了。

在这里插入图片描述

构造函数

迭代器类就是对节点进行了封装,成员变量只有一个,就是节点指针,其构造函数直接根据所给的节点指针构造一个迭代器对象即可。

list_iterator(node* x):_node(x)
{}

++运算符重载

首先是前置++,前置++原本的作用是先将数据自增,然后返回自增之后的数据。那么对于节点指针的前置++,我们就应该让节点指针先指向后一个节点,然后返回“自增”后的节点指针

iterator& operator++()//this指针的类型是iterator*
{_node = _node->_next;return *this;
}

对于后置++,我们应该先记录当前节点指针的指向,然后让节点指针指向后一个节点,最后返回“自增”前的节点指针即可

iterator operator++(int)
{iterator tmp(*this);//这里会调用list_node的拷贝构造_node = _node->_next;return tmp;
}

- -运算符重载

思想和++类似,就不过多赘述。
前置- -:

iterator& operator--()
{_node = _node->pre;return *this;
}

后置- -:

iterator operator--(int)
{iterator tmp(*this);_node = _node->_pre;return tmp;
}

==运算符重载

当使用==运算符比较时,我们实际上是比较这两个迭代器是否是同一个位置的迭代器,也就是判断迭代器中的节点指针是否相同

bool operator==(const iterator& lt) const
{return _node == lt._node;
}

!=运算符重载

!=运算符和==的作用相反,只需要判断迭代器中的节点指针的指向是否不同即可。

bool operator!=(const iterator& lt) const
{return _node != lt._node;
}

*运算符重载

当我们使用解引用操作符时,是想知道该位置的数据内容。因此,我们直接返回当前节点指针所指的数据即可,但这里需要使用引用返回,因为解引用后可能需要对数据进行修改。

Ref operator*()
{return _node->_val;
}

->运算符重载

某些情况下,我们使用迭代器时会用到->运算符。
例如下面的场景:
当list容器当中的每个节点存储的不是内置类型,而是自定义类型,例如日期类,当我们拿到一个位置的迭代器时,我们可能会使用->运算符访问Date的成员。

	list<Date> lt;Date d1(2021, 8, 10);Date d2(1980, 4, 3);Date d3(1931, 6, 29);lt.push_back(d1);lt.push_back(d2);lt.push_back(d3);list<Date>::iterator pos = lt.begin();cout << pos->_year << endl; 

所以对于->运算符的重载,我们直接返回节点当中所存储数据的地址即可。

Ptr operator->()
{return &_node->_val;//_val里面还有其他内容时:lt.operator->() -> _a;
}

注意点:
按道理来说这样的话我们使用->运算符的时候应该是这样的:

lt.operator->() -> _a;

应该有两个->,但是由于这样程序的可读性太差,所以编译器做了特殊处理,把这边优化了,省略了一个箭头

四、list的模拟实现

默认成员函数

构造函数

list是一个带头双向循环链表,构造时,只需要申请一个头结点,然后让其前驱指针和后继指针都指向自己即可。

list()
{_head = new node;_head->_next = _head;_head->_pre = _head;
}

拷贝构造函数

拷贝构造函数就是根据所给list容器,拷贝构造出一个新的对象。先像默认构造那样创建一个指向自己的节点,然后使用迭代器遍历所给的容器,尾插到新构造的容器后就行。

list(const list<T>& lt)
{_head = new node;_head->_next = _head;_head->_pre = _head;for (const auto &it : lt){push_back(it);}
}

赋值运算符重载

方法一:传统写法
这是一种比较容易理解的写法,先调用clear函数清空,然后将容器lt中的数据通过迭代器的方式尾插到清空后的容器中。

list<T>& operator=(const list<T>& lt)
{if (this != &lt) //避免自己给自己赋值{clear(); //清空容器for (const auto& e : lt){push_back(e); }}return *this; //支持连续赋值
}

方法二:现代写法
首先利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造构造出一个list对象,然后调用swap函数将两个容器交换即可。

list<T>& operator=(list<T> lt) //编译器接收右值的时候自动调用其拷贝构造函数
{swap(lt); //交换这两个对象return *this; //支持连续赋值
}

这样做相当于将应该用clear清理的数据,通过交换函数交给了容器lt,而当该赋值运算符重载函数调用结束时,容器lt会自动销毁,并调用其析构函数进行清理。

析构函数

首先调用clear清空里面的数据,然后释放头结点,最后将头结点置空。

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

迭代器相关函数

begin和end

begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器

iterator begin()
{return iterator(_head->_next);
}
iterator end()
{return iterator(_head);
}

这里需要重载一下const对象的begin和end

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

访问容器相关函数

front和back

front和back分别是用于获取第一个有效数据和最后一个有效数据,所以只需要返回第一个有效数据的引用和最后一个有效数据的引用即可。

T& front()
{return *begin();
}
T& back()
{return *(--end());
}

当然也需要重载一下const对象的front和back。

const T& front() const
{return *begin(); //返回第一个有效数据的const引用
}
const T& back() const
{return *(--end()); //返回最后一个有效数据的const引用
}

插入和删除

insert

insert函数可以在所给迭代器之前插入一个新的节点。

iterator insert(iterator pos, const T& x)
{node* newnode = new node(x);node* cur = pos._node;node* pre = cur->_pre;pre->_next = newnode;newnode->_pre = pre;newnode->_next = cur;cur->_pre = newnode;return newnode;
}

erase

删除当前迭代器位置的节点。

iterator erase(iterator& it)
{assert(it != end());node* cur = it->_node;node* pre = cur->_pre;node* next = cur->_next;pre->_next = next;next->_pre = pre;delete cur;return next;
}

push_back和pop_back

方法一:原始版

//尾插
void push_back(const T& x)
{node* newnode = new node(x);node* tail = _head->_pre;tail->_next = newnode;newnode->_pre = tail;newnode->_next = _head;_head->_pre = newnode;
}
//尾删
void pop_back()
{node* tail = _head->_pre;_head->_pre = tail->_pre;tail->_pre->_next = _head;delete tail;tail = nullptr;
}

方法二:复用版

//尾插
void push_back(const T& x)
{insert(end(), x); //在头结点前插入结点
}
//尾删
void pop_back()
{erase(--end()); //删除头结点的前一个结点
}

push_front和pop_front

方法一:

//头插
void push_front(const T& x)
{node* newnode = new node(x);node* tmp = _head->_next;_head->_next = newnode;newnode->_pre = _head;newnode->_next = tmp;tmp->_pre = newnode;
}
//头删
void pop_front()
{node* front = _head->_next;_head->_next = front->_next;front->_next->_pre = _head;delete front;front = nullptr;
}

方法二:

//头插
void push_front(const T& x)
{insert(begin(), x); //在第一个有效结点前插入结点
}
//头删
void pop_front()
{erase(begin()); //删除第一个有效结点
}

其他函数

size

size函数用于获取当前容器当中的有效数据个数,因为list是链表,所以只能通过遍历的方式逐个统计有效数据的个数。

size_t size() const
{size_t sz = 0; //统计有效数据个数const_iterator it = begin(); //获取第一个有效数据的迭代器while (it != end()) //通过遍历统计有效数据个数{sz++;it++;}return sz; //返回有效数据个数
}

resize

resize函数的规则:

  1. 若当前容器的size小于所给n,则尾插结点,直到size等于n为止。
  2. 若当前容器的size大于所给n,则只保留前n个有效数据。

实现resize函数时,不要直接调用size函数获取当前容器的有效数据个数,因为当你调用size函数后就已经遍历了一次容器了,而如果结果是size大于n,那么还需要遍历容器,找到第n个有效结点并释放之后的结点。

这里实现resize的方法是,设置一个变量len,用于记录当前所遍历的数据个数,然后开始变量容器,在遍历过程中:

  1. 当len大于或是等于n时遍历结束,此时说明该结点后的结点都应该被释放,将之后的结点释放即可。
  2. 当容器遍历完毕时遍历结束,此时说明容器当中的有效数据个数小于n,则需要尾插结点,直到容器当中的有效数据个数为n时停止尾插即可。
void resize(size_t n, const T& val = T())
{iterator it = begin(); //获取第一个有效数据的迭代器size_t len = 0; //记录当前所遍历的数据个数while (len < n&&it != end()){len++;it++;}if (len == n) //说明容器当中的有效数据个数大于或是等于n{while (it != end()) //只保留前n个有效数据{it = erase(it); //每次删除后接收下一个数据的迭代器}}else //说明容器当中的有效数据个数小于n{while (len < n) //尾插数据为val的结点,直到容器当中的有效数据个数为n{push_back(val);len++;}}
}

clear

clear函数用于清空容器,我们通过遍历的方式,逐个删除结点,只保留头结点即可。

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

swap

void swap(list<T>& lt)
{std::swap(_head, lt._head);
}

五、源代码

namespace my_list
{//首先创建节点类template<class T>struct list_node{//成员对象list_node<T>* _pre;list_node<T>* _next;T _val;//成员函数list_node(const T& x = T()):_pre(nullptr), _next(nullptr), _val(x){}};//迭代器实现template<class T, class Ref, class Ptr>class list_iterator{public:typedef list_node<T> node;typedef list_iterator<T, Ref, Ptr> iterator;node* _node;list_iterator(node* x):_node(x){}//++ititerator& operator++()//this指针的类型是iterator*{_node = _node->_next;return *this;}//it++iterator operator++(int){/*node* tmp = new node;tmp = _node;_node = _node->_next;return tmp;*/iterator tmp(*this);_node = _node->_next;return tmp;}//--ititerator& operator--(){_node = _node->pre;return *this;}//it--iterator operator--(int){iterator tmp(*this);_node = _node->_pre;return tmp;}//==bool operator==(const iterator& lt) const{return _node == lt._node;}//!=bool operator!=(const iterator& lt) const{return _node != lt._node;}//*Ref operator*(){return _node->_val;}//->Ptr operator->(){return &_node->_val;//_val里面还有其他内容时:lt.operator->() -> _a;}};//list类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;//默认构造list(){_head = new node;_head->_next = _head;_head->_pre = _head;}//拷贝构造list(const list<T>& lt){_head = new node;_head->_next = _head;_head->_pre = _head;for (const auto& it : lt){push_back(it);}}//迭代器相关函数iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}//访问容器相关函数T& front(){//return _head->_next->_val;return *begin();}T& back(){//return _head->_pre->_val;return *(--end());}const T& front() const {return _head->_next->_val;}const T& back() const {return _head->_pre->_val;}//尾插void push_back(const T& x){/*node* newnode = new node(x);node* tail = _head->_pre;tail->_next = newnode;newnode->_pre = tail;newnode->_next = _head;_head->_pre = newnode;*/insert(end(), x);}//尾删void pop_back(){/*node* tail = _head->_pre;_head->_pre = tail->_pre;tail->_pre->_next = _head;delete tail;tail = nullptr;*/erase(--end());}//头插void push_front(const T& x){/*node* newnode = new node(x);node* tmp = _head->_next;_head->_next = newnode;newnode->_pre = _head;newnode->_next = tmp;tmp->_pre = newnode;*/insert(begin(), x);}//头删void pop_front(){/*node* front = _head->_next;_head->_next = front->_next;front->_next->_pre = _head;delete front;front = nullptr;*/erase(begin());}//清除void clear(){iterator it = begin();while (it != end()){it=erase(it);}}iterator erase(iterator& it){assert(it != end());node* cur = it->_node;node* pre = cur->_pre;node* next = cur->_next;pre->_next = next;next->_pre = pre;delete cur;//return this;return next;}iterator insert(iterator pos, const T& x){node* newnode = new node(x);node* cur = pos._node;node* pre = cur->_pre;pre->_next = newnode;newnode->_pre = pre;newnode->_next = cur;cur->_pre = newnode;return newnode;}size_t size() const{size_t sz = 0; //统计有效数据个数const_iterator it = begin(); //获取第一个有效数据的迭代器while (it != end()) //通过遍历统计有效数据个数{sz++;it++;}return sz; //返回有效数据个数}void resize(size_t n, const T& val = T()){iterator it = begin(); //获取第一个有效数据的迭代器size_t len = 0; //记录当前所遍历的数据个数while (len < n && it != end()){len++;it++;}if (len == n) //说明容器当中的有效数据个数大于或是等于n{while (it != end()) //只保留前n个有效数据{it = erase(it); //每次删除后接收下一个数据的迭代器}}else //说明容器当中的有效数据个数小于n{while (len < n) //尾插数据为val的结点,直到容器当中的有效数据个数为n{push_back(val);len++;}}}void swap(list<T>& lt){std::swap(_head, lt._head);}~list(){clear();delete _head;_head = nullptr;}private:node* _head;};void test1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);for (auto it : lt){std::cout << it << " ";}std::cout << std::endl;lt.pop_back();for (auto it : lt){std::cout << it << " ";}std::cout << std::endl;for (auto it : lt){std::cout << it << " ";}std::cout << std::endl;}
};

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AIGC】构建自己的谷歌搜索引擎服务并使用

一、谷歌 谷歌的搜索引擎需要自己创建服务才能启用检索api。&#xff08;需自行翻墙和创建自己的谷歌账号&#xff09; 1.1 API服务创建 1&#xff09;登陆https://console.cloud.google.com/: 2&#xff09; 选择新建项目&#xff0c;取号项目名即可&#xff08;比如:Olin…

arinc664总线协议

AFDX总线协议简介 &#xff08;1&#xff09;AFDX的传输速率高&#xff1a;带宽100MHZ&#xff0c;远远高于其他的类型的航空总线。&#xff08;2&#xff09;AFDX网络的鲁棒性高&#xff1a;AFDX的双冗余备份网络可以在某一个网络出现故障时&#xff0c;仍能正常通讯。 其中…

IP数据报结构详解:从基础到进阶

目录 IP数据报的格式 IP数据报首部的固定部分 IP数据报首部的可变部分 实例分析&#xff1a;数据报的分片 生存时间&#xff08;TTL&#xff09;与协议 首部检验和 总结 在网络通信中&#xff0c;IP数据报是至关重要的基本单元。本文将带您深入了解IP数据报的格式及其各个…

聚焦保险行业客户经营现状,概述神策数据 CJO 解决方案

触点红利时代&#xff0c;企业的经营需求从「深度的用户行为分析」转变为「个性化、全渠道一致的客户体验」。客户旅程编排&#xff08;Customer Journey Orchestration&#xff0c;简称 CJO&#xff09;从体验出发&#xff0c;关注客户需求、感受和满意度&#xff0c;能够帮助…

JavaWeb笔记_JSTL标签库JavaEE三层架构案例

一.JSTL标签库 1.1 JSTL概述 JSTL(jsp standard tag library):JSP标准标签库,它是针对EL表达式一个扩展,通过JSTL标签库与EL表达式结合可以完成更强大的功能 JSTL它是一种标签语言,JSTL不是JSP内置标签 JSTL标签库主要包含: ****核心标签 格式化标签 …

Windows本地启动Redis

找到本地redis目录 输入cmd,然后输入redis-server.exe redis.windows.conf&#xff0c;默认端口为6379 再新打开一个cmd&#xff0c;输入redis-cli.exe -p 6379 -a &#xff08;你在redis.windows.conf中设置的密码&#xff09;

.NET程序集编辑器/调试器 dnSpy 使用介绍

原文链接&#xff1a;https://www.cnblogs.com/zhaotianff/p/17352882.html dnSpy dnSpy是一个.NET程序集调试器和编辑器。它可以用它来编辑和调试程序集&#xff0c;即使在没有源码的情况下。 主要功能&#xff1a; 调试.NET和Unity程序集 编辑.NET和Unity程序集 项目地…

25.x86游戏实战-理解发包流程

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

IOS微软语音转文本,lame压缩音频

在IOS开发中&#xff0c;用微软进行语音转文本操作&#xff0c;并将录音文件压缩后返回 项目中遇到了利用微软SDK进行实时录音转文本操作&#xff0c;如果操作失败&#xff0c;那么就利用原始音频文件通过网络请求操作&#xff0c;最终这份文件上传到阿里云保存&#xff0c;考…

Golang学习笔记20240725,Go语言基础语法

第一个Go程序 package mainimport "fmt"func main() {fmt.Println("hello world") }运行方式1&#xff1a; go run main.go运行方式2&#xff1a; go build .\hello_go.exe运行方式3&#xff1a;goland右键运行 字符串拼接 使用加号可以对字符串进行…

基于微信小程序的校园警务系统/校园安全管理系统/校园出入管理系统

摘要 伴随着社会以及科学技术的发展&#xff0c;小程序已经渗透在人们的身边&#xff0c;小程序慢慢的变成了人们的生活必不可少的一部分&#xff0c;紧接着网络飞速的发展&#xff0c;小程序这一名词已不陌生&#xff0c;越来越多的学校机构等都会定制一款属于自己个性化的小程…

UML通信图建模技术及应用例

新书速览|《UML 2.5基础、建模与设计实践》 在对系统的动态行为进行建模时&#xff0c;通信图常被用于按组织结构对控制流进行建模。与顺序图一样&#xff0c;一个单独的通信图只能显示一个控制流。 使用通信图建模时可以遵循如下策略&#xff1a; &#xff08;1&#xff09…

Centos 8 配置网络源

备份当前的软件源配置文件&#xff1a; sudo cp -a /etc/yum.repos.d /etc/yum.repos.d.bak 清理原有的 yum 仓库配置信息&#xff1a; sudo rm -f /etc/yum.repos.d/*.repo 获取阿里云的 CentOS 8 源配置&#xff1a; sudo curl -o /etc/yum.repos.d/CentOS-Base.repo ht…

错误解决 error CS0117: ‘Buffer‘ does not contain a definition for ‘BlockCopy‘

Unity 2022.3.9f1 导入 Runtime OBJ Importer 后出现&#xff1a; error CS0117: ‘Buffer’ does not contain a definition for ‘BlockCopy’ 解决办法&#xff1a; 源代码&#xff1a; int DDS_HEADER_SIZE 128; byte[] dxtBytes new byte[ddsBytes.Length - DDS_HEAD…

【Vue实战教程】之Vue工程化项目详解

Vue工程化项目 随着多年的发展&#xff0c;前端越来越模块化、组件化、工程化&#xff0c;这是前端发展的大趋势。webpack是目前用于构建前端工程化项目的主流工具之一&#xff0c;也正变得越来越重要。本章节我们来详细讲解一下如何使用webpack搭建Vue工程化项目。 1 使用we…

Axure Web端元件库:从Quick UI到500+组件的飞跃

在快速变化的数字世界中&#xff0c;产品设计不仅仅是功能的堆砌&#xff0c;更是用户体验的精心雕琢。原型设计作为产品开发过程中的关键环节&#xff0c;其重要性不言而喻。Axure&#xff0c;作为业界领先的原型设计工具&#xff0c;凭借其强大的交互设计和丰富的功能&#x…

智慧工地视频汇聚管理平台:打造现代化工程管理的全新视界

一、方案背景 科技高速发展的今天&#xff0c;工地施工已发生翻天覆地的变化&#xff0c;传统工地管理模式很容易造成工地管理混乱、安全事故、数据延迟等问题&#xff0c;人力资源的不足也进一步加剧了监管不到位的局面&#xff0c;严重影响了施工进度质量和安全。 视频监控…

外行对自动驾驶汽车的一些想法-2024-

起源 前段时间有关于自动驾驶汽车的讨论&#xff0c;现在热度终于快过去了。 (⊙﹏⊙) 其实&#xff0c;完全不用担心自动驾驶取代人类。 引用 这是一篇24年4月的报道。 上图为引用&#xff0c;可以看到打工人的忙碌。 2023 一个热爱自动驾驶但妥妥外行之人的思考-2023-C…

mac怎样清理photoshop垃圾的方法 ps清理缓存和垃圾 苹果电脑暂存盘已满怎么清理

很多使用过ps&#xff0c;尤其是Adobe全家桶的小伙伴会发现&#xff0c;这些软件占用缓存很多&#xff0c;而且随着使用时间的增长&#xff0c;缓存也会越多&#xff0c;并不会自动清理。那么mac系统怎么清理ps暂存盘呢&#xff1f;mac又该怎么最高效清理磁盘空间呢&#xff1f…

芯片的计算能力由什么决定

芯片的计算能力由什么决定 芯片的计算能力由多个因素决定。 包括但不限于&#xff1a; 架构设计&#xff1a; 芯片的架构设计决定了其计算单元的数量和类型&#xff0c;以及它们之间的连接方式和通信方式。不同的架构可以提供不同的计算能力和性能。处理器速度&#xff1a; 处…