C++继承中你不知道的事
一、继承(inheritance)是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在原有类特性的基础上进行扩展,增加功能。
例:
二、继承定义格式:
三、继承关系和访问限定符
3种继承关系:public(公有继承)、protected(保护继承)、private(私有继承)
在继承的过程中由于继承类型的关系,基类的成员继承类型可能会在子类中发生改变。
子类继承基类:
继承类型为public时:基类中的public、protected、private成员的访问属性均没发生变化。
class Base
{
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
class D :public Base
{
public:
void test1()
{
_pub = 3;
_pro = 4;
_pri = 7;//不可访问
}
protected:
int data;
};
void test()
{
D d;
d._pub = 2;
d._pro = 9;//不可访问
d._pri = 12;//不可访问
}
继承类型为protected时:基类中的public成员的访问属性降级为protected,其他两个均没发生变化。
class Base
{
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
class D :protected Base
{
public:
void test1()
{
_pub = 3;
_pro = 4;
_pri = 7;//不可访问
}
protected:
int data;
};
void test()
{
D d;
d._pub = 2;//不可访问
d._pro = 9;//不可访问
d._pri = 12;//不可访问
}
继承类型为private时:基类中的public、protected成员均降级为子类的private成员,不可见。
class Base
{
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
class D :private Base
{
public:
void test1()
{
_pub = 3;//不可访问
_pro = 4;//不可访问
_pri = 7;//不可访问
}
protected:
int data;
};
void test()
{
D d;
d._pub = 2;//不可访问
d._pro = 9;//不可访问
d._pri = 12;//不可访问
}
例如给一个基类Base,其对象模型如下图所示:
class Base
{public:
Base()
{}
public:
int _public;
protected:
int _protected;
private:
int _private;
};
注:private只可在本类内使用
protected可以在子类和基类内用
public基类、子类和类外均可使用
总结:1、如果基类成员不想在类外被直接访问,但需要在子类中访问,此时可定义为protected。
2、如果基类成员不想在子类中被访问,可定义为private。
3、不管哪种继承方式,子类都可以访问基类的公有成员和保护成员,在子类中基类的私有成员存在但不可以访问(不可见)。
4、使用关键字class时默认继承方式为private,使用关键字struct时默认继承方式为public。(最好显式的写出继承方式)
class D :Base
{
void test1()
{
_pub = 10;
}
public:
int _D_pub;
};
struct E :Base
{
void test2()
{
_pub = 20;
}
public:
int _E_pub;
};
子类D继承基类Base,在进入test1()时编译器会报错
子类E继承基类Base,在进入test2()后会显示
5、在继承关系里面,在派生类中如果没有显式定义六个成员函数,编译系统会默认合成这些函数。(构造函数、拷贝构造函数、析构函数、赋值运算符重载、取址操作符重载、const修饰的取址运算符重载)
6、继承关系中,构造函数的调用顺序:子类对象先调用子类构造函数,在子类构造函数的初始化列表中调用基类的构造函数,完成子类对象的初始化,然后进入子类构造函数的函数体。
析构函数的调用顺序:先调用子类中的析构函数,在调用基类中的析构函数。如下:
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
};
class D :public Base
{
public:
D()
{
cout << "D()" << endl;
}
~D()
{
cout << "~D()" << endl;
}
};
所以会出现这的结果。
说明:1、基类没有缺省的构造函数,子类必须要在初始化列表中显式给出基类名和参数列表。
2、基类没有定义构造函数,则子类可以不用定义,全部使用缺省构造函数。
3、基类定义了带有形参表的构造函数,子类就一定要定义构造函数。(隐藏)
四、继承体系中的作用域:
-
在继承体系中基类和子类是两个不同的作用域。
-
在子类和父类中有同名成员,子类成员将屏蔽对父类成员的直接访问。
注:实际中继承体系里最好不要定义同名成员。
隐藏
class Base { public: void test1() { cout << "Base::test1()" << endl; } }; class D :public Base { public: void test1() { cout << "D::test1()" << endl; } }; Void test() { D d; d.test1(); //d.Base::test1(); }
只调d,test1(),打印如下:
此处隐藏了Base中的test1()
调d.Base::test1(),打印如下:
五、隐藏与重载的区别:
重载:必须在同一作用域内,函数名相同,参数不同。
隐藏:在不同的作用域内,函数名相同,参数可以不相同。
六、继承--赋值兼容规则
-
子类对象可以赋值给基类对象
-
父类对象不能赋值给子类对象
-
父类的指针/引用可以指向子类对象
-
子类的指针/引用不能只想父类对象
class Base { public: int _pub; }; class D :public Base { public: int _pub; }; void test() { Base b; D d; b = d;//子类对象可以给父类对象赋值 //d = b;//父类对象不可以给子类对象赋值 Base *pb = &b;//创建基类指针 pb = &d;//父类的指针或引用可以指向子类对象 D *pd = &d;//创建子类指针 pd = &b;//子类的指针或引用不可以指向父类 pd = (D*)&b; pb->_pub = 2;//可以强制类型转换,但有可能造成程序崩溃 }
说明:
基类指针 Base *pb;
该指针即可指向基类对象,又可以指向派生类对象,但指向派生类的对象是派生类中集成的基类的对象。
七、类的组合:在一个类中以另一个类的对象作为数据成员的称为类的组合。
class Base { public: int _pub; }; class D :public Base { public: void test1() { _b._pub = 0; } private: Base _b; };
八、友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
class Base { friend void test1(Base &b, Derive &d); public: int _data; }; class Derive :public Base { public: int data; }; void test1(Base &b,Derive &d) { cout << b._data << endl; cout << d._data << endl;//不可访问,友元关系不能被继承 cout << d.data << endl; }
编译器会报错,故表明友元关系不能继承。
九、继承与静态成员
基类里面定义static成员,则整个继承体系中只有一个这样的成员。无论派生多少个子类,都只有一个static成员实例。
class Base { public: Base() { data++; } public: static int data; protected: int _pro; }; int Base::data = 3; class D :public Base { protected: int _D_pro; }; void test() { D d1; D d2; D d3; cout << Base::data << endl; D::data = 3; cout << Base::data << endl; }
结果为如下,静态成员可以继承。
十、单继承、多继承和菱形继承
1、单继承:即一个子类只有一个父类。
单继承对象模型
class B { protected: int _data; }; class C :public B { protected: int _data1; }; class D :public C { protected: int _data2; };
-
多继承:即一个子类有两个或两个以上直接父类。
多继承对象模型
class B { protected: int _data; }; class C { protected: int _data1; }; class D :public B, public C { public: int _data2; };
-
菱形(钻石)继承
菱形(钻石)继承对象模型
class B { protected: int data1; }; class C1 :public B { protected: int data2; }; class C2 :public B { protected: int data3; }; class D :public C1, public C2 { protected: int data4; };
菱形继承存在二义性和数据冗余问题。为了解决此问题引出了虚继承的概念。
-
虚继承
class B { public: int data; }; class C1 :virtual public B//虚继承 { public: int data1; }; class C2 :virtual public B//虚继承 { public: int data3; }; class D :public C1, public C2 { public: int data4; }; void test() { D d; d.data = 2; d.data1 = 4; d.data3 = 7; d.data4 = 8; }
调内存块,可见其在内存中的布局如下:
虚继承的对象模型:
-
以上代码在VS2013环境下运行