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

[STL]详解list模拟实现

[STL]list模拟实现

文章目录

  • [STL]list模拟实现
    • 1. 整体结构总览
    • 2. 成员变量解析
    • 3. 默认成员函数
      • 构造函数1
      • 迭代器区间构造函数
      • 拷贝构造函数
      • 赋值运算符重载
      • 析构函数
    • 4. 迭代器及相关函数
      • 迭代器整体结构总览
      • 迭代器的模拟实现
      • begin函数和end函数
      • begin函数和end函数const版本
    • 5. 数据修改函数
      • push_back函数
      • insert函数
      • push_front函数
      • erase函数
      • pop_back函数
      • pop_front函数
      • clear函数
    • 6. 完整代码链接

1. 整体结构总览

template<class T>struct list_node //结点结构 --ListNode为类名,加上模板参数后为类型{list_node* _prev;list_node* _next;T _data;list_node(const T& val = T()) //结点的构造函数{_data = val;_prev = nullptr;_next = nullptr;}};template<class T, class Ref, class Ptr> //迭代器类实现struct __list_iterator{typedef list_node<T> node; //对结点重命名typedef __list_iterator<T, Ref, Ptr> self; //对迭代器重命名//...node* _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; //const迭代器public:void empty_init();list(); //默认构造函数template<class Iteartor>list(Iteartor begin, Iteartor end);void swap(list<T>& tmp);list(const list<T>& l);list<T>& operator=(list<T> l);~list();iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;void push_back(const T& x);void insert(iterator pos, const T x);void push_front(const T& x);iterator erase(iterator pos);void pop_back();void pop_front();void clear();private:node* _head;};

2. 成员变量解析

成员变量相关代码结构如下:

template<class T>
struct list_node //结点结构 --ListNode为类名,加上模板参数后为类型
{list_node* _prev; //指向上一个结点的指针list_node* _next; //指向下一个结点的指针T _val;	//结点存储的数据// ...
};
template<class T>
class list
{typedef list_node<T> node;// ...
private:node* _head; //指向哨兵位的头结点
};

image-20230723183036391

由于list是由双向循环链表实现的,因此只需要一个指向哨兵位的头结点的指针_head作成员变量,通过_head和指向上一个结点和下一个结点的指针能够很快的找到头结点和尾结点。

3. 默认成员函数

构造函数1

无参的默认构造函数,由于实现的双向循环链表,因此需要在创建链表时创建哨兵位的头结点,由于创建哨兵位的过程在后续实现中还需使用,因此将其封装成一个单独的empty_init函数。

void empty_init() //便于后续复用
{_head = new node;_head->_prev = _head;_head->_next = _head;
}list() //-const list也可以调用此构造函数,因为在初始化后才加的const属性
{empty_init();
}

迭代器区间构造函数

迭代器区间构造就是将传入的容器按其迭代器范围内的数据作为要构造的容器的数据进行构造,只需要将传入迭代器内的数据尾插即可。

template<class Iteartor>
list(Iteartor begin, Iteartor end)
{empty_init();while (begin != end){push_back(*begin);++begin;}
}

拷贝构造函数

拷贝构造函数的实现分为传统写法和现代写法。

传统写法是将要拷贝的list的数据依次进行尾插:

list(const list<T>& l)
{//传统写法empty_init();const_iterator it = l.begin();while (it != l.end()){push_back(*it);it++;}
}

现代写法是创建一个临时的list进行数据拷贝,然后将临时的list内的结点交换过来:

void swap(list<T>& tmp)
{std::swap(_head, tmp._head);
}list(const list<T>& l)
{//现代写法empty_init();  // -- 不申请结点会因为tmp变量析构野指针而报错list<T> tmp(l.begin().l.end());swap(tmp);
}

赋值运算符重载

赋值运算符重载的实现利用参数会拷贝构造的特性,然后交换参数的数据。

list<T>& operator=(list<T> l)
{swap(l);return *this;
}

析构函数

析构函数的实现可以复用能够将除了哨兵位结点外的所有结点删除的clear函数,然后删除哨兵位结点。(clear函数的实现在文末。)

qxm::list<T>::~list()
{clear();delete _head;_head = nullptr;
}

4. 迭代器及相关函数

迭代器整体结构总览

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* n){};//构造函数__list_iterator(const __list_iterator<T, T&, T*>& it){}; //普通迭代器构造const迭代器__list_iterator(const __list_iterator<T, const T&, const T*>& it){};//cosnt迭代器构造普通迭代器self& operator++(){};//前置++self operator++(int){};//后置++self& operator--(){};//前置--self operator--(int){};//后置--Ref operator*() {};//*运算符重载Ptr operator->() {};//->运算符重载bool operator!=(const self& s){};//!=运算符重载bool operator==(const self& s){};//==运算符重载};

迭代器的模拟实现

由于迭代器需要的功能很多,因此需要给迭代器单独封装一个类,成员变量是结点指针。迭代器成员变量有关的代码如下:

template<class T>struct __list_iterator{typedef list_node<T> node; //对结点重命名// ...node* _node; //指向结点的指针};

迭代器构造函数

迭代器只有一个指向结点的指针变量,迭代器构造函数只要将其初始化即可。

__list_iterator(node* n):_node(n){}

迭代器构造函数

为了实现普通迭代器和const迭代器的转换需要实现如下构造函数:

__list_iterator(const __list_iterator<T, T&, T*>& it) //普通迭代器构造const迭代器:_node(it._node){}__list_iterator(const __list_iterator<T, const T&, const T*>& it)//cosnt迭代器构造普通迭代器:_node(it._node){}

迭代器前置++运算符重载

迭代器实现++操作只需要将指针指向下一个结点即可。

template<class T>    
self& operator++()//前置++
{_node = _node->_next;return _node;
}

迭代器后置++运算符重载

实现后置++和前置++相比只需要将++前的迭代器保存并且返回即可。

self operator++(int)//后置++
{self tmp(_node);_node = _node->_next;return tmp;
}

迭代器前置–运算符重载

迭代器实现–操作只需要将指针指向上一个结点即可。

self& operator--()//前置--
{_node = _node->_prev;return *this;
}

迭代器后置–运算符重载

实现后置–和前置–相比只需要将–前的迭代器保存并且返回即可。

self operator--(int)//后置--
{self tmp(_node);_node = _node->_prev;return tmp;
}

迭代器*运算符重载

迭代器进行*操作是要获取迭代器指向的数据,因此只需要将结点指向的数据返回即可。

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

迭代器->运算符重载

迭代器进行->操作是因为list存储的是自定义数据类型,->运算符的重载只需要返回数据的地址即可。

T* operator->()
{return &_node->_data;
}

为了理解->运算符重载的实现,我们看下面的例子:

struct AA
{AA(int a1 = 1, int a2 = 2):_a1(a1),_a2(a2){}int _a1;int _a2;
};void test_list2()
{qxm::list<AA> l; //qxm作用域是模拟实现时设置的命名空间l.push_back(AA(1, 1));l.push_back(AA(2, 2));l.push_back(AA(3, 3));for (qxm::list<AA>::iterator it = l.begin(); it != l.end(); it++){cout << it->_a1 << ":" << it->_a2 << endl;}
}

在这个场景中如果调用迭代器的->会返回AA类型的指针,it->_a1相当于是&AA_a1将数据地址和变量写到一块,应该是访问不到数据的错误代码,但是编译器会自动做优化,此时一个->运算符当两个->使用,也就是说本来需要(it.operator->())->_a1访问数据的,但是编译器把其中一个->优化掉了,因此现在只要it->_a1就可以访问成功了。

迭代器!=运算符重载

迭代器的!=是指向的数据不同,因此只需要判断迭代器内的指针是否相同。

bool operator!=(const self& it)
{return this->_node != it->_node;
}

迭代器!=运算符重载

迭代器的==是指向的数据相同,因此只需要判断迭代器内的指针是否相同。

bool operator==(const self& s)
{return _node == s._node;
}

const迭代器实现

const迭代器和普通迭代器的实现只有在*运算符重载和->运算符的重载的返回值上有所不同。

const迭代器的*运算符重载函数返回的是const的数据,这样const迭代器就不能修改数据了。

const T& operator*() 
{return _node->_data;
}

const迭代器的->运算符重载函数返回的是const的指针,这样const迭代器就不能修改数据了。

const T* operator->()
{return &_node->_data;
}

迭代器模拟实现整体代码:

template<class T, class Ref, class Ptr> // -- Ref/Ptr控制是普通迭代器还是const迭代器struct __list_iterator{typedef list_node<T> node; //对结点重命名typedef __list_iterator<T, Ref, Ptr> self; //对迭代器重命名node* _node;__list_iterator(node* n):_node(n){}__list_iterator(const __list_iterator<T, T&, T*>& it):_node(it._node){}__list_iterator(const __list_iterator<T, const T&, const T*>& it):_node(it._node){}self& operator++()//前置++{_node = _node->_next;return *this;}self operator++(int)//后置++{self tmp(_node);_node = _node->_next;return tmp;}self& operator--()//前置--{_node = _node->_prev;return *this;}self operator--(int)//后置--{self tmp(_node);_node = _node->_prev;return tmp;}Ref operator*() //传引用返回可以修改结点内的数据{return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const self& s) //!=运算符重载{return _node != s._node;}bool operator==(const self& s) //==运算符重载{return _node == s._node;}};

注意: 模板参数Ref控制的是*运算符重载的返回值类型,模板参数Ptr控制的是->运算符重载的返回值类型,进而控制了迭代器是普通迭代器还是const迭代器。

begin函数和end函数

注: 文中此位置往下是迭代器相关函数实现,实现在list类内。

begin函数:

begin函数只需要返回拥有有效数据的头结点即可。

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

end函数:

end函数只需要返回哨兵位就可,因为哨兵位没有有效数据。

iterator end()
{return iterator(_head);
}

begin函数和end函数const版本

const版本begin函数:

begin函数只需要返回拥有有效数据的头结点即可。

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

const版本end函数:

end函数只需要返回哨兵位就可,因为哨兵位没有有效数据。

const_iterator end()const
{return const_iterator(_head);
}

5. 数据修改函数

push_back函数

push_back函数为尾插函数,尾插示意图如下:

image-20230723165738795

实现尾插函数时创建一个临时变量tail,记录插入数据前的尾结点,方便进行指针指向的改动,避免因为指针指向改动而找不到正确的结点。

void push_back(const T& val)
{node* tail = _head->_prev; //记录插入数据前的尾部结点node* newnode = new node(x);tail->_next = newnode;//指针改变的顺序不影响结果newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
}

insert函数

insert函数的功能是通过迭代器,在迭代器指向的结点前插入结点,insert函数的实现只需要将传入的迭代器的前一个结点和迭代器指向的结点记录,然后将要插入的新节点进行链接。

insert函数插入结点示意图:

image-20230727131020076

void insert(iterator pos, const T x)
{node* cur = pos._node;node* prev = cur->_prev;node* newnode = new node(x);prev->_next = newnode;cur->_prev = newnode;newnode->_prev = prev;newnode->_next = cur;
}

说明: insert函数不会使迭代器失效,因为迭代器指向的结点不会随着插入而改变。

有了insert函数后,push_back函数可以改写为复用insert函数的版本:

void push_back(const T& x)
{insert(end(), x);
}

end函数返回的是有_head封装的迭代器,指向哨兵位,哨兵位的前一个结点就是尾结点,因此复用insert函数和end函数能实现尾插。

push_front函数

push_front函数的功能是头插结点,有了insert函数实现头插只需要在insert函数中传入头结点就可以实现。

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

erase函数

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);
}

pop_back函数

pop_back函数的功能是尾删,只需要复用erase函数和end函数即可,end函数返回的是哨兵位的迭代器,–得到的是尾结点的迭代器。

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

pop_front函数

pop_front函数的功能是尾删,只需要复用erase函数和begin函数即可,begin函数返回的头结点的迭代器。

void pop_front()
{erase(begin());
}

clear函数

clear函数的功能是将除了哨兵位结点外的所有结点都删除,只需要复用erase函数循环删除结点。(erase函数的实现需上翻本文)

void clear()
{iterator it = begin();while (it != end){it = erase(it); //--erase后需要重新对迭代器赋值,不然迭代器会失效。//erase(it++);  --后置++会返回++前的值,因此迭代器不会失效}
}

6. 完整代码链接

STL/List/List/List.h · 钱雪明/日常代码 - 码云 - 开源中国 (gitee.com)

相关文章:

[STL]详解list模拟实现

[STL]list模拟实现 文章目录 [STL]list模拟实现1. 整体结构总览2. 成员变量解析3. 默认成员函数构造函数1迭代器区间构造函数拷贝构造函数赋值运算符重载析构函数 4. 迭代器及相关函数迭代器整体结构总览迭代器的模拟实现begin函数和end函数begin函数和end函数const版本 5. 数据…...

C和C++的区别与联系

C语言&#xff08;C&#xff09;和C语言&#xff08;C&#xff09;是两种编程语言&#xff0c;它们之间有许多区别和联系。以下是它们之间的主要区别和联系&#xff1a; 区别&#xff1a; 历史和起源&#xff1a; C语言是由Dennis Ritchie于20世纪70年代初在贝尔实验室开发的。…...

springboot通过接口执行本地shell脚本

首先创建springboot项目 shell脚本 #!/bin/shecho Hello World&#xff01;然后编写执行shell脚本的util类 import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List;pub…...

工欲善其事必先利其器,IT工作电脑更要维护好

目录 一&#xff1a;电脑的组成 二&#xff1a;维护措施 三&#xff1a;助力记忆 一&#xff1a;电脑的组成 当谈到电脑主机时&#xff0c;我们通常指的是电脑的中央处理器(CPU)、内存、主板、电源、硬盘、显卡、声卡、网卡等核心部件组成的整体。这些部件共同协作&#xff…...

移动端个人中心UI设计

效果图 源码如下 页面设计 <template><div class"container"><!-- 顶部用户信息 start--><div class"header"><div class"user-info"><van-image class"user-img" round width"70" :sr…...

开发接口,你需要先搞懂这些概念!

SOA Service Oriented Ambiguity 即面向服务架构&#xff0c; 简称SOA。 SOA的提出是在企业计算领域&#xff0c;就是要将紧耦合的系统&#xff0c;划分为面向业务的&#xff0c;粗粒度&#xff0c;松耦合&#xff0c;无状态的服务。服务发布出来供其他服务调用&#xff0c;一…...

zookeeper常用命令

zkClient 简介 zkClient是简易的客户端程序 进入zkClient 在bin目录下输入zkCli.sh 节点命令 增 create 路径 数据 -s&#xff1a;顺序节点 -e&#xff1a;临时节点 默认情况下&#xff0c;不添加-s或者-e参数的&#xff0c;创建的是持久节点改 set 路径 数据 版本…...

亚马逊水基灭火器UL8测试报告ISO17025实验室办理

在跨境电商平台上销售的境外电商&#xff0c;在美国市场中需要提供相关的安全规范报告。其中&#xff0c;美国相关部门要求&#xff0c;如果商家未能提交UL&#xff08;Underwriters Laboratories&#xff09;标准的检测报告&#xff0c;将会被责令停止销售。而为了在亚马逊、T…...

Shell学习脚本-if多分支结构

语法&#xff1a; if 条件then指令集 else指令集 fi特殊写法&#xff1a; if [ -f "$file1" ]; then echo 1; else echo 0; fi 相当于&#xff1a; [ -f "$file1" ] && echo 1 || echo 0 多分支结构&#xff1a; if 条件then指令 elif 条件th…...

[SQL挖掘机] - 窗口函数 - lead

介绍: lead() 是一种常用的窗口函数&#xff0c;它用于获取某一行之后的行的值。它可以用来在结果集中的当前行后面访问指定列的值。 用法: lead() 函数的语法如下&#xff1a; lead(列名, 偏移量, 默认值) over (partition by 列名1, 列名2, ... order by 列名 [asc|desc]…...

PyTorch Lightning教程四:超参数的使用

如果需要和命令行接口进行交互&#xff0c;可以使用Python中的argparse包&#xff0c;快捷方便&#xff0c;对于Lightning而言&#xff0c;可以利用它&#xff0c;在命令行窗口中&#xff0c;直接配置超参数等操作&#xff0c;但也可以使用LightningCLI的方法&#xff0c;更加轻…...

2023 蓝桥杯真题B组 C/C++

https://www.dotcpp.com/oj/train/1089/ 题目 3150: 蓝桥杯2023年第十四届省赛真题-冶炼金属 题目描述 小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V&#xff0c;V 是一个正整数&#xff0c;这意味着消耗 V 个普通金 属 O…...

视频怎样分割成两段?分享几种视频分割方法

当需要制作长时间的视频时&#xff0c;将视频分割成几段可以帮助你更好地组织视频内容&#xff0c;使其更易于理解和学习。与此同时&#xff0c;将视频分割成多个小部分也可以在不影响整体视频质量的情况下将其上传到各种平台&#xff0c;并节省存储空间。此外&#xff0c;如果…...

cyber_back

1.1 话题通信 模式&#xff1a; 以发布订阅的方式实现不同节点之间数据交互的通信模式。 如图1-1所示&#xff0c;Listener-Talker通信首先创建了两个Node&#xff0c;分别是Talker Node和 Listener Node。 每个Node实例化Writer类和Reader类对Channel进行消息的读写。 Writer…...

价值 1k 嵌入式面试题-单片机 main 函数之前都做了啥?

开门见山 请说下单片机&#xff08;Arm&#xff09;在运行到 main() 函数前&#xff0c;都做了哪些工作&#xff1f; 常见问题 系统初始化工作&#xff0c;太泛泛硬件初始化&#xff0c;比较不具体 答题思路 这道题应该从两方面回答&#xff0c;一个是比较表面的硬件的初始化…...

美团2024校招6000人;伯克利博士讲Llama 2技术细节;互联网转行AIGC最全指北;技术进步周期与创客崛起 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 美团 2024 届校园招聘将录用 6000 人&#xff0c;技术类岗位扩招超 50% 美团招聘公众号宣布启动 2024 届校园招聘&#xff01;此次招聘…...

【严重】PowerJob<=4.3.3 远程代码执行漏洞

漏洞描述 PowerJob 是一款开源的分布式任务调度框架。 由于 PowerJob 未对网关进行鉴权&#xff0c;4.3.3 及之前版本中&#xff0c;未经授权的攻击者可向 /instance/detail 端点发送恶意构造的 instanceId 参数远程执行任意代码。 漏洞名称 PowerJob<4.3.3 远程代码执行漏…...

什么是 ASP.NET Core SignalR?

所有连接了 Internet 的应用程序都由服务器和客户端组成。 客户端依赖于服务器获取数据&#xff0c;而它们获取数据的主要机制是通过发出超文本传输协议 (HTTP) 请求来进行的。 某些客户端应用程序需要经常更改的数据。 ASP.NET Core SignalR 提供了一个 API&#xff0c;用于创…...

Centos/Ubuntu 替换yum/apt源?

yum/apt源如果不满足要求, 可以考虑将源替换为阿里云的源。 1.CentOS更换阿里云yum源 1. 备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base-bak.repo 2. 下载 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Cent…...

【RabbitMQ(day3)】扇形交换机和主题交换机的应用

文章目录 第三种模型&#xff08;Publish/Subscribe 发布/订阅&#xff09;扇型&#xff08;funout&#xff09;交换机Public/Subscribe 模型绑定 第四、第五种模型&#xff08;Routing、Topics&#xff09;第四种模型&#xff08;Routing&#xff09;主题交换机&#xff08;To…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】

1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件&#xff08;System Property Definition File&#xff09;&#xff0c;用于声明和管理 Bluetooth 模块相…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...