1
0

C++:面对对象进阶

This commit is contained in:
周中平 2022-05-07 16:24:23 +08:00
parent 335a5f549d
commit 3bfabd02ad
No known key found for this signature in database
GPG Key ID: B1DF9DD42D8E00DC
2 changed files with 601 additions and 64 deletions

View File

@ -337,35 +337,7 @@ class Base {
- **私有private成员**:私有成员变量或函数在类的外部是**不可访问、查看的**。只有类和友元函数可以访问私有成员。**默认情况下,类的所有成员都是私有的。**
- **保护protected成员**:保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。
#### 继承中的特点
有 public, protected, private 三种继承方式,它们相应地改变了基类成员的访问属性。
**public 继承:**
| 基类 | public | protected | private |
| ------ | ------ | --------- | ------- |
| 派生类 | 可 | 可 | 不可 |
| 类外 | 可 | 不可 | 不可 |
**protected 继承:**
| 基类 | public | protected | private |
| ------ | ------ | --------- | ------- |
| 派生类 | 可 | 可 | 不可 |
| 类外 | 不可 | 不可 | 不可 |
**private 继承:**
| 基类 | public | protected | private |
| ------ | ------ | --------- | ------- |
| 派生类 | 可 | 可 | 不可 |
| 类外 | 不可 | 不可 | 不可 |
无论哪种继承方式,有两点都没有改变:
- private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
- protected 成员可以被派生类访问。
-
### 成员函数
@ -538,11 +510,7 @@ int main( )
}
```
**输出:**
```
Width of box : 10
```
**输出Width of box : 10**
### 内联函数
@ -777,3 +745,570 @@ Box2 is equal to or larger than Box1
### 指向类的指针
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 **->**,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
```cpp
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
Box *ptrBox; // Declare pointer to a class.
// 保存第一个对象的地址
ptrBox = &Box1;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box1: " << ptrBox->Volume() << endl;
// 保存第二个对象的地址
ptrBox = &Box2;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box2: " << ptrBox->Volume() << endl;
return 0;
}
```
**输出:**
```
Constructor called.
Constructor called.
Volume of Box1: 5.94
Volume of Box2: 102
```
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 **->**,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
```cpp
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
Box *ptrBox; // Declare pointer to a class.
// 保存第一个对象的地址
ptrBox = &Box1;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box1: " << ptrBox->Volume() << endl;
// 保存第二个对象的地址
ptrBox = &Box2;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box2: " << ptrBox->Volume() << endl;
return 0;
}
```
**输出:**
```
Constructor called.
Constructor called.
Volume of Box1: 5.94
Volume of Box2: 102
```
## 封装
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
```cpp
#include <iostream>
using namespace std;
class Adder{
public:
// 构造函数
Adder(int i = 0)
{
total = i;
}
// 对外的接口
void addNum(int number)
{
total += number;
}
// 对外的接口
int getTotal()
{
return total;
};
private:
// 对外隐藏的数据
int total;
};
int main( )
{
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total " << a.getTotal() <<endl;
return 0;
}
```
**输出Total 60**
## 继承
继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。借此达到了重用代码功能和提高执行时间的效果。继承语法定义如下:
```cpp
// 单继承
class <派生类名>: <继承方式> <基类名>
// 多继承
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
```
```cpp
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 基类 PaintCost
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: " << Rect.getArea() << endl;
// 输出总花费
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;
}
```
**输出:**
```
Total area: 35
Total paint cost: $2450
```
### 继承类型
有 public, protected, private 三种继承方式,它们相应地改变了基类成员的访问属性。
**public 继承:**
| 基类 | public | protected | private |
| ------ | ------ | --------- | ------- |
| 派生类 | 可 | 可 | 不可 |
| 类外 | 可 | 不可 | 不可 |
**protected 继承:**
| 基类 | public | protected | private |
| ------ | ------ | --------- | ------- |
| 派生类 | 可 | 可 | 不可 |
| 类外 | 不可 | 不可 | 不可 |
**private 继承:**
| 基类 | public | protected | private |
| ------ | ------ | --------- | ------- |
| 派生类 | 可 | 可 | 不可 |
| 类外 | 不可 | 不可 | 不可 |
无论哪种继承方式,有两点都没有改变:
- private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
- protected 成员可以被派生类访问。
## 多态
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
```cpp
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
```
**输出:**
```
Parent class area
Parent class area
```
> 导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的**静态多态**,或**静态链接** - 函数调用在程序执行前就准备好了。有时候这也被称为**早绑定**,因为 area() 函数在程序编译期间就已经设置好了。
现在在 Shape 类中area() 的声明前放置关键字 **virtual**,如下所示:
```cpp
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
```
**输出:**
```
Rectangle class area
Triangle class area
```
### 虚函数
虚函数是在基类中使用关键字 **virtual** 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为**动态链接**,或**后期绑定**。
**纯虚函数**
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
```cpp
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// 纯虚函数,= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
virtual int area() = 0;
};
```
### 重载函数
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。
```cpp
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
```
**输出:**
```
整数为: 5
浮点数为: 500.263
字符串为: Hello C++
```
### 重载运算符
您可以重定义或重载大部分 C++ 内置的运算符。这样,您就能使用自定义类型的运算符。
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
```cpp
Box operator+(const Box&);
```
```cpp
#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 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1类型为 Box
Box Box2; // 声明 Box2类型为 Box
Box Box3; // 声明 Box3类型为 Box
double volume = 0.0; // 把体积存储在该变量中
// Box1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的体积
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的体积
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的体积
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
```
**输出:**
```
Volume of Box1 : 210
Volume of Box2 : 1560
Volume of Box3 : 5400
```
#### 可重载运算符
| 双目算术运算符 | + (加)-(减)*(乘)/(除)% (取模) |
| :------------: | :----------------------------------------------------------: |
| 关系运算符 | ==(等于)!= (不等于)< (小于)> (大于><=(小于等于)>=(大于等于) |
| 逻辑运算符 | \|\|(逻辑或)&&(逻辑与)!(逻辑非) |
| 单目运算符 | + (正)-(负)*(指针)&(取地址) |
| 自增自减运算符 | ++(自增)--(自减) |
| 位运算符 | \| (按位或)& (按位与)~(按位取反)^(按位异或),<< (左移)>>(右移) |
| 赋值运算符 | =, +=, -=, *=, /= , % = , &=, \|=, ^=, <<=, >>= |
| 空间申请与释放 | new, delete, new[ ] , delete[] |
| 其他运算符 | **()**(函数调用)**->**(成员访问)**,**(逗号)**[]**(下标) |
#### 不可重载运算符
- **.**:成员访问运算符
- **.\***, **->\***:成员指针访问运算符
- **::**:域运算符
- **sizeof**:长度运算符
- **?:**:条件运算符
- **#** 预处理符号

View File

@ -239,6 +239,37 @@ final class 类名 { /*方法体*/ }
[public / private / default / protected] final 返回值类型 方法名(){ /*方法体*/ }
```
### 多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
![多态图例](https://static.7wate.com/img/2022/04/27/ac5f133d3c528.png)
#### 优点
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
#### 多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
#### 多态的实现方式
- 重写
- 接口
- 抽象类和抽象方法
### 重写与重载
#### 重写
@ -313,36 +344,7 @@ public class Apple {
![区别图例](https://static.7wate.com/img/2022/04/27/4fdd076693397.png)
### 多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
![多态图例](https://static.7wate.com/img/2022/04/27/ac5f133d3c528.png)
#### 优点
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
#### 多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
#### 多态的实现方式
- 重写
- 接口
- 抽象类和抽象方法
-
## 抽象