高级语言程序设计 第四章 c ++语言基础-继承类和派生类
DESCRIPTION
高级语言程序设计 第四章 C ++语言基础-继承类和派生类. 周律. 基类和派生类 单继承 可访问性,继承方式,继承后的构造函数 多继承 虚基类. 继承 :重新使用接口 , 高效的重用代码方法。. 基类和派生类. 继承是类之间定义的一种重要关系。 定义类 B 时,自动得到类 A 的操作和数据属性,使得程序员只需定义类 A 中所没有的新成分就可完成在类 B 的定义,这样称类 B 继承了类 A ,类 A 派生了类 B , A 是基类(父类), B 是派生类(子类)。这种机制称为继承。 称已存在的用来派生新类的类为 基类 ,又称为 父类 。 - PowerPoint PPT PresentationTRANSCRIPT
高级语言程序设计第四章 C ++语言基础-继承
类和派生类周律
基类和派生类 单继承
• 可访问性,继承方式,继承后的构造函数 多继承 虚基类
继承 :重新使用接口 , 高效的重用代码方法。
基类和派生类
继承是类之间定义的一种重要关系。定义类 B 时,自动得到类 A 的操作和数据属性,使得程序员只需定义类 A 中所没有的新成分就可完成在类 B 的定义,这样称类 B 继承了类 A ,类A 派生了类 B , A 是基类(父类), B 是派生类(子类)。这种机制称为继承。
称已存在的用来派生新类的类为基类,又称为父类。
由已存在的类派生出的新类称为派生类,又称为子类。
B1 , B2 的 派 生 类 ( 多 继承)
A
B1 B2
C1 C2 C3
A 的派生类(单继承)C3 的基类
B1 , B2 的基类
A 的派生类C1 , C2 , C3 的基类
B1 的派生类
类之间的继承与派生关系
基类和派生类
派生类的定义 _ 单继承
单继承的定义格式如下:class< 派生类名 > : < 继承方式 >< 基类名 >{
public : // 派生类新定义成员members ;
<private : >members ;
<protected : >members ;
} ;
派生类的定义 _ 单继承其中, < 派生类名 > 是新定义的一个
类的名字,它是从 < 基类名 > 中派生的,并且按指定的 < 继承方式 > 派生的。 < 继承方式 > 常作用如下三种关键字给予表示:
public :表示公有继承;private :表示私有继承,可默认声明;protected :表示保护继承。
派生类的定义 _ 多继承多继承的定义格式如下:class< 派 生 类 名 > : < 继 承 方 式 1>< 基 类 名
1> , < 继承方式 2>< 基类名 2> ,…{
public : // 派生类新定义成员members ;
<private : >members ;
<protected : >members ;
} ;
类 : StudentStu_No, Score,ClassId,getScore()
类 : PersonName, Age,Sex
单继承
在类层次结构中,派生类继承了基类的方法和变量
它们也可有其自身的属性和方法
优点 最重要优点:代码的可重用性
• 一旦创建了基类,它不需要做任何更改就能在不同的情况下使用
代码可重用的结果是类库的开发
• 类库由封装在类中的数据和方法组成
• 通过从现有类派生一个类可重新定义基类的成员函数,还可将新成员添加到派生类中
• 在整个过程中,基类保持不变
直接基类和间接基类
如果某个基类在基类列表中提及,则称它是直接基类。例如: :class A{ };class B:public A{ }; // 其中,类 A 为直接基类。
可将间接基类写为: class A{ };class B:public A{ };class C:public B{ }; // 可扩展到任意级数
可访问性在类之 类成员总可被它们自己类中的成员函数
访问,而不论这些成员是 private ,还是 public 。外定义的对象仅当类成员为 public 时方可访问它
们• 例如,如果 emp1 是 Employee 类的实例,且display() 是 Employee 的成员函数,则在 main()
中语句 emp1.display(); 在 display() 是 public 成员时,是有效的。
• 对象 emp1 不能访问 Employee 类的私有成员
访问基类成员
凭借继承:派生类的成员函数在基类的成员为 public 时能
访问这些成员。 派生类成员不能访问基类的 private 成员。
Protected 访问说明符
从作用域和访问方面来说, protected 部分与
private 部分类似。protected 成员仅可被所属类的成员访问。protected 成员不能由来自该类外部的对象或函数访
问,如 main() 。 protected 成员与 private 成员的区别仅出现在派生
类中
示例
class Employee // 基类
{
private:
int privA;
protected:
int protA;
public:
int pubA;
};
示例
class Manager : public Employee // 派生类
{
public:
void fn()
{
int a;
a = privA; // 错误:不可访问
a = protA; // 有效
a = pubA; // 有效
}
};
示例
void main()
{
Employee emp; // 基类对象 emp.privA = 1; // 错误:不可访问 emp.protA = 1; // 错误:不可访问 emp.pubA = 1; // 有效 Manager mgr; // 派生类对象 mgr.privA = 1; // 错误:不可访问 mgr.protA = 1; // 错误:不可访问 mgr.pubA = 1; // 有效 }
继承类型1. 公有继承( public)。
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。
2. 私有继承( private)。私有继承的特点是基类的公有成员和保护成员
作为派生类的私有成员,并且不能被这个派生类的子类访问。
3. 保护继承( protected)。保护继承的特点是基类的所有公有成员和保护
成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
继承类型示例class A // 基类{ private: int privA; protected: int protA; public: int pubA;};class B : public A // 公共派生类 { public: void fn()
{ int a;
a = privA; // 错误:不可访问 a = protA; // 有效 a = pubA; // 有效
}};
示例class C : private A // 私有派生类{
public: void fn() {
int a; a = privA; // 错误:不可访问 a = protA; // 有效 a = pubA; // 有效 }
};void main(){ int m; B obj1; // 公共私有派生类对象
示例
m = obj1.privA; // 错误:不可访问 m = obj1.protA; // 错误:不可访问 m = obj1.pubA; // 有效: B 是从 A 公共派生的
C obj2; // 私有派生类的对象 m = obj2.privA; // 错误:不可访问 m = obj2.protA; // 错误:不可访问 m = obj2.pubA;
// 错误:不可访问:不可访问 :C 是从类 A 私有派生的 }
注意: 如果创建类时未给定访问说明符,则假定其为 private
继承类型
派生类中的函数能访问基类中的 protected 和 public 成员 类外的或 main() 中的派生类的对象不能访问基类的 private
或 protected 成员
公有派生类和私有派生类之间的不同点:• 类 B (从 A 公有派生)的对象可以访问基类的 public 成员。
• 但是,类 C (它从 A 私有派生)的对象则不能访问基类的任何成员
继承类型
Protected 派生类中的函数能够访问基类的 protected 和 public 成员。但是,派生类(在 main 中或在类外)的对象则不能访问基类的任何成员
继承方式 基类特性 派生类特性
公有继承
public public
protected protected
private 不可访问
私有继承
public private
protected private
private 不可访问
保护继承
public protected
protected protected
private 不可访问
派生类的继承方式
多级继承
派生类的类型(即 public 、 private 或 protected )将影响派生类函数对多级继承中的基类的成员的访问
多级继承示例 在下面的代码中,类 B 从类 A 私有派生,类 C 依次从类 B 公有派生。
class A{ public : int a;};class B : private A{ public : int b; void func_b() {
int x,y; x=a; // 有效 y=b; // 有效 }};
示例(续)class C : public B
{
public :
void func_c()
{
int x,y;
x=a; // 无效
y=b; // 有效
}
};
继承下的构造函数
单继承下的构造函数派生类构造函数的一般格式如下:< 派生类名 > ( < 派生类构造函数总参数表 > ) : <
基类构造函数 > ( < 参数表 1> ),< 子对象名 > ( < 参数表 2> ){< 派生类中数据成员初始化 >} ;
派生类构造函数的调用顺序如下:( 1 )调用基类的构造函数,调用顺序按照它们继承时说明的顺序。( 2 )调用子对象类的构造函数,调用顺序按照它们在类中说明的顺序。( 3 )派生类构造函数体中的内容。
举例 首先调用的是对象基本部分的构造函数,然后调用派生类的适当构造函数。
class Base{ protected: int a; public: Base() // 默认构造函数
{ a = 0; }
Base(int c) // 单参数构造函数 {
a = c; }
};class Derived:public Base{ public: Derived():Base(){} ; // 默认构造函数 Derived(int c):Base(c){} ; // 单参数构造函数};
构造函数
在调用派生类的构造函数过程中,可明确选择应调用基类的哪一个构造函数。 Derived obj1(20);
使用 Derived 中的一个参数的构造函数。这一构造函数也调用基类中的相应构造函数 。
Derived(int c):Base(c); // 传递参数 c 给 Base
构造函数 当声明派生类的某一对象时,用语句:
Derived obj;
首先调用使基类的构造函数,然后调用派生类的构造函数
基类构造函数在派生类构造函数后给出,并用冒号分隔,如: Derived(): Base(){}
构造函数 _注意的问题
注意的问题在实际应用中,使用派生类构造函数时应注意如下几
个问题:( 1 )派生类构造函数的定义中可以省略对基类构造
函数的调用,其条件是在基类中必须有默认的构造函数或者根本没有定义构造函数。当然,基类中没有定义构造函数,派生类根本不必负责调用基类构造函数。
( 2 )当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数途径。在有的情况下,派生类构造函数体可能为空,仅起到参数传递作用。
析构函数
析构函数以与构造函数相反的顺序调用 析构函数首先为派生类调用,然后为基类调用。
仅当派生类的构造函数通过动态内存管理分配内存时才定义派生类的析构函数。
如果派生类的构造函数不起任何作用或派生类中未添加任何附加数据成员,则派生类的析构函数可以是一个空函数
调用成员函数
派生类中的成员函数与基类中的成员函数可以有相同的名称 当使用基类的对象调用函数时,基类的函数被调用 当您使用派生类对象的名称时,派生类的函数被调用
如果派生类的成员函数要调用相同名称的基类函数,它必须使用作用域运算符
调用成员函数
基类中的函数既可使用基类的对象,也可使用派生类的对象调用
如果函数存在于派生类而不是基类中,那么它只能被派生类的对象调用
举例class Base{ protected: int ss; public: int func() {
return ss; }
};class Derived: public Base{ public: int func() {
return Base::func(); }
};
举例
void main()
{
Base b1; // 基类对象
b1.func(); // 调用基类函数 func
Derived a1;// 派生类对象
a1.func(); // 调用派生类对象 func
}
多重继承
多重继承是从多个基类中创建一个新类的过程。
派生类继承了两个或多个基类的特性。
多重继承可以将多个基类的行为合并到一个类中。
多重继承层次结构代表了其基类的组合
StudentTeacher
Teaching assistant
基类
多重继承
多重继承 多重继承语法和单继承语法相似
class Teacher
{ };
class Student
{ };
class Teach_asst: public Teacher, public
Student
{ };
• 提供两个基类的名称 , 通过逗号分开。
• 继承和多重继承访问的规则和单继承相同
多重继承下的构造函数派生类构造函数执行顺序是先执行所有基类的构造
函数,再执行派生类本身构造函数。处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的各基类顺序,与派生类构造函数中所定义的成员初始化列表的各项顺序无关。
多继承下派生类的构造函数与单继承下派生类构造函数相似,它必须同时负责该派生类所有基类构造函数的调用。同时,派生类的参数个数必须包含完成所有基类初始化所需的参数个数。
多重继承下的构造函数
多继承的构造函数在多继承的情况下,多个基类构造函数的调用次序是按基类在
被继承时所声明的次序从左到右依次调用,与它们在派生类的构造函数实现中的初始化列表出现的次序无关。
派生类的构造函数格式如下:< 派生类名 > ( < 总参数表 > ): < 基类名 1> ( < 参数表 1> ), < 基类名 2> ( <
参数表 2> ),… < 子对象名 > ( < 参数表 n+1> ),…{< 派生类构造函数体 >}
其中, < 总参数表 > 中各个参数包含了其后的各个分参数表。
举例class Teacher{ private: int x; public: Teacher() // 构造函数
{x =0;} Teacher(int s) {x = s;}
};class Student{ private: int y; public: Student() // 构造函数
{y = 0;} Student(int a)
{y = a;}};
举例class Teach_asst: public Teacher,public Student{
private: int z;
public: Teach_asst():Teacher(),Student() // 构造函数
{z = 0;
} Teach_asst(int s,int a,int b):Teacher(s),Student(a) {
z = b; }
};
构造函数 必须定义构造函数以初始化所有类的数据成员。 就像下面的语句中一样,基类构造函数的名称位于冒号之后,各名称之间以逗号分开。 Teach_asst():Teacher(),Student();
如果构造函数有参数:Teach_asst(int s, //Teacher 类参数 int a, //Student 类参数 int b): // 本类参数 Teacher(s), // 调用 Teacher 构造
函数 Student(a) // 调用 Student 构造函
数{ // 设置本类的数据成员
z = b;}
构造函数和析构函数 调用构造函数的一般顺序是:
① 基础类按照它们在基类列表中的顺序显示: Teacher, Student
② 如果类中有成员对象,则按照它们在派生类声明中的顺序,对它们进行初始化。
③ 对象本身(使用其构造函数代码)
调用析构函数的一般顺序是:• 首先调用类的析构函数,然后是那些成员对象的析构函数,接着
是基类的析构函数
多继承 _二义性和支配原则
二义性和支配原则一般说来,在派生类中对基类成员的访问应该是惟一的。但是,
由于多继承情况下,可能造成对基类中某个成员的访问出现了不惟一的情况,则称为对基类成员访问的二义性问题。
1. 同名成员的二义性在多重继承中,如果不同基类中有同名的函数,则在派生类中
就有同名的成员,这种成员会造成二义性。
举例如: class A { public: void f(); }; class B { public: void f(); void g(); }; class C: public A,public B { public: void g(); void h(); }; C obj;
……
则对函数 f()的访问是二义的:obj.f(); // 无法确定访问 A中或是 B中的 f()
二义性使用基类名可避免这种二义:
obj.A::f();//A 中的 f();obj.B::f();//B 中的 f();
C 类的成员访问 f ()时也必须避免这种二义。以上这种用基类名来控制成员访问的规则称为支配原则。例如:
obj.g();// 隐 含 用 C 的 g ( )obj.B::g();// 用 B 的 g ()
以上两个语句是无二义的。
二义性
成员的访问权限不能区分有二义性的同名成员:class A { public: void fun();};class B { protected; void fun();};class C : public A, public B { };
虽然类 C 中的两个 fun ()函数,一个公有,一个保护,但: C obj;
obj.fun(); // 错误,仍是二义的
二义性
同一基类被多次继承产生的二义性由于二义的原因,一个类不能从同一类直接继承二次
或更多次。如:class derived:public base,public base {…} 是错的。 如果必须这样,可以使用中间类。然而,尽管可以使用作用域限定符来解决二义性的问
题,但是仍然不建议这样做,在使用多重继承机制时,最好还是保证所有的成员都不存在二义性问题。
赋值兼容规则
赋值兼容规则赋值兼容规则是指:在公有派生的情况下,一个派生类的对象
可用于基类对象适用的地方。赋值兼容规则有三种情况(假定类 derived由类 base 派生) :
( 1 )派生类的对象可以赋值给基类的对象。derived d;base b;b=d;
( 2 )派生类的对象可以初始化基类的引用。derived d;base& br=d;
赋值兼容规则
( 3 )派生类的对象的地址可以赋给指向基类的指针。derived d;
base *pb=&d;
虚基类
虚基类的引入如果一个派生类从多个基类派生,而这些基类又有一
个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
引进虚基类的真正目的是为了解决二义性问题。当基类被继承时,在基类的访问控制保留字的前面加上保留字virtual 来定义。
如果基类被声明为虚基类,则重复继承的基类在派生类对象实例中只好存储一个副本,否则,将出现多个基类成员的副本。
虚基类
虚基类说明格式如下:virtual< 继承方式 >< 基类名 >
其中, virtual 是虚基类的关键字。虚基类的说明是用在定义派生类时,写在派生类名的后面。
引进虚基类后,派生类(即子类)的对象中只存在一个虚基类的子对象。当一个类有虚基类时,编译系统将为该类的对象定义一个指针成员,让它指向虚基类的子对象。该指针被称为虚基类指针。
示例class A{public:
void F( );protected:
int m_a;};class B:virtual public A{protected:
int m_b;};
示例
class C:virtual public A{protected:
int m_c;};class D:public B, public C{public:
int G( );private:
int m_d;};
虚基类
两点注意:D n:
n.F( ); // 对 F()引用是正确的。void D::G( ){
F( ); // 对 F( )引用是正确的。}
A{F(),m_a}
B{m_b} C{m_c}
D{G(),m_d}
虚基类的出现改变了构造函数的调用顺序。• 在初始化任何非虚基类之前,将先初始化虚基类。
• 如果存在多个虚基类,初始化顺序由它们在继承图中的位置决定,其顺序是从上到下、从左到右。
调用析构函数遵守相同的规则,但是顺序相反
虚基类的构造函数 #include<iostream.h>class A{public:A(const char *s1) { cout<<s1<<endl;} //A 的构造函数~A( ) { }};class B:virtual public A{public:B (const char * s1, const char * s2): A(s1) //B 的构造函数{cout<<s2<<endl;}};
虚基类的构造函数class C: virtual public A //C 的构造函数{public:C (const char * s1, const char * s2): A(s1) {cout<<s2<<endl;}};class D:public B, public C //D 的构造函数{public:D (const char * s1, const char * s2, const char * s3, const char * s4):B(s1, s2), C(s1, s3), A(s1) {cout<<s4<<endl;}};
虚基类的构造函数
void main( )
{
D *ptr=new D("class A", "class B", "class C", "class D");
delete ptr;
}
总结
基类和派生类 单继承 多继承 虚基类
面向对象概念回放 抽象 对象 类 包 消息 继承 封装 重裁 重写 多态
面向对象概念回放 抽象:抽象是通过从特定的实例中抽取共同的性质以形成一般化的概念
的过程。 对象:可将对象想象成一种新型变量;它保存着数据,但可要求它对自
身进行操作。 类:是对象的进行一步抽象 包:一组类的集合 消息:对象间通信的信息 继承 :重新使用接口 封装:把变量 , 函数及其它对象隐藏起来,调用时,不必了解实现特
定功能的详细代码和过程 , 只需给规定参数,就可得到想要的结果 . 多态:对于重载或继承的方法 , 在运行时根据调用该方法的实例的类型
来决定选择哪个方法来调用 , 这就是多态