C++ primer 第十五章

1.OPP:概述

面向对象程序设计的核心思想是数据抽象、继承和动态绑定。

通过继承联系在一起的类构成一种层次关系,在层次关系的根部的是基类,基类下面的类是派生类

基类负责定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自特有的成员。

虚函数:由基类来声明,但基类希望它的派生类各自重新定义适合自身的版本的函数。

class quote{
public:virtual double net_price(std::size_t n) const; //基类quote声明的虚函数
};

派生类必须通过使用类派生列表明确指出它是从哪个基类继承而来。 

class bulk_quote : public quote{  //在基类前写上访问说明符...
};

派生类必须在其内部对所有重新定义的虚函数进行声明。 

新标准允许派生类使用关键字override来显式地注明它将使用哪个成员函数来改写基类的虚函数。

double net_price(std::size_t) const override; //派生类重新定义的虚函数

通过动态绑定,我们能用同一段代码分别处理基类和派生类的对象。

在C++语言中,当我们使用基类的引用或指针调用一个虚函数时将发生动态绑定。

double print_total(ostream& os,const quote& item, size_t n)
{auto ret = item.net_price();
}//basic是基类类型,bulk是派生类类型
print_total(cout,basic,20);   //调用基类的net_price
print_total(cout,bulk,20);    //调用派生类的net_price

函数的执行版本由实参决定,在运行时选择函数的版本,动态绑定又称为运行时绑定

2.定义基类和派生类

2.1、定义基类

基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此。

派生类可以继承其基类的成员。

在C++中,基类必须将它的两种成员函数区分开来:一种是希望派生类进行覆盖的函数,另一种是希望派生类直接继承而不要改变的函数。

基类通过在其成员函数语句前加上关键字virtual,使得该函数执行动态绑定,成为虚函数。

任何构造函数之外的非静态函数都可以是虚函数。

关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。

若基类把一个函数声明成虚函数,则该函数在派生类中隐式地也是虚函数。

成员函数如果没被声明为虚函数,则其解析过程发生在编译时而非运行时。

派生类能访问基类的公有成员,而不能访问基类的私有成员。

若基类希望它的派生类有权访问该成员,同时禁止其他用户访问,可使用protected访问运算符来说明该成员。

class quote{  //基类
public:quote() = default;  //默认构造函数quote(const std::string& book,double sales_price) : books(book),price(sales_price) {}  //接受两个参数的构造函数virtual double net_price(std::size_t n) const {return n*price;}  //虚函数virtual ~quote() = default;  //虚析构函数
private:std::string books; 
protected:  //派生类能够访问的成员double price = 0.0;
};

 2.2、定义派生类

派生类在每个基类前面可以有三种访问说明符中的一种:public、protected、private。

访问说明符的作用是控制派生类从基类继承而来的成员是否对派生类用户可见。

若一个派生是公有的,则基类的公有成员也是派生类接口的组成部分。(在任何需要基类的引用或指针的地方我们都可以使用派生类的对象)

若派生类没有覆盖基类中的某个虚函数,则派生类会直接继承其在基类中的版本。

新标准允许派生类使用关键字override显式地注明它使用某个成员函数覆盖了它所继承的虚函数。

一个派生类对象包含多个组成部分:一个含有派生类自己定义的非静态成员的子对象,一个与该派生类继承的基类对应的子对象。

 由于在派生类对象中含有与其基类对应的组成部分,所以能把派生类的对象当作基类对象来使用,也能将基类的指针或引用绑定到派生类对象中的基类部分。

 这种转换称为派生类到基类的类型转换,编译器会隐式地执行派生类到基类的转换。

在派生类对象中含有与其基类对应的组成部分,这一事实是继承的关键所在。

派生类只能通过使用基类的构造函数来初始化它的基类部分,每个类控制他自己的成员的初始化过程。

派生类构造函数是通过构造函数初始化列表来将实参传递给基类构造函数的。

首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员。

bulk_quote(const std::string& book,double p,std::size_t qty,double disc) :quote(book,p),qtys(qty),discount(disc) {}
//将实参book,p传给quote的构造函数,剩余的实参依次初始化派生类的成员

派生类的作用域嵌套在基类的作用域之内,派生类可以直接访问基类的公有成员和受保护成员。

 若基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。

静态成员遵循通用的访问控制规则,若基类中的成员是private的,则派生类无权访问该成员。

派生类的声明包含类名但不包含它的派生列表。

class bulk_quote : public quote; //错误,派生列表不能出现
class bulk_quote; //正确

 派生列表以及与定义有关的其他细节必须与类的主体一起出现。

若我们想将某个类用作基类,则该类必须已经定义而非仅仅声明。

 一个类可以是基类,同时也可以是派生类。

class base{...};
class b1 : public base {...};
class b2 : public b1 {...};
//base是b1的直接基类,同时也是b2的间接基类

 每个类都会继承直接基类的所有成员。

新标准中提供了一种防止继承发生的方法,即使用关键字final来阻止继承。

class base{...};
class b1 final : base{...};  //b1不能被继承
class b2 : b1{...};  //错误,b1是final的

2.3、类型转换与继承 

存在继承关系的类能将基类的指针或引用绑定到派生类的对象上。

当使用基类的引用或指针时,实际上我们并不清楚该引用或指针所绑定的对象的真实类型。

智能指针类也支持派生类向基类的类型转换,这意味着允许将一个派生类对象的指针存储在基类的智能指针内。

当我们使用存在继承关系的类型时,应该区分开一个变量或表达式的静态类型该表达式表示对象的动态类型

基类的指针或引用的静态类型可能与其动态类型不一致。

静态类型在编译时就是已知的,动态类型直到运行时才可知,由实参类型决定。

double ret = item.net_price(n);
//item是quote类型的引用,在函数定义时就已经确定了,是静态类型
//由于item是quote类型的引用,可以使用基类版本或派生类版本的net_price,
//具体执行哪个函数由传入实参决定,所以net_price的类型要在运行时才可知,它的类型是动态类型

 由于每个派生类对象都包含一个基类部分,基类的引用或指针可以绑定到该基类部分上。

存在派生类向基类的类型转换,不存在从基类向派生类的类型转换。

即使一个基类指针或引用绑定在一个派生类对象上,我们也不能执行从基类向派生类的转换。

bulk_quote bulk;
quote* item = &bulk;    //正确,派生类向基类的转换
bulk_quote* bulkp = item;  //错误,不存在基类向派生类的转换,哪怕基类中存储的是派生类也不行

 若我们已知某个基类向派生类的转换是安全,可通过static_cast来强制覆盖掉编译器的检查工作。

派生类向基类的自动类型转换只对指针或引用类型有效,在派生类类型和基类类型中不存在。

当我们初始化或赋值一个类类型的对象时,实际上在调用它们的构造函数或赋值运算符函数,因为这些成员都接受引用作为参数,所以派生类向基类的转换允许我们给基类的拷贝/移动操作传递一个派生类的对象。

bulk_quote bulk;  //派生类对象
quote item(bulk);  //使用quote::quote(const quote&)构造函数
item = bulk;  //调用quote::operator=(const quote&);

但上述操作会忽略掉bulk_quote部分,只将派生类中的quote部分保存在item中。

3.虚函数

当我们使用基类的引用或指针调用一个虚成员函数时会进行动态绑定,因此必须为每个虚函数都提供定义,而不管它是否被用到。

当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定应该匹配哪个版本。

通过一个具有普通类型的表达式调用虚函数时,在编译时就会将调用的版本确定下来,对象的动态类型与静态类型相同。

quote base;
base.net_price(20); //调用quote::net_price

 一旦某个函数被声明成虚函数,则在所以派生类中它都是虚函数。

派生类的函数若覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。

派生类中的虚函数的返回类型也必须与基类函数匹配,但当类的虚函数返回类型是类本身的指针或引用时,上述规则则无效。

若派生类定义了一个与基类中虚函数的名字相同但形参列表不同的函数,那该函数与基类中原有的函数是相互独立的。

新标准中我们可以使用override关键字来说明派生类中的虚函数,若该函数没有覆盖已存在的虚函数,则编译器会报错。

struct a{virtual void f1(int) const;
};struct b : public a{void f1(int) const override;  //使用override来标记虚函数
};

和其他函数一样,虚函数可以拥有默认实参

若某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定的。

若虚函数使用默认实参,则基类和派生类中定义的默认实参最好保持一致。

希望对虚函数的调用不要进行动态绑定,而是强制让其执行虚函数的某个特定版本,可以使用作用域运算符来实现。

double ret = base->quote::net_price(22);
//强制调用quote的net_price版本,不管base实际指向的对象类型是什么

通常情况下,只有成员函数或友元中的代码才需要使用作用域运算符来回避虚函数的机制。

如果一个派生类虚函数需要调用它的基类版本,但没有使用作用域运算符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无限递归。

4.抽象基类

抽象基类是含有不完整的内容的基类,类似于模板,用于生产特定条件的类。

纯虚函数是一种特殊的虚函数,该函数没有具体的定义,由子类来提供实现

通过在函数体的位置(即声明语句)书写=0就可以将一个虚函数说明为纯虚函数,只能在类内部进行声明。

double net_price(std::size_t) const = 0; //纯虚函数

我们可以为纯虚函数提供定义,不过函数体必须定义在类的外部。 

 含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类。

抽象基类负责定义接口,而后续的派生类可以覆盖该接口。

由于纯虚函数是不完整的,因此不能直接创建一个抽象基类的对象。

抽象基类的派生类必须给出自己的纯虚函数的定义,否则它们将是抽象基类。

5.访问控制与继承

每个类分别控制着其成员对于派生类来说是否可访问。

派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。

某个类对其继承而来的成员的访问权限受到基类中该成员的访问说明符在派生类列表中的访问说明符的影响。

派生访问说明符是控制派生类从基类所继承的成员在派生类中的访问权限。

派生访问说明符可以控制继承自派生类的新类的访问权限,这无关于基类的访问权限。

class base{
protected:int prot;
};struct priv : private base{int f1() const { return prot; }  //prot继承于base,是priv的私有成员
};struct priv1 : public priv{int use() { return prot; }   //错误,prot在priv中是私有的
};

无论派生类以什么方式继承基类,派生类向其直接基类的类型转换对于派生类的成员和友元来说永远是可访问的。

只有当派生类公有地继承基类时,用户代码才能使用派生类向基类的转换。 

友元关系不能传递,也不能继承。

派生类的友元不能随意访问基类类的成员,但基类的友元能访问派生类的基类部分。

我们可以通过使用using声明来改变派生类继承的某个名字的访问级别。

using声明语句中名字的访问权限由该using声明语句之前的访问说明符来决定。

class base{
public:std::size_t size() const { return n;}
protected:std::size_t n;
};struct priv : private base{
public:using base::size;  //size的访问权限为public
protected:using base::n;   //n的访问权限为protected
}

派生类只能为那些它可以访问的名字提供using声明。

 默认派生运算符由定义派生类所用的关键字来决定(class是私有的,struct是公有的)。

6.继承中的类作用域

当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内。

一个对象、引用或指针的静态类型决定了该对象的哪些成员是可见的。

class disc : public quote{   //disc是bulk_quote的直接基类
public:std::pair<size_t,double> discont() const { return {quantity,discount};}
};bulk_quote bulk;  
bulk_quote* bulkp = &bulk;  //直接基类指针
quote* itemp = &bulk;   //间接基类指针
bulkp->discont(); //正确,搜索从bulk_quote开始
itemp->discont(); //错误,搜索从quote开始

派生类能逐级向上查找匹配项,而基类不能向下寻找。 

派生类也能重用定义在其直接基类或间接基类中的名字,此时外层作用域的名字将被隐藏。

可以通过作用域运算符来使用一个被隐藏的基类成员。

除了覆盖继承而来的虚函数以外,派生类最好不要重用其他定义在基类中的名字。

声明在内层作用域的函数并不会重载声明在外层作用域的函数。

被隐藏的成员或函数将无法调用。

假如基类与派生类的虚函数接受的实参不同,则我们无法通过基类的引用或指针来调用派生类的虚函数。

成员函数无论是否是虚函数都能被重载。

7.构造函数与拷贝控制

7.1、虚析构函数

基类通常应该定义一个虚析构函数,这样就能动态分配继承体系中的对象。

若基类的析构函数不是虚函数,则delete一个指向派生类对象的基类指针将产生未定义的行为。

基类的析构函数并不遵循若定义析构函数,则要定义拷贝和赋值操作的经验准则。

若类定义了析构函数,则会阻止合成移动操作。

7.2、合成拷贝控制与继承

 基类或派生类的合成拷贝控制成员的行为与其他合成的构造函数、赋值运算符或析构函数类似。

某些定义基类的方式可能导致有的派生类成员成为被删除的函数:
1、若基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符和析构函数是被删除的或不可访问的,则派生类中对应的成员将是被删除的。

2、若基类中有一个不可访问的或删除掉的析构函数,则派生类中合成的默认和拷贝构造函数将被删除的。

上述现象的发生原因在于派生类对象中含有基类部分。

在实际编程过程中,如果在基类中没有默认、拷贝或移动构造函数,则一般情况下派生类也不会定义相关的操作。

基类缺少移动操作会阻止派生类拥有自己的合成移动操作。

7.3、派生类的拷贝控制成员

派生类的析构函数只负责销毁派生类自己分配的资源,它的基类部分会被自动销毁的。

当派生类定义了拷贝或移动操作时,该操作负责拷贝或移动派生类自己的成员和基类部分成员。

在派生类定义拷贝或移动构造函数时,通常使用对应的的基类构造函数初始化对象的基类部分。

派生类的赋值运算符必须显式地为其基类部分赋值。

基类的运算符能正确地处理自赋值的情况。

对象销毁的顺序与其创建的顺序相反。

若构造函数或析构函数调用了某个虚函数,则我们应该执行与调用函数所属类型相对应的虚函数版本。

7.4、继承的构造函数

 新标准允许派生类能够重用其直接基类定义的构造函数。

类不能继承默认、拷贝和移动构造函数,若派生类没有直接定义这些构造函数,则编译器会合成。

派生类继承基类构造函数的方式是提供一条注明了基类名的using声明语句。

class bulk_quote : public disc{
public:using disc::disc;  //继承disc的构造函数
};

当using作用域构造函数时,对于基类的每个构造函数,编译器都在派生类中生成一个形参列表完全相同的构造函数。

一个构造函数的using声明并不会改变该构造函数的访问级别。

using声明语句不能指定explicit或constexpr。

若基类的构造函数是explicit或constexpr,则继承的构造函数也拥有相同的属性。

当一个基类构造函数含有默认实参时,这些实参并不会被继承,但派生类会获得多个继承的构造函数,其中每个构造函数分别省略掉一个含有默认实参的形参。

若基类含有几个构造函数,大多数时候派生类会继承所有的构造函数。

8.容器与继承

由于不允许在容器中保存不同类型的元素,当我们使用容器存放继承体系中的对象时,通常必须采取间接存储的方式。

当派生类对象被赋值给基类对象时,其中的派生类部分将被“切掉”,因此容器和存在继承关系的类型无法兼容。

当我们希望在容器中存放具有继承关系的对象时,我们实际上存放的是基类的指针。

9.文本查询程序再探

class queryresult; //未定义,但使用到,先声明
class textquery  //保存输入文件
{
public:using line_no = std::vector<std::string>::size_type; //类型别名textquery(std::ifstream&); //构造函数,接受一个文件流为参数queryresult query(const std::string&) const; //
private:std::shared_ptr<std::vector<std::string>> file; //输入文件std::map<std::string, std::shared_ptr<std::set<line_no>>> wm; //用查找的单词做关键字,保存行号的set做值
};textquery::textquery(std::ifstream& is) : file(new std::vector<std::string>)
{std::string text;while (getline(is, text)){file->push_back(text); //保存每一行,file是指针,需解引用int n = file->size() - 1;  //当前行号std::istringstream line(text); //分解成单个单词std::string word;while (line >> word){auto& line = wm[word]; //line与wm[word]绑定,line是智能指针if (!line)  //若单词不在wm中,返回一个空指针line.reset(new std::set<line_no>); //指针指向新创建的setline->insert(n); //将行号插入set中}}
}queryresult textquery::query(const std::string& sought) const  //查找单词
{static std::shared_ptr<std::set<line_no>> nodata(new std::set<line_no>); //空setauto loc = wm.find(sought);if (loc == wm.end())return queryresult(sought, nodata, file); //未找到elsereturn queryresult(sought, loc->second, file);
}class queryresult{friend std::ostream & printis(std::ostream&, const queryresult&); //友元声明
public:typedef std::set<textquery::line_no>::const_iterator line_it;queryresult(std::string s,std::shared_ptr<std::set<textquery::line_no>> p,std::shared_ptr<std::vector<std::string>> f) :  //构造函数sought(s), lines(p),file(f) {}line_it begin() const { return lines->cbegin(); }line_it end() const { return lines->cend(); }std::shared_ptr<std::vector<std::string>> get_file() { return file; }
private:std::string sought;  //查找的单词std::shared_ptr<std::set<textquery::line_no>> lines;  //指向保存单词出现的行号的setstd::shared_ptr<std::vector<std::string>> file;  //指向保存文件的vector
};//抽象基类,具体的查询类型从query_base派生,成员都是private的
class query_base {friend class query;
protected:using line_no = textquery::line_no; //类型别名virtual ~query_base() = default; //析构函数
private://返回与当前query匹配的queryresultvirtual queryresult eval(const textquery&) const = 0; //rep是表示查询的一个stringvirtual std::string rep() const = 0;
};class query {//运算符需要访问接受shared_ptr的构造函数,来构造新的query对象,构造函数是私有的friend query operator~(const query&);friend query operator|(const query&, const query&);friend query operator&(const query&, const query&);
public:query(const std::string&); //构造一个新的wordquery//接口函数:调用对应的query_base操作queryresult eval(const textquery& t) const { return q->eval(t);}std::string rep() const { return q->rep(); }
private://接受一个指向query_base的shared_ptr指针作为参数,存储给定的指针query(std::shared_ptr<query_base> query) : q(query) {} std::shared_ptr<query_base> q;
};inline query::query(const std::string& s) : q(new wordquery(s)) {} //分配一个新的wordqueryclass wordquery:public query_base { //查找单个单词friend class query;wordquery(const std::string& s) : query_word(s) {} //构造函数queryresult eval(const textquery& t) const {return t.query(query_word); //调用textquery的query成员,在文件中查找}std::string rep() const { return query_word; } //返回查找的单词std::string query_word; //要查找的单词
};class notquery :public query_base {friend query operator~(const query&);notquery(const query& q) : query(q) {}std::string rep() const { return "~(" + query.rep() + ")"; }queryresult eval(const textquery&) const;query query;
};inline query operator~(const query& operand)
{return std::shared_ptr<query_base>(new notquery(operand));//将新分配的notquery指针绑定到shared_ptr<query_base>//return 负责将shared_ptr<query_base>类型转换成query(调用query的构造函数)
}//抽象基类,保存操作两个运算对象的查询类型所需的数据
class binaryquery : public query_base {
protected:binaryquery(const query& l,const query& r,std::string s) : lhs(l),rhs(r),opsym(s) {}std::string rep() const { return "(" + lhs.rep() + " " + opsym + " " + rhs.rep() + ")"; }query lhs, rhs; //左侧和右侧运算对象std::string opsym; //运算符名称
};class andquery : public binaryquery {friend query operator&(const query&, const query&);andquery(const query& left, const query& right) :binaryquery(left, right, "&") {}//从基类binaryquery继承了rep并定义了eval的虚函数queryresult eval(const textquery&) const override;
};inline query operator&(const query& lhs, const query& rhs)
{return std::shared_ptr<query_base>(new andquery(lhs, rhs));
}class orquery : public binaryquery {friend query operator|(const query&, const query&);orquery(const query &left,const query& right) : binaryquery(left,right,"|") {}queryresult eval(const textquery&) const;
};inline query operator|(const query& lhs, const query& rhs)
{return std::shared_ptr<query_base>(new orquery(lhs, rhs));
}queryresult orquery::eval(const textquery& text) const
{//????auto right = rhs.eval(text), left = lhs.eval(text);//含有左侧单词的文本插入到ret_lines指向的set中auto ret_lines = std::make_shared<std::set<line_no>>(left.begin(), left.end()); //含有右侧单词的文本插入到set中ret_lines->insert(right.begin(), right.end());//返回一个queryresult,表示并集return queryresult(rep(), ret_lines, left.get_file());
}queryresult andquery::eval(const textquery& text) const
{auto left = lhs.eval(text), right = rhs.eval(text);//保存left和right交集的setauto ret_lines = std::make_shared<std::set<line_no>>();//该算法将输入序列中共同出现的元素写入目的位置中std::set_intersection(left.begin(), left.begin(), right.begin(), right.end(), std::inserter(*ret_lines, ret_lines->begin()));return queryresult(rep(), ret_lines, left.get_file());
}queryresult notquery::eval(const textquery& text) const
{//包含运算对象出现的行号集合setauto result = query.eval(text);auto ret_lines = std::make_shared<std::set<line_no>>(); //存放的行号auto beg = result.begin(), end = result.end();auto sz = result.get_file()->size();for (size_t n = 0; n != sz; ++n){if (beg == end || *beg != n) //若不在result中,则插入ret_lines->insert(n);else if (beg != end) //获取result的下一行++beg;}return queryresult(rep(), ret_lines, result.get_file());
}

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

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

相关文章

原生数据开发软件 TablePlus for mac

一款非常好用的本地原生数据开发软件&#xff1a;TablePlus激活版。 软件下载&#xff1a;TablePlus for mac v3.11.0激活版 这款优秀的数据库编辑工具支持 MySQL、SQL Server、PostgreSQL 等多种数据库&#xff0c;具备备份、恢复、云同步等功能。它可以帮助您轻松编辑数据库中…

新能源汽车充电桩消防安全视频智能可视化监管建设方案

一、方案背景 据应急管理部门统计公布的数据显示&#xff0c;仅2023年第一季度&#xff0c;新能源汽车自燃率就上涨了32%&#xff0c;平均每天就有8辆新能源汽车发生火灾&#xff08;含自燃&#xff09;。在已查明起火原因中&#xff0c;58%源于电池问题&#xff0c;19%源于碰…

【Unity】UI九宫格

什么是九宫格&#xff1f; 顾名思义&#xff0c;九宫格就是指UI切成9个格子&#xff0c;9个格子可以任意拉伸。 1、3、7、9不拉伸。 2、8水平拉伸。 4、6垂直拉伸。 5既可以水平也可以垂直拉伸。 怎么切九宫格&#xff1f; 选中图片&#xff0c;改成Sprite模式&#xff0c;点…

2015年认证杯SPSSPRO杯数学建模D题(第二阶段)城市公共自行车全过程文档及程序

2015年认证杯SPSSPRO杯数学建模 D题 城市公共自行车 原题再现&#xff1a; 城市交通问题直接影响市民的生活和工作。在地形平坦的城市&#xff0c;公共自行车出行系统是一种很好的辅助手段。一般来说&#xff0c;公共自行车出行系统由数据中心、驻车站点、驻车桩、自行车&…

基于nodejs+vue家装一体化平台python-flask-django-php

提高现下家装一体化平台的准确度&#xff0c;同时降低经济波动带来的不良影响&#xff0c;希望本文能对广大学者的研究提供参考。 前端技术&#xff1a;nodejsvueelementui, Express 框架于Node运行环境的Web框架, 语言 node.js 框架&#xff1a;Express 前端:Vue.js 数据库&am…

C语言 汉诺塔问题

目录 1.前言 2.问题描述 3.问题分析 4.定义一个主函数 5.再定义一个hanoi函数 6.所有代码 7.结语 1.前言 汉诺塔问题&#xff0c;是心理学实验研究常用的任务之一。该问题的主要材料包括三根高度相同的柱子和一些大小及颜色不同的圆盘&#xff0c;三根柱子分别为起始柱A…

TextMeshPro图文混排的两种实现方式,不打图集

TMP图文混排 方案一&#xff1a;TMP自带图文混排使用方法打包图集使用 方案二&#xff1a;不打图集&#xff0c;可以使用任何图片 接到一个需求&#xff0c;TextMeshPro 图文混排。 方案一&#xff1a;TMP自带图文混排 优点布局适应优秀&#xff0c;字体左中右布局位置都很不错…

Elasticsearch8 - Docker安装Elasticsearch8.12.2

前言 最近在学习 ES&#xff0c;所以需要在服务器上装一个单节点的 ES 服务器环境&#xff1a;centos 7.9 安装 下载镜像 目前最新版本是 8.12.2 docker pull docker.elastic.co/elasticsearch/elasticsearch:8.12.2创建配置 新增配置文件 elasticsearch.yml http.host…

费曼学习法:快速掌握新知识!

首先&#xff0c;讲一个关于学习法则的小故事。 马克思普朗克在1918年荣获诺贝尔物理学奖之后&#xff0c;他就在全德国作巡回报告。 不管被邀请到哪里&#xff0c;他就会对新的量子力学演讲一番。 因此&#xff0c;慢慢他的司机也对他的报告烂熟于心&#xff0c;于是就告诉他。…

Java中 List 集合,通过 Stream 流进行排序总结

一、数据准备 public class OrderTest {private String channelCode;private BigDecimal rate;// 省略 getter、setter、toString()、constructor }List<OrderTest> orderTestList new ArrayList<>();OrderTest z09 new OrderTest("Z09", new BigDeci…

是德科技KEYSIGHT E4990A阻抗分析仪

181/2461/8938产品概述: E4990A 阻抗分析仪具有 20 Hz 至 120 MHz 的频率范围&#xff0c;可在宽阻抗范围内提供出色的 0.045%&#xff08;典型值&#xff09;基本准确度&#xff0c;并内置 40 V 直流偏置源&#xff0c;适用于元器件、半导体和材料测量。 无论研发、生产、质…

SpringMvc执行流程源码解析

一、简介 Spring web Mvc是基于ServletApi构建的原始Web模块&#xff0c;从一开始就包含在Spring框架中&#xff1b; 从Servlet到SpringMvc 最典型的MVc就是JSPServletjavaBean的模式&#xff1b; 弊端&#xff1a; 1、xml下配置Servlet的映射非常麻烦&#xff0c;效率低&…

【 yolo红外微小无人机-直升机-飞机-飞鸟目标检测】

yolo无人机-直升机-飞机-飞鸟目标检测 1. 小型旋翼无人机目标检测2. yolo红外微小无人机-直升机-飞机-飞鸟目标检测3. yolo细分类型飞机-鸟类-无人机检测4. yolo红外大尺度无人机检测5. 小型固定翼无人机检测6. 大型固定翼无人机检测7. yolo航空俯视场景下机场飞机检测 1. 小型…

git clone拉取项目时报错xxx bytes of body are still expected

报错如下&#xff1a; 尝试了网友们的各种方法&#xff1a; 1、可能缓存区溢出&#xff0c;执行以下语句&#xff0c;将缓存设置为500M或者更大 git config http.postBuffer 524288000 2、可能项目过大&#xff0c;先进行浅克隆&#xff0c;然后更新远程库到本地 git clone…

鸿蒙APP应用开发教程—超详细的项目结构说明

1. 新建项目 打开DevEco Studio, 选择 Create Project: 1.1 选择模版 Create Project - Choose Template 1.2 配置项目 Create Project - Configure Project 如果使用的是 DevEco 3.X 版本, 可以根据 Compile SDK版本选择不同的模式, 比如: 3.0.0(API 8)及更早 - 仅支持 …

Python学习:循环语句

Python循环语句 概念 循环语句是编程中常用的结构&#xff0c;用于多次执行相同或类似的代码块。Python中有两种主要的循环语句&#xff1a;for循环和while循环。 for循环&#xff1a; for循环用于遍历一个序列&#xff08;如列表、元组、字符串等&#xff09;中的元素&#x…

VUE之首次加载项目缓慢

最近公司有个大型的项目&#xff0c;使用vue2开发的&#xff0c;但是最终开发完成之后&#xff0c;项目发布到线上&#xff0c;首次加载项目特别缓慢&#xff0c;有时候至少三十秒才能加载完成&#xff0c;加载太慢了&#xff0c;太影响用户体验了&#xff0c;最近研究了一下优…

“一树生三花”战略背后,梦之蓝·手工班的新使命

执笔 | 尼 奥 编辑 | 扬 灵 绵柔酒香&#xff0c;洋河酒韵。3月17日&#xff0c;由洋河股份主办的中国白酒发展趋势高峰论坛暨梦之蓝手工班战略升级发布会在成都隆重举办&#xff0c;协会领导、行业专家、名商大商齐聚一堂&#xff0c;梦之蓝手工班三款产品首次集体亮相&…

【第十二届“泰迪杯”数据挖掘挑战赛】【2024泰迪杯】B题基于多模态特征融合的图像文本检索—解题全流程(持续更新)

2024 年(第 12 届)“泰迪杯”数据挖掘挑战赛B题 解题全流程&#xff08;持续更新&#xff09; -----基于多模态特征融合的图像文本检索 一、写在前面&#xff1a; ​ 本题的全部资料打包为“全家桶”&#xff0c; “全家桶”包含&#xff1a;模型数据、全套代码、训练好的模…

NVIDIA NIM 提供优化的推理微服务以大规模部署 AI 模型

NVIDIA NIM 提供优化的推理微服务以大规模部署 AI 模型 生成式人工智能的采用率显着上升。 在 2022 年 OpenAI ChatGPT 推出的推动下&#xff0c;这项新技术在几个月内就积累了超过 1 亿用户&#xff0c;并推动了几乎所有行业的开发活动激增。 到 2023 年&#xff0c;开发人员…