类与对象-03
继承与派生
1. 继承的概念
c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型,来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
一个 B 类继承于 A 类,或称从类 A 派生类 B。这样的话,类 A 成为基类(父类),类 B 成为派生类(子类)。
派生类中的成员,包含两大部分:
- 一类是从基类继承过来的,
- 一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
2. 继承的优点
1)减少重复的代码,减轻程序整体的体量。
2)继承的好处,可以将共性的内容封装成一个基类(父类),遇到专项业务时,可以扩展基类变为一个新类,在新类中重点扩展功能。
3. 继承的语法
class 子类:继承方式 父类名
{
子类新增自己的数据和方法;
};
4. 继承方式
public(推荐)
:子类将 原封不动的继 承父类成员,但是 不能直接访问 父类 私有的成员
protected
:子类将
继承到父类的成员
转换为 protected修饰的成员,但是 不能直接访问 父类 私有的成员
private
:子类将继承到
的所有父类成员
转换为 私有的成员,但是 不能直接访问 父类 私有的成员
示例1:public
#include <iostream>
using namespace std;
class A
{
public:
int a;
void testA()
{
cout << "testA" << endl;
}
};
class B:public A
{
};
int main(int argc, char *argv[])
{
B b;
b.a = 10;
b.testA(); //testA
return 0;
}
示例2:protected
,若是protected继承,父类中的public将变为 protected,只能在当前类或子类中使用。
#include <iostream>
using namespace std;
class A
{
public:
int a;
void testA()
{
cout << "testA" << endl;
}
};
class B:protected A
{
public:
void testB()
{
cout << a << endl;
testA();
}
};
int main(int argc, char *argv[])
{
B b;
//此时是protected继承,父类中的public将变为 protected,只能在当前类或子类中使,
//所以下面报错
//b.a = 10;//报错
//b.testA();//报错
b.testB(); //93 testA
return 0;
}
5. 注意
- 子类可以继承父类所有成员,但是父类私有成员不可访问
- 子类 不能继承父类 的
构造函数
、拷贝构造
、析构函数
,但是子类中可以调用- 子类可以
多继承
- 子类在
创建对象
时会 调用父类构造函数,如果没有明确
写出调用的父类构造
函数,默认调用父类无参构造
,此时如果父类没有无参构造,程序报错
- 子类调用父类构造函数,在 子类构造函数后使用初始化列表方式调用父类构造函数
- operator=不能被继承
示例:
#include <iostream>
#include <cstring>
using namespace std;
class Anim{
private:
char type[50];
int age;
char sex[10];
public:
Anim(){
cout << "Anim无参构造" << endl;
}
Anim(char *type,char *sex,int age)
{
strcpy(this->type,type);
strcpy(this->sex,sex);
this->age = age;
cout << "Anim有参构造" << endl;
}
Anim(const Anim& anim)
{
strcpy(this->type,anim.type);
strcpy(this->sex,anim.sex);
this->age = anim.age;
}
void setType(char *type)
{
strcpy(this->type,type);
}
char* getType()
{
return type;
}
void setSex(char *sex)
{
strcpy(this->sex,sex);
}
char* getSex()
{
return sex;
}
void setAge(int age)
{
this->age = age;
}
int getAge()
{
return age;
}
};
class A{
};
//3、子类可以多继承
class Yang:public Anim,public A
{
public:
Yang(){}
//5、初始化列表调用父类构造函数
Yang(char *type,char *sex,int age):Anim(type,sex,age){
}
};
int main(int argc, char *argv[])
{
//Yang y;
//1、子类可以继承父类所有成员,但是父类私有成员不可访问
//y.age;
//2、子类不能继承父类的构造函数,拷贝构造,析构函数
//Yang y("绵羊","公",18); //Anim有参构造
//4、创建子类对象时会调用父类构造函数
//默认调用父类无参构造
//Yang y;
//如果子类构造函数在初始化列表中指定调用父类的构造函数
//那么就不会默认调用父类无参构造
//Yang y("山羊","公",3);
//如果父类中没有无参构造,子类没有明确写出调用父类构造函数
//此时子类将调用父类无参构造,但是父类没有无参构造
//程序报错
Yang y;
return 0;
}
6. 构造与析构的执行顺序
继承中的构造和析构:
- 子类对象在创建时会首先调用父类的构造函数
- 父类构造函数执行完毕后,才会调用子类的构造函数
- 当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数
- 析构函数调用顺序和构造函数相反
示例1:子类继承父类
#include <iostream>
using namespace std;
class A{
public:
A(){
cout << "A的构造函数" << endl;
}
~A(){
cout << "A的析构函数" << endl;
}
};
class B:public A{
public:
B(){
cout << "B的构造函数" << endl;
}
~B(){
cout << "B的析构函数" << endl;
}
};
int main(int argc, char *argv[])
{
B b;
return 0;
}
//A的构造函数
//B的构造函数
//B的析构函数
//A的析构函数
示例2:子类继承父类,该子类中还要其他类的成员
#include <iostream>
using namespace std;
class A{
public:
A(){
cout << "A的构造函数" << endl;
}
~A(){
cout << "A的析构函数" << endl;
}
};
class C{
public:
C(){
cout << "C的构造函数" << endl;
}
~C(){
cout << "C的析构函数" << endl;
}
};
class B:public A{
private:
C c;
public:
B(){
cout << "B的构造函数" << endl;
}
~B(){
cout << "B的析构函数" << endl;
}
};
int main(int argc, char *argv[])
{
B b;
return 0;
}
//A的构造函数
//C的构造函数
//B的构造函数
//B的析构函数
//C的析构函数
//A的析构函数
7. 继承中父子类成员重名
7.1 成员变量重名
调用方式:
- 操作
子类成员
变量:子类对象.成员变量名- 操作
父类
提供的成员
变量:子类对象.父类名::成员变量名
示例:
#include <iostream>
using namespace std;
class Basic{
public:
int x;
int y;
Basic(int x):x(x){}
};
class Son:public Basic{
public:
int x;
//子类构造函数中必须调用父类构造函数
//默认调用父类无参构造
Son(int x1, int x2):Basic(x1){
this->x = x2;
}
};
int main(int argc, char *argv[])
{
Son son(10,100);
//当父子类成员变量重名时
//此时子类对象中有两个名称相同的变量
//1个是继承父类的
//1个是子类特有的
//获取子类特有的该变量
cout << son.x << endl;//100
//获取父类提供的x变量
cout << son.Basic::x << endl;//10
//没有重名的成员直接获取
cout << son.y << endl; //随机数
return 0;
}
7.2 成员函数重名
概念:
子类成员函数名
与父类成员函数名
重名,此时 子类该函数就是对父类名为该函数名的函数进行 重定义。- 重定义
- 继承关系中
- 子类函数名与父类函数名相同
- 特点:屏蔽父类该函数
调用方式:
- 操作
子类成员
函数:子类对象.成员函数名(实参列表)- 操作
父类
提供的成员
函数:子类对象.父类名::成员函数名(实参列表)
示例:
#include <iostream>
using namespace std;
class Fu{
public:
void test(){
cout << "fu test()" << endl;
}
void test(int a){
cout << "fu test(int)" << endl;
}
void test(int a, int b){
cout << "fu test(int, int)" << endl;
}
void fun01(){
cout << "fu fun01()" << endl;
}
};
class Zi:public Fu{
public:
void test(){
cout << "zi test()" << endl;
}
};
int main(int argc, char *argv[])
{
Zi zi;
//当父子类函数名重名时
//子类调用重名函数,默认调用的是子类自己的函数
zi.test(); //zi test()
//子类调用继承于父类的 重名函数
zi.Fu::test(); //fu test()
zi.Fu::test(1); //fu test(int)
zi.Fu::test(10,20); //fu test(int, int)
//子类调用继承于父类的 非重名函数
zi.fun01(); //fu fun01()
return 0;
}
8. 多继承
概念:一个子类继承与 多个父类
语法:
class 子类名:继承方式1 父类1, 继承方式2 父类2, ...
{
子类特有成员
}
父类构造顺序:
- 子类对象创建时,按继承 编写的顺序 依次执行父类对象构造,与子类构造函数后初始化列表中的顺序无关
示例1:
#include <iostream>
using namespace std;
class A{
public:
int x;
A(int a):x(a){}
};
class B{
public:
int x;
B(int a):x(a){}
};
class C:public A,public B
{
public:
int x;
C(int a,int b,int c):x(a),A(b),B(c){}
};
int main(int argc, char *argv[])
{
C c(1,2,3);
//获取c类中的x
cout << c.x << endl; //1
//获取c类中继承与A类的x
cout << c.A::x << endl; //2
//获取c类中继承与B类的x
cout << c.B::x << endl; //3
return 0;
}
示例2:
#include <iostream>
using namespace std;
class A{
public:
A(){
cout << "A的构造函数" << endl;
}
~A(){
cout << "A的析构函数" << endl;
}
};
class B{
public:
B(){
cout << "B的构造函数" << endl;
}
~B(){
cout << "B的析构函数" << endl;
}
};
class C:public B,public A{
public:
C():A(),B(){
cout << "C的构造函数" << endl;
}
~C(){
cout << "C的析构函数" << endl;
}
};
int main(int argc, char *argv[])
{
C c;
return 0;
}
//B的构造函数
//A的构造函数
//C的构造函数
//C的析构函数
//A的析构函数
//B的析构函数
9. 菱形继承(了解)
概念:
A的子类A1与A2
B类多继承A1与A2类
此时这种关系称为菱形继承
注意:
- 菱形继承会导致子类用于多份祖先数据,当孙子类调用成员(函数或数据)时,会产生二义性。
- 如:
- A类中提供num成员变量
- A1与A2类属于A的子类,,那么A1与A2类将各自拥有一份num
- B作为A1与A2的子类,那么
B将拥有两个num
示例:
#include <iostream>
#include <cstring>
using namespace std;
class Anim{
public:
char name[50];
Anim(char *name)
{
strcpy(this->name, name);
}
};
class Yang:public Anim{
public:
Yang(char *name):Anim(name)
{
}
};
class Tuo:public Anim{
public:
Tuo(char *name):Anim(name)
{
}
};
class YangTuo:public Yang, public Tuo{
public:
YangTuo(char *name01, char *name02):Yang(name01),Tuo(name02)
{
}
};
int main(int argc, char *argv[])
{
YangTuo yt("tom", "jerry");
cout << yt.Yang::name << endl; //tom
cout << yt.Tuo::name << endl; //jerry
cout << &(yt.Yang::name) << endl; //0x61fe2c
cout << &(yt.Tuo::name) << endl; //0x61fe5e
return 0;
}
菱形继承类布局:
10. 虚继承
概念:使用 virtual修饰继承关系
语法:
class 子类名:virtual 继承关系 父类名
{
};
解决问题:
解决菱形继承的调用二义性,多个类只保存一份相同数据
如:
- A类中提供num成员变量
- A1与A2类属于A的子类,那么A1与A2类将各自拥有一份num
- B作为A1与A2的子类,那么B将拥有两个num,此时调用num会出现二义性
- 所以只能虚继承使其存储一个num.
示例:
#include <iostream>
#include <cstring>
using namespace std;
class Anim{
public:
char name[50];
Anim(char *name)
{
strcpy(this->name, name);
}
};
class Yang:virtual public Anim{
public:
Yang(char *name):Anim(name)
{
}
};
class Tuo:virtual public Anim{
public:
Tuo(char *name):Anim(name)
{
}
};
class YangTuo:public Yang, public Tuo{
public:
YangTuo(char *name01, char *name02):Anim(name01),Yang(name01),Tuo(name02)
{
}
};
int main(int argc, char *argv[])
{
YangTuo yt("tom", "jerry");
cout << yt.Yang::name << endl; //tom
cout << yt.Tuo::name << endl; //tom
cout << &(yt.Yang::name) << endl; //0x61fe5c
cout << &(yt.Tuo::name) << endl; //0x61fe5c
return 0;
}
虚继承类布局: