C++继承

面向对象一个重要概念:继承,继承可以使我们依据另一个类来定义一个类;当我们创建一个类时无需编写新的数据成员和成员函数,已有类称为基类,新建的类称为派生类。

1
2
3
4
5
6
7
8
class animal{
//eat()
//sleep()
};

class dog : public animal{
//bark()
};

一个类可以派生自多个类,也就是说可以从多个基类继承数据和函数,定义一个派生类使用一个派生类列表来指定基类。

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
#include <iostream>
using namespace std;
class shape{
public:
void setwidth(int w){
width = w;
}

void setheight(int h){
height = h;
}

protected:
int width;
int height;
};

class rectangle:public shape{
public:
int getarea(){
return width * height;
}
};

int main(void){
rectangle rect;
rect.setwidth(5);
rect.setheight(7);
cout << "Total area: " << rect.getarea() << endl;
return 0;
}

访问控制和继承:

派生类可以访问基类中的所有的非私有成员,因此基类成员若是不想被派生类的成员函数访问,则应该在基类中声明为private,访问类型对应的访问权限:

一般派生类继承了所有的基类方法,下列情况除外:

基类的构造函数、析构函数以及拷贝构造函数;重载运算符;友元函数。

继承类型:

当一个类派生自基类,该基类可以被继承为public、protected、private三个类型,一般几乎不会使用后两个类型,使用public居多,公有继承的访问权限基本保持不变,保护继承除了私有成员外都变保护成员,私有继承则是全部变为私有成员。

多继承:

一个子类可以有多个父类,继承多个父类的特性。

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
34
35
36
37
38
39
40
41
#include <iostream>
using namespace std;
class shape{
public:
void setwidth(int w){
width = w;
}

void setheight(int h){
height = h;
}

protected:
int width;
int height;
};

class paintcost{
public:
int getcost(int area){
return area * 70;
}
};

class rectangle : public shape, public paintcost{
public:
int getarea(){
return width * height;
}
};

int main(void){
rectangle rect;
int area;
rect.setwidth(5);
rect.setheight(7);
area = rect.getarea();
cout << "Total area: " << area << endl;
cout << "Total cost: " << rect.getcost(); << endl;
return 0;
}

C++重载运算符和重载函数

在同一作用域中的某个函数和运算符指定多个定义称为函数重载和运算符重载,重载声明指的是已经在该作用域内声明过的函数或方法具有相同名称的声明,但是参数列表和定义不同;调用重载函数和重载运算符时,编译器通过参数类型进行比较,决定选用那个合适的定义,这个过程叫做重载决策。

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
#include <iostream>
#include <windows.h>
#include <string>
using namespace std;
class printdata{
public:
void print(int i){
cout << "整数为:" << i << endl;
}

void print(double b){
cout << "浮点数为:" << b << endl;
}

void print(string c){
cout << "字符串为:" << c << endl;
}
};

int main(void){
SetConsoleOutputCP(CP_UTF8);
printdata pd;
pd.print(5);
pd.print(3.1);
pd.print("I love China.");
return 0;
}

运算符重载:

重载的运算符是带有特殊名称的函数,函数名是由关键字operator和其后要重载的运算符符号构成的,重载运算符也有一个返回类型和参数列表。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
using namespace std;
class Box{
public:
double getvolume(void){
return length * breadth * height;
}

void setlength(double len){
length = len;
}

void setbreadth(double bre){
breadth = bre;
}

void setheight(double hei){
height = hei;
}

Box operator+(const Box& b){
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.hieght = this->height + b.height;
return box;
}

private:
double length;
double breadth;
double height;
};

int main(void){
Box box1;
Box box2;
Box box3;

double volume = 0.0;

box1.setlength(6.0);
box1.setbreadth(7.0);
box1.setheight(5.0);

box2.setlength(12.0);
box2.setbreadth(13.0);
box2.setheight(10.0);

volume = box1.getvolume();
cout << "volume of box1:" << volume << endl;

volume = box2.getvolume();
cout << "volume of box2:" << volume << endl;

box3 = box1 + box2;
volume = box3.getvolume();
cout << "volume of box3:" << volume << endl;

return 0;
}

可否重载运算符:

C++多态

按照字面意思就是多种形态,多态也是面向对象编程的重要特性之一,C++多态允许使用基类指针或者引用来调用子类的重写方法,从而使得同一个接口可以表现不同的行为。

多态的几个关键点:

1.虚函数

在基类声明一个函数为虚函数,使用关键字virtual。

派生类可以重写这个虚函数。

虚函数调用时,可以根据对象的实际类型来决定调用那个版本的函数。

2.动态绑定

称作是晚期绑定,在运行时确定函数的具体实现。

需要使用指向基类的指针或者引用来调用虚函数,编译器在运行时根据对象的实际类型来决定调用那个函数。

3.纯虚函数

一个包含纯虚函数的类被称为抽象类,不能直接被实例化。

纯虚函数没有函数体,声明时使用= 0。

它强制派生类提供具体实现。

4.多态的实现机制

虚函数表:每个包含虚函数的类都有一个虚函数表,表中存储了指向类中所有虚函数的指针。

虚函数指针:对象中包含一个指向该类虚函数表的指针。

5.多态的优势

代码的复用:通过基类的指针或者引用,可以操作不同类型的派生类对象。

扩展性:新增派生类,不需要修改基类代码,只需要确保新类的虚函数即可。

解耦:多态允许程序设计更加模块化,降低类的耦合度。

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
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
using namespace std;
class animal{
public:
virtual void sound() const{
cout << "animal makes a sound" << endl;
}

virtual ~animal(){
cout << "animal destoryed" << endl;
}
};

class dog : public animal{
public:
void sound() const override{
cout << "dog barks" << endl;
}

~dog(){
cout << "dog destroyed" << endl;
}
};

class cat : public animal{
public:
void sound() const override{
cout << "cat meows" << endl;
}

~cat(){
cout << "cat destroyed" << endl;
}
};

int main(){
animal *ptr;
ptr = new dog();
ptr->sound();
delete ptr;
ptr = new cat();
ptr->sound();
delete ptr;
return 0;
}

纯虚函数:

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
#include <iostream>
using namespace std;

class shape{
virtual int area() = 0;
};

class rectangle : public shape{
protected:
int length;
int height;

public:
rectangle(int l, int h) : length(l), height(h){}
int area() override(){
return length * height;
}
};

int main(){
shape *shape = new rectangle(10, 5);
cout << "rectangle area:" << shape->area() << endl;
delete shape;
return 0;
}

C++数据抽象

指的是向外界提供关键信息而隐藏实现细节,数据抽象是一种接口与实现分离的技术。

例如cout对象就是iostream类中,将数据输出到标准输出的,我们并不需要知道cout是如何在用户的屏幕上显示文本。

访问标签强制抽象:

在C++中可以使用访问标签进行强制抽象,一个类中可以包含0个或者多个的访问标签,使用公共标签的成员可以访问该程序的所有部分;使用私有标签定义的成员无法访问到使用类的代码。

数据抽象的好处:

类的内部收到保护,不会因为用户级错误而导致对象状态受损。

类的实现可能随着时间而发生变化,以应对变化的需求。

C++数据封装

数据封装是面向对象的三大概念之一,通过将数据和操作数据的函数封装在一个类中来实现。

封装确保了数据的私有性和完整性,防止外部代码对其直接进行修改和访问。

设计策略:

通常情况下,会设置类成员状态为私有,除非我们需要对其进行暴露,一般用于数据成员。

数据封装的优点:

数据隐藏,将数据成员设置为私有,防止外部代码直接访问这些数据。

提高代码可维护性,提供公共方法来访问和修改数据。

增强安全性,防止不合法的输入和不当操作。

实现抽象,使得用户无需理解内部实现细节。

C++接口(抽象类)

接口描述了类的行为和功能,而不需要完成类的特定实现,C++接口是使用抽象类来实现的,抽象类和数据抽象互不混淆,若是类中至少有一个函数被声明为纯虚函数,那么这个类就是抽象类,纯虚函数的指定不用在叙述。

设计抽象类的目的在于给其他类提供一个可以继承的基类,抽象类不能被用于实例化对象,只能作为接口使用,若是子类需要实例化,那么必须实现每一个纯虚函数,用于实例化对象的被称为具体类。

设计策略:

面向对象的系统可能会使用一个抽象基类为所有的外部应用提供一个适当的接口,然后派生类通过继承抽象基类就把所有的操作继承下来,外部的应用提供的功能在抽象基类中是以纯虚函数的形式存在,在派生类中被实现,这个架构也使得新的应用程序可以很容易的被添加到系统中。

实例在前面都有体现。