1
0
wiki/docs/开发/C++/面对对象.md
2022-05-06 17:41:50 +08:00

780 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: 面对对象
title: 面对对象
sidebar_position: 5
data: 2022年5月26日
---
C++ 在 C 语言的基础上增加了面向对象编程C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
## 类
类定义是以关键字 **class** 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。
### 定义
```cpp
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
```
### 创建
对象 Box1 和 Box2 拥有独立的数据成员。
```cpp
Box Box1; // 声明 Box1类型为 Box
Box Box2; // 声明 Box2类型为 Box
```
### 实例
类的对象的公共数据成员可以使用直接成员访问运算符 (.) 来访问。
```cpp
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
int main( )
{
Box Box1; // 声明 Box1类型为 Box
Box Box2; // 声明 Box2类型为 Box
double volume = 0.0; // 用于存储体积
// box 1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 2 详述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box 1 的体积
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的体积:" << volume <<endl;
// box 2 的体积
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的体积:" << volume <<endl;
return 0;
}
```
**输出:**
```
Box1 的体积210
Box2 的体积1560
```
## 类的详解
### 构造函数
类的构造函数是类的一种特殊的成员函数,它会在**每次创建类的新对象时执行。**
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
如果需要,构造函数也可以带有参数,可以通过重载实现构造函数多元化。
```cpp
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
```
#### 初始化列表
假设有一个类 C具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:
```cpp
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
....
}
```
**等同于:**
```
C::C( double a, double b, double c)
{
X = a;
Y = b;
Z = c;
}
```
#### 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
```cpp
classname (const classname &obj) {
// 构造函数的主体
}
```
```cpp
#include <iostream>
using namespace std;
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr;
}
int Line::getLength( void )
{
return *ptr;
}
void display(Line obj)
{
cout << "line 大小 : " << obj.getLength() <<endl;
}
// 程序的主函数
int main( )
{
Line line(10);
display(line);
return 0;
}
```
**输出:**
```
调用构造函数
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
释放内存
```
### 析构函数
类的**析构函数**是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
#### 实例
```cpp
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数声明
~Line(); // 这是析构函数声明
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
```
**输出:**
```
Object is being created
Length of line : 6
Object is being deleted
```
### 访问修饰符
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 **public、private、protected** 来指定的。关键字 **public、private、protected** 称为访问修饰符。
```cpp
class Base {
public:
// 公有成员
protected:
// 受保护成员
private:
// 私有成员
};
```
- **公有public成员**:公有成员在程序中类的外部是**可访问的**。
- **私有private成员**:私有成员变量或函数在类的外部是**不可访问、查看的**。只有类和友元函数可以访问私有成员。**默认情况下,类的所有成员都是私有的。**
- **保护protected成员**:保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。
#### 继承中的特点
有 public, protected, private 三种继承方式,它们相应地改变了基类成员的访问属性。
**public 继承:**
| 基类 | public | protected | private |
| ------ | ------ | --------- | ------- |
| 派生类 | 可 | 可 | 不可 |
| 类外 | 可 | 不可 | 不可 |
**protected 继承:**
| 基类 | public | protected | private |
| ------ | ------ | --------- | ------- |
| 派生类 | 可 | 可 | 不可 |
| 类外 | 不可 | 不可 | 不可 |
**private 继承:**
| 基类 | public | protected | private |
| ------ | ------ | --------- | ------- |
| 派生类 | 可 | 可 | 不可 |
| 类外 | 不可 | 不可 | 不可 |
无论哪种继承方式,有两点都没有改变:
- private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
- protected 成员可以被派生类访问。
### 成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
```cpp
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void)
{
return length * breadth * height;
}
};
```
在类的内部声明函数后,可以在类的外部使用**范围解析运算符 ::** 定义该函数,在 **::** 运算符之前必须使用类名。
```cpp
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void);// 必须在类的内部声明函数
};
double Box::getVolume(void)
{
return length * breadth * height;
}
```
```cpp
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void)
{
return length * breadth * height;
}
// 成员函数声明
void setLength( double len );
void setBreadth( double bre );
void setHeight( double hei );
};
// 成员函数定义
void Box::setLength( double len )
{
length = len;
}
void Box::setBreadth( double bre )
{
breadth = bre;
}
void Box::setHeight( double hei )
{
height = hei;
}
// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1类型为 Box
Box Box2; // 声明 Box2类型为 Box
double volume = 0.0; // 用于存储体积
// box 1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// box 2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// box 1 的体积
volume = Box1.getVolume();
cout << "Box1 的体积:" << volume <<endl;
// box 2 的体积
volume = Box2.getVolume();
cout << "Box2 的体积:" << volume <<endl;
return 0;
}
```
**输出:**
```cpp
Box1 的体积: 210
Box2 的体积: 1560
```
### 友元函数
类的友元函数是定义在类外部但有权访问类的所有私有private成员和保护protected成员。尽管友元函数的原型有在类的定义中出现过但是**友元函数并不是成员函数。**
**友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类**,在这种情况下,整个类及其所有成员都是友元。
```cpp
class Box
{
double width;
public:
double length;
friend void printWidth( Box box ); // 友元函数
friend class ClassTwo; // 友元类
void setWidth( double wid );
};
```
```cpp
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}
// 请注意printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
// 程序的主函数
int main( )
{
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
```
**输出:**
```
Width of box : 10
```
### 内联函数
C++ **内联函数**是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
内联的主要不同是:它会复制源函数代码到调用的位置,而不像普通函数一样进行现场保护、跳转执行然后恢复现场等操作,可以使程序效率提高,比较典型的空间换取时间的思路。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 **inline**,在调用函数之前需要对函数进行定义。
```cpp
#include <iostream>
using namespace std;
inline int Max(int x, int y)
{
return (x > y)? x : y;
}
// 程序的主函数
int main( )
{
cout << "Max (20,10): " << Max(20,10) << endl;
cout << "Max (0,200): " << Max(0,200) << endl;
cout << "Max (100,1010): " << Max(100,1010) << endl;
return 0;
}
```
**输出:**
```cpp
Max (20,10): 20
Max (0,200): 200
Max (100,1010): 1010
```
### 静态成员
可以使用 **static** 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 **::** 来重新声明静态变量从而对它进行初始化,如下面的实例所示。
```cpp
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 输出对象的总数
cout << "Total objects: " << Box::objectCount << endl;
return 0;
}
```
**输出:**
```
Constructor called.
Constructor called.
Total objects: 2
```
### 静态成员函数
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,**静态函数**只要使用类名加范围解析运算符 **::** 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
**静态成员函数与普通成员函数的区别:**
- 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
```cpp
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount()
{
return objectCount;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}
```
**输出:**
```
Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2
```
### this 指针
在 C++ 中,每一个对象都能通过 **this** 指针来访问自己的地址。**this** 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 **this** 指针,因为友元不是类的成员。只有成员函数才有 **this** 指针。
```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;
}
int compare(Box box)
{
return this->Volume() > box.Volume();
}
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
if(Box1.compare(Box2))
{
cout << "Box2 is smaller than Box1" <<endl;
}
else
{
cout << "Box2 is equal to or larger than Box1" <<endl;
}
return 0;
}
```
**输出:**
```
Constructor called.
Constructor called.
Box2 is equal to or larger than Box1
```
### 指向类的指针