1.继承与派生的概念
- 一个新类从已有的类那里获得其已有的特性,这种现象称为类的继承
从已有的类产生一个新的子类,称为类的派生 - 派生类继承了基类的基本特征(所有数据成员和成员函数),同时又根据需要调整和扩充原有类
- 单继承:一个派生类只从一个基类派生
多重继承:一个派生类有两个或多个基类的称为多重继承
2.派生类的声明方式
- 假设已声明基类Student,在此基础上通过单继承建立一个派生类Student1
Student前的关键字public,用来表示基类中的成员在派生类中的继承方式 - 继承方式包括:public(公用的),private(私有的)和 protected(受保护的)
若不写继承方式,默认为private - 声明派生类的一般形式为
class 派生类名: [继承方式] 基类名
{
派生类新增加的成员
};
class Student1:public Student
{public:
void display_1() //新增成员函数
{cout<<"age:"<<age<<endl;
cout<<"address:"<<addr<<endl;}
private:
int age; //新增数据成员
string addr; //新增数据成员
};
3.派生类的构成
派生类分为两大部分:从基类继承来的成员和在声明派生类时增加的部分
构造一个派生类包括以下3部分
- 从基类接收成员。派生类把基类全部的成员(不包括构造函数和析构函数)接收过来
- 调整从基类接收的成员。接受基类成员是程序员不能选择的,但是程序员可以对这些成员作某些调整。
- 在声明派生类时增加的成员。
在声明派生类时,一般还应当自己定义派生类的构造函数和析构函数,因为构造函数和析构函数是不能从基类继承的。
4.派生类成员的访问属性
- 公用继承
基类的公用成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有。 - 私有继承
基类的公有成员和保护成员在派生类中成了私有成员。其私有仍未基类私有。 - 受保护的继承
基类的公有成员和保护成员在派生类中成了保护成员。其私有成员仍未基类私有。 - 基类成员在派生类中的访问属性
基类成员在基类的访问属性 | 继承方式 | 基类成员在派生类的访问属性 |
---|---|---|
public | public | public |
private | private | |
protected | protected | |
private | public | 不可访问 |
private | 不可访问 | |
protected | 不可访问 | |
protected | public | protected |
private | private | |
protected | protected |
- 派生类中的成员访问属性
派生类中访问属性 | 在派生类中 | 在派生类外部 | 在下一层公用派生类中 |
---|---|---|---|
公用 | 可以 | 可以 | 可以 |
保护 | 可以 | 不可以 | 可以 |
私有 | 可以 | 不可以 | 不可以 |
不可访问 | 不可以 | 不可以 | 不可以 |
4.1公用继承
- 在定义一个派生时将基类的继承方式指定为public的,称为公用继承,公用继承方式建立的派生类称为公用派生类,其基类称为公用基类。
#include <iostream>
#include <string>
using namespace std;
class Student //声明基类
{public: //基类公用成员
void get_value()
{cin>>num>>name>>sex;}
void display()
{cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
private: //基类私有成员
int num;
string name;
char sex;
};
class Student1:public Student
{public: //派生类公用成员
void get_value_1()
{cin>>age>>addr;}
void display_1()
{cout<<"age:"<<age<<endl;
cout<<"addr:"<<addr<<endl;
}
private: //派生类私有成员
int age;
string addr;
};
int main()
{
Student1 stud; //定义派生类Student1对象
stud.get_value();
stud.get_value_1();
stud.display();
stud.display_1();
return 0;
}
4.2 私有继承
- 在声明一个派生类时将基类的继承方式指定为private的,称为私有继承,用此方式建立的派生类称为私有派生类,其基类称为私有基类。
- 私有基类的成员可以被基类的成员函数访问,但不能被派生类的成员函数访问,但派生类的成员函数可访问基类公用成员。
- 不能通过派生类对象引用从私有基类继承过来的任何成员。
#include <iostream>
using namespace std;
class Student //声明基类
{public: //基类公用成员
void get_value()
{cin>>num>>name>>sex;}
void display()
{cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
private: //基类私有成员
int num;
int name;
int sex;
};
class Student1:private Student //用私有继承方式声明派生类Student1
{private:
void get_value_1()
{cin>>num>>name>>sex;}
void display1()
{cout<<"age:"<<age<<endl;
cout<<"address:"<<addr<<endl;}
private:
int age;
string addr;
};
int main()
{
Student1 stud1;
stud1.display(); //错误,私有基类的公用成员函数在派生类中是私有函数
stud1.display_1() //正确,display_1函数是Student1类的公用函数
stud1.age=18; //错误,外界不能引用派生类的私有成员
return 0;
}
- 如何调用私有基类的成员函数,从而引用私有基类的私有成员?
可通过派生类中的成员函数调用私有基类的公用成员函数(此时其为派生类中的私有成员函数,可被派生类的成员函数调用)
//将上个代码的私有派生类的两个成员函数定义改写为
void get_value_1()
{
get_value();
cin>>age>>addr;
}
void display_1()
{
display();
cout<<"age:"<<age<<endl;
cout<<"address"<<addr<<endl;
}
//main函数改写为
int main()
{
Student1 stud1;
stud1.get_value_1(); //输入5个数据的函数
stud1.display_1(); //输出5个数据成员的值
return 0;
}
4.3 保护成员和保护继承
- 在定义一个派生类时将基类的继承方式指定为protected的,成为保护继承,用保护继承方式建立的派生类成为保护派生类,其基类称为保护基类。
- 受保护成员与私有成员相似不能被类外访问,但有一点与私有成员不同,保护成员可以被派生类的成员函数引用
(私有成员如同个人隐私,外人与子女(其派生类)均不得窥视,而保护成员如同个人财产,外人不能窥视,只有子女才能使用)
#include <iostream>
#include <string>
using namespace std;
class Student
{public:
protected:
int num;
string name;
char sex;
};
class Student1:protected Student
{public:
void get_value1();
void display1();
private:
int age;
string addr;
};
void get_value1()
{
cin>>num>>name>>sex; //输入保护基类数据成员
cin>>age>>addr; //输入派生类数据成员
}
void Student1::display1()
{
cout<<"num:"<<num<<endl; //引用基类保护成员
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
cout<<"age:"<<age<<endl; //引用派生类的私有成员
cout<<"address:"<<addr<<endl;
}
int main()
{
Student1 stud1;
stud1.get_value1();
stud1.display1();
return 0;
}
4.4多级派生时的访问属性
- 类A为基类,类B为类A派生类,类C为类B派生类。
- 类B称为类A的直接派生类,类C成为类A的间接派生类。
- 类A是类B的直接基类,是类C的间接基类。
各成员在不同类中的访问属性
i | f1() | j | k | f2() | f3() | m | f4 | n | |
---|---|---|---|---|---|---|---|---|---|
基类A | 公用 | 保护 | 保护 | 私有 | |||||
公用派生类B | 公用 | 保护 | 保护 | 不可访问 | 公用 | 保护 | 私有 | ||
保护派生类C | 保护 | 保护 | 保护 | 不可访问 | 保护 | 保护 | 不可访问 | 共有 | 私有 |
5 派生类的构造函数和析构函数
- 若用户在声明类时不定义构造函数,系统会自动设置一个默认的构造函数(实际上是一个空函数)在定义类对象时自动调用此默认构造函数。
- 派生类不能继承基类的构造函数,因此继承过来的基类成员初始化需由派生类的构造函数承担。
- 若要在执行派生类的构造函数时,使派生类数据成员和基类数据成员同时都被初始化,就要在执行派生类的构造函数时,调用基类的构造函数。
5.1 简单的派生类的构造函数
- 派生类构造函数一般形式为
派生类构造函数名(总参数表):基类构造函数名(参数表)
{派生类中新增数据成员初始化语句} - 派生类构造函数首行中,派生类构造函数名后括号内参数表中包括参数类型和参数名,而基类构造函数名后括号内参数表只有参数名而不包括参数类型。因为这里不是定义基类构造函数,而是调用基类构造函数,因此这些参数是实参而不是形参
定义简单的派生类的构造函数
#include <iostream>
#include <string>
using namespace std;
class Student //声明基类Student
{public:
Student(int n,string nam,char s) //定义基类构造函数
{num=n;
name=nam;
sex=s;}
~Student(){} //基类析构函数
protected: //保护函数
int num;
string name;
char sex;
};
class Student1;public Student //声明公用派生类Student1
{public: //派生类的公用部分
Student1(int n,string nam,char,s,int a,string ad):Student(n,nam,s) //定义派生类构造函数
{age=a; //在函数体中只对派生类新增的数据成员初始化
addr=ad;
}
void show()
{cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
cout<<"age:"<<age<<endl;
cout<<"address:"<<addr<<endl;
}
~Student1(){} //派生类析构函数
private:
int age;
string addr;
};
int main()
{Student stud1(/**/);
Student stud2(/**/);
stud1.show(); //输出
stud2.show();
return 0;
}
在类外定义派生类构造函数:
- 在类中对派生类构造函数声明时,不包括上面给出的一般形式中的“基类构造函数名(参数表)”部分,即Student(n,nam,s),只在定义函数时列出。
Student1::Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)
{age=a;
addr=ad;
}
- 构造函数初始化表不仅可以对构造函数的数据成员初始化,也可利用初始化表调用派生类的基类构造函数,实现对基类数据成员初始化。以上两功能可在同一构造函数中同时实现
Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s),age(a),addr(ad){}
- 在建立一个对象时,执行构造函数的顺序是:
1.派生类函数先调用基类构造函数
2.在执行派生类函数本身
例:先初始化num,name,sex,再初始化age和addr。 - 在派生类对象释放时
1.先执行派生类析构函数~Student1()
2.再执行其基类析构函数~Student()
5.2 有子对象的派生类的构造函数
-
当一个对象为基类的对象时,当其被继承后即为派生类的子对象。
-
子对象的初始化是在建立派生类时通过调用派生类构造函数来实现的。
-
定义派生类构造函数的一般形式为:
派生类构造函数名(总参数表): 基类构造函数名(参数表),子对象名(参数表)
{派生类中新增数据成员初始化语句} -
执行派生类构造函数的任务及顺序是:
(1)调用基类构造函数,对基类数据成员初始化
(2)调用子对象构造函数,对子对象数据成员初始化
(3)再执行派生类构造函数本身,对派生类构造函数成员初始化 -
若有多个子对象,派生类构造函数写法以此类推。
#include <iostream>
#include <string>
using namespace std;
class Student
{public:
Student(int n,string nam)
{num=n;
name=nam;}
void display()
{cout<<"num="<<num<<endl<<"name="<<name<<endl;}
protected:
int num;
string name;
};
class Student1:public Student
{public:
Student(int n,string nam,int n1,string nam1,int a,string ad):Student(n,nam),monitor(n1,nam1)
{age=a;
addr=ad;}
void show()
{cout<<"This student is:"<<endl;
dispaly();
cout<<"age:"<<age<<endl;
cout<<"address:"<<address<<endl;
}
void show_monitor()
{cout<<endl<<"Class monitor is:"<<endl;
monitor.display();
}
private:
Student monitor;
int age;
string addr;
};
int main()
{
Student1 stud1(10010,"Wang_li",10001,"Li_sun",19,"115 Beijing Road,Shanghai");
stud1.show();
stud1.show_monitor();
return 0;
}
5.3 多层派生时的构造函数
基类和两个派生类的构造函数的写法
//基类构造函数首部
Student(int n,string nam)
//派生类Student1的构造函数首部
Student1(int n,string nam,int a):Student(n,nam)
//派生类Student2的构造函数首部
Student2(int n,string nam,int a,int s):Student2(n,nam,a)
//不要写成
Student2(int n,string nam,int a,int s):Student(n,nam),Student1(n,nam,a)
5.4 派生类构造函数的特殊形式
使用派生类构造函数的两种特殊形式
- 可以不对派生类的新增成员进行任何操作,及派生类构造函数的函数体为空。此时派生类的构造函数只将参数传递给基类构造函数和子对象,并在执行派生类构造函数时调用基类构造函数和子对象构造函数。如:
Student(int n,string nam,int n1,string nam1):Student(n,nam),monitor(n1,nam1){}
5.5 派生类的析构函数
- 派生类不能继承基类的析构函数,需通过派生类的析构函数去调用基类的析构函数。
- 派生类可定义自己的析构函数,用来对派生类中所增加的成员进行清理工作。
- 基类的清理仍由基类的析构函数负责,在执行派生类析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。
- 调用顺序与构造函数相反:
(1)先执行派生类自己的析构函数,对派生类新增成员进行清理。
(2)然后调用子对象析构函数,对子对象进行清理.
(3)最后调用基类的析构函数,对基类进行清理。
6 多重继承
多重继承:允许一个派生类同时继承多个基类。
6.1 声明多重继承的方法
若已声明类A、类B和类C,可以声明多重继承的派生类D:
class D:public A,private B,protected C
{类D新增的成员}
6.2 多重继承派生类的构造函数
-
派生类构造函数名(总参数表):基类1构造函数(参数表),基类2构造函数(参数表),基类3构造函数(参数列表)
{派生类中新增数据成员初始化语句} -
为不引起二义性,且两个基类中都使用同一个数据成员名,可以通过在函数中引用数据成员时指明其作用域。如:
cout<<"name:"<<Teacher::name<<endl;
#include<iostream>
#include<string>
using namespace std;
class Teacher
{public:
Teacher(string nam,int a,string t)
{name=nam;
age=a;
title=t;}
void display()
{cout<<"name:"<<name<<\n<<"age:"<<age<<\n<<"title:"<<title<<endl;}
protected:
string name;
int age;
string title;
};
class Student
{public:
Student(string nam,char s,float sco)
{strcpy(name1,nam);
sex=s;
score=sco;}
void display1()
{cout<<"name:"<<name1<<'\n'<<"sex:"<<sex<<'\n'<<"score:"<<score<<endl;}
protected:
string name1;
char sex;
float score;
};
class Graduate:public Teacher,public Student
{public:
Graduate(string nam,int a,char s,string t,float sco,float w):Teacher(nam,a,t),Student(nam,s,sco),wage(w){}
void show()
{cout<<"name:"<<name<<'\n'<<"age:"<<age<<'\n'<<"sex:"<<sex<<'\n'<<"score:"<<score<<'\n'<<"title:"<<title<<'\n'<<"wages:"<<wage<<endl;}
private:
float wage;
};
int main()
{
Graduate gradl("Wang_li",24,'f',"assistant",89.5,1200);
gradl.show();
return 0;
}
6.3 多重继承引起的二义性问题
- 两个基类有同名成员
int a; //基类A的成员
void display();
int a; //基类B的成员
void display();
int b; //派生类C新增的成员
void show();
C c1; //定义C对象c1
c1.A::a=3; //引用c1对象中的基类A的数据成员a
c1.A::display(); //调用c1对象中的基类A的成员函数display
//若定义C类对象c1完,直接调用数据成员a和成员函数display而未用基类名进行限定,如
C c1; //定义C对象c1
c1.a=3; //引用c1数据成员a
c1.display(); //调用c1成员函数display
//系统无法判别要访问的是哪一个基类的成员,因此,程序编译出错。
- 两个基类和派生类三者都有同名成员
//此时派生类C类
int a; //数据成员
int A::a;
int B::a;
void display(); //成员函数
void A::display();
void B::display();
//若在main函数中定义C类对象c1,并调用数据成员a和成员函数display
C c1;
c1.a=3;
c1.display();
/*
此时程序能通过编译且可正常运行,但访问的试派生类C中的成员
规则:基类的同名成员在派生类中被隐蔽,派生类新增加的同名成员覆盖了基类的同名成员。
因此在定义派生类对象的模块中通过对象名访问同名的成员,则访问的是派生类的成员。
注:不同成员函数只有在函数名和参数个数相同、类型相匹配的情况下才发生同名覆盖,
若只有函数名相同而参数不相同,不会发生同名覆盖,而属于函数重载。
*/
//所以,要在派生类外访问基类A中的成员,应指明作用域A.
c1.A::a=3;
c1.A::display();
- 若类A和类B从同一个基类派生的,如图所示
Class N
{public:
int a;
void display();
};
class A:public N
{public:
int a1;
};
class B:public N
{public:
int a2;
};
class C:public A,public B
{public:
int a3;
void show();
};
//此时派生类C类
int A::a; //数据成员
int A::a1;
int B::a;
int B::a2;
int a3;
void A::display(); //成员函数
void B::display();
void show();
//若想访问类A中从基类N继承下来的成员,不可以用
c1.a=3;
c1.display();
c1.N::a=3;
c1.N::display();
/*因为这样做无法区别是类A还是类B继承下来的成员。
应当通过类N的直接派生类来指出要访问的是类N的哪一个派生类中的基类成员,如:
*/
c1.A::a=3; //要访问的是类N的派生类A中的基类成员
c1.A::display();
6.4 虚基类
1.虚基类的作用
- 虚基类:派生类在继承间接共同基类时只保留一份成员。
- 声明虚基类的一般形式为:
class 派生类名 :virtual 继承方式 基类名 - 经上述声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。
- 注意:
(1)虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。
(2)为保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明虚基类,否则仍然会出现对基类的多次继承。
2.虚基类的初始化
- 由于基类在派生类中只有一份数据成员,所以这份数据成员的初始化必须由派生类直接给出。若不由最后的派生类直接对虚基类初始化,而由虚基类的直接派生类对虚基类初始化,就有可能由于直接派生类们的构造函数中对虚基类给出不同的初始化参数而产生矛盾。所以规定,在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
- C++系统只执行最后的派生类对虚基类的构造函数的调用,而忽略基类其它派生类对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。
3.虚基类的简单应用举例
#include <iostream>
#include <string>
using namespace std;
//声明公共基类Person
class Person
{public:
Person(string nam,char s,int a)
{name=nam;sex=s;age=a;}
protected:
string name;
char sex;
int age;
};
//声明Person的直接派生类Teacher
class Teacher:virtual public Person
{public:
Teacher(string nam,char s,int a,string t):Person(nam,s,a),title(t){}
protected:
string title;
};
//声明Persong的直接派生类Student
class Student:virtual public Person
{public:
Student(string nam,char s,int a,float sco):Person(nam,s,a),score(sco){}
protected:
float score;
};
//声明多重继承的派生类Graduate
class Graduate:public Teacher,public Student
{public:
Graduate(string nam,char s,int a,string t,float sco,float w):Person(nam,s,a),Student(nam,s,a,sco),Teacher(nam,s,a,t),wage(w){}
void show()
{cout<<"name:"<<name<<endl;
cout<<"age:"<<age<<endl;
cout<<"sex:"<<sex<<endl;
cout<<"score:"<<score<<endl;
cout<<"title:"<<title<<endl;
cout<<"wages:"<<wage<<endl;
}
private:
float wage;
};
int main()
{Graduate grad1("bill",'m',19,"IT",59.5,100000);
grad1.show();
return 0;
}
11.7 基类与派生类的转化
- 只有公有派生类才是基类真正的子类型,它完整地继承了基类的功能。
- 赋值兼容:不同类型数据之间的自动转换和赋值。
- 基类与派生类对象之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替。
- 派生类对象可以向基类对象赋值
//可以用子类对象对其基类对象赋值
A a1; //定义基类A对象a1
B b1; //定义类A的公用派生类B的对象b1
a1=b1; //用派生类B对象b1对基类对象a1赋值
11.8 继承与组合
- 通过组合建立了成员类与组合类(或称复合类)的关系,在一个类中包含另一个类的对象成员。
- 继承是纵向的,组合是横向的。
class Teacher //声明教师类
{public:
private:
int year;
string name;
char sex;
};
class BirthDate //声明生日类
{public:
private:
int year;
int month;
int day;
};
class Professor:public Teacher //声明教授类
{public:
privaate:
BirthDate birthday; //BirthDate 类的对象作为数据成员
}