3.1继承概念
面向对象程序设计有4个主要特点:抽象、封装、继承和多态性。我们已经讲解了类和对象,了解了面向对象程序设计的两个重要特征一数据抽象与封装,已经能够设计出基于对象的程序,这是面向对象程序设计的基础。
要较好地进行面向对象程序设计,还必须了解面向对象程序设计另外两个重要特征——继承性和多态性。本章主要介绍有关继承的知识,多态性将在后续章节中讲解。
继承性是面向对象程序设计最重要的特征,可以说,如果没有掌握继承性,就等于没有掌握类和对象的精华,就是没有掌握面向对象程序设计的真谛。
3.1.1类之间的关系
has-A,uses-A 和 is-A
has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。
is-A 机制称为“继承”。关系具有传递性,不具有对称性。
3.1.2继承关系举例
万事万物中皆有继承,是重要的现象
两个案例:1)植物继承图;2)程序员继承图
3.1.3 继承相关概念
3.1.4 派生类的定义
注意:C++中的继承方式(public、private、protected)会影响子类的对外访问属性。
3.1.5 继承重要说明
1、子类拥有父类的所有成员变量和成员函数
4、子类可以拥有父类没有的方法和属性
2、子类就是一种特殊的父类
3、子类对象可以当作父类对象使用
3.2派生类的访问控制
派生类继承了基类的全部成员变量和成员方法(除了构造和析构之外的成员方法),但是这些成员的访问属性,在派生过程中是可以调整的。
3.2.1单个类的访问控制
1、类成员访问级别(public、private、protected)
2、思考:类成员的访问级别只有public和private是否足够?
3.2.2不同的继承方式会改变继承成员的访问属性
1)C++中的继承方式会影响子类的对外访问属性
public继承:父类成员在子类中保持原有访问级别
private继承:父类成员在子类中变为private成员
protected继承:父类中public成员会变成protected
父类中protected成员仍然为protected
父类中private成员仍然为private
2)private成员在子类中依然存在,但是却无法访问到。不论何种方式继承基类,派生类都不能直接使用基类的私有成员。
3)C++中子类对外访问属性表
| 父类成员访问级别 | |||
继 承 方 式 |
| public | proteced | private |
public | public | proteced | private | |
proteced | proteced | proteced | private | |
private | private | private | Private |
4)继承中的访问控制
//demo_01继承的基本语法
#include <iostream>
using namespace std;
class parent
{
public:
void print()
{
cout << "a:" << a << "b:" << b << endl;
}
int b;
private:
int a;
};
//public继承:父类成员在子类中保持原有访问级别
//private继承:父类成员在子类中变为private成员
//protected继承:父类中public成员会变成protected
//父类中protected成员仍然为protected
//父类中private成员仍然为private
class Child:public parent
{
public:
private:
int c;
};
int main()
{
Child c1;
//c1.a = 1;//为基类的
c1.b = 3;
//c1.c = 1;成员 "Child::c" 不可访问
system("pause");
return 0;
}
demo_02单个类的访问控制.cpp
#include <iostream>
using namespace std;
/*
C++中的继承方式(public、private、protected)会影响子类的对外访问属性
判断某一句话,能否被访问
1)看调用语句,这句话写在子类的内部、外部
2)看子类如何从父类继承(public、private、protected)
3)看父类中的访问级别(public、private、protected)
*/
//public 修饰的成员变量 在类的内部、外部都能使用
class Parent1
{
public:
int a;//老爹的名字
protected:
int b;//老爹的银行卡密码 仅仅允许家族人内部知道,外面人不给知道==>protected在公用继承后,在类的内部能使用,外部不能被使用,protected在protected继承后,还是protected,protected在private继承后private
private:
int c;//相当于 老爹的情人
};
class Child1:public Parent1
{
public:
void UseVar()
{
a = 0; //ok public 在子类中,类外都能访问
b = 0; //ok protected:在子类中能访问,但是在类外不能访问
//c = 0; //err private:在子类中,在类外都不能访问
}
protected:
private:
};
//公用继承
int main0201()
{
Child1 c1;
c1.a = 0; //ok public 在子类中,类外都能访问
//c1.b = 0; //err protected:在子类中能访问,但是在类外不能访问
//c1.c = 0; //err private:在子类中,在类外都不能访问
}
//protected: 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ;其他 类的外部不能被使用
//protected 关键字修饰的成员变量和成员函数,是为了在家族中使用,是为了继承
/*
protected继承:父类中public成员会变成protected
父类中protected成员仍然为protected
父类中private成员仍然为private
*/
class Child2 :protected Parent1
{
void UseVar()
{
a = 0; //ok 父类中public成员会变成protected: 在子类中能访问,类外不能访问
b = 0; //ok 父类中protected成员仍然为protected: 在子类中能访问,但是在类外不能访问
//c = 0; //err 父类中private成员仍然为private: 在子类中,在类外都不能访问
}
};
void main0202()
{
Child2 c2;
//c2.a = 0;//ok 父类中public成员会变成protected: 在子类中能访问,类外不能访问
//c2.b = 1;//ok 父类中protected成员仍然为protected: 在子类中能访问,但是在类外不能访问
//c2.c = 2; //err 父类中private成员仍然为private: 在子类中,在类外都不能访问
}
//private: 修饰的成员变量方法 只能在类的内部使用 不能在类的外部
class Child3 :private Parent1
{
void UseVar()
{
a = 0; //ok 父类中public成员会变成private: 1.写在子类中,2.看从父类里private继承过来的,3.父类里访问级别是public 故在子类中能访问,类外不能访问
b = 0; //ok 父类中protected成员仍然为protected: 1.写在子类中,2.看从父类里private继承过来的,3.父类里访问级别是protected 故在子类中能访问,但是在类外不能访问
//c = 0; //err 父类中private成员仍然为private: 在子类中,在类外都不能访问
}
};
//记住private继承过来的,类外都不能使用
void main()
{
Child2 c2;
//c2.a = 0;//ok 父类中public成员会变成private: 1.写在子类中,2.看从父类里private继承过来的,3.父类里访问级别是public 故在子类中能访问,类外不能访问
//c2.b = 1;//ok 父类中protected成员仍然为protected: 1.写在子类中,2.看从父类里private继承过来的,3.父类里访问级别是protected 故在子类中能访问,但是在类外不能访问
//c2.c = 2; //err 父类中private成员仍然为private: 在子类中,在类外都不能访问
}
3.2.3“三看”原则
C++中的继承方式(public、private、protected)会影响子类的对外访问属性
判断某一句话,能否被访问
1)看调用语句,这句话写在子类的内部、外部
2)看子类如何从父类继承(public、private、protected)
3)看父类中的访问级别(public、private、protected)
3.2.3派生类类成员访问级别设置的原则
思考:如何恰当的使用public,protected和private为成员声明访问级别?
1、需要被外界访问的成员直接设置为public
2、只能在当前类中访问的成员设置为private
3、只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。
3.2.4综合训练
练习:
public继承不会改变父类对外访问属性;
private继承会改变父类对外访问属性为private;
protected继承会部分改变父类对外访问属性。
结论:一般情况下class B : public A
//demo_03派生类访问控制综合训练.cpp
//类的继承方式对子类对外访问属性影响
#include <cstdlib>
#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; //err private还是private,只能在父类类内访问
cout << "b = " << b; //ok protected还是protected 基类子类中访问,类的外部不能访问
cout << "c = " << endl; //ok public还是public 都能访问
}
};
/*
1、需要被外界访问的成员直接设置为public
2、只能在当前类中访问的成员设置为private
3、只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。
*/
//保护继承
class C : protected A
{
public:
void print()
{
//cout<<"a = "<<a; //err 父类中private还是private 只能在基类中访问的成员
cout << "b = " << b; //ok 父类中protected还是protected 能在基类和子类中访问,类外部不能访问
cout << "c = " << endl; //ok 父类中public变成protected 能类的内部访问,不能外部访问
}
};
//private私有继承
class D : private A
{
public:
void print()
{
//cout<<"a = "<<a; //err 父类中private还是private,只能在基类中访问
cout << "b = " << b << endl; //ok protected 变成private,能在类内部访问
cout << "c = " << c << endl; // ok 只能在子类的内部访问了
}
};
int main()
{
A aa;
B bb;
C cc;
D dd;
aa.c = 100; //1.看写在类的外部,2.看类中的访问级别,c是public,故可以类的外部访问
bb.c = 100; // ok 1.看写在类的外部 2.看子类是从父类中公用继承过来的,c在父类中访问级别是public,总体还是public,故可以在类的外部访问
//cc.c = 100; // err 1.看写在类的外部 2.看子类是从父类中protected继承过来的,c在父类中访问级别是public,总体还是protected,不可以在类的外部访问
//dd.c = 100; // err1.看写在类的外部 2.看子类是从父类中private继承过来的,不可以在类的外部访问
aa.set(1, 2, 3); //ok 1.看写在类外的,2.看类中的访问级别public
bb.set(10, 20, 30); //ok1.看写在类外的,2.看类中的访问级别public
//cc.set(40, 50, 60); // err 1.看写在类外的,2.看子类从父类中保护继承过来的,故不能访问
//dd.set(70, 80, 90); //err1.看写在类外的,2.看子类从父类中私有继承过来的,故不能访问
bb.print(); //ok 1.看写在类外的,2.不是子类就不看,直接看父类的访问级别是public,肯定就可以类外访问
cc.print(); //ok 1.看写在类外的,2.不是子类就不看,直接看父类的访问级别是public,肯定就可以类外访问
system("pause");
return 0;
}
3.3.1类型兼容性原则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下情况:
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
类型兼容规则是多态性的重要基础之一。
//demo_05类型兼容性原则.cpp
/*
兼容规则中所指的替代包括以下情况:
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
*/
#include <iostream>
using namespace std;
class Parent
{
public:
void prinT()
{
cout << "我是父类" << endl;
}
Parent()
{
cout << "Parent 构造函数" << endl;
}
Parent(const Parent &obj)
{
cout << "Parent 拷贝构造函数" << endl;
}
private:
};
//class Child:Parent class 默认私有继承
class Child:public Parent
{
public:
void printC()
{
cout << "我是儿子" << endl;
}
private:
};
//在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员
void howToprinT(Parent *base)
{
base->prinT();
}
void howToprinT2(Parent &base)
{
base.prinT();//基类的成员函数
}
int main()
{
Parent p1;
p1.prinT();
Child c1;
c1.prinT();
c1.printC();
//赋值兼容性原则
//1-1 基类指针 (引用) 指向 子类对象
Parent *p = NULL;
p = &c1;
p->prinT();
//1-2 指针做函数参数
howToprinT(&p1);
howToprinT(&c1);//子类对象可以当作父类对象使用
//1-3引用做函数参数
howToprinT2(p1);
howToprinT2(c1);
//第二层含义
//可以让子类对象 初始化 父类对象
//子类就是一种特殊的父类
Parent p3 = c1;//需要拷贝构造函数
p3.prinT();
// 总结:子类就是特殊的父类(base *p = &child;)
system("pause");
return 0;
}
3.3.2继承中的对象模型
类在C++编译器的内部可以理解为结构体
子类是由父类成员叠加子类新成员得到的
继承中构造和析构
问题:如何初始化父类成员?父类与子类的构造函数有什么关系
在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化
在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理
3.3.3继承中的构造析构调用原则
1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反
//demo06_继承中构造和析构.cpp
#include <iostream>
using namespace std;
//观看一个类多个对象中 继承中的构造析构调用原则
//结论
//先 调用父类构造函数 在调用 子类构造函数
//析构的顺序 和构造相反
/*
1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反
*/
class Parent
{
public:
Parent()
{
this->a = 0;
this->b = 0;
cout << "父类的默认构造函数" << endl;
}
Parent(int m_a, int m_b)
{
this->a = m_a;
this->b = m_b;
cout << "父类的有参构造函数" << endl;
}
~Parent()
{
cout << "父类的析构函数" << endl;
}
private:
int a;
int b;
};
class Child:public Parent
{
public:
Child(int a, int b, int c) : Parent(a, b)//初始化列表,否则会报错
{
this->c = c;
cout << "子类的构造函数" << endl;
}
~Child()
{
cout << "子类的析构函数" << endl;
}
void printC()
{
cout << "我是儿子" << endl;
}
private:
int c;
};
void playObj()
{
Child c1(1, 2, 3);
}
void main()
{
playObj();
}
/*
父类的有参构造函数
子类的构造函数
子类的析构函数
父类的析构函数
请按任意键继续. . .
*/