【C++】继承(inheritance)
引入
假设我们有一个动物类
class Animal {
public:
int age;
void eat() {
std::cout << "吃东西!" << std::endl;
}
};
又想写一个狗类,它也有年龄,也会吃,除此之外还有种类
class Dog {
public:
const char* kind;
int age;
void eat() {
std::cout << "吃东西!" << std::endl;
}
};
我们发现有重复的代码,如果我们写猫类、鸟类等都要再写一遍,这样很麻烦
那么有没有一种方法能提高代码的复用性,不需要再写一遍就能达到同样的效果呢?
我们让Dog继承Animal
#include<iostream>
class Animal {
public:
int age;
void eat() {
std::cout << "吃东西!" << std::endl;
}
};
class Dog :public Animal {
public:
const char* kind;
};
int main() {
Dog dog;
dog.kind = "柯基";
dog.age = 3;
dog.eat();
}
没有写那部分重复代码却也能给age赋值,调用eat()方法
也就是说,Dog继承了Animal,Animal所拥有的age和eat()它也就有了,就像父亲遗传给儿子一样
- Animal就叫做父类或者基类
- Dog就叫做子类或者派生类
而且,狗本来就属于动物,我们让其继承动物也是符合思维的
单继承和多继承
class A {
};
class B :public A {//单继承
};
class D {
};
class E {
};
class F :public D, public E {//多继承
};
那么,子类继承父类后,父类的所有内容都能被子类访问吗?
继承方式
我们看到上面的继承有"public",这其实是继承方式
继承方式就是类中的三种访问属性,每种访问属性通过继承方式之后在派生类中可能会有新的属性
- 基类私有成员,不管用什么方式继承,都不能被访问
- 其他的成员访问属性和继承方式,两者看谁更严格就按严格的来:public<protected<private
基类成员的访问控制 | public继承 | protected继承 | private继承 |
---|---|---|---|
private | 可以继承,但不可以访问 | 可以继承,但不可以访问 | 可以继承,但不可以访问 |
protected | protected | protected | private |
public | public | protected | private |
派生类的构成
- 派生类会继承除基类的构造析构函数之外的所有成员变量和成员函数
- 可以在派生类中添加新的成员,通过派生类对象来调用
- 如果派生类中添加的成员名和基类成员名相同,那么派生类会隐藏基类的成员,可以通过
.基类名::基类成员名
来访问。如果是继承的多个基类,而多个基类中也有同名的,也是通过此种方式调用同名的成员。子类对象直接访问同名成员,访问的是子类对象自己的同名成员。
示例:
#include<iostream>
class Animal {
public:
int age;
void eat() {
std::cout << "吃东西!" << std::endl;
}
};
class Dog :public Animal {
public:
const char* kind;
void eat() {
std::cout << "啃骨头!" << std::endl;
}
};
int main() {
Dog dog;
dog.eat();
dog.Animal::eat();
}
派生类和基类的关系
子类不包含父类,而是子类中有父类的所有数据成员和函数成员(除构造析构)。派生类是基类对象,但是基类不是派生类对象;派生类可以赋值给基类,而基类不能给派生类赋值。
派生类的构造析构顺序
- 派生类对象在实例化的时候是会调用基类的构造函数的,先基类后派生类(先有父亲后有儿子),释放就是先儿子后父亲,因为栈结构(先进后出)
- 如果是多继承,其与单继承中构造顺序一致;区别在于,在构造基类时有多个基类,那么会按照基类的继承声明顺序来依次调用基类的构造函数,然后构造子对象,最后构造自己
- 在写继承的时候,要确保基类有可以调用的构造函数
- 带参构造:在构造过程中,如果基类或子对象需要参数来进行构造,就需要通过成员初始化列表来构造
示例1:
#include<iostream>
class A {
public:
A() {
std::cout << "调用A的无参构造" << std::endl;
}
A(int a) {
std::cout << "调用A的有参构造" << std::endl;
}
~A() {
std::cout << "调用A的析构" << std::endl;
}
};
class B :public A {
public:
B() {
std::cout << "调用B的无参构造" << std::endl;
}
B(int b) {
std::cout << "调用B的有参构造" << std::endl;
}
~B() {
std::cout << "调用B的析构" << std::endl;
}
};
int main() {
B b1;
B b2(5);
}
运行结果:
调用A的无参构造
调用B的无参构造
调用A的无参构造
调用B的有参构造
调用B的析构
调用A的析构
调用B的析构
调用A的析构
示例2:
#include<iostream>
class A {
public:
A(int a) {
std::cout << a << std::endl;
}
};
class B :public A {
public:
B():A(1){}//在初始化列表中调用父类的带参构造
//不这样写直接实例化会报错
};
int main() {
B b1;
}
派生类的内存大小
- 派生类的内存大小=所有父类内存大小之和+本身新增的成员内存大小
- 派生类的内存中,基类的内存在最低位,派生类的内存在最高位
菱形继承
类B,C分别继承A;类D继承类B,C
这样继承会导致类D继承两份类A的成员,在类D对象,想要访问类A的成员的时候,会导致访问不明确。因为类B,C各自继承类A的成员
解决方法
(1)通过类名::成员名
来确定调用哪个成员(不常用)
(2)通过虚继承,在继承方式的前面加上关键字virtual,虚继承之后会使在虚继承的类中多个指针(内存就会变大),但是在最后的D类,不会再继承两份A的成员了
示例1:
#include<iostream>
class A {
int a = 1;
};
class B :public A {
int c = 2;
};
class C :public A {
int d = 3;
};
class D :public B, public C {
int e = 4;
};
int main() {
std::cout << sizeof(A) << std::endl;//4
std::cout << sizeof(B) << std::endl;//8
std::cout << sizeof(C) << std::endl;//8
std::cout << sizeof(D) << std::endl;//20
}
示例2:
#include<iostream>
class A {
int a = 1;
};
class B :virtual public A {
int c = 2;
};
class C :virtual public A {
int d = 3;
};
class D :public B, public C {
int e = 4;
};
int main() {
std::cout << sizeof(A) << std::endl;//32位:4 64位:4
std::cout << sizeof(B) << std::endl;//32位:12 64位:24
std::cout << sizeof(C) << std::endl;//32位:12 64位:24
std::cout << sizeof(D) << std::endl;//32位:24 64位:48
}