目录
1 继承和派生的概念
1.1 派生的概念
函数
库
源代码
类继承
继承可以做什么?
在已经有的类上添加功能。
给已有的类添加新数据。
修改原有的类方法。
Cperson
int age;
char sex;
添加
double score
string num
变成学生
CStudent
int age
char sex
double score
string num
1.2 派生的过程
吸收基类成员
改造基类成员 -->设置新的访问权限
加入派生类新成员
定义派生类构造、析构函数 -->构造、析构函数不能继承
1.3 派生类的定义
class <派生类名>:<继承方式> <基类名>
{
<派生类新定义成员>
};
#include <iostream>
using namespace std;
//基类
class CPerson
{
public:
int age;
char sex;
CPerson()
{
age = 20;
sex = 'f';
}
};
//派生类
//CStudent类继承自CPerson类
//CStudent类是CPerson的派生类
class CStudent:public CPerson
{
public:
int score;
string num;
CStudent()
{
score = 100;
num = "1000";
}
};
int main()
{
CStudent stu1;
cout << stu1.age <<","<<stu1.sex<<","<<stu1.score<<","<<stu1.num<<endl;
return 0;
}
派生类对象
CStudent stu1;
int age ; ->继承自基类的成员
int sex;
int score; ->派生类新增的成员
int num;
2 派生类的继承方式
2.1 继承方式
公有继承:public
私有继承:private
受保护继承:protected
不同继承方式的影响主要体现在:(这归根结底取决于从基类接收的成员在派生类中是什么属性)
1)派生类成员函数对基类成员的访问控制。
2)派生类对象对基类成员的访问控制。
1 基类的private成员
无论是哪种继承方式,基类的私有成员都是只有基类的成员函数和其友元才能访问。
派生类的成员函数不能访问基类的private成员。
派生类的对象也不能访问基类的private成员。
2 基类的public和protected成员
根据派生类的继承方式决定该成员在派生类中的访问级别。
2.2 公有继承
公有继承的基类成员在派生类中保持自己的访问级别:
2.3 私有继承
私有继承的基类成员在派生类中都改变属性为private成员:
2.4 受保护继承
受保护继承的基类成员在派生类中都改变属性为protected成员:
2.5 受保护成员的特点
可以认为它是private和public的结合:
1)像private成员一样,类外不能直接访问protected成员。
2)像public成员一样,该类的派生类的成员函数可以访问protected成员。
3)protected成员既实现了数据隐藏,又方便继承,实现代码重用。
2.6 总结
注意:
只有基类的成员函数和其友元能访问基类的私有成员。
派生类的成员函数和派生类的对象无法访问继承自基类的私有成员。
2.7 练习
is-a关系
继承建立的是is-a关系,如:苹果是水果、学生是人。
继承不建立has-a关系,如:午餐是水果
继承不能建立is-like-a关系,如:这个人像猪
继承不能建立is-implemented-as-a关系,如:用数组实现栈
继承不建立uses-a关系,如:计算机可以使用打印机
2.8 类和结构体在继承层面上的不同
在C++中类和结构体有两点不同:
1)class默认的成员属性是private,struct默认的成员属性是Public
2) 在继承层面:class默认的继承方式是私有成员,而struct默认的继承方式是公有继承。
3 继承与静态成员
3.1 知识要点
Base
{
public:
static int s_data;
};
Derived:public Base
{
private:
int m_data;
};
int main()
{
Deirved d;
Base::s_data; //作用域访问
Derived::s_data;
d.s_data; //类对象访问
}
1) 如果基类里定义了static成员,则整个继承层次中只有一个这样的成员。无论从基类派生了多少个派生类,static成员是基类和派生类共享的。
2) 可以通过作用域限定符或者类对象访问静态成员。
3) static成员遵循常规的访问控制,若static成员在基类中为private,则派生类不能访问它。
class Base
{
public:
static int i;
Base()
{
i++;
}
};
int Base::i = 0;
class Derived:public Base
{
Derived()
{
i++;
}
};
4 继承与转换
1) 派生类对象是更特殊的概念,基类对象是更一般的概念。
2) 派生类对象包含基类内嵌对象,可以将基类指针指向派生类对象,也可以将基类引用绑定到派生类对象。
Derived d;
Base *pb = &d; //基类指针
Base &rb = d; //基类引用
3)不能将派生类指针指向基类对象,也不能将派生类引用绑定到基类对象。
Base b;
Derived *pd = &b;
Derived &rd = b;
这样是错误的。
4.1 转换的可访问性
1)公有继承,类外和后代类成员函数中可以实现派生类到基类的转换
2)私有继承,类外和后代类成员函数中都不能实现派生类到基类的转换
3)受保护继承,类外不能实现派生类到基类的转换,后代类成员函数中可以实现。
4)要查看派生类继承自基类的Public成员在需要转换的位置的访问权限,如果可以访问,则可以实现派生类到基类的转换
内容回顾:
1) 派生类的成员函数可以访问继承自基类的私有变量吗?不可以
2)受保护继承和私有继承的区别是什么?
3)如果改变派生类对象中继承自基类的静态成员,基类对象中的该成员改变吗?
4)在类外定义的私有继承的基类引用可以绑定派生类对象吗?
5 派生类的构造与复制控制
5.1 派生类的构造
派生类对象由派生类新增的成员加上一个或者多个基类的子对象构成,当构造、复制、赋值和析构派生类的对象是,也需要构造,复制,赋值和析构这些基类子对象。
1)如果派生类没有显式定义构造函数,则会合成一个默认的构造函数,它会自动调用基类的默认构造函数对基类成员进行初始化。
2)定义派生类的构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化通过调用基类的构造函数完成。
3)如果基类没有定义默认构造函数,则必须使用初始化列表的方式来初始化基类成员。
4)一个派生类只能初始化自己的直接基类,直接基类是指在派生列表中指定的类。
#include <iostream>
using namespace std;
class A
{
public:
int data1;
public:
A(int x=0):data1(x){
cout<<"A(int x=0)"<<endl;
}
};
class C
{
public:
int data3;
public:
C(int x=0):data3(x){
cout<< "C(int x=0)"<<endl;
}
};
class B:public A{
public:
int data2;
C c;
B(int x=0):data2(x)
{
cout << "B(int x=0)" << endl;
}
};
输出结果
A(int x=0)
C(int x=0)
B(int x=0)
单继承的构造函数定义:
<派生类名>(<总参数表>):<基类名>(<参数列表1>)
{
<派生类数据成员初始化>
};
派生类对象的构造顺序:
首先调用基类的构造函数,再调用对象成员类(内嵌对象)的构造函数,最后执行派生类构造函数的函数体。
5.2 派生类的析构
1)派生类的析构函数不负责撤销基类对象的成员,编译器会调用基类的析构函数来做基类成员的清理工作。
2)每个类的析构函数只负责清理自己的成员。
3)派生类对象的撤销顺序与构造函数相反:先运行派生类的析构函数,再运行对象成员类的析构函数(内嵌对象),最后调用基类的析构函数。
5.3 派生类的复制控制
当类中包含指针成员时,一般自定义复制构造函数与赋值操作符。
否则,使用编译器自动合成的复制构造与赋值操作符就可以满足复制要求。
如果派生类显式定义了自己的复制控制函数,则会完全覆盖默认的定义,即此时派生类的复制控制函数不仅要负责派生类新增成员的复制控制,同时也要负责对基类成员的复制或者赋值。
Class Base;
Class Derived:public Base
{
public:
Derived(const Derived& d);
Derived& operator =(const Derived &d);
};
Derived::Derived(const Derived& d):Base(d)
{
}
Derived::Derived::operator=(const Derived &d)
{
if(this!=&d)
{
Base::operator = (d); //调用基类的赋值运算符
}
return *this;
}
派生类对象转换为基类对象的引用,并显式的调用基类的复制构造函数和赋值操作符重载函数,基类的两个函数可以自定义的,也可以是编译器自动合成的。
6 多重继承派生类的定义和构造
6.1 定义多重继承的派生类
6.2 多重继承派生类的构造
构造多重继承的派生类对象需要构造其所有的直接基类子对象。
构造顺序为:
先调用所有基类的构造函数,再执行派生类的的构造函数体。
处于同一层次的各基类构造函数的调用顺序取决于定义派生类时基类列表所指定的基类顺序,与派生类构造函数中的初始化列表顺序无关。
如果派生类中包含对象成员(内嵌对象),则构造顺序为:
调用所有直接基类的构造函数。
调用内嵌对象的构造函数,调用顺序按照它们在类中声明的顺序。
执行派生类的构造函数体。
7 多重继承的二义性
7.1 二义性举例
class A
{
public:
void f();
};
class B
{
public:
void f();
void g();
};
class C: public A, public B
{
public:
void g();
void h();
};
class B
{
public:
int b;
};
class B1 : public B
{
private:
int b1;
};
class B2 : public B
{
private:
int b2;
};
class C : public B1, public B2
{
public:
int f();
private:
int d;
};
C c1;
c1.b
c1.B::b
Solution
1) 指定同名成员是从哪个类继承的
c1.B1::b;
c1.B2::b;
2) 虚基类
8 虚继承与虚基类
8.1 虚基类的概念
虚继承是一种机制,类通过虚继承来表示在其后代类中共享虚基类的状态。
在虚基类下,无论虚基类在派生层次中作为基类出现多少次,其派生层次中的派生类只继承一个共享的基类子对象。
派生层次中的派生类只继承一个共享的基类子对象。
共享的基类子对象称为虚基本子对象。
8.2 虚基类的规则
1)如果多重继承不牵扯到对同一基类的派生,就没必要定义虚基类。
2)为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚继承。
3)由最底层的派生类构造函数初始化虚基类。
4)能用单一继承解决的问题九不要使用多重继承。
8.3 虚继承对象的构造
8.4 构造析构顺序
1)无论虚基类出现在继承层次的哪个地方,总是在构造非虚基类之前构造虚基类。
2)若同一层次中包含多个虚基类,其调用顺序按定义时顺序。
3)若虚基类由非虚基类派生而来,则仍按先调用基类构造函数,再调用派生类构造函数的顺序。
4)析构顺序与构造顺序相反。
内容回顾:
1)如果一个派生类中包含对象成员则派生类对象构造顺序?
2)派生类中显式定义了复制构造函数,此时编译器会自动调用基类的默认复制构造函数吗?
3)多重继承的二义性如何解决?什么是虚基类?