C++代码优化(3):条款13~17

news2024/12/25 16:38:15

"野性袒露着灵魂纯粹"


条款13:以对象管理资源

(1)什么是资源?

C++中最常使用的资源就是动态内存分配,在系统编程层面上,文件描述符(fd)、互斥锁(mutex)、套接字网络socket……不管是哪一种资源,重要的是,你不使用时,就应该将其释放掉。

(2)谈谈智能指针

C++11中引入了这种方式来对类对象资源的管理。

我们在此基础上使用库文件提供的智能指针,对代码进行优化。

void f()
{
    //调用工厂factory函数 经由auto_ptr管理控制该资源
    std::auto_ptr<Investment> pInv(Investment::GetInstance());
    //Investment* ptr = Investment::GetInstance();
    //delete ptr;
}

auto_ptr的析构函数,会在其析构函数中自动调用管理对象的delete,从而能够有效避免f函数潜在的资源泄漏的问题。

用"对象管理资源",我们得出了两个关键想法:
①获得资源后,立即将资源放入对象中管理。
②管理对象运用析构函数确保资源释放。

勿乱copy智能指针:

由于auto_ptr对象在出了自己的作用域时,会去调用其析构函数,并delete掉自己所指向的对象。如果我们让多个auto_ptr指向同一个对象呢?? 那是否意味着会 对同一个对象调用多次析构函数???显然是这样的!但是auto_ptr绝不会让我们进行这样的操作。

"要让auto_ptr通过copy构造或者copy assignment操作复制它们,它们就会变为null。而复制所得的指针才拥有取得资源的唯一拥有权!"

计数型智能指针:

auto_ptr的复制行为,只能产生唯一一个指针对对象拥有管理权力,即"受到auto_ptr管理的对象,在这之前必须没有任何一个以上的auto_ptr同时指向它"。显然,auto_ptr对于动态分配资源的管理,并非神兵利器!举个例子map的例子,红黑树的结点通过new出来的,但是map是支持深拷贝的(STL容器都允许这样),那么这些容器显然不能使用auto_ptr管理其元素对象。

替代auto_ptr的方案是,引入"计数型智能指针"(RCSP)。RCSP工作的原理是,持续追踪有多少个对象指向某笔资源,并在无人指向它时,自动删除该资源。

但,shared_ptr无法打破环状引用。(例如一个对象它们互相指向彼此)。当然此种情况并非本篇所要细讲的。

勿用智能指针管理动态分配的array:

auto_ptr 和 shared_ptr两者都在析构函数内做的是delete 而不是 delete[]。

//这两种形式 都是不可取的
std::auto_ptr<std::string> 
            ptr(new std::string[10]);
std::shared_ptr<int> spi(new int[1024]);

编译器虽然能让我们编译通过,但采取这样的行为,一定是个馊主意。

(3)开胃菜

我们建立模拟一个投资的行为。并且采用工厂设计模式,对外提供一个获取该类对象的接口函数。

class Investment
{
public:
    static Investment* GetInstance()
    {
        return new Investment();         //获取指针
    }
};

void f()
{ 
    Investment* ptr = Investment::GetInstance();  //获取指针
    //...进行一系列操作     
    delete ptr;        //对new 申请的资源进行释放
}

f()

从代码本身来看,似乎没什么明显的错误。我们动态申请了一块内存空间指向Investment类对象,并在使用完成后对其进行了 delete操作。但问题是真的没有隐含的危险吗??

例举以下情况,可能造成delete释放资源失败:
①或许在 "..."区域, 过早进行了return。一旦函数return后,return之后的语句也不会被执行流执行了。
②在"..."区域进行了 抛异常,很不幸捕获异常的地方不在该函数内。
……

当这些情况出现时,我们很难对曾经向系统申请的资源进行有效地管理释放。因此,单纯依赖、相信着f()函数会执行到"delete ptr"这一步是很不可靠的!

"以对象管理资源"提出的设计模式很像一个类对象的析构函数。什么情况下意味着该内存资源对象应该被释放呢?答案是,"出了f()的作用域后"。

而这种利用对象的生命周期来控制资源管理的技术,又叫做,"RAII"。也叫做智能指针。

auto_ptr:

std::auto_ptr<Investment> pInv1(Investment::GetInstance());                 

shared_ptr:

std::shared_ptr<Investment> pInv1(Investment::GetInstance()); 

最后,单纯地用工厂函数返回Investment* (raw pointer),本身就是造成资源泄漏的前提。调用者本身就可能忘记对这个指针的delete。更好的做法是,在返回对象指针的函数体内,就将该指针放入到智能指针里进行管理。

请记住:
1.为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
2.两个常被使用的RAIII classes分别是auto_ptr \ shared_ptr。前者是较佳选择,因为其copy行为比较直观。若选择auto_ptr赋值动作会使它们指向null。


条款14:在资源管理类中小心copying行为

在条款13引入了这样一个观念,"资源取得时机就是初始化时机。"。事实上很多系统资源并非像heap-based那样,我们可以用auto_ptr 和 shared_ptr对资源进行释放管理。既然如此,有时候就需要,我们自己去实现一个资源管理的类。

例如,我们使用一个基于C的API接口函数,该处理函数是处理Mutex互斥对象的锁,并提供了lock、unlock函数。

void lock(Mutex* pm);     //锁定互斥锁
void unlock(Mutex* pm);   //解除锁定

我们为了防止调用者忘记unlock,解除释放掉锁资源,并为管理Mutex而设计一个Lock类

class Lock
{
public:
    Lock(Mutex* pm)
        :mutexptr(pm){
        lock(mutexptr); //构造函数时 锁定互斥锁
    }
    ~Lock(){
        unlock(mutexptr);  //析构函数时 解除锁
    }
    Mutex* mutexptr;
};

//现在用户对lock进行调用
Mutex m; //创建锁对象

{
   Lock ml(&m); // 锁定互斥锁
} //释放资源

我们可以看到,当客户按照这样的思路定义使用互斥锁时,当不再使用互斥锁时,因为对象出作用域自动调用析构函数的机制,Lock会为调用着自动解除锁。

如果此时Lock对象发生拷贝复制会发生什么?我们面对的问题是什么?

Lock m1(&m);
Lock m2(m1); //拷贝构造

禁止复制:

很多情况下,让RAII对象被复制就是不合理的做法。对于像Lock这样的一个class,一个已经被加锁的对象,只是因为你允许类的拷贝,又会再一次对这个对象进行了一次加锁,并且在出了Lock对象的作用域时,原管理对象会调用自己的析构函数,但又因为支持拷贝构造的缘故,它的副本又会去进行一次锁释放!因此,如果RAII的赋值操作不合理,你就应该禁止掉!

我们可以参考将类对象的拷贝构造声明为私有,C++11引入的 delete关键字禁用掉,或者采用类继承的方式,私有继承一个不能拷贝的基类。

class Lock
{
private:
    //1.声明私有
    //Lock(const Lock&);
    //Lock& operator=(const Lock&);

    //2.delete 
    Lock(const Lock&) = delete;
    Lock& operator=(const Lock&) = delete;
}

//3.继承防拷贝的基类
class Lock:private Uncopyable
{}

对底层资源祭出"引用计数法":

但有时候,我们又需要保有资源,直到最后一个使用者不再使用时,才将它进行销毁。这种情况下的对RAII对象复制拷贝时,我们仅希望于该资源的"引用"递增,我们看到的shared_ptr就是这样处理拷贝的。

如果对于上述Lock对象里,我们想实现引用计数的RAII管理,即允许Lock类对象进行拷贝。虽然shared_ptr的默认行为是,当没有对象引用时,会直接做出释放资源的动作,而我们想要的不是删除Mutex,而是释放Mutex。不过,shared_ptr区别于auto_ptr的是,它提供了所谓的"定制删除器"。

我们可以更改当引用对象为0时,"释放资源"更改为让该资源"解除锁定"。

class Lock
{
public:
    Lock(Mutex* pm)
        :mutexptr(pm,unlock) //填写定制删除器
    {
        lock(mutexptr.get()); //获取 raw pointer
    }
    //使用shared_ptr 替换原始的 raw pointer
    std::shared_ptr<Mutex> mutexptr;
};

此时还需要析构函数嘛??不需要!当shared_ptr引用的次数为0时,会去自动调用其指定的删除器(unlock)。不过这不是说你轻松了许多,你只是依赖了编译器的默认行为罢了。

为此,你总希望你的 RAII对象能够根据不同的场景,做出合理的选择。

复制底部资源:

如果你希望这一份资源拥有任意数量的副本,当你不需要使用这个副本时,又需要确保其资源得以释放。在这样的情况下,那么你在复制其资源时,也得同样拷贝一份管理该资源的对象。

很现实的例子,我们向heap内存区申请空间,存放一系列string类型,这些字符串内含一个指向heap区的指针,并通过返回指向这些指向字符串的起始地址的指针供使用者管理。当这样的字符串发生拷贝时,不论其指针所指向的内存空间内容,还是该指针,都将会被制作成一个副本。这种行为也叫做"深拷贝"。

转移底部资源拥有权:

如果你就单纯希望,确保这一个未加工的资源始终永远只会受到一个RAII对象管理。那么RAII对象的复制拷贝,应该参照像auto_ptr那样,此时的"拥有权会从被复制物转移到目标物上"。

当然如果你厌恶RAII对象的管理中出现copying 的行为,你就应该明确地拒绝!

请记住:
1.复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
2.普遍而常见的RAII class的copying行为是: 抑制copying \ 施行引用计数法。不过其他行为也都可能被实现。


条款15:在资源管理类中提供对原始资源的访问

看完上面的好几条条款,emm资源管理类很棒!它可以确保我们在"正常使用它"的情况下,协助调用者进行系统资源的管理。我们处理资源的动作,变为与维护、管理这个资源的classes类进行互动。不过,大千世界无奇不有,可能你会遇到很多种API接口直接指涉原始资源(raw pointer),除非你发誓你不会用这些APIs,否则我们不得不绕过资源管理类对象,直接访问其包含的原始资源。

(1)获取资源管理类中的原始资源

当然,在RAII中提供对管理对象的原始资源,并非空穴来风。在C++标准库中,智能指针都会提供一个叫get()的函数,用来获取包裹下的原始资源。

我们回到条款13举例的类

class Investment
{
public:
    static Investment* GetInstance()
    {
        return new Investment();
    }
};

int daysHeld(const Investment* pi) //返回天数
{
    return 0;
}

int main()
{
    std::shared_ptr<Investment> pInv(Investment::GetInstance());
    int days = daysHeld(pInv);  错误! 类型不匹配
    return 0;
}

①auto_ptr\shared_ptr提供的get()方法

    int days = daysHeld(pInv.get());

通过get()函数获取RAII管理对象的原始资源,这也叫做显示类型转换

②auto_ptr\shared_ptr的operator重载

智能指针重载了(operator-> 或者 operator*),他们允许隐式类型转换成底部的原始指针。

③class提供的函数转换

很多情况下,我们必须取得RAII对象内的原始资源,由此,RAII的一些设计者干脆提供一个隐式类型转换的函数。我们来看看下面的RAII class。

FrontHandle getFont(){}            //C API接口
void realseFont(FrontHandle fh){}  // C API接口

class Font
{
public:
    Font(FrontHandle f)  //pass-by-value
        :fh(f)
    {}

    ~Font() { realseFont(fh); } 
private:
    FrontHandle fh;    //原始raw
};

假设客户大量的调用函数与C API接口相关,也就意味着它们需要频繁地处FrontHandle(原始资源),为此,Font class可以为之提供诸如auto_ptr \ shared_ptr一样的get()方法。

class Font
{
public:  
    //...  
    FrontHandle get() { return fh; }
    //...
};

void changeFontSize(FrontHandle f, int newSize) {}
int main()
{
    int newSize;
    Font f(getFont());

    changeFontSize(f.get(), newSize); //显示转换
    return 0;
}

虽然这个提供get()方法,能够有效解决,"Font -> FrontHandle"的转换,但是如此般地显示转换未免显得太麻烦。麻烦的事情,总会让人容易犯错。

另一种方法,是令Font提供隐式类型转换函数,转换为FrontHandle。

class Font
{
public:  
    //...      
    operator FrontHandle() const
    {
        return fh; //隐式类型转换
    }
    //...
}
int main()
{
    Font f(getFont()); 
    int newSize;
    changeFontSize(f, newSize);     //Font隐式转换为FrontHandle
}

不过,隐式类型转换也会增加发生错误的机会。例如,客户创建Font时,意外创建FrontHandle。

int main()
{
    Font f(getFont());
    //...
    //愿意是让f1 去构造一个f2的类对象
    //但实际上却是让f1 隐式类型转换为底部的FrontHandle 并复制它
    FrontHandle f2 = f1; 
    return 0;
}

由此,一旦f1被销毁后,f2其实也只是徒有虚名。原始资源已经被释放掉,它因而成为"虚吊"。

因此,是否该为一个RAII对象提供一个显示转换函数,还是隐式类型转换函数,拿到底层的原始资源,主要取决于RAII被设计执行的工作情况。

通过get()方法显示类型转换得到原始资源也许更加地受欢迎,并将"非有意转换"的可能性降到最小化。然而有时候,隐式类型转化的"自然而然"又会引发天平的倾斜。

不过,获取原始资源 != 破坏了封装。RAII对象类的出现不是为了实现封装,而是为了实现资源的有效管理,确保资源释放的行为发生。此外也有某些RAII classes结合十分松散的底层资源封装,其目的就是为了获得真正的原始资源。例如,shared_ptr将引用计数封装了起来,但是外界可以更容易访问到其内部所含的指针。

请记住:
1.APIs往往要求访问原始资源,所以RAII class应该提供一个"取得其所管理之资源"的办法。
2.对原始资源的访问可能经由显示转换或隐式转换,一般而言显示转换比较安全,但隐式转换对于客户比较方便。


条款16:成对使用new和delete时采用相同形式

我们来看看下述代码:

int main()
{
    std::string* StringArray = new std::string[100];
    //....
    delete StringArray;

    return 0;
}

唔……我们使用操作符new向堆空间申请一批空间,也配套使用了delete。但程序运行起来就出问题了!

当你使用new时:
第一件事,内存被分配出来(new 在底层会转化为operator new())。
第二件事,针对此内存,会一次(多次)去调用构造函数。

当你使用delete时:
第一件事,针对此内存,会一次(多次)调用析构函数。
第二件事,内存被释放掉(delete 在底层会转化为operator delete())。

因此,上述现象最大的问题在于,到底调用一次还是多次(构造\析构)函数。换句话说,该指针,指向的这块即将被释放掉的内存资源,是单个对象 或是 数组对象?

从现象来看,我们给delete 填上"[]",问题也就能够解决了。

由此:
当你调用new时 使用 [] ,那么你在delete时也要带上[]。
反之如果你在调用new时没有使用[] ,那么delete 也就不需要使用[]。

尽量不要对数组进行typedef:

我想开辟一个类型为int,个数为4个的数组。

typedef std::string AddressLine[4];

int main()
{
    std::string* sptr = new AddressLine; //类型是什么??
    delete[] sptr; //正常释放
    delete sptr; //结果未定义
}

出现上述代码的动作,让使用者难以区分到底改用delete[] 还是 delete。为此,还是少用为好,比较STL提供了vector这种超值的序列式容器。

请记住:
1.new 与 delete成对使用时要采取相同的形式。


条款17:以独立语句将newed对象性置入智能指针

假设我们有个函数用来揭示处理程序的优先权,另一个函数用来在某动态分配所得的Widget上进行某些带有优先权的处理:

int priority(){}
void ProcessWidget(Widget* pw,int priority){}

诶,由于我们是读过 《Effictive C++》的,并且费了老大精力"研究","以对象管理资源"。因此,我们这里用智能指针(这里采用shared_ptr)来管理这个Widget对象。

int priority(){}
void ProcessWidget(std::shared_ptr<Widget> pw,int priority){}

emm,现在到我们该调用了。

    ProcessWidget(new Widget, priority());

但其实是编译不过的!因为函数参数的类型是std::shared_ptr<Widget>。

    //ProcessWidget(new Widget, priority())
    ProcessWidget(std::shared_ptr<Widget> (new Widget), priority()); 

我们现在来分析分析函数调用的过程。

可以看出,priority()函数的调用次序可以是多种情况。但最能存在潜在危险的是第一种情况!因为,如果priority()的调用一旦抛异常,此时向堆空间申请的 Widget对象,尚未传递参数给ProcessWidget中的智能指针构造,是的,我们期盼筑牢防范内存泄漏的武器并没有被使用上!从而,在调用ProcessWidget的过程中发生了内存泄漏。

这是,"资源被创建"和"资源转换为资源管理对象"的两个时间点之间,可能会受到异常的干扰!

为此,简单的做法是,分离语句!在创建Widget对象时,就将其置入智能指针当中。

    std::shared_ptr<Widget> pw(new Widget); //单独的语句 将对象置入智能指针
    ProcessWidget(pw, priority());  //此时就绝不会发生内存泄漏

此时,"new Widget"与"priority()",经由语句分割开来。此时编译器也不能在它们之间任意选择执行次序。

请记住:
以独立的语句将newed对象置入智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。


本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

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

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

相关文章

CEC2014:鱼鹰优化算法(Osprey optimization algorithm,OOA)求解CEC2014(提供MATLAB代码

一、鱼鹰优化算法简介 鱼鹰优化算法&#xff08;Osprey optimization algorithm&#xff0c;OOA&#xff09;由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出&#xff0c;其模拟鱼鹰的捕食行为。 鱼鹰是鹰形目、鹗科、鹗属的仅有的一种中型猛禽。雌雄相似。体长51-64厘米…

Spark 任务调度机制

1.Spark任务提交流程 Spark YARN-Cluster模式下的任务提交流程&#xff0c;如下图所示&#xff1a; 图YARN-Cluster任务提交流程 下面的时序图清晰地说明了一个Spark应用程序从提交到运行的完整流程&#xff1a; 图Spark任务提交时序图 提交一个Spark应用程序&#xff0c;首…

mysql数据库之存储过程

一、存储过程简介。 存储过程是事先经过编译并存储在数据库中的一段sql语句的集合&#xff0c;调用存储过程可以简化应用开发人员的很多工作&#xff0c;减少数据在数据库和应用服务器之间的传输&#xff0c;对于提高数据处理的效率是也有好处的。 存储过程思想上很简单&…

Mysql常见面试题总结

1、什么是存储引擎 存储引擎指定了表的类型&#xff0c;即如何存储和索引数据&#xff0c;是否支持事务&#xff0c;同时存储引擎也决定了表在计算机中的存储方式。 2、查看数据库支持哪些存储引擎使用什么命令&#xff1f; -- 查看数据库支持的存储引擎 show engines; 或者 …

百趣代谢组学分享,关于儿童Graves病相关的新环境物质的鉴定

代谢组学文章标题&#xff1a;Identification of Novel Environmental Substances Relevant to Pediatric Graves’ Disease 发表期刊&#xff1a;Frontiers in endocrinology 影响因子&#xff1a;6.055 作者单位&#xff1a;苏州大学附属儿童医院 百趣提供服务&#xf…

外贸建站多少钱才能达到预期效果?

外贸建站多少钱才能达到预期效果&#xff1f;这是每个外贸企业都会问的问题。作为一个做外贸建站多年的人&#xff0c;我有一些个人的操盘感想。 首先&#xff0c;我认为外贸建站的投资是非常必要的。 因为在现代社会&#xff0c;网站已经成为外贸企业开展业务的必要工具之一…

3种方法删除7-Zip压缩包的密码

7-Zip压缩软件是一款完全免费且开源的软件&#xff0c;不仅能压缩和解压7-Zip压缩包&#xff0c;还能给压缩包设置打开密码。 有些小伙伴可能会遇到这样的问题&#xff0c;7-Zip压缩包设置密码后&#xff0c;过了一段时间不需要密码保护了&#xff0c;或者一不小心忘记了密码&…

后端快速上手前端三剑客 HtmlCSSJavaScript

文章目录前言HTML1.基础标签2.多媒体标签&#xff1a;3.表格&列表&布局4.表单CSS1.简介2.导入方式3.选择器JavaScript1.简介2.引入方式3.基本语法4.对象(1) 基本对象(2) BOM对象(3) DOM对象5.事件前言 结构&#xff1a;HTML 表现&#xff1a;CSS 行为&#xff1a;Java…

D. Linguistics(思维 + 贪心)

Problem - D - Codeforces Alina发现了一种奇怪的语言&#xff0c;它只有4个单词:a, B, AB, BA。事实也证明&#xff0c;在这种语言中没有空格:一个句子是通过将单词连接成一个字符串来写的。Alina发现了一个这样的句子&#xff0c;她很好奇:有没有可能它恰好由a个单词a, b个单…

EasyExcel You can try specifying the ‘excelType‘ yourself 异常排查与处理

目录 问题发现 报错信息 问题排查 1、确定异常 2、查询easyexcel源码读取文件源码 3、查看业务代码 优化方案 1、将路径获取文件流的方式换为httpclient获取 2、dug测试修改代码 总结 问题发现 在测试环境测试导入订单&#xff0c;发现订单导入提示数据导入异常。 …

Python dict字典全部操作方法

文章目录一. 介绍二. 字典的创建1. 手动创建2. 使用内置函数dict()创建3. 使用dict.fromkeys()方法创建三. 字典元素的读取1. 下标方式读取Value2. dict.get()读取Value3. keys()方法返回“键”4. values()方法返回“值”5. items()方法返回“键-值”对四. 字典元素的添加与修改…

【20230227】回溯算法小结

回溯法又叫回溯搜索法&#xff0c;是搜索的一种方式。回溯法本质是穷举所有可能。如果想让回溯法高效一些&#xff0c;可以加一些剪枝操作。回溯算法解决的经典问题&#xff1a;组合问题切割问题子集问题排列问题棋盘问题如何去理解回溯法&#xff1f;回溯法解决的问题都可以抽…

hadoop调优

hadoop调优 1 HDFS核心参数 1.1 NameNode内存生产配置 1.1.1 NameNode内存计算 每个文件块大概占用150byte&#xff0c;如果一台服务器128G&#xff0c;能存储的文件块如下 128 (G)* 1024(MB) * 1024(KB) * 1024(Byte) / 150 Byte 9.1 亿 1.1.2 Hadoop2.x 在Hadoop2.x中…

Linux--多线程(3)

目录1. POSIX信号量1.1 概念2. 基于环形队列的生产消费者模型2.1 环形队列的基本原理2.2 基本实现思想3. 多生产多消费1. POSIX信号量 1.1 概念 信号量本质是一个计数器&#xff0c;申请了信号量以后&#xff0c;可以达到预定临界资源的效果。 POSIX信号量和SystemV信号量相同…

【自动包装线标签打印翻转问题沟通】

最近纺丝自动包装线的标签打印机自动打印标签&#xff0c;是翻转状态。) 但是这个打印机它不是平放的&#xff0c;它是通过悬臂安装在半空的中的&#xff0c;是翻转的&#xff0c; 它的标签一个打在侧面&#xff0c;一个打在正前方&#xff0c;打印出来的样子是这样的。 是反…

全国媒体邀约怎么做?邀请媒体有哪些注意事项呢?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好好多企业或者机构都在参加外地的展览展会&#xff0c;活动会议&#xff0c;或者由于多种方面的考虑&#xff0c;会在公司总部以外的地方去做活动和发布会&#xff0c;在一个相对陌生的地方&#xff0c;不论是活动准备&#…

WebRTC → 多人通讯架构浅析

1、一对一通信模型一对一通信中&#xff0c;WebRTC会先尝试两个终端之间是否可以通过P2P直接进行通信&#xff0c;无法通信时会通过STUN/TURN服务器进行中转&#xff1b;其中STUN/TURN服务器的作用在不能直连时是中继服务器&#xff0c;通过该服务器进行端到端之间的数据中转&a…

数据挖掘多模块接口(二分类)python旗舰版

数据挖掘任务一般分为四大步骤&#xff1a;1、数据预处理2、特征选择3、模型训练与测试4、模型评估本文为四大步骤提供接口&#xff0c;使得能够快速进行一个数据挖掘多种任务中&#xff0c;常见的二分类任务。0. 导包0.1 忽略警告信息&#xff1a;import warnings warnings.fi…

【Java学习笔记】2.Java 开发环境配置

Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境。 window系统安装java 下载JDK 首先我们需要下载 java 开发工具包 JDK&#xff0c;下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/&#xff0c;在下载页面中根据自己的系统选…

企业寄件现代化管理教程

现代化企业为了跟上时代发展的步伐&#xff0c;在不断完善着管理制度&#xff0c;其中公司寄件管理&#xff0c;也是重要的一个模块。为了提高公司快递的寄件效率&#xff0c;以及节约寄件成本&#xff0c;实现快递寄件的规范化&#xff0c;越来越多的现代化企业&#xff0c;开…