继承就是在原有类的基础上产生新类,从而减少了代码重复的必要
格式:
class A{};
class B:继承方式 A{};
在c++继承中,有以下几点注意
(1)基类的构造函数与析构函数不能被继承
(2)派生类对基类成员的继承没有选择权,不能选择继承或不继承某些成员
(3)派生类中可以增加新的成员,用于实现新功能,保证派生类的功能在基类基础上有所扩展
(4)一个基类可以派生出多个派生类,一个派生类也可以继承自多个基类
c++中访问权限与继承方式
成员函数 | 类对象 | 子类对象 | 友元函数 | 友元类 | |
Public | √ | √ | √ | √ | √ |
protected | √ | √ | √ | √ | |
private | √ | √ | √ |
了解c++中的访问权限是很有必要的,因为c++中有继承方式,不同的继承方式,派生类对基类中的成员访问权限有所不同,下面介绍三种继承方式
public公有继承
基类中访问权限 | public | protected | private |
派生类中访问权限 | public | protected | 不可访问 |
protected保护继承
基类中访问权限 | public | protected | private |
派生类中访问权限 | ptotected | protected | 不可访问 |
private私有继承
基类中访问权限 | public | protected | private |
派生类中访问权限 | private | private | 不可访问 |
其中private权限无论那种继承方式都是不可访问,但是private成员还是会被继承下,只是被编译器隐藏了,所有的非静态成员都会被继承
#include <iostream>
using namespace std;
#include <string>
class Person {
public:
int age;
protected:
int car;
private:
int money;
};
class American:public Person{
};
int main() {
American american;
cout << sizeof(American) << endl;
cout << sizeof(american) << endl;
}
可以看到Person 类的三个int 成员都被American类继承下来了,只是私有属性被隐藏了起来,不能访问。
如果掌握了c++的访问权限以及继承方式,剩下就只看程序的需求了,故不在赘述
###############################################
c++ 派生类对象赋值给基类对象
c++中通过公有继承,派生类获得除基类构造函数,析构函数之外的所有公有成员。因此,在语法上,公有派生类对象总是可以充当基类对象,即可以将派生类对象赋值给基类对象,在用到基类对象的地方可以用其公有派生类对象代替,从而调用基类的方法,但不可调用派生类新增的成员。
可以使用的方式:
(1)使用公有派生类对象为基类对象赋值
//Dog类继承自Animal类
Animal animal_3 = dog;
(2)使用公有派生类对象为基类对象的引用赋值
Animal &animal_1 = dog;
(3)使用公有派生类对象为基类指针赋值
Animal* animal_2 = &dog;
这样 就可以调用父类方法Walk(),但是不可调用子类新增的方法Sound();
animal_1.Walk();
//animal_1.Sound();不合法
这个稍微了解一下,相当于java里的上转型对象
##############################################
c++派生类的构造函数与析构函数
派生类的构造函数要负责调用基类的构造函数进行初始化基类的成员变量
格式:
派生类构造函数名(参数列表):基类1 构造函数名(参数列表),基类2 构造函数名(参数列表)...{
派生类初始化新增成员变量语句;
}
强调以下几点
(1)构造函数调用顺序:首先按照继承顺序依次调用基类构造函数,如果派生类中有成员对象,第二步调用成员对象的构造函数,最后调用派生类构造函数
(2)基类构造函数的参数要从派生类构造函数的参数列表中获取
(3)如果基类定义了有参构造函数,派生类必须定义构造函数
(4)析构函数调用则与构造函数相反 (栈)
简单示例
class Tom :public Person, public Country {
public:
string name;
Car car;
Tom(string name1, string name2, string name3, string name4);
~Tom();
void test();
};
//构造函数示例
Tom::Tom(string name1, string name2, string name3, string name4)
:Person(name1), Country(name2), car(name3) {
this->name = name4;
cout << "调用Tom类构造函数" << endl;
}
int main() {
Tom tom("人", "美国", "大众", "Tom");
tom.test();
}
这里省略了Person类,Country类,Car类以及他们的构造函数,我们需要知道Tom派生类的构造函数需要实现基类的有参构造函数,以及实现成员对象的构造函数。也需要知到他们之间调用构造函数和析构函数的顺序。
###############################################
c++多继承二义性
如果多个基类成员同名则会产生二义性问题,编译器无法区分调用的是哪个基类的成员,这时只要加上作用域即可
如:上一个案例中出现了name 成员出现在好几个基类,这时加上作用域即可
void Tom::test() {
cout << "这里有一个" << Person::name << ",名字叫"
<< this->name << "他来自" << Country::name
<<",他总是开着" << car.name << "欣赏沿途的风景" << endl;
}
Person::name,Country::name等
###################################################
c++ 在派生类中隐藏基类成员函数
在派生类中重新定义基类同名函数,基类同名函数在派生类中被隐藏,通过派生类对象调用同名函数时,调用的是改写后的派生类成员函数,而基类的同名函数不会被调用。
class Animal {
public:
void Sound();
};
class Dog :public Animal {
public:
void Sound();
};
int main() {
Dog dog;
dog.Sound();
}
本例中基类与派生类都声明了Sound()方法,其中基类的方法被隐藏。
如果需要调用需加权限符::
int main() {
Dog dog;
dog.Animal::Sound();
}
##########################################
c++虚继承
我们希望间接基类的成员变量在底层派生类中只有一份拷贝,从而避免成员访问的二义性,也有效减少了派生类继承的两份相同的数据而导致的资源浪费。
就比如我们只希望生成CattleHorse类的int age,不需要Animal类,Cattle类,Horse类的int age,但是通过继承又能得到这些数据,如果使用虚继承就能通过虚基类指针访问Animal类的int age数据,也即保留一份数据在Animal类里就ok,需要注意的是这份数据是共享的,也就避免了二义性的出现
格式:
class 派生类名: virtual 权限控制符 基类名{
};
示例
#include<iostream>
using namespace std;
#include<string>
class Animal {
public:
int age;
};
class Cattle :virtual public Animal {};
class Horse :virtual public Animal {};
class CattleHorse :virtual public Cattle, virtual public Horse {};
int main() {
CattleHorse ch;
ch.age = 6;
cout << "加上虚继承后,间接基类的成员变量在底层派生类中只有一份拷贝" << endl;
cout << "ch.Cattle::age=" << ch.Cattle::age << endl;
cout << "ch.Horse::age=" << ch.Horse::age << endl;
cout << "ch.Animal::age=" << ch.Animal::age << endl;
}
可以看到声明了ch.age = 6;后,其它类去访问也是6;是共享的
查看虚基类表
按win下找到
Visual Studio 2022 Developer Command Prompt
然后 cd到此类的该目录下
然后 cl /d1 reportSingleClassLayout类名 文件名.cpp
被虚继承的类叫虚基类,比如本例中的Animal类。虚基类只是针对虚继承,而不是针对基类本身,在普通继承中,该基类并不称为虚基类