- 类和类的关系
- 继承
2.1 定义
2.2 派生类的组成
2.3 几点说明 - 继承的方式
3.1 protected 访问控制
3.2 派生类成员的标识和访问 - 练习
- 继承中的构造和析构
5.1 类型兼容性原则
5.2 初始化父类成员,父类与子类的构造函数的关系
5.3 继承中同名成员变量处理方法
5.4 派生类中的static关键字
5.5 多继承
在 C++中可重用性(software reusability)是通过继承(inheritance)这一机制来实现的。 如果没有掌握继承性,就没有掌握类与对象的精华。
1.类和类之间的关系
has-A uses-A is-A
- has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
- uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。
- is-A 机制称为“继承”。关系具有传递性,不具有对称性。
2.继承
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
Student(int age,string name){
this->age = age;
this->name = name;
}
void printS(){
cout<<name<<endl;
cout<<age<<endl;
}
private:
string name;
int age;
};
//类Student2继承与类Student
class Student2:public Student
{
public:
Student2(int age,string name,char sex,float score):Student(age,name){//私有变量无法继承,只能通过初始化传递!
//子类继承过来的成员变量,通过父类的构造器来构造
this->sex = s;
this->score = f;
}
void printS(){
Student::printS();
cout<<this->sex<<endl;
cout<<this->score<<endl;
}
private:
char sex;
float score;
};
2.1定义
类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。
派生与继承,是同一种意义两种称谓。isA 的关系。
1.png2.2派生类的组成
派生类中的成员,包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。
2.png2.3 几点说明:
- 全盘接收,除了构造器与析构器。基类有可能会造成派生类的成员冗余,所以说基类是需设计的。
- 派生类有了自己的个性,使派生类有了意义。
3.继承的方式
class 派⽣生类名:[继承⽅方式] 基类名{
派⽣生类成员声明;
};
一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类, 称为单继承。下面从单继承讲起。
3.1protected 访问控制
protected 对于外界访问属性来说,等同于私有,但可以派生类中可见。
#include <iostream>
using namespace std;
class Base {
public://访问控制权限
int pub;//对内部外部均可以
protected:
int pro;//对内可以,外不可以//protected访问控制权限下的成员 儿子可见
private:
int pri;//对内外 均不可以
};
class Drive:public Base{
public:
void func(){
pub = 10;
pro = 100;
//pri=1000;//error
}
};
//三看原则
//1 看当前的成员调用的是类的外部 还是在类的内部
//2 看儿子的继承方式,是公有继承还是 私有继承
//3 看当前的额成员变量在父亲中的访问控制权限
//1 基类的私有成员 不管子类如何继承,子类都访问不了。
//2 如果是公有继承,那么基类中的访问控制权限,除了私有成员,在子类中保持不变
//3 如果是保护继承protected,那么子类中除了基类的私有成员,全部是protected权限
//4 如果是私有继承private,父类中除了私有成员,在子类都是私有成员
int main(void)
{
Base b;
b.pub = 10;
//b.pro = 100;//error
//b.pri = 1000;//error
return 0;
}
3.2派生类成员的标识和访问
---- | public | protected | private |
---|---|---|---|
公有继承(public) | public | protected | 不可见 |
保护继承(protected) | protected | protected | 不可见 |
私有继承(private) | private | private | 不可见 |
public公有继承
当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类 中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无 论派生类的成员还是派生类的对象都无法访问基类的私有成员。
private私有继承
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。。基类的公有成员和保 护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但 是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对 象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都 会成为不可访问。因此私有继承比较少用。
protect保护继承
保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的 公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员 还是派生类的对象,都无法访问基类的私有成员。
private成员在子类中依然存在,但是却无法访问到。不论何种方式继承基类,派生类都不能直接使用基类的私有成员。
如何恰当的使用public,protected和private为成员声明访问级别?
- 需要被外界访问的成员直接设置为public
- 只能在当前类中访问的成员设置为private
- 只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。
4.练习
#include <iostream>
using namespace std;
class A
{
private:
int a;
protected:
int b;
public:
int c;
A(){
a = 0;
b = 0;
c = 0;
}
void set(int a,int b,int c){
this‐>a = a;
this‐>b = b;
this‐>c = c;
}
};
class B:public A
{
public:
void print()
{
cout<<"a = "<<a;//能否访问??? 不可以
cout<<"b = "<<b;//能否访问??? 可以
cout<<"c = "<<endl;//能否访问??? 可以
}
};
class C:protected A
{
public:
void print()
{
cout<<"a = "<<a;//能否访问??? 不可以
cout<<"b = "<<b;//能否访问??? 可以
cout<<"c = "<<endl;//能否访问??? 可以
}
};
class D:private A
{
public:
void print()
{
cout<<"a = "<<a;//能否访问??? 不可以
cout<<"b = "<<b<<endl;//能否访问??? 不可以
cout<<"c = "<<c<<endl;//能否访问??? 不可以
}
};
int main(void)
{
A aa;
B bb;
C cc;
D dd;
aa.c = 100;//能否访问??? 可以
bb.c = 100;//能否访问??? 可以
cc.c = 100;//能否访问??? 不可以
dd.c = 100;//能否访问??? 不可以
aa.set(1,2,3);//能否访问??? 可以
bb.set(10,20,30);//能否访问??? 可以
cc.set(40,50,60);//能否访问??? 不可以 set是protected,在外部不能访问
dd.set(70,80,90);//能否访问??? 不可以
bb.print();//能否访问??? 可以
cc.print();//能否访问??? 可以
dd.print();//能否访问??? 可以
return 0;
}
5.继承中的构造和析构
5.1类型兼容性原则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。
类型兼容规则中所指的替代包括以下情况:
- 子类对象可以当作父类对象使用
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象
- 父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
子类就是特殊的父类 (base *p = &child;)
/*
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
*/
class Parent
{
public:
void printP(){
cout<<"Parent::printP()..."<<endl;
}
int a;
};
class Child:public Parent
{
public:
void printC(){
cout<<"Child::printC()..."<<endl;
}
int b;
};
void print(Parent *p)
{
p->printP();
}
int main(void)
{
Child c;//子类对象
c.printP(); //子类对象可以当作父类对象使用(方法继承)
//子类对象可以直接赋值给父类对象
Parent p = c;
//Child c2 = p;报错!
//由于子类用于父类的全部内存空间,子类能够保障父类初始化完成
//子类对象可以直接初始化父类对象,因为子类内存空间包含的父类的内存空间,能够保证完全赋值
//父类指针可以直接指向子类对象
//子类对象能够完全满足父类指针的需求,所以可以
Parent *pp = &c;//pp->printP,pp->printC不存在这个方法
//不能用子类指针指向父类对象
//父类对象满足不了子类指针的所有需求,所以不能够
Child *cp = &p;//报错!cp->printP,cp->printC
//cp指向父类对象,父类是没有printC方法的!所以报错
//父类引用可以直接引用子类对象
Parent &pr = c;
//子类引用不可以引用父类的对象
Child &cr = p;//报错,和父类指针一样
//利用兼容性原则传参!
print(&p);//Parent *p = &p;
print(&c);//Parent *p = &c;
return 0;
}
5.2初始化父类成员,父类与子类的构造函数有什么关系?
3.jpg 4.png- 在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化.
- 在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理.
//子类在进行初始化成员变量的时候,如果此成员变量是继承过来的,那么需要调用父类的构造器来初始化
class Parent
{
public:
Parent(int a){
this->a = a;
cout<<"Parent(int a)..."<<endl;
}
~Parent(){
cout<<"~Parent()..."<<endl;
}
void printA(){
cout<<"a = "<<a<<endl;
}
private:
int a;
}
class Son:public Parent
{
public:
//子类继承于父类,父类中的成员变量 应该用 父类的构造函数来初始化
Son(int a,int b):Parent(a){
this->b = b;
cout<<"Son(int a,int b)...."<<endl;
}
~Son(){
cout<<"~Son()..."<<endl;
}
void printB(){
cout<<"b = "<<b<<endl;
}
void printAB(){
Parent::printA();
this->printB();
}
private:
int b;
}
void test1()
{
Son s(10,20);//先构造父类,再构造子类
s.printAB();
}//子类先析构,父类后析构,和栈一样!
int main(void)
{
test1();
return 0;
}
1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反
5.3继承中同名成员变量处理方法
- 当子类成员变量与父类成员变量同名时
- 子类依然从父类继承同名成员
- 在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
- 同名成员存储在内存中的不同位置
同名成员变量和成员函数通过作用域分辨符进行区分
5.4派生类中的static关键字
- 基类定义的静态成员,将被所有派生类共享
- 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质(遵守派生类的访问控制)
- 派生类中访问静态成员,用以下形式显式说明:
类名::成员
或通过对象访问
对象名.成员
class A
{
public:
static int s;
private:
}
int A::s = 0;//静态成员变量要在类的外部初始化
class B:public A
{
public:
private:
}
int main(void)
{
B b;
cout<<b.s<<endl;//0
b.s = 100;
cout<<b.s<<endl;//100
cout<<A::s<<endl;//也是100
return 0;
}
- static函数也遵守3个访问原则
- static易犯错误(不但要初始化,更重要的显示的告诉编译器分配内存)
5.5多继承
一个类有多个直接基类的继承关系称为多继承
5.pngclass Funiture
{
public:
int m;//材质
};//如果子类虚继承本类,编译器会将父类中的成员,只拷贝一份
//沙发床
//床类
class Bed:virtual public Funiture
{
public:
void sleep(){
cout<<"在床上睡觉..."<<endl;
}
};
//沙发
class Sofa:virtual public Funiture
{
public :
void sit(){
cout<<"在沙发上睡觉..."<<endl;
}
};
class SofaBed:public Bed,public Sofa
{
public:
void sitAndSleep(){
sit();//sofa
sleep();//bed;
}
};
int main(void){
Bed b;
b.sleep();
cout<<"----------------------"<<endl;
Sofa s;
s.sit();
cout<<"----------------------"<<endl;
SofaBed sb;
sb.sitAndSleep();
sb.m;//报错!!!因为不知道用哪个父类的m,要用虚继承
//改虚继承后不报错!
return 0;
}
6.png
7.jpg
网友评论