面向对象 OOP

面向对象程序设计三大特性:封装,继承和多态

继承

继承方式 public 继承 protected 继承 private 继承
基类 public 成员变成 派生类的公有成员 派生类的保护成员 派生类的私有成员
基类 protected 成员变成 派生类的保护成员 派生类的保护成员 派生类的私有成员
基类 private 成员变成 只能通过基类接口访问 只能通过基类接口访问 只能通过基类接口访问

结构体默认 public 继承,类默认 private 继承

使用 final 关键字,可以防止类被继承

1
class NoDerived final {}; // NoDerived 不能作为基类

使用 using 关键字时,可以重新定义成员的访问权限为基类中的任何访问级别(public、protected 或 private),这样可以根据需要调整派生类中成员的可访问性

1
2
3
4
5
6
7
8
9
10
11
12
13
class Base {  
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};

class Derived : private Base {
public:
using Base::size;
protected:
using Base::n;
};

多继承

多继承即一个子类可以有多个父类

1
class <派生类名> : <继承方式1> <父类名1> , <继承方式2> <父类名2> ...

虚函数

为什么要使用虚函数?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

using namespace std;

// 基类 People
class People {
public:
void display() {
cout << "我是一个普通人" << endl;
}
};

// 派生类 Student
class Student : public People {
public:
void display() {
cout << "我是学生,我要白嫖" << endl;
}
};


int main() {
People *p = new People();
p->display();
p = new Student();
p->display();

return 0;
}

在不使用虚函数的情况下,两次调用的都是基类的方法,如果想让第二次调用使用的是 Student 的方法,那么就使用 virtual 关键字

1
2
3
4
5
6
7
// 基类 People
class People {
public:
virtual void display() {
cout << "我是一个普通人" << endl;
}
};

如果没有使用关键字 virtual,程序将根据引用类型或指针类型选择方法;如果使用了 virtual,程序将根据引用或指针指向的对象的类型来选择方法

当函数被声明成虚函数,则所有派生类中它都是虚函数

另外,C++11 新标准允许派生类显式地注明它将使用哪个成员函数改写基类的虚函数,做法是在函数形参列表之后添加 override 关键字

1
2
3
4
5
6
7
// 派生类 Student
class Student : public People {
public:
void display() {
cout << "我是学生,我要白嫖" << endl;
}
};

构造函数不能是虚函数,但是析构函数可以是虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;

// 基类 People
class People {
public:
virtual void display() {
cout << "我是一个普通人" << endl;
}

~People() {
cout << "释放 People" << endl;
}
};

// 派生类 Student
class Student : public People {
public:
void display() override{
cout << "我是学生,我要白嫖" << endl;
}

~Student() {
cout << "释放 Student" << endl;
}
};


int main() {
People *p = new Student();
delete p;
return 0;
}

此时基类的析构函数不是虚函数,会输出

1
释放 People

p 指向了 Student 对象,此时只调用了基类的析构函数,没有调用派生类的析构函数,导致派生类的资源没有得到释放,所以在基类的析构函数前加上 virtual

1
2
3
4
virtual ~People()
{
cout << "释放 People" << endl;
}

最后就能正确输出

1
2
释放 Student
释放 People

派生类重写的函数的形参必须与基类的虚函数完全一致。除非返回类型是类本身的指针或引用时,返回类型也必须一致。

final 除了可以用于防止类被继承,也可以加载函数形参列表之后,表示该函数不能被派生类 override 覆盖

通过作用域运算符可以强迫执行虚函数的某个版本

1
double undiscounted = baseP->Quote::net_price(42);

纯虚函数

如果不想给出虚函数的具体实现,可以使用纯虚函数

1
2
// virtual 返回值类型 函数名(函数参数) = 0;
virtual void display() = 0;

= 0 是纯虚函数的标志

抽象类

包含纯虚函数的类就是抽象类,抽象类是无法被实例化的

派生类如果想被实例化,需要实现所有的纯虚函数,否则,也是抽象类