一、知识点
1.概念:在已有类的基础上创建新类的过程。一个B类继承A类或称从类A派生类B,类A称为基类(父类),类B称为派生类(子类)。【其中基类与派生类对应,父类与子类对应】
2.类继承关系的语法形式:
class 派生类名 : 基类名表
{
数据成员和成员函数声明
};
基类名表构成:
访问控制 基类名1,访问控制 基类名2,…,访问控制 基类名n
访问控制表示派生类对基类的继承方式,使用关键字:
public 公有继承
private 私有继承
protected 保护继承
3.派生类的生成过程经历了三个步骤:
●吸收基类成员(全部吸收(构造、析构除外),但不一定可见)
●改造基类成员
●添加派生类新成员
(1)吸收基类成员
在C++的继承机制中,派生类吸收基类中除构造函数和析构函数之外的全部成员。
(2)改造基类成员
通过在派生类中定义同名成员(包括成员函数和数据成员)来屏蔽(隐藏)在派生类中不起作用的部分基类成员。
(3)添加新成员
仅仅继承基类的成员是不够的,需要在派生类中添加新成员,以保证派生类自身特殊属性和行为的实现。
4.常见继承关系
class Father
{
int a,b;
public:
// 成员函数
};
class Son:public Father
{
int c;
public:
// 成员函数
};
5.公有继承
基类 | 派生类 |
private成员 |
|
protected成员 | protected成员 |
public成员 | public成员 |
| private成员 |
| protected成员 |
| public成员 |
例:定义一个基类person(不定义构造函数)
姓名、性别、年龄(访问权限设置为私有)
定义公有的成员函数set_p()
定义公有的成员函数display_p(),显示person的信息
再由基类派生出学生类(不定义构造函数,采用公有继承的方式)
n 增加学号、班级、专业和入学成绩
n 定义公有成员函数set_t()
n 定义成员函定义公有的成员函数display_s(),显示所有的信息
#include<iostream>
#include <string>
using namespace std;
class Person
{
string name;
int age;
string sex;
public:
void set_p() {
cout<<"name\tage\tsex"<<endl;
cin>>name>>age>>sex;
}
void show_p() {
cout<<name<<" "<<age<<" "<<sex<<endl;
}
};
class student :public Person
{
string no;
string zhuanye;
string t_class;
float score;
public:
void set_t(){
set_p(); //调用继承于基类的成员函数访问继承于基类的私有数据成员
cout<<"zhuanye\tt_class\tscore"<<endl;
cin>>zhuanye>>t_class>>score;
}
void show_t() {
show_p();
cout<<zhuanye<<" "<<t_class<<" "<<score<<endl;
}
};
6.重名成员
(1)概念:派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽了基类的同名成员。
(2)句法形式:
在派生类中使用基类的同名成员:
类名::成员
(3)例:
class base
{
public :
int a , b ;
} ;
class derived : public base
{
public :
int b , c ;
} ;
void f ()
{
derived d ;
d . a = 1 ;//直接调用基类的公有成员
d . base :: b = 2 ;//使用重名成员
d . b = 3 ;
d . c = 4 ;
};
7.派生类中访问静态成员
Ø 基类定义的静态成员,将被所有派生类共享(基类和派生类共享基类中的静态成员)
Ø 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
Ø 派生类中访问静态成员,用以下形式显式说明:
类名 :: 成员
或通过对象访问对象名 .成员
例:
#include<iostream>
using namespace std ;
class B
{
public:
static void Add() { i++ ; }
static int i;
void out() { cout<<"static i="<<i<<endl; }
};
int B::i=0;
class D : private B
{
public:
void f()
{
i=5;
Add();
B::i++;
B::Add();
}
};
int main()
{
B x; D y;
x.Add();
x.out();
y.f();
cout<<"static i="<<B::i<<endl;
cout<<"static i="<<x.i<<endl;
//cout<<"static i="<<y.i<<endl;
}
8.基类的初始化
(1)在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据
(2) 派生类构造函数声明为:
派生类构造函数( 变元表) : 基类( 变元表) , 对象成员1( 变元表)
…对象成员n (变元表 ) ;
(3)构造函数执行顺序:基类à对象成员à派生类
(4)例: 调用构造函数顺序测试,构造函数无参数
#include<iostream>
using namespace std ;
class Base
{
public : Base ( ) { cout << "Base created.\n" ; }
} ;
class D_class : public Base
{
public : D_class ( ) { cout << "D_class created.\n" ; }
} ;
int main ( )
{
D_class d1 ;
}
9.派生类构造函数和析构函数的定义规则:
(1)基类的构造函数和析构函数不能被继承
(2)如果基类没有定义构造函数或有无参的构造函数,派生类也可以不用定义构造函数
(3)如果基类无无参的构造函数,派生类必须定义构造函数
(4)如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造
(5)派生类是否定义析构函数与所属的基类无关
派生类构造函数的定义:
派生类的数据成员既包括基类的数据成员,也包括派生类新增数据成员。
在C++中,派生类构造函数的一般格式为:
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用
派生类析构函数:
(1)当派生类中不含对象成员时
●在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
●在撤消派生类对象时,析构函数的执行顺序是:派生类的析构函数→基类的析构函数。
(2)当派生类中含有对象成员时
●在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
●在撤消派生类对象时,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数。
例:
#include<iostream.h>
class base
{
public:
base(){cout<<"constructing base class"<<endl;}
~base(){cout<<"destructing base class"<<endl; }
};
class subs:public base
{
public:
subs(){cout<<"constructing sub class"<<endl;}
~subs(){cout<<"destructing sub class"<<endl;}
};
void main()
{
subs s;
}
执行结果:
constructingbase class
constructingsub class
destructingsub class
destructingbase class
10.多继承
(1)概念:一个类有多个直接基类的继承关系称为多继承
(2)多继承语法声明:
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
(3)类 C 可以根据访问控制同时继承类 A 和类 B 的成员,并添加自己的成员
classA
classB
class C:public A,public B
(4)多继承的派生类构造和访问
Ø 多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员。
Ø 执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
Ø 一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
例:多继承的简单应用
class Base1
{
public:
Base1(int x) { value = x ; }
int getData() const { return value ; }
protected:
int value;
};
class Base2
{
public:
Base2(char c) { letter=c; }
char getData() const { return letter;}
protected:
char letter;
};
class Derived : public Base1, public Base2
{
friend ostream &operator<< ( ostream &, const Derived & ) ;
public :
Derived ( int, char, double ) ;
double getReal() const ;
private :
double real ;
};
(5)多继承的构造函数
句法形式:
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n)
{
// 派生类新增成员的初始化语句
}
执行顺序:
●先执行所有基类的构造函数
●再执行对象成员的构造函数
●最后执行派生类的构造函数
【注】处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的基类顺序与派生类构造函数中所定义的成员初始化列表顺序没有关系。
内嵌对象成员的构造函数执行顺序与对象在派生类中声明的顺序一致
(6)多继承的析构函数:
析构函数名同样与类名相同,无返回值、无参数,而且其定义方式与基类中的析构函数的定义方式完全相同。
●功能是在派生类中对新增的有关成员进行必要的清理工作。
●析构函数的执行顺序与多继承方式下构造函数的执行顺序完全相反,首先对派生类新增的数据成员进行清理,再对派生类对象成员进行清理,最后才对基类继承来的成员进行清理
11.赋值兼容规则:
(1)概念:在程序中需要使用基类对象的任何地方,都可以用公有派生类的对象来替代。
(2)其所指的替代包括以下几种情况:
a 派生类的对象可以赋给基类对象
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针
(3)可行性:
通过公有继承,派生类得到了除了构造、析构函数以外的所有成员且这些成员的访问控制属性也和基类完全相同。这样,它便具备了基类的所有功能。
(4)例:下面声明两个类:
class Base
{
…
};
class Derived:public Base
{
…
};
根据赋值兼容规则,以下几种情况是合法的:
1)可以用派生类对象给基类对象赋值。例如:
Base b;
Derived d;
b=d;
这样赋值的效果是,对象b中所有数据成员都将具有对象d中对应数据成员的值。
2)可以用派生类对象来初始化基类的引用。例如:
Derived d;
Base &br=d;
3)可以把派生类对象的地址赋值给指向基类的指针。例如:
Derived d;
Base *bptr=&d;
这种形式的转换,是在实际应用程序中最常见到的。
4)可以把指向派生类对象的指针赋值给指向基类对象的指针。例如:
Derived *dptr,obj; dptr=&obj;
Base *bptr=dptr;
(5)特点:
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
(6)应注意的问题:
1)声明为指向基类的指针可以指向它的公有派生类的对象,但不允许指向它的私有派生类的对象。例如:
classB {…};
class D:private B {…};
B b1,*pb1;Dd1;
pb1=&b1; //合法,基类B的对象b1和B类的指针
pb1=&d1; //非法,不允许将基类指针指向它的私有派生类对象
2)允许将一个声明为指向基类的指针指向其公有派生类对象,但是不能将一个声明为指向派生类对象的指针指向其基类的一个对象。
3) 声明为指向基类对象的指针,当其指向公有派生类对象时,只能用它来直接访问派生类中从基类继承来的成员,而不能直接访问公有派生类的定义的成员。
二、心得体会
通过对继承的学习,我们可以更好地精简代码。所谓继承,顾名思义,是一个类继承另一个类的部分,如果我们想要实现的两个类的部分功能或成员是相同的,那么我们就可以通过继承来缩减代码。
当然,想要熟练地使用继承,首先我们应该有良好的逻辑关系,因为继承是两个类之间的联系,所以这就要求我们在平时的学习中多思考,理清类与类之间的关系,而不是糊里糊涂。其次,是对其具体知识的熟练掌握,这就要求我们课下多练习用继承来巩固基础知识。