C++智能指针的原理、分类、使用

news2024/11/25 0:52:52

1. 智能指针介绍

为解决裸指针可能导致的内存泄漏问题。如:

        a)忘记释放内存;

        b)程序提前退出导致资源释放代码未执行到。

就出现了智能指针,能够做到资源的自动释放


2. 智能指针的原理和简单实现

2.1 智能指针的原理

将裸指针封装为一个智能指针类,需要使用该裸指针时,就创建该类的对象;利用栈区对象出作用域会自动析构的特性,保证资源的自动释放。

2.2 智能指针的简单实现

代码示例:

template<typename T>
class MySmartPtr {
public:
    MySmartPtr(T* ptr = nullptr):mptr(ptr) { // 创建该对象时,裸指针会传给对象
    }

    ~MySmartPtr() {  // 对象出作用域会自动析构,因此会释放裸指针指向的资源
        delete mptr;
    }
    
    // *运算符重载
    T& operator*() {  // 提供智能指针的解引用操作,即返回它包装的裸指针的解引用
        return *mptr; 
    }

    // ->运算符重载
    T* operator->() { // 即返回裸指针
        return mptr;
    }
private:
    T* mptr;
};

class Obj {
public:
    void func() {
        cout << "Obj::func" << endl;
    }
};

void test01() {

    /*创建一个int型的裸指针,
    使用MySmartPtr将其封装为智能指针对象ptr,ptr对象除了作用域就会自动调用析构函数。
    智能指针就是利用栈上对象出作用域自动析构这一特性。*/
    MySmartPtr<int> ptr0(new int);
    *ptr0 = 10;

    MySmartPtr<Obj> ptr1(new Obj);
    ptr1->func();
    (ptr1.operator->())->func(); // 等价于上面

    /*  中间异常退出,智能指针也会自动释放资源。
    if (xxx) {
        throw "....";
    }
    
    if (yyy) {
        return -1;
    }
    */
}

3. 智能指针分类

3.1 问题引入

 接着使用上述自己实现的智能指针进行拷贝构造:

void test02() {
    MySmartPtr<int> p1(new int); // p1指向一块int型内存空间
    MySmartPtr<int> p2(p1);      // p2指向p1指向的内存空间
    
    *p1 = 10;   // 内存空间的值为10
    *p2 = 20;   // 内存空间的值被改为20
}

但运行时出错:

原因在于p1和p2指向同一块int型堆区内存空间,p2析构将该int型空间释放,p1再析构时释放同一块内存,则出错。

那可否使用如下深拷贝解决该问题?

MySmartPtr(cosnt MySmartPtr<T>& src) {
    mptr = new T(*src.mptr);
}

不可以。因为按照裸指针的使用方式,用户本意是想将p1和p2都指向该int型堆区内存,使用指针p1、p2都可改变该内存空间的值,显然深拷贝不符合此场景。


3.2 两类智能指针

不带引用计数的智能指针:只能有一个指针管理资源。

        auto_ptr;

        scoped_ptr;

        unique_ptr;.

带引用计数的智能指针:可以有多个指针同时管理资源。

        shared_ptr;强智能指针。

        weak_ptr: 弱智能指针。这是特例,不能控制资源的生命周期,不能控制资源的自动释放!


3.3 不带引用计数的智能指针

只能有一个指针管理资源。

3.3.1 auto_ptr (不推荐使用)

void test03() {
	auto_ptr<int> ptr1(new int);
	auto_ptr<int> ptr2(ptr1);
	*ptr2 = 20;
	// cout << *ptr2 << endl; // 可访问*ptr2
	cout << *ptr1 << endl; //访问*ptr1却报错
}

如上代码,访问*ptr1为何报错?

因为调用auto_ptr的拷贝构造将ptr1的值赋值给ptr2后,底层会将ptr1指向nullptr;即将同一个指针拷贝构造多次时,只让最后一次拷贝的指针管理资源,前面的指针全指向nullptr

不推荐将auto_ptr存入容器。

3.3.2 scoped_ptr (使用较少)

scoped_ptr已将拷贝构造函数赋值运算符重载delete了。

scoped_ptr(const scoped_ptr<T>&) = delete; // 删除拷贝构造
scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;  // 删除赋值重载

3.3.3 unique_ptr (推荐使用)

unique_ptr也已将拷贝构造函数赋值运算符重载delete

unique_ptr(const unique_ptr<T>&) = delete; // 删除拷贝构造
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;  // 删除赋值重载

unique_ptr提供了右值引用参数的拷贝构造函数赋值运算符重载,如下:

void test04() {
	unique_ptr<int> ptr1(new int);
	// unique_ptr<int> ptr2(ptr1);  和scoped_ptr一样无法通过编译
	unique_ptr<int> ptr2(std::move(ptr1)); // 但可使用move得到ptr1的右值类型
    // *ptr1  也无法访问
}

3.4 带引用计数的智能指针

可以有多个指针同时管理资源。

原理:给智能指针添加其指向资源的引用计数属性,若引用计数 > 0,则不会释放资源,若引用计数 = 0就释放资源。

具体来说:额外创建资源引用计数类,在智能指针类中加入该资源引用计数类的指针作为其中的一个属性;当使用裸指针创建智能指针对象时,创建智能指针中的资源引用计数对象,并将其中的引用计数属性初始化为1,当后面对该智能指针对象进行拷贝(使用其他智能指针指向该资源时)或时,需要在其他智能指针对象类中将被拷贝的智能指针对象中的资源引用计数类的指针获取过来,然后将引用计数+1;当用该智能指针给其他智能指针进行赋值时,因为其他智能指针被赋值后,它们就不指向原先的资源了,原先资源的引用计数就-1,直至引用计数为0时delete掉资源;当智能指针对象析构时,会使用其中的资源引用计数指针将共享的引用计数-1,直至引用计数为0时delete掉资源。

shared_ptr:强智能指针;可改变资源的引用计数

weak_ptr:弱智能指针;不可改变资源的引用计数

带引用计数的智能指针的简单实现:

/*资源的引用计数类*/
template<typename T>
class RefCnt {
public:
    RefCnt(T* ptr=nullptr):mptr(ptr) {
        if (mptr != nullptr) {
            mcount = 1; // 刚创建指针指针时,引用计数初始化为1
        }
    }

    void addRef() {  // 增加引用计数
        mcount++;
    }

    int delRef() {   // 减少引用计数
        mcount--;
        return mcount;
    }
private:
    T* mptr;  // 资源地址
    int mcount; // 资源的引用计数
};

/*智能指针类*/
template<typename T>
class MySmartPtr {
public:
    MySmartPtr(T* ptr = nullptr) :mptr(ptr) { // 创建该对象时,裸指针会传给对象
        mpRefCnt = new RefCnt<T>(mptr);
    }

    ~MySmartPtr() {  // 对象出作用域会自动析构,因此会释放裸指针指向的资源
        if (0 == mpRefCnt->delRef()) {
            delete mptr;
            mptr = nullptr;
        }
    }

    // *运算符重载
    T& operator*() {  // 提供智能指针的解引用操作,即返回它包装的裸指针的解引用
        return *mptr;
    }

    // ->运算符重载
    T* operator->() { // 即返回裸指针
        return mptr;
    }

    // 拷贝构造
    MySmartPtr(const MySmartPtr<T>& src):mptr(src.mptr),mpRefCnt(src.mpRefCnt) {
        if (mptr != nullptr) {
            mpRefCnt->addRef();
        }
    }

    // 赋值重载
    MySmartPtr<T>& operator=(const MySmartPtr<T>& src) {
        if (this == &src) // 防止自赋值
            return *this;

        /*若本指针改为指向src管理的资源,则本指针原先指向的资源的引用计数-1,
        若原资源的引用计数为0,就释放资源*/
        if (0 == mpRefCnt->delRef()) {  
            delete mptr;
        }

        mptr = src.mptr;
        mpRefCnt = src.mpRefCnt;
        mpRefCnt->addRef();
        return *this;
    }
private:
    T* mptr;  // 指向资源的指针
    RefCnt<T>* mpRefCnt; // 资源的引用计数
};

3.4.1 shared_ptr

强智能指针。可改变资源的引用计数。

(1)强智能指针的交叉引用问题

class B;

class A {
public:
    A() {
        cout << "A()" << endl;
    }
    ~A() {
        cout << "~A()" << endl;
    }
    shared_ptr<B> _ptrb;
};

class B {
public:
    B() {
        cout << "B()" << endl;
    }
    ~B() {
        cout << "~B()" << endl;
    }
    shared_ptr<A> _ptra;
};

void test06() {
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());

    pa->_ptrb = pb;
    pb->_ptra = pa;

    /*打印pa、pb指向资源的引用计数*/
    cout << pa.use_count() << endl;
    cout << pb.use_count() << endl;
}

输出结果:

 

可见pa、pb指向的资源的引用计数都为2,因此出了作用域导致pa、pb指向的资源都无法释放,如下图所示:

解决: 

建议定义对象时使用强智能指针,引用对象时使用弱智能指针,防止出现交叉引用的问题。

什么是定义对象?什么是引用对象?

定义对象:

        使用new创建对象,并创建一个新的智能指针管理它。

引用对象:

        使用一个已存在的智能指针来创建一个新的智能指针。

        定义对象和引用对象的示例如下:

shared_ptr<int> p1(new int());              // 定义智能指针对象p1
shared_ptr<int> p2 = make_shared<int>(10);  // 定义智能指针对象p2

shared_ptr<int> p3 = p1;  // 引用智能指针p1,并使用p3来共享它
weak_ptr<int> p4 = p2;    // 引用智能指针p2,并使用p4来观察它

如上述代码,因为在test06函数中使用pa对象的_ptrb引用pb对象,使用pb对象的_ptra引用pa对象,因此需要将A类、B类中的_ptrb_ptra的类型改为弱智能指针weak_ptr即可,这样就不会改变资源的引用计数,能够正确释放资源。

3.4.2 weak_ptr 

弱智能指针。不可改变资源的引用计数。不能创建对象,也不能访问资源(因为weak_ptr未提供operator->operator*重载运算符),即不能通过弱智能指针调用函数、不能将其解引用。只能从一个已有的shared_ptr或weak_ptr获得资源的弱引用。

弱智能指针weak_ptr若想用访问资源,则需要使用lock方法将其提升为一个强智能指针,提升失败则返回nullptr。(提升的情形常使用于多线程环境,避免无效的访问,提升程序安全性)

注意:弱智能指针weak_ptr只能观察资源的状态,但不能管理资源的生命周期,不会改变资源的引用计数,不能控制资源的释放。

weak_ptr示例:

void test07() {
    shared_ptr<Boy> boy_sptr(new Boy());
    weak_ptr<Boy> boy_wptr(boy_sptr);
    // boy_wptr->study(); 错误!无法使用弱智能指针访问资源
    cout << boy_sptr.use_count() << endl; // 引用计数为1,因为弱智能指针不改变引用计数

    shared_ptr<int> i_sptr(new int(99));
    weak_ptr<int> i_wptr(i_sptr);
    // cout << *i_wptr << endl; 错误!无法使用弱智能指针访问资源
    cout << i_sptr.use_count() << endl; // 引用计数为1,因为弱智能指针不改变引用计数

    /*弱智能指针提升为强智能指针*/
    shared_ptr<Boy> boy_sptr1 = boy_wptr.lock();
    if (boy_sptr1 != nullptr) {
        cout << boy_sptr1.use_count() << endl; // 提升成功,引用计数为2
        boy_sptr1->study(); // 可以调用
    }

    shared_ptr<int> i_sptr1 = i_wptr.lock();
    if (i_sptr1 != nullptr) {
        cout << i_sptr1.use_count() << endl; // 提升成功,引用计数为2
        cout << *i_sptr1 << endl; // 可以输出
    }  
}

4. 智能指针与多线程访问共享资源的安全问题

有如下两种启动线程的方式:

方式1:主线程调用test08函数,在test08函数中启动子线程执行线程函数,如下:

void handler() {
	cout << "Hello" << endl;
}

void func() {
	thread t1(handler);
}

int main(int argc, char** argv) {
	func();
	this_thread::sleep_for(chrono::seconds(1));
	system("pause");
	return 0;
}

运行报错:

 方式2:主线程中直接创建子线程来执行线程函数,如下:

void handler() {
	cout << "Hello" << endl;
}

int main(int argc, char** argv) {
    thread t1(handler);
	this_thread::sleep_for(chrono::seconds(1));
	system("pause");
	return 0;
}

运行结果:无报错

 

上面两种方式旨在通过子线程调用函数输出Hello,但为什么方式1就报错了?很简单,不再赘述。


回归本节标题的正题,有如下程序:

class C {
public:
    C() {
        cout << "C()" << endl;
    }

    ~C() {
        cout << "~C()" << endl;
    }

    void funcC() {
        cout << "C::funcC()" << endl;
    }
private:

};

/*子线程执行函数*/
void threadHandler(C* c) {
    this_thread::sleep_for(chrono::seconds(1));
    c->funcC();
}

/* 主线程 */
int main(int argc, char** argv) {
    C* c = new C();
    thread t1(threadHandler, c);
    delete c;
	t1.join();
	return 0;
}

运行结果:

结果显示c指向的对象被析构了,但是仍然使用该被析构的对象调用了其中的funcC函数,显然不合理。

因此在线程函数中,使用c指针访问A对象时,需要观察A对象是否存活

使用弱智能指针weak_ptr接收对象,访问对象之前尝试提升为强智能指针shared_ptr,提升成功则访问,否则对象被析构。

情形1:对象被访问之前就被析构了:

class C {
public:
    C() {
        cout << "C()" << endl;
    }

    ~C() {
        cout << "~C()" << endl;
    }

    void funcC() {
        cout << "C::funcC()" << endl;
    }
private:

};

/*子线程执行函数*/
void threadHandler(weak_ptr<C> pw) {  // 引用时使用弱智能指针
    this_thread::sleep_for(chrono::seconds(1));
    shared_ptr<C> ps = pw.lock();  // 尝试提升
    if (ps != nullptr) {
        ps->funcC();
    } else {
        cout << "对象已经析构!" << endl;
    }
}

/* 主线程 */
int main(int argc, char** argv) {
    {
        shared_ptr<C> p(new C());
        thread t1(threadHandler, weak_ptr<C>(p));
        t1.detach();
    }
    this_thread::sleep_for(chrono::seconds(5));
	return 0;
}

运行结果:

情形2: 对象访问完才被析构:

class C {
public:
    C() {
        cout << "C()" << endl;
    }

    ~C() {
        cout << "~C()" << endl;
    }

    void funcC() {
        cout << "C::funcC()" << endl;
    }
private:

};

/*子线程执行函数*/
void threadHandler(weak_ptr<C> pw) {  // 引用时使用弱智能指针
    this_thread::sleep_for(chrono::seconds(1));
    shared_ptr<C> ps = pw.lock();  // 尝试提升
    if (ps != nullptr) {
        ps->funcC();
    } else {
        cout << "对象已经析构!" << endl;
    }
}

/* 主线程 */
int main(int argc, char** argv) {
    {
        shared_ptr<C> p(new C());
        thread t1(threadHandler, weak_ptr<C>(p));
        t1.detach();
        this_thread::sleep_for(chrono::seconds(5));
    }  
	return 0;
}

运行结果:

 很显然shared_ptr与weak_ptr结合使用,能够较好地保证多线程访问共享资源的安全。


5.智能指针的删除器deleter

删除器是智能指针释放资源的方式,默认使用操作符delete来释放资源。

但并非所有智能指针管理的资源都可通过delete释放,如数组、文件资源、数据库连接资源等。

有如下智能指针对象管理一个数组资源:

unique_ptr<int> ptr1(new int[100]);

此时再用默认的删除器则会造成资源泄露。因此需要自定义删除器。

/* 方式1:类模板 */
template<typename T>
class MyDeleter {
public:
    void operator()(T* ptr) const {
        cout << "数组自定义删除器1." << endl;
        delete[] ptr;
    }
};

/* 方式2:函数 */
void myDeleter(int* p) {
    cout << "数组自定义删除器2." << endl;
    delete[] p;
}

void test09() {
    unique_ptr<int, MyDeleter<int>> ptr1(new int[100]);
    unique_ptr<int, void(*)(int*)> ptr2(new int[100], myDeleter);

    /* 方式3:Lambda表达式 */
    unique_ptr<int, void(*)(int*)> ptr3(new int[100], [](int* p) {
        cout << "数组自定义删除器3." << endl;
        delete[] p;
        });
}

void test10() {
    unique_ptr<FILE, void(*)(FILE*)> ptr2(fopen("1.txt", "w"), [](FILE* f) {
        cout << "文件自定义删除器." << endl;
        fclose(f);
        });
}

运行结果:

 待补充

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

『python爬虫』19. aiohttp模块应用之下载图片(保姆级图文)

目录 1. aiohttp库安装2. 代码解析3. 实现代码总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 通过爬取下载得到图片 1. aiohttp库安装 pip install aiohttp这个库的作用基本上和request一致&#xff0c;理解…

SpringBoot——RUST风格以及如何快速发送不同方式的请求

RUST风格&#xff1a; 简单来说&#xff0c;RUST就是一种将请求方式融合到路径中的一种请求路径书写风格&#xff0c;注意这里是风格&#xff0c;不是规定&#xff0c;我们也可以不使用他或者不是非常严格的按照他规定的样式来写&#xff0c;但是由于行业中大多数的人在编程的…

Firefox 112 发布:右键单击显示密码、改进标签管理等!

Firefox 发布 112 版本&#xff0c;为我们带来了显示密码的新选择、同时改进了标签管理等。 Mozilla 于 2023 年 4 月 11 日发布了 Firefox 112&#xff0c;新版本包含一些令人兴奋的新功能和改进。该浏览器的最新版本带来了一些功能以增强用户体验和提高性能。 在 Ubuntu 上运…

UNIAPP实战项目笔记69 订单确认时显示为默认地址

UNIAPP实战项目笔记69 订单确认时显示为默认地址 思路 需要用到vuex 默认显示isDefault为1的地址 案例截图 订单结算页面 地址页面 代码 shopcart.vue <template><view class"shop-cart"><template v-if" list.length > 0 "><!-…

【Linux学习笔记】设备驱动模型详解——总线、设备、驱动和类

学习内容 设备驱动模型视频讲解 简介 设备驱动是计算机系统中的重要组成部分&#xff0c;它们允许操作系统与硬件交互。设备驱动模型是一种通用的抽象框架&#xff0c;用于描述操作系统如何管理硬件设备。这里我们将介绍设备驱动模型中的四个关键概念&#xff1a;总线、设备…

GitLab+Drone CI持续集成自动部署web项目

一、环境介绍 1.gitlab服务器 172.16.11.1 搭建参考&#xff1a;Gitlab教程 2.Drone服务器 172.16.11.2 搭建参考: 基于gitlab搭建Drone CI 3.web服务器 172.16.11.3 已配置好nginx相关web服务&#xff0c;这里nginx配置的web目录为/www/test 还有生成ssh key密钥即可&am…

编程的未来

从 ChatGPT 诞生至今&#xff0c;在程序员的圈子里&#xff0c;我们一直有两种讨论&#xff1a; 最开始所恐慌的&#xff1a;编程没有未来&#xff0c;ChatGPT 是不是要取代程序员。编程的方式前所未有地发生了变化。 现如今&#xff0c;GitHub Copilot Chat 可以让开发者们直…

算法修炼之练气篇——练气十八层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

支付系统设计三:渠道网关设计02-客户端报文解析

文章目录 前言一、后台配置管理1.1 渠道配置1.1.1 渠道基本信息新增1.1.2 渠道交易类型配置1.1.3 渠道商户信息配置1.1.4 账户配置1.1.5 交易类型机构配置 1.2 渠道通讯配置1.2.1 内部渠道通讯1.2.1 外部渠道通讯 1.3 资源配置1.4 证书管理1.5 路由配置 二、运行时逻辑处理1. 控…

Fluent的视角设置

1 背景 针对不同设计方案在同一工况下的差异点进行细节分析&#xff0c;其中一个很重要的要求就是需要在同一视角下比较不同设计方案的差异性。设置视角并进行快速重用是很重要的提高仿真效率的方法。 2 视角设置 视角设置有2类方法&#xff1a;手动拖拉和定量指定。 手动拖拉即…

一图看懂 tomli 模块:一个 TOML解析器、使用 mformat -toc 生成的目录,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 tomli 模块&#xff1a;一个 TOML解析器、使用 mformat -toc 生成的目录&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1f9ca;模块图&am…

前端开发中,定位bug的几种常用方法

目录 第一章 前言 第二章 解决bug的方法 2.1 百度 2.2 有道翻译 2.3 debugger 2.4 console.log 日志打印 2.5 请求体是否携带参数 2.6 注释页面渲染代码 2.7 其他 第三章 尾声 备注&#xff1a;该文章只是本人在工作/学习中常用的几种方法&#xff0c;如果有不对大家…

智慧园区移动应用发展面临瓶颈,如何解决?

智慧园区移动应用将在多元化服务、生态建设、智能化管理和跨界融合等方面发展&#xff0c;成为园区管理和服务的重要手段之一&#xff0c;为员工和企业提供更加智能化和便捷化的管理和服务。伴随着智慧城市的建设和智慧园区的崛起&#xff0c;智慧园区数字一体化建设成为园区发…

docker搭建nginx负载均衡

一点小背景 docker起了几个服务&#xff0c;没有配置端口映射&#xff0c;导致不能通过网络访问。当然&#xff0c;更简单的方式是加端口映射&#xff0c;笔者的情况更复杂一些&#xff0c;就想到了用nginx映射一下。 Nginx&#xff08;发音同“engine X”&#xff09;是异步框…

避免“文献综抄”,5种写作结构助你完成文献综述→

很多作者可能有过这样的体验&#xff1a;读了很多文献&#xff0c;但在写综述的时候总感觉不像是在写文献综述&#xff0c;更像在写文献总结 如果引用方面不注意&#xff0c;甚至会成为文献综抄。 那么&#xff0c;你可以参考下我们整理的以下资料哦~ 01 文献总结和文献综述的…

DCDC反馈电阻的阻值如何取值?

DCDC芯片的反馈电阻 下图为我们公司现在常用的两款DCDC芯片&#xff0c;TPS54335ADDA/TI和LMR14050SDDA/TI。 其中RFBT和RFBB都是反馈电阻&#xff0c;可以通过调节这两个电阻的比值来输出 预期电压。 FBT&#xff1a;feedback top&a…

保姆级丨XAMPP安装使用

0x00 前言 XAMPP 是一个完全免费&#xff0c;易于安装的 Apache 发行版&#xff0c;包含 MariaDB &#xff0c; PHP 和 Perl 。 XAMPP 开源软件包已经设置为非常容易安装和使用。 0x01 环境说明 Windows 11xampp-windows-x64-8.2.4-0-VS16-installer 0x02 准备工作 首先要访问…

数据库分区;pgAdmin操作pgsql分区;修改pgsql数据库名字

目录 分区 什么是分区 分区的优势 pgAdmin操作pgsql分区 创建父表 创建分区 数据入库分区 扩展&#xff08;按天创建分区脚本&#xff09; 修改数据库名字 链接 分区 什么是分区 指将一个大的表或索引分成多个小的、独立的部分&#xff0c;每个部分称为一个分区&#x…

带你了解家居智能的心脏:物联网关

本文将介绍家庭物联网关的相关内容&#xff0c;将明白物联网关在家庭这个场景当中的应用。现在市面上各种各样的智能家居的家电或者其他设备非常多&#xff0c;那么这就需要一个智能的设备去对所有的家电进行管控。这样一个设备就是家庭智能网关&#xff0c;家庭物联网关是家居…

Py之onnx:onnx/onnxruntime库的简介、安装、使用方法之详细攻略

Py之onnx&#xff1a;onnx/onnxruntime库的简介、安装、使用方法之详细攻略 目录 onnx/onnxruntime库的简介 onnx/onnxruntime库的安装 onnx/onnxruntime库的使用方法 1、基础用法 onnx/onnxruntime库的简介 Open Neural Network Exchange&#xff08;ONNX&#xff09;是一…