1.继承的引入
1.1继承的概念:
继承:所谓继承,就是从先辈处得到属性和特征。类的继承就是新类从已有类得到已有的特性,新类被称为派生类,已有类被称为基类。可抽象为派生类是基类的具体化,而基类则是派生类的抽象。
1.2继承的意义:
继承机制是面向对象程序设计中避免代码重复的最重要的手段,它允许程序员对已有类进行扩展,增加功能。
2.继承的定义格式
2.1定义格式
class 派生类名:继承方式 基类名
{
派生类新增的方法和属性;
};
2.2:三种继承方式:
公有继承public;保护继承protected;私有继承private;
2.3注意事项:
(1)class如果没有显示的给出继承方式,系统默认为private继承,但是最好还是显示定义继承类型,即使是私有继承。
(2)struct的默认继承方式则是public。
3.继承方式&访问限定符
3.1派生类可以继承基类中除了构造函数和析构函数之外的所有成员,但是这些成员的访问属性是由继承方式决定的。
3.2三种类成员的访问限定符:公有public;保护protected;私有private;
3.3三种继承方式:公有继承public;保护继承protected;私有继承private;
3.4不同的继承方式下基类成员在派生类中的访问属性:
详解此表格:
(1)public继承的访问规则:
文字解释此表格:
1.公有继承中,派生类中可访问基类的公有成员(包括属性和方法),类外派生类的对象可访问基类的公有成员(包括属性和方法)
2.公有继承中,派生类中可访问基类的保护成员(包括属性和方法),类外派生类的对象不可以访问基类的保护成员(包括属性和方法)
3.公有继承中,派生类中不可以访问基类的私有成员(包括属性和方法),类外派生类的对象不可以访问基类的私有成员(包括属性和方法)
(二)保护继承的访问规则:
文字解释此表格:
1.保护继承中,派生类中可以访问基类的公有成员(包括属性和方法),类外派生类的对象不可以访问基类的私有成员(包括属性和方法)
2.保护继承中,派生类中可以访问基类的保护成员(包括属性和方法),类外派生类的对象不可以访问基类的保护成员(包括属性和方法)
3.保护继承中,派生类中不可以访问基类的私有成员(包括属性和方法),类外派生类的对象不可以访问基类的私有成员(包括属性和方法)
(3)私有继承的访问规则:
文字解释此表格:
1.私有继承中,派生类中可以访问基类的公有成员(包括属性和方法),类外派生类的对象不可以访问基类的公有成员(包括属性和方法)
2.私有继承中,派生类中可以访问基类的保护成员(包括属性和方法),类外派生类的对象不可以访问基类的保护成员(包括属性和方法)
(1)三种继承方式下,在派生类中均可访问基类的共有成员和保护成员,私有成员不能访问(因为基类和派生类不属于同一作用域)。
(2)公有继承中, 在类外通过子类的对象可访问基类的公有成员(包括成员数据和成员函数),其他情况在类外均访问不了。
(3) 保护继承中,派生类中基类的public成员降级为protected,protected成员仍为protected。
(4)私有继承中,派生类中基类的public成员降级为private,protected降级为private。
(5)派生类继承了除基类的构造函数和析构函数之外的所有成员,稍后讲解。
(6) public继承是一个接口继承,保持is-a原则。每个父类可用的成员对子类都可用,每个子类成员都可看做一个父类成员。
(7)private和protected是实现继承,保持has-a原则。类似一组合/聚合。但它比组合更低级,当一个派生类对象需要访问基类的保护成员或需要重新定义虚函数时它就是合理的,绝大多数情况下我们都会选择public继承。
4.派生类的六个默认构造函数而析构函数的的调用顺序与构造函数的调用顺序刚好相反,先调用派生类的析构函数,再调用基类的析构函数。
4.4派生类的构造函数和析构函数
我们知道,基类的构造函数和析构函数是不能被继承的,鉴于这一点,派生类的构造函数和析构函数定义时需要注意那些问题呢?
派生类的构造函数:
(1)当基类显示地定义了缺省构造函数(即构造函数没有参数或全缺省)时,派生类可以显示定义自己的构造函数,也可以不定义,如果没有显示定义,编译器会自动替我们合成默认构造函数去调基类的构造函数。
(2)当基类有带参的构造函数时,派生类必须定义构造函数(即便函数体为空也必须定义),必须将基类的构造函数放在派生类的初始化列表中,以提供把参数传给基类构造函数的途径。
派生类构造函数的一般格式:
派生类名(参数总表):基类名(参数表)
{
派生类新增数据成员的初始化语句;
}
(3)含有子对象(基类对象)的派生类的构造函数
派生类的数据成员中包含基类的对象时,此对象称为子对象,即对象中的对象。
当派生类中包含子对象时,其构造函数的一般格式:
派生类名(参数总表):基类名(参数表0),子对象名1(参数表1),...,子对象名n(参数表n)
{
派生类新增数据成员的初始化语句;
}
在定义派生类对象时,构造函数的调用顺序:先调用派生类的构造函数---->调用基类的构造函数并执行函数体---->调用子对象的构造函数并执行函数体---->调用派生类的构造函数体
5.1继承体系中,子类的作用域和父类的作用域属于两个作用域。(在子类中不能访问父类的私有成员足以说明此点)
5.2同名隐藏:如果子类中包含和父类相同名字的成员,则子类成员将屏蔽对父类成员的
直接 访问, 如果想要在子类中访问父类的同名成员,就必须采用作用域限定符。前面有提到,对于公有继承,数据成员函数继承到派生类中的访问属性不发生降级,通过派生类的对象仍可以在类外访问基类的公有成员,但是对于私有继承却做不到,因为他们在继承过程中对基类成员的访问属性进行了不同程度的降级,但是我们仍想通过派生类的对象在类外访问基类的私有成员该怎么办呢?这就引入了我们的访问声明。
访问声明的方法就是把基类的保护成员或公有成员直接写在私有派生类定义式中的同名段中,同时给成员名前冠以基类名和作用域标识符。利用该种方法,该成员就成为派生类的保护成员或公有成员了。
使用访问声明时需注意的问题:
(1)数据成员也可以使用访问声明。
(2)访问声明中只含不带参数和类型的函数名或变量名。
(3)访问声明不能改变成员在基类中的访问属性。
(4)对于基类中的重载函数名,访问声明对基类中所有同名函数起作用。
六.赋值与转换----赋值兼容规则
6.1规则
1,子类对象可以直接赋值给父类对象(切片/割片)。
2,父类对象不能直接赋值给子类对象。
3,父类对象的引用或指针可以直接指向子类对象。
4,子类对象的引用或指针不可以直接指向父类对象。(强制类型转换可完成)
6.2对象赋值:
class B
{
protected:
int _b;
};
class D:public B
{
private:
int _d;
};
void Funtest()
{
D d;
B *b;
b = &d;//父类指针指向子类对象
D *d1;
B b1;
d1 = (D*)&b1;//子类对象可通过强制类型转换指向父类对象(尽量避免)
D &d2 = d;
B b2;
b2 = d2;//父类引用指向子类对象
D d3;
B &b3 = b1;
d3 = (D&)b3;//父类引用指向子类对象
}
int main()
{
Funtest();
return 0;
}
七:虚拟继承
class B
{
int _b;
};
class C1:public B
{
int _c1;
};
class C2:public B
{
int _c2;
};
class D:public C1,public C2
{
int _d;
};
void Funtest()
{
D d;
d._b = 10;//错误,访问不明确
d.C1::_b = 10;//正确
d.C2::_b = 10;//正确
}
如何避免这种访问不明确呢,是否可以将重复部分_b只在D类中保存一份呢,这将引入
虚拟继承
的概念。
class B
{
int _b;
};
class C1:virtual public B
{
int _c1;
};
class C2:virtual public B
{
int _c2;
};
class D:public C1,public C2
{
int _d;
};
void Funtest()
{
B b;
C1 c1;
}
上面这段代码就是一个虚拟继承的例子,注意关键字virtual的位置不要写错哦!!
当创建好C1类的变量c1时,编译器会为C1合成一个默认的构造函数,这个合成的默认构造函数会做哪些事呢?
首先,如果基类有缺省构造函数,它会去调它,其次,它会将偏移量的地址指针(虚指针)放在c1对象的前4个字节处。
再来看一下如果是D类的对象,又是怎么存储的呢?
最后,需要注意的几点:
1、友元关系不能继承,因为友元关系不属于类的成员(就好比你朋友的女朋友并不是你的女朋友)。
2、如果类中包含静态成员,无论继承了多少派生类,静态成员都只保存一份。
3、析构函数和构造函数不能被继承下来。原因:派生类除了继承基类的成员外,还可以添加只属于自己的新成员,如果用继承来的构造函数初始化,只能初始化从基类继承来的那部分,而派生类本身新添加的那部分成员初始化不了。析构函数也是一样的,析构不到派生类新添加的成员,导致内存泄漏。(原因很重要)
晚安~~~