继承
点类继承几何类的简单示例
创建了一个几何类Geometry
和一个点类Point
,Point
继承了Geometry
:
cpp
#include <iostream>
#include <string>
using namespace std;
class Geometry {
protected:
string _wkt;
public:
void printWkt() {
cout << _wkt << endl;
}
};
class Point : public Geometry {
private:
double _x;
double _y;
public:
Point(double x, double y) {
_x = x;
_y = y;
_wkt = "Point(" + to_string(_x) + "," + to_string(_y) + ")";
}
};
int main()
{
Point pt(1, 2);
pt.printWkt();//Point(1.000000,2.000000)
system("pause");
return 0;
}
继承方式
继承的语法是:class 子类 : 继承方式 父类
继承方式有三种:
- 公共继承
public
· - 保护继承
protected
- 私有继承
private
父类中的非静态成员属性都被子类继承了,只是成员属性的访问权限不同。
继承中构造和析构顺序
- 父类构造
- 子类构造
- 子类析构
- 父类析构
同名非静态成员处理方式
- 子类同名成员变量,直接访问:
son.name
- 父类同名成员变量,需要加作用域,加父类作用域后访问:
son.Father::name
cpp
#include <iostream>
using namespace std;
class Father {
public:
string name;
Father() {
name = "Father";
}
};
class Son : public Father{
public:
string name;
Son(string name) {
this->name = name;
}
};
int main()
{
Son son("son");
cout << son.name << endl;//"son"
cout << son.Father::name << endl;//"Father"
system("pause");
return 0;
}
如果是同名成员函数,也是同样的处理方式,需要加父类作用域。
父类中重载的同名函数,子类调用时也必须加父类作用域(因为父类中同名函数无论是否重载,都被隐藏了)。
同名静态成员处理方式
- 如果是通过对象访问,则处理方式和同名非静态成员处理方式相同。
- 如果是通过类名访问,也可以加父类作用域(
Son::Father::name
)。
cpp
#include <iostream>
using namespace std;
class Father {
public:
static string name;
};
string Father::name = "F";
class Son : public Father{
public:
static string name;
};
string Son::name = "S";
int main()
{
Son son;
cout << son.name << endl;//"S"
cout << son.Father::name << endl;//"F"
cout << Son::name << endl;//"S"
cout << Son::Father::name << endl;//"F"
system("pause");
return 0;
}
同名静态成员函数,也是一样的处理方式。
多继承语法
多继承语法示例:class Son : public Father, public Mother
多个父类之间用逗号隔开。
如果父类之间有同名成员,子类调用时需要加父类作用域。
菱形继承
菱形继承问题:B、C都继承了A的name,D多继承了B、C,就会有两个name。
D访问name时,可以加B作用域访问从B继承到的name,也可以加C作用域访问从C继承到的name。但是实际上,D只需要继承一个name就行了。
cpp
#include <iostream>
using namespace std;
class A {
public:
string name;
};
class B : public A {};
class C : public A {};
class D : public B, public C {};
int main()
{
D d;
d.B::name = "AB";
d.C::name = "AC";
cout << d.B::name << endl;//"AB"
cout << d.C::name << endl;//"AC"
system("pause");
return 0;
}
菱形继承问题解决方法:虚继承,在继承前加上关键字virtual
,所继承的基类也就成了虚基类
:
cpp
#include <iostream>
using namespace std;
class A {
public:
string name;
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
int main()
{
D d;
d.B::name = "AB";
d.C::name = "AC";
cout << d.B::name << endl;//"AC"
cout << d.C::name << endl;//"AC"
cout << d.name << endl;//"AC"//也能直接访问了
system("pause");
return 0;
}
原理上是D从B、C分别继承两个vbptr(虚基类指针)(所以D中会有空间存两个虚基类指针),可以根据这两个指针分别从两个vbtable(虚基类表)中找到偏移值,根据偏移值找到同一个name变量的位置。
多态
基础语法
- 有继承关系
- 父类有虚函数(用
virtual
修饰的成员函数) - 子类重写父类的虚函数(子类的函数的
virtual
修饰可以省略)
多态使用时需要:父类指针或引用指向子类对象
cpp
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() {
cout << "动物" << endl;
}
};
class Cat : public Animal {
void speak() {
cout << "猫" << endl;
}
};
class Dog : public Animal {
void speak() {
cout << "狗" << endl;
}
};
int main()
{
Dog dog;
Cat cat;
Animal& a1 = dog;
Animal& a2 = cat;
a1.speak();//狗
a2.speak();//猫
system("pause");
return 0;
}
如果父类函数不加virtual
,那么a1.speak();
和a2.speak();
调用的都是Animal
的speak
函数。
纯虚函数和抽象类
上面虚基类中虚函数通常是无意义的,其方法体可以省略,因为一般都是使用子类中所重写的函数。
纯虚函数格式:virtual 返回值 方法名 (参数列表) = 0;
通过去掉{}
,加上=0;
,将虚函数变成纯虚函数:
cpp
class Animal {
public:
virtual void speak() = 0;
};
只要类中有一个纯虚函数,那么这个类就称为抽象类 。
抽象类的特点:
- 不能被实例化
- 抽象类的子类必须重写父类的纯虚函数,否则也是抽象类,也不能被实例化
- 子类重写父类(抽象类)的纯虚函数后,就可以被实例化
另外,子类也没法通过增加父类作用域,调用父类(抽象类)的纯虚函数。
虚析构和纯虚析构
问题:
父类指针析构时,无法自动调用子类对象的析构,导致资源没法释放。
cpp
#include <iostream>
using namespace std;
class Animal {
public:
Animal() {
cout << "Animal构造函数" << endl;
}
~Animal() {
cout << "Animal析构函数" << endl;
}
};
class Cat : public Animal {
public:
string* name;
Cat(string _name) {
name = new string(_name);
cout << *name << "的构造函数" << endl;
}
~Cat() {
cout << *name << "的析构函数" << endl;
if(name != NULL){
delete name;
name = NULL:
}
}
};
int main()
{
Animal* animal = new Cat("汤姆");
delete animal;
system("pause");
return 0;
}
控制台结果:
Animal构造函数
汤姆的构造函数
Animal析构函数
解决方法:将父类的析构改为虚析构或纯虚析构。
有了纯虚析构,类也就成了抽象类。
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名() = 0;
所以给Animal增加虚析构:
cpp
class Animal {
public:
Animal() {
cout << "Animal构造函数" << endl;
}
virtual ~Animal() {
cout << "Animal析构函数" << endl;
}
};
控制台结果:
Animal构造函数
汤姆的构造函数
汤姆的析构函数
Animal析构函数
与虚析构不同的是,纯虚析构不仅需要声明,还需要实现(因为父类也可能有堆区要释放):
cpp
class Animal {
public:
Animal() {
cout << "Animal构造函数" << endl;
}
virtual ~Animal() = 0;
};
Animal::~Animal() {
cout << "Animal析构函数" << endl;
}
如果子类中没有堆区资源要释放,可以不用在父类中提供虚析构或纯虚析构。