【C++】vector的模拟实现【完整版】

目录

一、vector的默认成员函数 

1、vector类的大体结构

 2、无参构造函数

3、拷贝构造函数

 4、Swap(operator=需要用)

5、赋值重载operator=

 6、析构函数

二、vector的三种遍历方式

 1、size和capacity(大小和容量)

2、 operator[]遍历

3、迭代器iterator遍历和范围for

三、vector相关的增容和删除

 1、reserve (指定容量)

2、resize(指定大小)

3、push_back(尾插)

4、pop_back(尾删)

5、insert

6、erase

四、完整代码 

vector.h: 

test.cpp:


一、vector的默认成员函数 

1、vector类的大体结构

vector的成员变量本质是由三个T类型(模板)的指针变量组成的,T是因为用了模板,因为vector具体存储什么类型的需要指定

namespace mz
{
using std::cout;
using std::endl;
using std::string;template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;iterator _finish;iterator _endofstorage;}
}

 ①、为什么要在新的命名空间中模拟实现vector?

防止与库里面的vector冲突,因为我们要自己模拟实现一个

②、iterator为什么要用两个版本(const和非const)?

因为迭代器遍历时我们要有只读又能读又能写的状态,所以要有个const版本的

 2、无参构造函数

vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr)
{}

3、拷贝构造函数

注:此函数最好放最后面看,因为其内部调用了下文才讲的成员函数

①、正常版本

首先我们不自己实现,就是浅拷贝,会出现问题,故需我们自己实现深拷贝,v2(v1),即只要让v2拷贝一份新的数据即可(使v2和v1不是指向同一份数据)

②、现代版本

v2(v1),先让v2 reserve和v1一样的容量大小,防止频繁增容降低效率,再把v1中的每个数据尾插到v2中即可

为什么要const auto&e?

其是一种用于迭代容器元素的引用方式。它表示在循环过程中,我们用一个常量引用来访问容器中的元素。好处是不会对容器进行修改,并不会产生拷贝操作,可提高代码的效率。

在用范围for循环时,如果我们只是想读取容器中的元素而不对其进行修改,可以使用const auto&来声明循环变量。例如,当我们遍历一个vector或者数组时,可以使用const auto&来避免对容器进行修改操作。

例如,当我们遍历一个数组时,如果使用const auto&来声明循环变量,则不能修改数组中的元素。例如,对于以下代码:

int arr = {0, 1, 2, 3, 4};
for (const auto& a : arr) {
a = 1; // 错误,不能修改
cout << a << “\t”;
}

由于使用了const auto&声明循环变量a,所以对a进行修改会导致编译错误。

//v2(v1) 正常版本的实现
/*vector(const vector<T>& v)
{_start = new T[v.capacity()];_finish = _start;_endofstorage = _start + v.capacity();for (size_t i = 0; i < v.size(); ++i){*_finish = v[i];++_finish;}
}*///v2(v1) 现代版本的实现(更推荐)
vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(v.capacity());for (const auto& e : v)push_back(e);
}

 4、Swap(operator=需要用)

为什么需要自己提供swap,而不调用里提供的?

因为库里面提供的对于自定义类型代价极大,因为需要深拷贝,但对于内置类型用库函数里面的还好,所以应该自己写一个成员函数swap

void swap(vector<T>& v)
{//调用全局的swap,交换指针,其为内置类型,无需深拷贝//代价相比于直接调用库里面函数交换比较小std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}

5、赋值重载operator=

①、正常版本

先要避免自己给自己赋值,先释放旧空间,再使_start指向一份自己新开辟的新空间,再把需要赋值的数据拷过去即可

②、现代版本(更推荐)

v是v3的临时拷贝,然后v会与v1交换,出了函数v就被销毁了,不用我们自己再销毁了,省力

//v1 = v3 正常版本的实现
/*vector<T>& operator=(const vector<T>& v)
{if (this != &v){delete[] _start;_start = new T[v.capacity()];memcpy(_start, v._start, sizeof(T) * v.size());}return *this;
}*///v1 = v3 现代版本
vector<T>& operator=(vector<T> v)
{swap(v);//this->swap(v);return *this;
}

 6、析构函数

    ~vector()
{if (_start){delete[]_start;_start = _finish = _endofstorage = nullptr;}
}

二、vector的三种遍历方式

 1、size和capacity(大小和容量)

原理:指针相减就是指针间的元素个数

	size_t size()const{return _finish - _start;}size_t capacity()const{return _endofstorage - _start;}

①、为什么要加const?

成员函数只要不改变成员变量的,最好都要加上const  

2、 operator[]遍历

T& operator[](size_t i)
{//确保范围的合法性assert(i < size());//记得引头文件assert.hreturn _start[i];
}const T& operator[](size_t i)const
{assert(i < size());return _start[i];
}

①、为什么要实现两个版本的operator[]?

直接把非const的operator[]加上const不行吗?不可以,因为operator[]访问完后,有两种状态:1、只读 2、可读可写  你直接加上const就只读了,万一我想改数据又不行了,故写两个版本的(const和非const版本) 

3、迭代器iterator遍历和范围for

迭代器有只读的和可读可写的,故也要写两个版本

iterator begin()
{return _start;
}iterator end()
{return _finish;
}const_iterator begin()const
{return _start;
}const_iterator end()const
{return _finish;
}

只要把迭代器写出来了,迭代器遍历和范围for遍历就都可以用了

测试operator[]迭代器范围for三种遍历

void test1()
{vector<int>v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);cout << v.size() << endl;cout << v.capacity() << endl;vector<int>::iterator it = v.begin();while (it != v.end()){*it += 1;cout << *it << " ";++it;}cout << endl; for (auto& e : v){e -= 1;cout << e << " ";}cout << endl;for (size_t i = 0; i < v.size(); ++i){cout << v[i] << " ";}cout << endl;

运行结果:

三、vector相关的增容和删除

 1、reserve (指定容量)

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();//大小要在增容前就算好T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * sz);//按字节拷贝,浅拷贝//作深拷贝,使新旧空间指向自己对应的数据for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];//调用的是T的operator=,深拷贝}delete[]_start;}_start = tmp;_finish = tmp + sz;//不能写为=tmp + size()_endofstorage = tmp + n;}
}

①、为什么要在增容前就算出大小?

因为增容涉及新和旧空间的问题,比如_finish = tmp + size(),而size的求法是用_finish - _start,但_start已指向新空间,可_finish还是指向旧空间,两块不连续的空间相减就是随机值了,故要在增容之前就把大小算出来,不然增容之后的大小就是随机值了

②、为什么不能用memcpy拷贝数据?

测试时,当用string作为vector存储的类型时,程序崩溃了,为什么?

本质原因是因为增容中用了memcpy,memcpy导致了问题的出现,因为memcpy是按字节拷贝的,它本质上造成了浅拷贝问题, memcpy使新旧空间都指向了一份数据,旧空间释放后,它指向的对应数据也被释放,而新空间指针还是指向这份旧空间,这就造成非法访问,所以新空间与旧空间不应指向同一份数据,应该不用memcpy,写成深拷贝

解决(reserve的实现不用memcpy): 

把旧空间的每个值赋值给新空间对应的每个值就好了,就能实现深拷贝了,指向不同的数据

2、resize(指定大小)

void resize(size_t n, const T& val = T())//因为不知道T的类型,故给T类型的缺省值
{if (n < size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}//填数据不能用memset//填数据while (_finish < _start + n){*_finish = val;++_finish;}}
}

①、填数据为什么不能用memset?

当对于a数组,设置为0时,没问题,但各设置为1和2时,就出现随机值了 ,因为memset只适合把所有值初始化为0,当初始化为1时,是把每个字节初始化为1 

即00000001 00000001 00000001 00000001,这就不是整形的1了(2也同理,就初始化0可以)!memset连内置类型都处理不好,别说自定义类型了,问题会更大,本质上就是因为memxxx类函数都是按字节处理的,

即慎用memxxx,因为其按字节进行操作

②、const T& val = T()中的T()使什么意思?

因为不知道T的类型,故用T的缺省值

C++中内置类型也可以像自定义类型那样调用构造函数,严格来说,内置类型是没有构造函数的,但C++强行赋予了这个构造函数概念,是为了更好地支持模板

int i = int();//0int j = int(1);//1double d = double();//0.0000000000double e = double(1.1);//1.100000000

3、push_back(尾插)

void push_back(const T& x) 
{//方法一if (_finish == _endofstorage){size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;//方法二、调用insert//insert(_finish, x);
}

4、pop_back(尾删)

void pop_back()
{//方法一assert(_start < _finish);--_finish;//方法二、复用eraseerase(_finish - 1);
}

5、insert

void insert(iterator pos, const T& x)
{assert(pos <= _finish);//pos == _finish时,相当于尾插//满了需要扩容if (_finish == _endofstorage){size_t n = pos - _start;//算出原pos距离size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);//增完容后要更新pos的位置,因为增容后原来的pos就失效了pos = _start + n;//新的_start + n}//插入之前需要往后挪动1个数据iterator end = _finish;while (end > pos){*end = *(end - 1);--end;}//插入数据*pos = x;++_finish;
}

为什么需要提前算出n?

有迭代器失效的问题,因为pos是迭代器类型,扩容前指向旧空间的某一位置,而reserve调用后会扩容,而我们是扩容完才插入数据的,此时pos无效,因为旧空间已经释放了,它这个迭代器还指向那里就失效了,故我们要更新pos位置,使它指向新空间,所以要先算n,即原pos的位置

6、erase

iterator erase(iterator pos)
{assert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;//返回被删除数据的下一位置,那就还是原位置,因为那个数据被删除了
}

 注意:erase是返回被删除数据的下一位置,当要被删除的数据被删除了,erase原地不动的话,就已自动指向了下一位置,因为那个数据被删除了

最后一个问题

如果T是字符串的话代码逻辑会不会忘掉'\0'?

不会,vector<char>末尾没有'\0',string才有,这也是他们的差别

四、完整代码 

总共分为两个文件:vector.h和test.cpp

vector.h: 

#pragma once
#include<assert.h>
#include<string>namespace mz
{
using std::cout;
using std::endl;
using std::string;template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}//v2(v1) 正常版本的实现/*vector(const vector<T>& v){_start = new T[v.capacity()];_finish = _start;_endofstorage = _start + v.capacity();for (size_t i = 0; i < v.size(); ++i){*_finish = v[i];++_finish;}}*///v2(v1) 现代版本的实现(更推荐)vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (const auto& e : v)push_back(e);}//v1 = v3 正常版本的实现/*vector<T>& operator=(const vector<T>& v){if (this != &v){delete[] _start;_start = new T[v.capacity()];memcpy(_start, v._start, sizeof(T) * v.size());}return *this;}*///v1 = v3 现代版本vector<T>& operator=(vector<T> v){swap(v);//this->swap(v);return *this;}void swap(vector<T>& v){//调用全局的swap,交换指针,其为内置类型,无需深拷贝//代价相比于直接调用库里面函数交换比较小std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}void reserve(size_t n){if (n > capacity()){size_t sz = size();//大小要在增容前就算好T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * sz);//按字节拷贝,浅拷贝//作深拷贝,使新旧空间指向自己对应的数据for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];//调用的是T的operator=,深拷贝}delete[]_start;}_start = tmp;_finish = tmp + sz;//不能写为=tmp + size()_endofstorage = tmp + n;}}void resize(size_t n, const T& val = T())//因为不知道T的类型,故给T类型的缺省值{if (n < size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}//填数据不能用memset//填数据while (_finish < _start + n){*_finish = val;++_finish;}}}void push_back(const T& x){//方法一if (_finish == _endofstorage){size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;//方法二、调用insert//insert(_finish, x);}void pop_back(){//方法一assert(_start < _finish);--_finish;//方法二、复用eraseerase(_finish - 1);}void insert(iterator pos, const T& x){assert(pos <= _finish);//pos == _finish时,相当于尾插//满了需要扩容if (_finish == _endofstorage){size_t n = pos - _start;//算出原pos距离size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);//增完容后要更新pos的位置,因为增容后原来的pos就失效了pos = _start + n;//新的_start + n}//插入之前需要往后挪动1个数据iterator end = _finish;while (end > pos){*end = *(end - 1);--end;}//插入数据*pos = x;++_finish;}iterator erase(iterator pos){assert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return pos;//返回被删除数据的下一位置,那就还是原位置,因为那个数据被删除了}T& operator[](size_t i){//确保范围的合法性assert(i < size());//记得引头文件assert.hreturn _start[i];}const T& operator[](size_t i)const{assert(i < size());return _start[i];}size_t size()const{return _finish - _start;}size_t capacity()const{return _endofstorage - _start;}~vector(){if (_start){delete[]_start;_start = _finish = _endofstorage = nullptr;}}private:iterator _start;iterator _finish;iterator _endofstorage;};void print_vector(const vector<int>& v){vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;}void test1(){vector<int>v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);cout << v.size() << endl;cout << v.capacity() << endl;vector<int>::iterator it = v.begin();while (it != v.end()){*it += 1;cout << *it << " ";++it;}cout << endl; for (auto& e : v){e -= 1;cout << e << " ";}cout << endl;for (size_t i = 0; i < v.size(); ++i){cout << v[i] << " ";}cout << endl;}void test2(){vector<int>v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.insert(v.begin(), 0);//头插0print_vector(v);//删除偶数vector<int>::iterator it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.erase(it);}else{++it;}}print_vector(v);}void test3(){vector<int>v;v.reserve(10);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.resize(4);print_vector(v);cout << v.size() << endl;cout << v.capacity() << endl;v.resize(8);print_vector(v);cout << v.size() << endl;cout << v.capacity() << endl;v.resize(12,int());print_vector(v);cout << v.size() << endl;cout << v.capacity() << endl;int i = int();//0int j = int(1);//1double d = double();//0.0000000000double e = double(1.1);//1.100000000}void test4(){vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);vector<int>v2(v1);for (size_t i = 0; i < v1.size(); ++i){cout << v1[i] << " ";}cout << endl;for (size_t i = 0; i < v2.size(); ++i){cout << v2[i] << " ";}cout << endl;vector<int>v3;v3.push_back(10);v3.push_back(20);v3.push_back(30);v3.push_back(40);v1 = v3;for (auto e : v1){cout << e << " ";}cout << endl;}void test5(){vector<string>v;v.push_back("111111111111111111111");v.push_back("222222222222222222222");v.push_back("333333333333333333333");v.push_back("444444444444444444444");for (auto e : v){cout << e << " ";}cout << endl;}
}

test.cpp:

#include<iostream>
#include"vector.h"int main()
{mz::test1();//memxxx 按字节处理/*int a[10];memset(a, 0, sizeof(int) * 10);memset(a, 1, sizeof(int) * 10);memset(a, 2, sizeof(int) * 10);*/return 0;
}

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

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

相关文章

Java版企业电子招标采购系统源码—企业战略布局下的采购寻源

功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外部供…

Ubuntu 22.04.2 LTS 安装python3.6后报错No module named ‘ufw‘

查明原因&#xff1a; vim /usr/sbin/ufw 初步判断是python版本的问题。 # 查看python3软链接 ll /usr/bin/python3 将python3的软链接从python3.6换成之前的3.10&#xff0c;根据自己电脑情况。 可以查看下 /usr/bin 下有什么 我这是python3.10 所以解决办法是 # 移除py…

Vue2面试题100问

Vue2面试题100问 Vue2面试题100问1.简述一下你对Vue的理解2.声明式和命令式编程概念的理解3.Vue 有哪些基本特征4.vue之防止页面加载时看到花括号解决方案有哪几种&#xff1f;5.Vue中v-for与v-if能否一起使用&#xff1f;6.vue中v-if与v-show的区别以及使用场景7.v-on可以监听…

web请求cookie中expires总结

用意 cookie 有失效日期 "expires"&#xff0c;如果还没有过失效期&#xff0c;即使重新启动电脑&#xff0c;cookie 仍然不会丢失 注意&#xff1a;如果没有指定 expires 值&#xff0c;那么在关闭浏览器时&#xff0c;cookie 即失效。 设置 如果cookie存储时间大…

uni-app点击复制指定内容(点击复制)

官方api uni.setClipboardData(OBJECT) uni.setClipboardData({data: 要被复制的内容,success: function () {console.log(success);} });

Qt--自定义搜索控件,QLineEdit带前缀图标

写在前面 这里自定义一个搜索控件&#xff0c;通过自定义LineEdit的textChange信号&#xff0c;搜索指定内容&#xff0c;并以QCheckBox的方式显示在QListWidget中。 开发版本 Qt: 5.15.2 Qt: Creator10.0.2 编译环境&#xff1a;msvc2019_64bit release 效果 代码 自定义…

三门问题讨论

三门问题讨论 三门问题第一种第二种 三门问题 三门问题&#xff08;Monty Hall problem&#xff09;亦称为蒙提霍尔问题、蒙特霍问题或蒙提霍尔悖论&#xff0c;大致出自美国的电视游戏节目Let’s Make a Deal。问题名字来自该节目的主持人蒙提霍尔&#xff08;Monty Hall&…

大数据组件-Flume集群环境搭建

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 个人主页&#xff1a;beixi 本文章收录于专栏&#xff08;点击传送&#xff09;&#xff1a;【大数据学习】 &#x1f493;&#x1f493;持续更新中&#xff0c;感谢各位前辈朋友们支持…

程序员面试逻辑题

红白帽子推理 答案&#xff1a; 这个题有点像数学归纳法&#xff0c;就是假设有 A A A和 B B B两个人是黑色的帽子&#xff0c;这样的话第一次开灯&#xff0c; A A A看到 B B B是黑色的&#xff0c;其他人都是白色的&#xff0c;那么 A A A会觉得 B B B是那个黑色的&#xff0…

word技巧之--表格与文本的转换(WPS版)

经常写文档的朋友们难免会需要在word文档中添加表格&#xff0c;也会发现word表格没有excel表格那么灵活&#xff0c;编辑起来有点麻烦。 今天分享一些Word的使用技巧&#xff0c;希望对大家有帮助。 文本快速转为表格 选中需要转换为表格的文本&#xff0c;点击【插入】选项…

AI时代的较量,MixTrust能否略胜一筹?

人工智能的能力正在迅速接近人类&#xff0c;而在许多细分领域&#xff0c;已经超越了人类。虽然短期内这个突破是否会导致人工通用智能&#xff08;AGI&#xff09;还不清楚&#xff0c;但我们现在有的模型被训练成在数字交互中完美地模仿高能人类。尽管AGI仍不确定&#xff0…

NAT地址转换,路由器作为出口设备,实现负载分担

路漫漫其修远兮&#xff0c;吾将上下而求索 一个善于创造的人&#xff0c;一定是一个善于分享的人。 今天整理了一个实验&#xff0c;具备NAT地址转换&#xff0c;路由器作为出口设备&#xff0c;实现负载分担&#xff0c;实现路由策略 目录 实验图 实验要求 实验配置 基…

雅思 《九分达人》阅读练习(二)

目录 雅思阅读练习 《九分达人》test3 paragraph3 1.单词含义要记准确&#xff0c;敏感度要上来。 2.找准定位&#xff0c;之后理解句子大致含义。 说说关于判断题的做题方法 关于“承认”有哪些单词 同替词汇 think 可以用什么其他单词来替换 单词 一些疑问 I have…

重磅功能 一键助你打造TikTok爆款视频

内容为王的时代&#xff0c;内容需求大爆炸。短视频电商即是当下的时代红利&#xff0c;也是营销领域最前沿的谜题。 TikTok短视频日新月异&#xff0c;但是内容选题、标签、热度视频该如何找&#xff1f;这些问题至关重要... 为了解决上述这些电商客户的痛点&#xff0c;超店…

docker报错解决方法

ERROR: readlink /var/lib/docker/overlay2/l: invalid argument 注意&#xff1a;会清空已有安装 sudo service docker stop sudo rm -rf /var/lib/docker sudo service docker start

软件测试适合零基础学么

零基础学习软件测试不失为一个好的选择&#xff0c;虽然IT行业里对小白最友好的非软件测试莫属了&#xff0c;但是也要看你个人在学习软件测试这件事上面花费了多少的时间和努力了~ 每年毕业季&#xff0c;IT行业依然是比较热门且收入是最高的行业。对于应届毕业生来说想要进入…

基于微信小程序的自习室系统设计与实现,可作为毕业设计

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1 简介2 技术栈3 需求分析3.1用户需求分析3.1.1 学生用户3.1.3 管理员用户 4 数据库设计4.4.1 E…

leetcode669. 修剪二叉搜索树(java)

修剪二叉搜索树 题目描述递归代码演示&#xff1a; 题目描述 难度 - 中等 LC - 669. 修剪二叉搜索树 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[low, high]中。修剪树 不应该 改变保留…

9.1.tensorRT高级(4)封装系列-自动驾驶案例项目self-driving-道路分割分析

目录 前言1. 道路分割总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-自动驾驶案例项目self-driving-道路分…

关于ChatGPT的个人的一些观点

问题 1 Q: 你认为ChatGPT是一款非常有用的工具吗&#xff1f; A: 我认为ChatGPT是一款非常有用的工具。它可以帮助人们解决各种问题&#xff0c;包括技术问题、心理问题、生活问题等等。同时&#xff0c;ChatGPT也可以成为人们分享想法和交流的平台&#xff0c;增强人与人之间…