球场 技术支持 东莞网站建设,直播app软件开发需要多少钱,单页销售网站制作制作,软文新闻发布平台1.概论多重继承是否有必要吗#xff1f;这个问题显然是一个哲学问题#xff0c;正确的解答方式是根据情况来看#xff0c;有时候需要#xff0c;有时候不需要#xff0c;这显然是一句废话#xff0c;有点像上马克思主义哲学或者中庸思。但是这个问题和那些思想一样#…1.概论多重继承是否有必要吗 这个问题显然是一个哲学问题正确的解答方式是根据情况来看有时候需要有时候不需要这显然是一句废话有点像上马克思主义哲学或者中庸思。 但是这个问题和那些思想一样被一知半解的人所误解和扭曲最后变成了和稀泥的借口。下面来谈一谈我的个人看法。 假设有两个类ABA类提供了一种我们需要的容器B类提供了我们需要的算法这两个类是不同途径提供而来现在创造一种满足这两个要求的类就可以使用继承 类c可以同时继承两个类获得他们的特征和行为。 但是c中的模板提供了新的思路它可以通过自动识别来使用对应的方法这样就不需要使用多重继承才能使用相关的功能了。在c标准库中iostream类有一个多重继承 iosistream 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(){}};templateclass Base1void interface1(const Base1 b1){b1.interface1();}templateclass Base2void interface2(const Base2 b2){b2.interface2();}templateclass Base3void 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
#includestdexcept
#includestring
#includecassert
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模板我曾经介绍过一种使用模板来计数的方式这里将混入类设定模板可以指定混入类的类型
templateclass 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;}
};
这里只有一个改变就是加入了templateclass Counter也可以把数据库作为模板类这样就允许使用不同的数据库如果这个类的混入类足够多则可以指定更多的模板类。4.重复子对象当派生类继承基类的时候它会继承基类所有的成员下面程序说明了多个基类子对象在内存中的布局情况。#include iostream
#includestdexcept
#includestring
#includecassert
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_castvoid*(ap) endl;cout bp static_castvoid*(bp) endl;C* cp static_castC*(bp);//强制向下转换类型 cout cp static_castvoid*(cp) endl;return 0;
}
//输出sizeof(A) 4sizeof(B) 4sizeof(C) 12c 0x6ffde0ap 0x6ffde0bp 0x6ffde4cp 0x6ffde0bp cp true0从程序输出来看对象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
#includestring
#includecassert
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
#includestring
#includecassert
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_castvoid*(b) endl;Top* p static_castTop*(b); cout *p endl;cout static_castvoid*(p) endl;cout dynamic_castvoid*(p) endl;return 0;
}虚继承这方面的事情讲得有点多了感觉没必要一一详细列举只需要说一个大概的情况即可首先使用虚继承可以保证基类在后续的直接多重继承中只被继承一个副本。其次Left和Right还有孙子类Bottem都继承了基类Top,且都有对Top的初始化如果都起作用了就会导致二义性那么对继承的基类进行初始化的任务就交给了Bottem这时Left和Right都将会失去对Top初始化的作用他们对Top的初始化会被忽略只有最高派生类Bottem的初始化才有效最后是关于虚继承的虚指针与虚函数表编译器会给每一个派生类创建一个虚指针它会指向一张虚函数表里面有各种数据成员或者方法在后面需要使用的时候它会根据这张虚函数表调用不同的函数来实现多态。这一点很多文章都有分析这里只是将一个大概的。以后有空再详细重复吧。还有一个需要谈到的问题子类对父类的对象的调用用下面这个程序作为总结子类会重复调用父类最好的解决办法是调用一个特殊的处理避免重复的工作#include iostream
#includestring
#includecassert
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_castconst 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_castconst 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_castconst Left(b) static_castconst 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
#includestring
#includecassert
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::mBC::mCA::mAE::mED::mDF::mFG::mG//输出 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 start6.名字查找问题如果一个派生类同时继承了多个类其中有的类有两个相同名字的函数在调用这个函数则会出错。当然这几个类需要在统一层次的继承的。#include iostream
#includestring
#includecassert
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
#includestring
#includecassert
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
#includestring
#includecassert
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;
}我们通过多重继承解决了接口问题而且也没用改变原来的库的性质。