c++多重继承
1.概论
多重继承是否有必要吗?
这个问题显然是一个哲学问题,正确的解答方式是根据情况来看,有时候需要,有时候不需要,这显然是一句废话,有点像上马克思主义哲学或者中庸思。
但是这个问题和那些思想一样,被一知半解的人所误解和扭曲,最后变成了和稀泥的借口。下面来谈一谈我的个人看法。
假设有两个类A,B,A类提供了一种我们需要的容器,B类提供了我们需要的算法,这两个类是不同途径提供而来,现在创造一种满足这两个要求的类,就可以使用继承
类c可以同时继承两个类,获得他们的特征和行为。
但是,c++中的模板提供了新的思路,它可以通过自动识别来使用对应的方法,这样就不需要使用多重继承才能使用相关的功能了。
在c++标准库中iostream类,有一个多重继承
ios
istream ostream
iostream
这种继承类似于代码分解机制,ios是istream和ostream的共有类,他们继承了ios之后各种扩展了功能,然后再由iostream继承。
当我们使用继承的时候,无论是公有继承还是保护继承。或者是私有继承,派生类都继承了基类的全部成员,没有例外。
2.接口继承
这是一种只继承接口的继承,基类没有成员变量,也没用实现方法,只有一个接口声明,这样的类也被称为虚基类。就像下面这个类
class Base{public:virtual ~Base(){}virtual void interface() = 0;};
假设有三个这样的类,他们各种声名了不同的功能接口,让一个类来继承他们,实现他们的接口,最后可以通过不同的基类指针或者引用调用不同的接口。
class Base1{public:virtual ~Base1(){}virtual void interface1() = 0;};class Base2{public:virtual ~Base2(){}virtual void interface2() = 0;};class Base3{public:virtual ~Base3(){}virtual void interface3() = 0;};class deriver: public Base1, public Base2, public Base3{public:实现三个基类的接口}void interface1(const Base1& b1){b1.interface1}void interface2(const Base2& b2){b2.interface2}void interface3(const Base3& b3){b3.interface3}
当我们只需要基类的每一个功能时,我们只需要知道基类是谁,而不需要了解派生类的实现。
但是这种继承,可以通过模板来化解,写出来的代码也简单很多。
只需要通过一个基类,写出三种方法,然后通过泛型编程调用不同的函数
class Base{public:void interface1(){}void interface2(){}void interface3(){}};template<class Base1>void interface1(const Base1& b1){b1.interface1();}template<class Base2>void interface2(const Base2& b2){b2.interface2();}template<class Base3>void interface3(const Base3& b3){b3.interface3();}
两种方式有何不同?
第一种通过继承,使得对象更为明确了,有一种各司其职的感觉。
第二种是一种集合程度较高的写法,类型较弱。
但是两种方式没有高下之别,适用于不同的场景。
3.实现继承
c++的实现继承意味着所有内容都来自于基类,这样就不用实现继承来的方法了,直接调用即可,多重继承的一个用途包括混入类,混入类的存在是为了通过继承来增加
其他类的功能,它自己不能实例化自己,而是通过其他类来实例化。以数据库操作为例:
//数据库异常类struct DatabaseError:std::runtime_error{DatabaseError(const std::string& msg){}};//数据库类,连接数据库和关闭数据库,还有一些其他功能,比如检索,删除。。。这里不写出来class Database{std::string dbid;public:Database(const std::string* dbStr) : dbid(dbStr){}virtual ~Database(){}void open() throw(DatabaseError){std::cout << "connected to" << dbid << std::endl;}void close(){std::cout << dbid << "close" <<std::endl;}};
在一个服务器=客户端的模式下,客户拥有多个对象,这些对象分享一个连接的数据库,只有当所有客户都断开连接的时候,数据库才调用close关闭自己。
为了记录连接数据库的客户数量,创建一个新的特殊的类,也就是混入类
class Countable{long count;protected:Countable(){count = 0;}virtual ~Countable(){assert(count == 0);}public:long attach(){return count++;}long detach(){return --count > 0 ? count : (delete this, 0);}long refcount() const{return count;}};
这个类的构造函数和析构函数都是保护成员,所有它无法自己生成,必须有一个友元类或者派生类来使用它;
析构函数这一点很重要,因为只有detach使用delete它的时候才会被正确的销毁
下面这个类会继承上面的两个类
class DBConnection : public Database, public Countable{private:DBConnection(const DBConnection&);DBConnection& operator=()(const DBConnection&);//不允许赋值或者赋值protected://构造函数--打开数据库DBConnection(const string& dbStr)throw(DatabaseError) : Database(dbStr){open();//}//析构函数,关闭数据库--无法从外面关闭~DBConnection(){ close();}public://静态方法static DBConnection* create(const string& dbStr)throw(DatabaseError){DBConnection* con = new DBConnection(dbStr);}con->attach();assert(con->refCount() == 1);return con;};
这个类只能通过静态成员创建自己,在创建自己的同时,另外两个类也调用了自己的构造函数
不用修改Database类就做到了记录连接数据库的数量,然后根据数量来调用 DBConnection的析构函数关闭数据库
class DBClient{DBConnection* db;public:DBClient (DBConnection* dbCon){db = dbCon;db->attach();}~DBClient(){ db->detach();}};客户端使用RAII(the Resource Acquisition Is Initation)的方法实现数据库的打开和关闭。int main(){DBConnection* db = DBConnection::create("database");assert(db->refCount()==1);return 0;}
整合上面的代码,并具体运行
//整合上面的代码#include <iostream>
#include<stdexcept>
#include<string>
#include<cassert>
using namespace std;//数据库异常类
struct DatabaseError:std::runtime_error{DatabaseError(const std::string& msg) : std::runtime_error(msg){}
};
//数据库类,连接数据库和关闭数据库,还有一些其他功能,比如检索,删除。。。这里不写出来
class Database{
std::string dbid;
public:Database(const std::string& dbStr) : dbid(dbStr){}virtual ~Database(){}void open() throw(DatabaseError){std::cout << "connected to" << dbid << std::endl;}void close(){std::cout << dbid << "close" <<std::endl;}
};
// 在一个服务器=客户端的模式下,客户拥有多个对象,这些对象分享一个连接的数据库,只有当所有客户都断开连接的时候,数据库才调用close关闭自己。
// 为了记录连接数据库的客户数量,创建一个新的特殊的类,也就是混入类
class Countable{
long count;
protected:Countable(){count = 0;}virtual ~Countable(){assert(count == 0);}
public:long attach(){return count++;}long detach(){return --count > 0 ? count : (delete this, 0);}long refCount() const{return count;}
};
// 这个类的构造函数和析构函数都是保护成员,所有它无法自己生成,必须有一个友元类或者派生类来使用它;
// 析构函数这一点很重要,因为只有detach使用delete它的时候才会被正确的销毁
//
// 下面这个类会继承上面的两个类
class DBConnection : public Database, public Countable{
private:DBConnection(const DBConnection&);DBConnection& operator=(const DBConnection&);//不允许赋值或者赋值protected:
//构造函数--打开数据库DBConnection(const string& dbStr)throw(DatabaseError) : Database(dbStr){open();//}//析构函数,关闭数据库--无法从外面关闭~DBConnection(){ close();}public://静态方法static DBConnection* create(const string& dbStr)throw(DatabaseError){DBConnection* con = new DBConnection(dbStr);con->attach();assert(con->refCount() == 1);return con;}
};
// 这个类只能通过静态成员创建自己,在创建自己的同时,另外两个类也调用了自己的构造函数
//
// 不用修改Database类就做到了记录连接数据库的数量,然后根据数量来调用 DBConnection的析构函数关闭数据库class DBClient{DBConnection* db;
public:DBClient (DBConnection* dbCon){db = dbCon;db->attach();}~DBClient(){ db->detach();}};
// 客户端使用RAII(the Resource Acquisition Is Initation)的方法实现数据库的打开和关闭。int main(){//创建数据库 DBConnection* db = DBConnection::create("database");assert(db->refCount()==1);DBClient c1(db);assert(db->refCount()==2);DBClient c2(db);assert(db->refCount()==3);DBClient c3(db);assert(db->refCount()==4);db->detach();assert(db->refCount() == 3);return 0;
}
在c++模板我曾经介绍过一种使用模板来计数的方式,这里将混入类设定模板,可以指定混入类的类型
template<class Counter>
class DBConnection : public Database, public Counter{
private:DBConnection(const DBConnection&);DBConnection& operator=(const DBConnection&);//不允许赋值或者赋值protected:
//构造函数--打开数据库DBConnection(const string& dbStr)throw(DatabaseError) : Database(dbStr){open();//}//析构函数,关闭数据库--无法从外面关闭~DBConnection(){ close();}public://静态方法static DBConnection* create(const string& dbStr)throw(DatabaseError){DBConnection* con = new DBConnection(dbStr);con->attach();assert(con->refCount() == 1);return con;}
};
这里只有一个改变,就是加入了template<class Counter>,也可以把数据库作为模板类,这样就允许使用不同的数据库,如果这个类的混入类足够多,则可以指定更多的模板类。
4.重复子对象
当派生类继承基类的时候,它会继承基类所有的成员,下面程序说明了多个基类子对象在内存中的布局情况。
#include <iostream>
#include<stdexcept>
#include<string>
#include<cassert>
using namespace std;class A{ int x;};
class B{int y;};
class C : public A, public B{ int z;};int main(){cout << "sizeof(A) = " << sizeof(A) << endl;cout << "sizeof(B) = " << sizeof(B) << endl;cout << "sizeof(C) = " << sizeof(C) << endl;C c;cout << "&c == " << &c << endl;A* ap = &c;B* bp = &c;cout << "ap == " << static_cast<void*>(ap) << endl;cout << "bp == " << static_cast<void*>(bp) << endl;C* cp = static_cast<C*>(bp);//强制向下转换类型 cout << "cp == " << static_cast<void*>(cp) << endl;return 0;
}
//输出
sizeof(A) = 4
sizeof(B) = 4
sizeof(C) = 12
&c == 0x6ffde0
ap == 0x6ffde0
bp == 0x6ffde4
cp == 0x6ffde0
bp == cp true
0
从程序输出来看,对象C的布局如下
A的数据
B的数据
C自己新增的数据
以对象A开头,依次向下偏移四个字节。
ap指向了对象C开头的位置,所以它的输出和&a一样
bp必须指向B对应的位置,所以它必须偏移四个字节,到达B的位置
而把bp向下转为C*的时候,它就需要后退四个字节,来到对象C的初始位置,但是如果bp指向一个独立的B对象,这种转化就是不合法了。
当使用bp == cp时,cp隐式转化为bp了,这种转化说明,向上转总是允许的
可以得出一个结论,子对象和完整类型间来回转换,要用到适当的偏移量
下面这个程序有点像菱形继承,但是其实不是,根据继承,子类会继承基类的全部成员,因此Left和Right都有一份Top,而Bottom则有两份Top了
#include <iostream>
#include<string>
#include<cassert>
using namespace std;class Top{
public:int x{10};//4 bit
public:Top(int n): x(n){}
}; class Left : public Top{
public:int y{20};//4
public:Left(int n, int m):Top(n),y(m){}
};class Right : public Top{
public:int z{30};// 4
public:Right(int n, int m):Top(n), z(m){}
};class Bottem: public Left, public Right{
public:int w;//4
public:Bottem(int i, int j, int k, int m):Left(i,k), Right(j,k), w(m){}
};
int main(){Bottem b(1,2,3,4);//Top + Top + Right + Left + int wcout << sizeof(b) << endl;Top* ptr = &b;//错误,因为存在两个Top因此产生二义性 cout << ptrreturn 0;
}
5. 虚基类
想要真正实现菱形继承,就要用到虚函数了,让Left和Right共享一份Top
另外对象的空间大小是由非静态成员变量和支持虚函数所产生的虚函数表所共同决定的,还有不要忘了字节对齐,至于虚函数表占据多大的空间,要看编译器的实现
#include <iostream>
#include<string>
#include<cassert>
using namespace std;class Top{
public:int x; //4字节
public:virtual ~Top(){}//8字节 Top(int n): x(n){}friend ostream& operator<<(ostream& os, const Top& t){return cout << t.x;}
};
//按照字节对其,Top占16字节 //继承父类的虚指针和数据成员16 + 4字节
class Left :virtual public Top{
public:int y;
public:Left(int n, int m):Top(n),y(m){}
};class Right :virtual public Top{
public:int z;
public:Right(int n, int m):Top(n), z(m){}
};//继承了Left和Right的虚指针以及数据 32
class Bottem: public Left, public Right{
public:int w;
public:Bottem(int i, int j, int k, int m):Left(i,j), Right(j,k),Top(m),w(m){}friend ostream& operator<<(ostream& os, const Bottem& b){return cout << b.x << " " << b.y << " " << b.z << " " << b.w << endl;}
};class A{int a;
};
int main(){cout << sizeof(Top) << endl;//16cout << sizeof(Left) << endl;//32cout << sizeof(Right) << endl;//32cout << sizeof(Bottem) << endl;//48Bottem b(1,2,3,4);cout << sizeof(b) << endl;cout << b << endl;cout <<static_cast<void*>(&b) << endl;Top* p = static_cast<Top*>(&b); cout << *p << endl;cout << static_cast<void*>(p) << endl;cout << dynamic_cast<void*>(p) << endl;return 0;
}
虚继承这方面的事情讲得有点多了,感觉没必要一一详细列举,只需要说一个大概的情况即可
首先,使用虚继承可以保证基类在后续的直接多重继承中只被继承一个副本。
其次,Left和Right还有孙子类Bottem都继承了基类Top,且都有对Top的初始化,如果都起作用了,就会导致二义性,那么对继承的基类进行初始化
的任务就交给了Bottem,这时Left和Right都将会失去对Top初始化的作用,他们对Top的初始化会被忽略,只有最高派生类Bottem的初始化才有效
最后是关于虚继承的虚指针与虚函数表,编译器会给每一个派生类创建一个虚指针,它会指向一张虚函数表,里面有各种数据成员或者方法,在后面需要使用的时候,
它会根据这张虚函数表调用不同的函数,来实现多态。这一点很多文章都有分析,这里只是将一个大概的。以后有空再详细重复吧。
还有一个需要谈到的问题,子类对父类的对象的调用,用下面这个程序作为总结,子类会重复调用父类,最好的解决办法是调用一个特殊的处理,避免重复的工作
#include <iostream>
#include<string>
#include<cassert>
using namespace std;class Top{
private:int x; //4字节
public:virtual ~Top(){}//8字节 Top(int n): x(n){}friend ostream& operator<<(ostream& os, const Top& t){return cout << t.x;}
};
//按照字节对其,Top占16字节 //继承父类的虚指针和数据成员16 + 4字节
class Left :virtual public Top{
private:int y;
public:Left(int n, int m):Top(n),y(m){}friend ostream& operator<<(ostream& os, const Left& l){return cout <<static_cast<const Top&>(l) << " " << l.y;}
};class Right :virtual public Top{
private:int z;
public:Right(int n, int m):Top(n), z(m){}friend ostream& operator<<(ostream& os, const Right& r){return cout <<static_cast<const Top&>(r) << " " << r.z;}
};//继承了Left和Right的虚指针以及数据 32
class Bottem: public Left, public Right{
private:int w;
public:Bottem(int i, int j, int k, int m):Left(i,i), Right(j,j),Top(k),w(m){}friend ostream& operator<<(ostream& os, const Bottem& b){return cout<< static_cast<const Left&>(b) << " "<< static_cast<const Right&>(b) << " "<< b.w;}
};class A{int a;
};
int main(){Bottem b(1,2,3,4);//3 1 3 2 4cout << b << endl;return 0;
}
问题在于Left和Right都调用了Top的输出,最好导致重复输出Top
所以,可以再Left和Right的保护成员中添加一个打印函数,这个函数就只会让派生类调用。
总结初始化的顺序:
(1) 所有虚基类子对象,按照他们在类定义中出现的位置,从上往下,从左往右初始化。
(2)然后非虚基类按通常顺序初始化
(3)所有的成员对象按声名的顺序初始化
(4)完整的对象的构造函数执行
#include <iostream>
#include<string>
#include<cassert>
using namespace std;class M{
public:M(const string& s){cout << " M " << s << endl;}
}; class A{
private:M m;
public:A(const string& s) : m("in A"){cout << " A " << s << endl;}virtual ~A(){}
};class B{M m;
public:B(const string& s) : m("in B"){cout << " B " << s << endl;}virtual ~B(){}
};class C{M m;
public:C(const string& s) : m("in C"){cout << " C " << s << endl;}virtual ~C(){}
};class D{M m;
public:D(const string& s) : m("in D"){cout << " D " << s << endl;}virtual ~D(){}
};class E: public A, virtual public B, virtual public C{M m;
public:E(const string& s): A("from E"), B("from E"),C("from E"), m("in E"){cout << " E " << s << endl;}
};class F: virtual public B, virtual public C, public D{M m;
public:F(const string& s): B("from F"),C("from F"),D("from F"),m("in F"){cout << " F " << s << endl;}
};class G: public E, public F{M m;
public:G(const string& s):B("from G"), C("from G"), E("from G"), F("from G"), m("in G"){cout << " G " << s << endl;}
};int main(){G g("main start");return 0;
}
总体来看,先对E,再对F,然后G自己
细分下去:先初始化虚基类B C,然后是A
然后是E本身
接下来的F也是如此,初始化自己的虚基类,这个任务是G 完成,而且已经完成,也就是B C的初始化,所以就初始化D
最后是G自己,最后输出了m in G
B::m
B
C::m
C
A::m
A
E::m
E
D::m
D
F::m
F
G::m
G
//输出
M in B
B from G
M in C
C from G
M in A
A from E
M in E
E from G
M in D
D from F
M in F
F from G
M in G
G main start
6.名字查找问题
如果一个派生类同时继承了多个类,其中有的类有两个相同名字的函数,在调用这个函数,则会出错。当然,这几个类需要在统一层次的继承的。
#include <iostream>
#include<string>
#include<cassert>
using namespace std;class Top{
public:virtual ~Top(){}
};class Left :virtual public Top{
public:void fun(){}
};class Right : virtual public Top{
public:void fun(){}
};class Bottem : public Left, public Right{};int main(){Bottem b;b.fun();//错误,产生了二义性 }
修改的方法是用基类名称来限定
#include <iostream>
#include<string>
#include<cassert>
using namespace std;class Top{
public:virtual ~Top(){}
};class Left :virtual public Top{
public:void fun(){cout << "Left::fun()" << endl; }
};class Right : virtual public Top{
public:void fun(){cout << "Right::fun()" << endl;}
};class Bottem : public Left, public Right{
public:
using Right::fun;};int main(){Bottem b;b.fun(); }
正如上面提到的,必须是同一层次的继承才会有名字冲突,如果是垂直继承下来的,就不会有二义性了。
#include <iostream>
#include<string>
#include<cassert>
using namespace std;class Top{
public:virtual ~Top(){}virtual void fun(){cout << "Top::fun()" << endl;}
};class Left :virtual public Top{
public:void fun(){cout << "Left::fun()" << endl; }
};class Right : virtual public Top{};class Bottem : public Left, public Right{};int main(){Bottem b;b.fun(); }
虽然Bottem继承了Left和Right还有Top,其中Top和Left都有fun函数,但是b调用fun的时候,会直接调用Left的fun而不是基类Top的fun
这说明,继承中调用同名函数的时候,会优先调用派生级别高一些的。
7.避免使用多重继承
多重继承是一个很复杂的东西,我们应该尽量避免
如果下面两个条件有一个不满足,就不要多重继承
(1)是否需要通过一个新的派生类来显示接口?
(2)需要向上类型转化为基类吗?
尽量使用组合,而不是继承
8. 使用多重继承的一个案例:扩充一个接口
假设有这么一个库,只有一些头文件和接口,具体实现方法都看不见,这个库是一个带有虚函数的类层次接口,并且有全局函数,函数的参数是基类的引用,它利用这个类继承的多态
现在程序员需要用到一些功能,需要这些类的函数是虚函数,但是提供的库并不是,现在怎么办?
//1.提供的头文件和类声名
//Base.h文件
class Base{
public:virtual void v() const;void f() const;//假设我们想要这个函数是虚函数~Base();
}; class Base1 : public Base{
public:void v() const;void f() const;~Base1();
};void fun1(const Base&);//全局函数,调用类中的某些接口
void fun2(const Base&);//同上
//上面这些d东西已经固定了,我们无法更改,现在我们想要添加一个功能g做某些事情,还想让Base1里的函数是虚函数,
//我们也不能令全局函数的功能发生变化
//为了解决问题可以使用多重继承
class MyProject{
public:virtual ~MyProject(){cout << "~MyProject()" << endl;}virtual void f() const = 0;//令这些函数全都为虚函数 virtual void v() const = 0;virtual void g() const = 0;//我们需要增加的功能
}; class MyWay: public MyProject, public Base1{
public:~MyWay(){cout << "~MyWay()" << endl;}void f()const{cout << "MyWay::f()" << endl;Base1::f();}void v()const{cout << "MyWay::v()" << endl;Base1::v();}void g()const{cout << "MyWay::g()\n";}
};
//2下面的文件对用户不可见,这里写出来是为了更好的说明
//Base.cpp文件
void Base::f()const {cout << "Base::fun()" << endl;
} void Base::v()const{cout << "Base::v()" << endl;
}Base::~Base(){cout << "Base::~Base()" << endl;
}void Base1::f()const{cout << "Base1::fun()" << endl;
} void Base1::v()const{cout << "Base1::v()" << endl;
}Base1::~Base1(){cout << "Base1::~Base1()" << endl;
}void fun1(const Base& b){b.f();b.v();
}void fun2(const Base& b){b.f();b.v();
}
int main(){MyWay& pw = *new MyWay;cout << "____________\n";pw.f();cout << "____________\n";pw.v();cout << "____________\n";pw.g();cout << "____________\n";fun1(pw);cout << "____________\n";fun2(pw);cout << "____________\n";delete &pw;return 0;
}
我们通过多重继承解决了接口问题,而且也没用改变原来的库的性质。
相关文章:
c++多重继承
1.概论多重继承是否有必要吗?这个问题显然是一个哲学问题,正确的解答方式是根据情况来看,有时候需要,有时候不需要,这显然是一句废话,有点像上马克思主义哲学或者中庸思。但是这个问题和那些思想一样&#…...
15_FreeRtos计数信号量优先级翻转互斥信号量
目录 计数型信号量 计数型信号量相关API函数 计数型信号量实验源码 优先级翻转简介 优先级翻转实验源码 互斥信号量 互斥信号量相关API函数 互斥信号量实验源码 计数型信号量 计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在…...
二叉树(一)
二叉树(一)1.树的概念2.树的相关概念3.树的表示4.树在实际中的运用5.二叉树概念及结构6.特殊的二叉树7.二叉树的性质🌟🌟hello,各位读者大大们你们好呀🌟🌟 🚀🚀系列专栏…...
【SCL】1200案例:天塔之光数码管显示液体混合水塔水位
使用scl编写天塔之光&数码管显示&液体混合&水塔水位 文章目录 目录 文章目录 前言 一、案例1:天塔之光 1.控制要求 2.编写程序 3.效果 二、案例2:液体混合 1.控制要求 2.编写程序 三、案例3:数码管显示 1.控制要求 2.编写程序 3…...
5.1配置IBGP和EBGP
5.2.1实验1:配置IBGP和EBGP 实验目的 熟悉IBGP和EBGP的应用场景掌握IBGP和EBGP的配置方法 实验拓扑 实验拓扑如图5-1所示: 图5-1:配置IBGP和EBGP 实验步骤 IP地址的配置 R1的配置 <Huawei>system-view Enter system view, return …...
c++中超级详细的一些知识,新手快来
目录 2.文章内容简介 3.理解虚函数表 3.1.多态与虚表 3.2.使用指针访问虚表 4.对象模型概述 4.1.简单对象模型 4.2.表格驱动模型 4.3.非继承下的C对象模型 5.继承下的C对象模型 5.1.单继承 5.2.多继承 5.2.1一般的多重继承(非菱形继承) 5.2…...
[答疑]经营困难时期谈建模和伪创新-长点心和长点良心
leonll 2022-11-26 9:53 我们今年真是太难了……(此处删除若干字)……去年底就想着邀请您来给我们讲课,现在也没有实行。我想再和我们老大提,您觉得怎么说个关键理由,这样的形势合适引进UML开发流程? UML…...
计算机基础知识
计算机网络的拓扑结构 一、OSI 7层网络模型是指什么? 7层分别是什么?每层的作用是什么? OSI7层模型是 国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系。 每层功能:(自底向上) 物理层:建立、…...
Java爬虫—WebMagic
一,WebMagic介绍WebMagic企业开发,比HttpClient和JSoup更方便一),WebMagic架构介绍WebMagic有DownLoad,PageProcessor,Schedule,Pipeline四大组件,并有Spider将他们组织起来…...
[软件工程导论(第六版)]第2章 可行性研究(复习笔记)
文章目录2.1 可行性研究的任务2.2 可行性研究过程2.3 系统流程图2.4 数据流图概念2.5 数据字典2.6 成本/效益分析2.1 可行性研究的任务 可行性研究的目的 用最小的代价在尽可能短的时间内确定问题是否能够解决。 可行性研究的3个方面 (1)技术可行性&…...
Mac下安装Tomcat以及IDEA中的配置
安装brew 打开终端输入以下命令: /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 搜索tomcat版本,输入以下命令: brew search tomcat 安装自己想要的版本,例…...
【Linux详解】——文件基础(I/O、文件描述符、重定向、缓冲区)
📖 前言:本期介绍文件基础I/O。 目录🕒 1. 文件回顾🕘 1.1 基本概念🕘 1.2 C语言文件操作🕤 1.2.1 概述🕤 1.2.2 实操🕤 1.2.3 OS接口open的使用(比特位标记)…...
HomMat2d
1.affine_trans_region(区域的任意变换) 2.hom_mat2d_identity(创建二位变换矩阵) 3.hom_mat2d_translate(平移) 4.hom_mat2d_scale(缩放) 5.hom_mat2d_rotate(旋转 &…...
Python3 JSON 数据解析
Python3 JSON 数据解析 JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。 Python3 中可以使用 json 模块来对 JSON 数据进行编解码,它包含了两个函数: json.dumps(): 对数据进行编码。json.loads(): 对数据进行解码。 在 json 的编解码…...
Homebrew 安装遇到的问题
Homebrew 安装遇到的问题 例如:第一章 Python 机器学习入门之pandas的使用 文章目录Homebrew 安装遇到的问题前言一、安装二、遇到的问题1.提示 zsh: command not found: brew三、解决问题前言 使用 Homebrew 能够 安装 Apple(或您的 Linux 系统&#…...
Metasploit框架基础(二)
文章目录前言一、Meatsplooit的架构二、目录结构datadocumentationlibmodulesplugins三、Measploit模块四、Metasploit的使用前言 Metasploit是用ruby语言开发的,所以你打开软件目录,会发现很多.rb结尾的文件。ruby是一门OOP的语言。 一、Meatsplooit的…...
c++容器
1、vector容器 1.1性质 a)该容器的数据结构和数组相似,被称为单端数组。 b)在存储数据时不是在原有空间上往后拓展,而是找到一个新的空间,将原数据深拷贝到新空间,释放原空间。该过程被称为动态拓展。 vec…...
Vue.js如何实现对一千张图片进行分页加载?
目录 vue处理一千张图片进行分页加载 分页加载、懒加载---概念介绍: 思路: 开发过程中,如果后端一次性返回你1000多条图片或数据,那我们前端应该怎么用什么思路去更好的渲染呢? 第一种:我们可以使用分页…...
计算机网络复习(六)
考点:MIME及其编码(base64,quoted-printable)网络协议http是基于什么协议,应用层到网络层基于什么协议6-27.试将数据 11001100 10000001 00111000 进行 base64 编码,并得到最后传输的 ASCII 数据。答:先将 24 比特的二…...
Redis进阶:布隆过滤器(Bloom Filter)及误判率数学推导
1 缘起 有一次偶然间听到有同事在说某个项目中使用了布隆过滤器, 哎呦,我去,我竟然不知道啥是布隆过滤器, 这我哪能忍?其实,也可以忍,但是,可能有的面试官不能忍!&#…...
Java创建对象的方式
Java创建对象的五种方式: (1)使用new关键字 (2)使用Object类的clone方法 (3)使用Class类的newInstance方法 (4)使用Constructor类中的newInstance方法 (5&am…...
dom基本操作
1、style修改样式 基本语法: 元素.style.样式’值‘ 注意: 1.修改样式通过style属性引出 2.如果属性有-连接符,需要转换为小驼峰命名法 3.赋值的时候,需要的时候不要忘记加css单位 4.后面的值必须是字符串 <div></div> // 1、…...
如何将python训练的XGBoost模型部署在C++环境推理
当前环境:Ubuntu,xgboost1.7.4过程介绍:首先用python训练XGBoost模型,在训练完成后注意使用xgb_model.save_model(checkpoint.model)进行模型的保存。找到xgboost的动态链接库和头文件动态链接库:如果你在conda环境下面…...
About Oracle Database Performance Method
bottleneck(瓶颈): a point where resource contention is highest throughput(吞吐量): the amount of work that can be completed in a specified time. response time (响应时间): the time to complete a spec…...
JavaScript 日期和时间的格式化大汇总(收集)
一、日期和时间的格式化 1、原生方法 1.1、使用 toLocaleString 方法 Date 对象有一个 toLocaleString 方法,该方法可以根据本地时间和地区设置格式化日期时间。例如: const date new Date(); console.log(date.toLocaleString(en-US, { timeZone: …...
【Python】缺失值可视化工具库:missingno
文章目录一、前言二、下载二、使用介绍2.1 绘制缺失值条形图2.2 绘制缺失值热力图2.3 缺失值树状图三、参考资料一、前言 在我们进行机器学习或者深度学习的时候,我们经常会遇到需要处理数据集缺失值的情况,那么如何可视化数据集的缺失情况呢࿱…...
【代码随想录二刷】Day18-二叉树-C++
代码随想录二刷Day18 今日任务 513.找树左下角的值 112.路径总和 113.路径总和ii 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树 语言:C 513.找树左下角的值 链接:https://leetcode.cn/problems/find-bottom-left-tree-va…...
制造业的云ERP在外网怎么访问?内网服务器一步映射到公网
随着企业信息化、智能化时代的到来,很多制造业企业都在用云ERP。用友U 9cloud通过双版本公有云专属、私有云订阅、传统软件购买三种模式满足众多制造业企业的需求,成为一款适配中型及中大型制造业的云ERP,是企业数智制造的创新平台。 用友U 9…...
zookeeper 复习 ---- 练习
zookeeper 复习 ---- 练习在同一节点配置三个 zookeeper,配置正确的是? A: zoo1.cfg tickTime2000 initLimit5 syncLimit2 dataDir/var/lib/zookeeper/zoo1 clientPort2181 server.1localhost:2666:3666 server.2localhost:2667:3667 serv…...
2023年全国最新道路运输从业人员精选真题及答案1
百分百题库提供道路运输安全员考试试题、道路运输从业人员考试预测题、道路安全员考试真题、道路运输从业人员证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 11.在以下选项中关于安全生产管理方针描述正确的是(…...
网站建设需要个体营业执照吗/100个经典创意营销方案
1.默认浏览器打开网站 import process; process.execute("网址")打开网址 wb.go("网址") 2. WEB窗体最大化、最小化 //点击最大化,但是这个函数如果发现最大化会还原窗口,并返回一个值表示当前是否最大化winform.hitmax()//点击最小化按钮wi…...
西安给公司做网站/站长seo软件
文章目录1. AspectJ1.1 什么是AspectJ1.2 基于AspectJ实现AOP操作2. Spring实现AOP2.1 基于注解2.1.1 完全注解开发2.2 基于配置文件1. AspectJ Spirng框架一般都是基于AspectJ实现AOP操作 1.1 什么是AspectJ AspectJ不是Spring组成部分,独立于AOP框架࿰…...
网站域名跟谁买/互联网广告营销是什么
这是一个缠绕了我差不多有大半年的噩梦,作为一个程序员,笔记本怎么可能不装linux系统,但是我的笔记本神舟系列,买回来屡次三番重装系统,废了很多功夫,网络连接那里一直就没有WiFi选项。 无奈之下,我一度把很多环境都迁移到windows里面去了,但是奈何ubuntu之心不死,总…...
花卉网站建设策划书/什么是搜索引擎优化的核心
对于 React 组件来说,refs 会指向一个组件类的实例,所以可以调用该类定义的任何方法。 如果需要访问该组件的真实 DOM,可以用 ReactDOM.findDOMNode 来找到 DOM 节点,但我们并 不推荐这样做。因为这在大部分情况下都打破了封装性&…...
站酷网官网/新闻发稿
多半的时间,都花在了格式转换上……或者在修转换bug上面。 const char* char* char[] stringQString类型常用: QString::fromStdString(string str); QString::Number(int str); QString::toString(QString str); 实际遇到的稀奇古怪的需要 1.string转…...
携程旅行网/正规优化公司哪家好
参考博客: http://www.cnblogs.com/xing901022/p/4264078.html http://www.cnblogs.com/liaojie970/p/5913272.html 搭建完SpringMVC后要开启定时任务,添加以下: xmlns:task"http://www.springframework.org/schema/task"http://ww…...