C++ 面向对象
# C++ 类 & 对象
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计,类是 C++ 的核心特性,通常被称为用户定义的类型。类用于指定对象的形式,是一种用户自定义的数据类型,它是一种封装了数据和函数的组合。类中的数据称为成员变量,函数称为成员函数。类可以看作是一种模板,可以用来创建具有相同属性和行为的多个对象。
# C++ 类定义
定义一个类需要使用关键字class,然后指定类的名称,类的主体是包含在一对花括号中,主体包含类的成员变量和函数。定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。

下面实例使用关键字class定义Box数据类型,包含了三个成员变量length、breadth和height。
class Box {
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
}
2
3
4
5
6
关键字public确定了类成员的访问属性,在类对象作用域内,公共成员在类的外部是可以访问的,也可以指定类的成员为private或protected。
# 定义 C++ 对象
类提供了对象的蓝图,所以基本上,对象是根据类来创建的,声明类的对象,就像声明基本类型的变量一样,下面的语句声明了类Box的两个对象:
Box Box1; // 声明 Box1, 类型为 Box
Box Box2; // 声明 Box2, 类型为 Box
2
对象 Box1 和 Box2 都有它们各自的数据成员。
# 访问数据成员
类的对象的公共数据成员可以使用直接成员访问运算符.来访问。

#include <iostream>
using namespace std;
class Box {
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double get(void);
void set(double len, double bre, double hei);
};
// 成员函数定义
double Box::get(void) {
return length * breadth * height;
}
void Box::set(double len, double bre, double hei) {
length = len;
breadth = bre;
height = hei;
}
int main() {
Box Box1; // 声明 Box1, 类型为 Box
Box Box2; // 声明 Box2, 类型为 Box
Box Box3; // 声明 Box3, 类型为 Box
double volume = 0.0; // 用于存储体积
// box1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box1 体积
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的体积: " << volume << endl;
// box2 详述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box2 体积
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的体积: " << volume << endl;
// box3 详述
Box3.set(16.0, 8.0, 12.0);
volume = Box3.get();
cout << "Box3 的体积: " << volume << endl;
return 0;
}
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

需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符(.)来直接访问。
# 类 & 对象详解
目前为止,已经对C++的类和对象有基本的了解,下面的列表中还列出了其他一些C++类和对象相关的概念。
# 类成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,可以操作类的任意对象,可以访问对象中的所有成员。
看看之前定义的类Box,现在要使用成员函数来访问类的成员,而不是直接访问这些类的成员:
class Box {
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void); // 返回体积
}
2
3
4
5
6
7
成员函数可以定义在类定义内部,或者单独使用范围解析运算符::来定义。在类定义中定义的成员函数将把函数声明为内联的,即便没有使用inline标识符,所以可以按照如下方式定义getVolume()函数:
class Box {
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void) {
return length * breadth * height;
}
}
2
3
4
5
6
7
8
9
10
也可以在类的外部使用**范围解析运算符::**定义该函数,如下:
double Box::getVolume(void) {
return length * breadth * height;
}
2
3
在这里,需要强调一点,在::运算符之前必须使用类名。调用成员函数是在对象上使用点运算符(.),这样它就能操作与该对象相关的数据,如下:
Box myBox; // 创建一个对象
myBox.getVolume(); // 调用该对象的成员函数
2
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double getVolume(void);
void setLength(double len);
void setBreadth(double bre);
void setHeight(double hei);
};
double Box::getVolume(void)
{
return length * breadth * height;
}
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; // 用于存储体积
// 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 << "box1 的体积: " << volume << endl;
// box2 体积
volume = box2.getVolume();
cout << "box2 的体积: " << volume << endl;
return 0;
}
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
62
63
64
65
# 类访问修饰符
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过类主体内部对各个区域标记public、private、protected来指定的,关键字public、private、protected称为访问修饰符。一个类可以有多个public、private、protected标记区域,每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的,成员和类的默认访问修饰符是private。
class Base
{
public:
// 共有成员
protected:
// 受保护成员
private:
// 私有成员
}
2
3
4
5
6
7
8
9
10
11
# 公有(public)成员
公有成员在程序中类的外部是可访问的,可以不使用任何成员函数来设置和获取公有变量的值,如下:
#include <iostream>
using namespace std;
class Line
{
public:
double length;
void setLength(double len);
double getLength(void);
};
// 成员函数定义
double Line::getLength(void)
{
return length;
}
void Line::setLength(double len)
{
length = len;
}
// 程序的主函数
int main()
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line: " << line.getLength() << endl;
// 不使用成员函数设置长度
line.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of line : " << line.length << endl;
return 0;
}
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
# 私有(private)成员
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的,只有类和友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的,例如在下面的类中,width是一个私有成员,这意味着,如果没有使用任何访问修饰符,类的成员将假定为私有成员:
class Box
{
double width;
public:
double length;
void setWidth(double wid);
double getWidth(void);
}
2
3
4
5
6
7
8
实际操作中,一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数,如下:
#include <iostream>
using namespace std;
class Box
{
public:
double length;
void setWidth(double wid);
double getWidth(void);
private:
double width;
};
// 成员函数定义
double Box::getWidth(void)
{
return width;
}
void Box::setWidth(double wid)
{
width = wid;
}
// 程序的主函数
int main()
{
Box box;
// 不使用成员函数设置长度
box.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of box: " << box.length << endl;
// 不使用成员函数设置宽度
// box.width = 10.0; // Error: 因为 width 是私有的
box.setWidth(10.0); // 使用成员函数设置宽度
cout << "Width of box: " << box.getWidth() << endl;
return 0;
}
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
# protected(受保护)成员
**protected(受保护)**成员变量或函数与私有成员十分相似,但有一点不同,protected成员在派生类(即子类)中是可访问的。
#include <iostream>
using namespace std;
class Box
{
protected:
double width;
};
class SmallBox:Box // SmallBox 是派生类
{
public:
void setSmallWidth(double wid);
double getSmallWidth(void);
};
// 子类的成员函数
double SmallBox::getSmallWidth(void)
{
return width;
}
void SmallBox::setSmallWidth(double wid)
{
width = wid;
}
// 程序的主函数
int main()
{
SmallBox box;
// 使用成员函数设置宽度
box.setSmallWidth(5.0);
cout << "width of box: " << box.getSmallWidth() << endl;
return 0;
}
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
# 继承中的特点
有public、protected、private三种继承方式,它们相应地改变了基类成员的访问属性
public继承: 基类public成员、protected成员、private成员的访问属性在派生类中分别变成:public、protected、privateprotected继承: 基类public成员、protected成员、private成员的访问属性在派生类中分别变成:protected、protected、privateprivate继承: 基类public成员、protected成员、private成员的访问属性在派生类中分别变成:private、private、private
但无论哪种继承方式,下面两点都没有改变:
private成员只能被本类成员(类内)和友元访问,不能被派生类访问protected成员可以被派生类访问
# public 继承
#include <iostream>
#include <assert.h>
using namespace std;
class A
{
public:
int a;
A()
{
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
}
void fun()
{
cout << a << endl; // 正确
cout << a1 << endl; // 正确
cout << a2 << endl; // 正确
cout << a3 << endl; // 正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : public A
{
public:
int a;
B(int i)
{
A();
a = i;
}
void fun()
{
cout << a << endl; // 正确,public成员
cout << a1 << endl; // 正确,基类的public成员,在派生类中仍是public成员
cout << a2 << endl; // 正确,基类的protected成员,在派生类中仍是protected可以被派生类访问
cout << a3 << endl; // 错误,基类的private成员不能被派生类访问
}
};
int main()
{
B b(10);
cout << b.a << endl;
cout << b.a1 << endl; // 正确
cout << b.a2 << endl; // 错误,类外不能访问protected成员
cout << b.a3 << endl; // 错误,类外不能访问private成员
system("pause");
return 0;
}
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
62
# protected 继承
#include <iostream>
#include <assert.h>
using namespace std;
class A
{
public:
int a;
A()
{
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
}
void fun()
{
cout << a << endl; // 正确
cout << a1 << endl; // 正确
cout << a2 << endl; // 正确
cout << a3 << endl; // 正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : protected A
{
public:
int a;
B(int i)
{
A();
a = i;
}
void fun()
{
cout << a << endl; // 正确,public成员
cout << a1 << endl; // 正确,基类的public成员,在派生类中是protected成员,可以被派生类访问
cout << a2 << endl; // 正确,基类的protected成员,在派生类中仍是protected可以被派生类访问
cout << a3 << endl; // 错误,基类的private成员不能被派生类访问
}
};
int main()
{
B b(10);
cout << b.a << endl;
cout << b.a1 << endl; // 错误,protected成员不能在类外访问
cout << b.a2 << endl; // 错误,类外不能访问protected成员
cout << b.a3 << endl; // 错误,类外不能访问private成员
system("pause");
return 0;
}
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
62
# private 继承
#include <iostream>
#include <assert.h>
using namespace std;
class A
{
public:
int a;
A()
{
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
}
void fun()
{
cout << a << endl; // 正确
cout << a1 << endl; // 正确
cout << a2 << endl; // 正确
cout << a3 << endl; // 正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : private A
{
public:
int a;
B(int i)
{
A();
a = i;
}
void fun()
{
cout << a << endl; // 正确,public成员
cout << a1 << endl; // 正确,基类的public成员,在派生类中是private成员,可以被派生类访问
cout << a2 << endl; // 正确,基类的protected成员,在派生类中仍是private可以被派生类访问
cout << a3 << endl; // 错误,基类的private成员不能被派生类访问
}
};
int main()
{
B b(10);
cout << b.a << endl;
cout << b.a1 << endl; // 错误,private成员不能在类外访问
cout << b.a2 << endl; // 错误,类外不能访问private成员
cout << b.a3 << endl; // 错误,类外不能访问private成员
system("pause");
return 0;
}
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
62
# 构造函数 & 析构函数
# 类的构造函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称完全相同,并且不会返回任何类型,也不会返回void。构造函数可用于为某些成员变量设置初始值,下面的实例有助于更好地理解构造函数的概念:
#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;
}
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
# 带参数的构造函数
默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数,这样在创建对象时就会给对象赋初始值,如下:
#include <iostream>
using namespace std;
class Line
{
public:
void setLength(double len);
double getLength(void);
Line(double len); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
void Line::setLength(double len)
{
length = len;
}
double Line::getLength(void)
{
return length;
}
// 程序的主函数
int main()
{
Line line(10.0);
// 获取默认设置的长度
cout << "Length of line: " << line.getLength() << endl;
// 再次设置长度
line.setLength(6.0);
cout << "Length of line: " << line.getLength() << endl;
return 0;
}
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
# 使用初始化列表来初始化字段
使用初始化列表来初始化字段
Line::Line(double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
2
3
4
上面的语法等同于如下语法:
Line::Line(double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
2
3
4
5
假设有一个类C,具有多个字段X、Y、Z等需要进行初始化,同理,可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下:
C::C(double a, double b, double c): X(a), Y(b), Z(c)
{
// code
}
2
3
4
# 类的析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行,析构函数的名称与类的名称完全相同,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
#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;
}
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

# C++ 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象,拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象
- 复制对象把它作为参数传递给函数
- 复制对象,并从函数返回这个对象
如果在类中没有定义拷贝构造函数,编译器会自行定义一个,如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
classname (const classname &obj)
{
// 构造函数的主体
}
2
3
4
在这里,obj是一个对象引用,该对象用于初始化另一个对象。
#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;
}
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
下面的实例对上面的实例稍作修改,通过使用已有的同类型的对象来初始化新创建的对象:
#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 line1(10);
Line line2 = line1; // 这里也调用了拷贝构造函数
display(line1);
display(line2);
return 0;
}
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
当上面的代码被编译和执行时,会产生下列结果:

# C++ 友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员,尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字friend,如下:
class Box
{
double width;
public:
double length;
friend void printWidth(Box box);
void setWidth(double wid);
}
2
3
4
5
6
7
8
声明类ClassTwo的所有成员函数作为类ClassOne的友元,需要在类ClassOne的定义中放置如下声明:
friend class ClassTwo;
#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;
}
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
# C++ 内联函数
C++ 内联函数 通常与类一起使用,如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方,对内联函数进行任何修改,都需要重新编译函数的所有客服端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字inline,在调用函数之前需要对函数进行定义,如果已定义的函数多于一行,编译器会忽略inline限定符。
#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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# C++ 中的 this 指针
在C++中,this指针是一个特殊的指针,它指向当前对象的实例,在C++中,每一个对象都能通过this指针来访问自己的地址。this是一个隐藏的指针,可以在类的成员函数中使用,它可以用来指向调用对象,当一个对象的成员函数被调用时,编译器会隐式地传递该对象的地址作为this指针。友元函数没有this指针,因为友元不是类的成员,只有成员函数才有this指针。
#include <iostream>
class MyClass
{
private:
int value;
public:
void setValue(int value)
{
this->value = value;
}
void printValue()
{
std::cout << "Value: " << this->value << std::endl;
}
};
int main()
{
MyClass obj;
obj.setValue(42);
obj.printValue();
return 0;
}
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

- 以上实例,定义了一个名为
MyClass的类,它有一个私有成员变量value - 类中的
setValue()函数用于设置value的值,而printValue()函数用于打印value的值 - 在
setValue()函数中,使用this指针来引用当前对象的成员变量value,并将传入的值赋给它,这样可以明确地告诉编译器想要访问当前对象的成员变量,而不是函数参数或局部变量 - 在
printValue()函数中,同样使用this指针来引用当前对象的成员变量value,并将其打印出来 - 在
main()函数中,创建了一个MyClass的对象obj,然后使用setValue()函数设置value的值为42,并通过printValue()函数打印出来 - 通过使用
this指针,可以在成员函数中访问当前对象的成员变量,即使它们与函数参数或局部变量同名,这样可以避免命名冲突,并确保我们访问的是正确的变量
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l = 2.0, double b = 2.0, double h = 2.0)
{
cout << "调用构造函数" << 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; // 宽度
double breadth; // 长度
double height; // 高度
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
if (Box1.compare(Box2))
{
cout << "Box2 的体积比 Box1 小" << endl;
}
else
{
cout << "Box2 的体积大于或等于 Box1" << endl;
}
return 0;
}
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

# C++ 中指向类的指针
一个指向C++类的指针与指向结构的指针类似,访问指向类的指针成员,需要使用成员访问运算符->,就像访问指向结构的指针一样,与所有的指针一样,必须在使用指针之前,对指针进行初始化。在C++中,指向类的指针指向一个类的对象,与普通指针相似,指向类的指针可以用于访问对象的成员变量和成员函数。
声明和初始化指向类的指针:
#include <iostream>
class MyClass
{
public:
int data;
void display()
{
std::cout << "Data: " << data << std::endl;
}
};
int main()
{
// 创建类对象
MyClass obj;
obj.data = 42;
// 声明和初始化指向类的指针
MyClass *ptr = &obj;
// 通过指针访问成员变量
std::cout << "Data via pointer: " << ptr->data << std::endl;
// 通过指针调用成员函数
ptr->display();
return 0;
}
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

动态分配内存: 指向类的指针还可以用于动态分配内存,创建类的对象
#include <iostream>
class MyClass
{
public:
int data;
void display()
{
std::cout << "Data: " << data << std::endl;
}
};
int main()
{
// 动态分配内存创建类对象
MyClass *ptr = new MyClass;
ptr->data = 42;
// 通过指针调用成员函数
ptr->display();
// 释放动态分配的内存
delete ptr;
return 0;
}
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>
class MyClass
{
public:
int data;
void display()
{
std::cout << "Data: " << data << std::endl;
}
};
// 函数接受指向类的指针作为参数
void processObject(MyClass *ptr)
{
ptr->display();
}
int main()
{
MyClass obj;
obj.data = 42;
// 将指向类的指针传递给函数
processObject(&obj);
return 0;
}
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;
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;
}
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

# C++ 类的静态成员
可以使用static关键字来把类成员定义为静态的,当声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的,如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零,不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符::来重新声明静态变量从而对它进行初始化,如下所示:
#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;
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;
}
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

# 静态成员函数
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来,静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符::就可以访问。静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。静态成员函数有一个类范围,它们不能访问类的this指针,可以使用静态成员函数来判断类的某些对象是否已被创建。
静态成员函数与普通成员函数的区别
- 静态成员函数没有
this指针,只能访问静态成员(包括静态成员变量和静态成员函数) - 普通成员函数有
this指针,可以访问类中的任意成员;而静态成员函数没有this指针
#include <iostream>
using namespace std;
class Box
{
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
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;
}
};
// 初始化类 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;
}
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

# C++ 继承
面向对象程序设计中最重要的一个概念是继承,继承允许依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易,这样,也达到了重用代码功能和提高执行效率的效果。
当创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类即可,已有的类称为基类,新建的类称为派生类。继承代表了is a关系,例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。

// 基类
class Animal
{
// eat() 函数
// sleep() 函数
};
// 派生类
class Dog : public Animal {
// bark() 函数
};
2
3
4
5
6
7
8
9
10
11
# 基类 & 派生类
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数,定义一个派生类,使用一个类派生列表来指定基类,类派生列表以一个或多个基类命名,形式如:
class derived-class : access-specifier base-class
其中,访问修饰符access-specifier是public、protected、private其中的一个,base-class是之前定义过的某个类的名称,如果未使用访问修饰符access-specifier,则默认为private
#include <iostream>
using namespace std;
// 基类
class Shape
{
protected:
int width;
int height;
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
};
// 派生类
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;
}
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

# 访问控制和继承
派生类可以访问基类中所有的非私有成员,因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为private,可以根据访问权限总结出不同的访问类型,如下:
| 访问 | public | protected | private |
|---|---|---|---|
| 同一个类 | yes | yes | yes |
| 派生类 | yes | yes | no |
| 外部的类 | yes | no | no |
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数
- 基类的重载运算符
- 基类的友元函数
# 继承类型
当一个类派生自基类,该基类可以被继承为public、protected或private几种类型,继承类型是通过上面讲解的访问修饰符access-specifier来指定的,通常几乎不使用protected或private继承,使用public继承,当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public): 当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员函数来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将称为派生类的保护成员。
- 私有继承(private): 当一个类派生自私有基类时,基类的公有和保护成员将称为派生类的私有成员。
# 多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。C++类可以从多个类继承成员,语法如下:
class <派生类名> : <继承方式1> <基类名1>, <继承方式2> <基类名2>, ...
{
<派生类体>
};
2
3
4
其中,访问修饰符继承方式是public、protected或private其中的一个,用来修饰每个基类,各个基类之间用逗号分隔,如上:
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
protected:
int width;
int heigth;
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
heigth = h;
}
};
// 基类 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};
// 派生类
class Rectangle : public Shape, public PaintCost
{
public:
int getArea()
{
return (width * heigth);
}
};
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;
}
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

# C++ 重载运算符和重载函数
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不同,当调用一个重载函数或重载运算符时,编译器通过把所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
# C++ 中的函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,不能仅通过返回类型的不同来重载函数。
#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;
}
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

# C++ 中的运算符重载
可以重定义或重载大部分C++内置的运算符,这样就能使用自定义类型的运算符。重载的运算符是带有特殊名称的函数,函数名是由关键字operator和其后要重载的运算符符号构成,与其他函数一样,重载运算符有一个返回类型和一个参数列表。
Box operator+(const Box&);
声明加法运算符用于把两个Box对象相加,返回最终的Box对象,大多数的重载运算符可被定义为普通的非成员函数或被定义为类的成员函数,如果定义上面的函数为类的非成员函数,那么需要为每次操作传递两个参数,如下:
Box operator+(const Box&, const Box&);
下面的实例使用成员函数演示了运算符重载的概念,在这里,对象作为参数进行传递,对象的属性使用this运算符进行访问,如下:
#include <iostream>
using namespace std;
class Box
{
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
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;
}
};
// 程序的主函数
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;
}
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
62
63
64
65
66
67
68
69
70
71
72
73

# 可重载运算符/不可重载运算符
下面是可重载的运算符列表:
| 运算符类型 | 运算符 |
|---|---|
| 双目算术运算符 | +(加)、-(减)、*(乘)、/(除)、%(取模) |
| 关系运算符 | ==等于、!=(不等于)、<(小于)、>(大于)、<=(小于等于)、>=(大于等于) |
| 逻辑运算符 | \|\|(逻辑或)、&&(逻辑与)、!(逻辑非) |
| 单目运算符 | +(正)、-(负)、*(指针)、&(取地址) |
| 自增自减运算符 | ++(自增)、--(自减) |
| 位运算符 | \|(按位或)、&(按位与)、~(按位取反)、^(按位异或)、<<(左移)、>>(右移) |
| 赋值运算符 | =、+=、-=、*=、%=、&=、\|=、^=、<<=、>>= |
| 空间申请与释放 | new、delete、new[]、delete[] |
| 其他运算符 | ()(函数调用)、->(成员访问)、,(逗号)、[](下标) |
下面是不可重载的运算符列表:
.: 成员访问运算符.*,->*: 成员指针访问运算符::: 域运算符sizeof: 长度运算符? :: 条件运算符#: 预处理符号
# 一元运算符重载
一元运算符只对一个操作数进行操作,下面是一元运算符的实例:
- 递增运算符(
++)和递减运算符(--) - 一元减运算符,即负号(
-) - 逻辑非运算符(
!)
一元运算符通常出现在它们所操作的对象的左边,比如!obj、-obj、++obj,但有时它们也可以作为后缀,比如obj++或obj--。下面的实例演示了如何重载一元减运算符(-)
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance()
{
feet = 0;
inches = 0;
}
Distance(int f, int i)
{
feet = f;
inches = i;
}
// 显示距离的方法
void displayDistance()
{
cout << "F: " << feet << " I: " << inches << endl;
}
// 重载负运算符(-)
Distance operator-()
{
feet = -feet;
inches = -inches;
return Distance(feet, inches);
}
};
int main()
{
Distance d1(11, 10), d2(-5, 11);
-d1; // 取相反数
d1.displayDistance(); // 距离 d1
-d2; // 取相反数
d2.displayDistance(); // 距离 d2
return 0;
}
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

# 二元运算符重载
二元运算符需要两个参数,下面是二元运算符的实例,平常使用的加运算符(+)、减运算符(-)、乘运算符(+)、除运算符(/)都属于二元运算符。
下面的实例演示了如何重载加运算符(+),类似地,可以尝试重载减运算符(-)和除运算分(/);
#include <iostream>
using namespace std;
class Box
{
double length; // 长度
double breadth; // 宽度
double height; // 高度
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;
}
};
// 程序的主函数
int main()
{
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;
}
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
62
63
64
65
66
# 关系运算符重载
C++语言支持各种关系运算符(<、>、<=、>=、==等等),它们可用于比较C++内置的数据类型,可以重载任何一个关系运算符,重载后的关系运算符可以比较类的对象。
下面的实例演示了如何重载<运算符:
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到 无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance()
{
feet = 0;
inches = 0;
}
Distance(int f, int i)
{
feet = f;
inches = i;
}
// 显示距离的方法
void displayDistance()
{
cout << "F: " << feet << " I: " << inches << endl;
}
// 重载负运算符(-)
Distance operator-()
{
feet = -feet;
inches = -inches;
return Distance(feet, inches);
}
// 重载小于运算符(<)
bool operator<(const Distance &d)
{
if (feet < d.feet)
{
return true;
}
if (feet == d.feet && inches < d.inches)
{
return true;
}
return false;
}
};
int main()
{
Distance d1(11, 10), d2(5, 11);
if (d1 < d2)
{
cout << "d1 is less than d2" << endl;
}
else
{
cout << "d2 is less than d1" << endl;
}
return 0;
}
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
62

# 输入/输出运算符重载
C++能够使用流提取运算符>>和流插入运算符<<来输入和输出内置的数据类型,可以重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。有一点很重要,需要把运算符重载声明为类的友元函数,这样就可以不用创建对象而直接调用函数。
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到 无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance()
{
feet = 0;
inches = 0;
}
Distance(int f, int i)
{
feet = f;
inches = i;
}
friend ostream &operator<<(ostream &output, const Distance &d)
{
output << "F: " << d.feet << " I: " << d.inches;
return output;
}
friend istream &operator>>(istream &input, Distance &d)
{
input >> d.feet >> d.inches;
return input;
}
};
int main()
{
Distance d1(11, 10), d2(5, 11), d3;
cout << "Enter the value of object: " << endl;
cin >> d3;
cout << "First Distance: " << d1 << endl;
cout << "Second Distance: " << d2 << endl;
cout << "Third Distance: " << d3 << endl;
return 0;
}
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

# ++ 和 -- 运算符重载
递增运算符(++)和递减运算符(--)是C++语言中两个重要的一元运算符。
#include <iostream>
using namespace std;
class Time
{
private:
int hours; // 0 到 23
int minutes; // 0 到 59
public:
// 所需的构造函数
Time()
{
hours = 0;
minutes = 0;
}
Time(int h, int m)
{
hours = h;
minutes = m;
}
// 显示时间的方法
void displayTime()
{
cout << "H: " << hours << " M: " << minutes << endl;
}
// 重载前缀递增运算符++
Time operator++()
{
// 对象加 1
++minutes;
if (minutes >= 60)
{
++hours;
minutes -= 60;
}
return Time(hours, minutes);
}
// 重载后缀递增运算符++
Time operator++(int)
{
// 保存原始值
Time T(hours, minutes);
// 对象加 1
++minutes;
if (minutes >= 60)
{
++hours;
minutes -= 60;
}
// 返回旧的原始值
return T;
}
};
int main()
{
Time t1(11, 59), t2(10, 40);
++t1; // t1 加 1
t1.displayTime(); // 显示 t1
++t1; // t1 再加 1
t1.displayTime(); // 显示 t1
t2++; // t2 加 1
t2.displayTime(); // 显示 t2
t2++; // t2 再加 1
t2.displayTime(); // 显示 t2
return 0;
}
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
62
63
64
65
66
67
68
69
70
71

# 赋值运算符重载
像其他运算符一样,可以重载赋值运算符(=),用于创建一个对象,比如拷贝构造函数。
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到 无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance()
{
feet = 0;
inches = 0;
}
Distance(int f, int i)
{
feet = f;
inches = i;
}
void operator=(const Distance &d)
{
feet = d.feet;
inches = d.inches;
}
// 显示距离的方法
void displayDistance()
{
cout << "F: " << feet << " I: " << inches << endl;
}
};
int main()
{
Distance d1(11, 10), d2(5, 10);
cout << "First Distance: ";
d1.displayDistance();
cout << "Second Distance: ";
d2.displayDistance();
// 使用赋值运算符
d1 = d2;
cout << "First Distance: ";
d1.displayDistance();
return 0;
}
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

# 函数调用运算符()重载
函数调用运算符()可以被重载用于类的对象,当重载()时,并不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到 无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance()
{
feet = 0;
inches = 0;
}
Distance(int f, int i)
{
feet = f;
inches = i;
}
// 常在函数调用运算符
Distance operator()(int a, int b, int c)
{
Distance d;
// 进行随机计算
d.feet = a + c + 10;
d.inches = b + c + 100;
return d;
}
// 显示距离的方法
void displayDistance()
{
cout << "F: " << feet << " I: " << inches << endl;
}
};
int main()
{
Distance d1(11, 10), d2;
cout << "First Distance: ";
d1.displayDistance();
d2 = d1(10, 10, 10); // invoke operator()
cout << "Second Distance: ";
d2.displayDistance();
return 0;
}
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

# 下标运算符[]重载
下标操作符[]通常用于访问数组元素,重载该运算符用于增强操作C++数组的功能。
#include <iostream>
using namespace std;
const int SIZE = 10;
class SafeArray
{
private:
int array[SIZE];
public:
SafeArray()
{
int i;
for (i = 0; i < SIZE; i++)
{
array[i] = i;
}
}
int& operator[](int i)
{
if (i >= SIZE)
{
cout << "索引超过最大值" << endl;
// 返回第一个元素
return array[0];
}
return array[i];
}
};
int main()
{
SafeArray a;
cout << "a[2] 的值为: " << a[2] << endl;
cout << "a[5] 的值为: " << a[5] << endl;
cout << "a[12] 的值为: " << a[12] << endl;
return 0;
}
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

# 类成员访问运算符->重载
类成员访问运算符(->)可以被重载,但较为麻烦,它被定义用于为一个类赋予指针行为,运算符->必须是一个成员函数,如果使用了->运算符,返回类型必须是指针或者类的对象。运算符->通常与指针引用运算符*结合使用,用于实现智能指针的功能,这些指针是行为与正常指针相似的对象,唯一不同的是,当通过指针访问对象时,它们会执行其他的任务。比如,当指针销毁时,或者当指针指向另一个对象时,会自动删除对象。
间接引用运算符->可被定义为一个一元后缀运算符:
class Ptr
{
// ...
X * operator->();
}
2
3
4
5
类Ptr的对象可用于访问类X的成员,使用方式与指针的用法十分相似,如:
void f(Ptr p)
{
p->m = 10; // (p.operator->())->m = 10
}
2
3
4
语句p->m被解释为(p.operator->())->m,下面的实例演示了如何重载类成员访问运算符->:
#include <iostream>
#include <vector>
using namespace std;
// 假设一个实际的类
class Obj
{
static int i, j;
public:
void f() const { cout << i++ << endl; }
void g() const { cout << j++ << endl; }
};
// 静态成员定义
int Obj::i = 10;
int Obj::j = 12;
// 为上面的类实现一个容器
class ObjContainer
{
vector<Obj *> a;
public:
void add(Obj *obj)
{
a.push_back(obj); // 调用向量的标准方法
}
friend class SmartPointer;
};
// 实现智能指针,用于访问类 Obj 的成员
class SmartPointer
{
ObjContainer oc;
int index;
public:
SmartPointer(ObjContainer &objc)
{
oc = objc;
index = 0;
}
// 返回值表示列表结束
bool operator++() // 前缀版本
{
if (index >= oc.a.size() - 1)
return false;
if (oc.a[++index] == 0)
return false;
return true;
}
bool operator++(int) // 后缀版本
{
return operator++();
}
// 重载运算符 ->
Obj *operator->() const
{
if (!oc.a[index])
{
cout << "Zero value";
return (Obj *)0;
}
return oc.a[index];
}
};
int main()
{
const int sz = 10;
Obj o[sz];
ObjContainer oc;
for (int i = 0; i < sz; i++)
{
oc.add(&o[i]);
}
SmartPointer sp(oc); // 创建一个迭代器
do
{
sp->f(); // 智能指针调用
sp->g();
} while (sp++);
return 0;
}
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

# C++ 多态
多态按字面意思就是多种形态,当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
#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;
// 调用矩形的求面积函数
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数
shape->area();
return 0;
}
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
62
63
64
65
66
67
当上面的代码被编译和执行时,会产生下列结果:

导致错误输出的原因是,调用函数area()被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接-函数调用在程序执行前就准备好了,有时候也被称为早绑定,因为area()函数在程序编译期间就已经设置好了。
但现在,对程序稍作修改,在Shape类中,area()的声明前放置关键字virtual,如下:
#include <iostream>
using namespace std;
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;
}
};
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;
// 调用矩形的求面积函数
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数
shape->area();
return 0;
}
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
62
63
64
65
66
67

此时,编译器看到的是指针的内容,而不是它的类型,因此,由于tri和rec类的对象的地址存储在*shape中,所以会调用各自的area()函数。正如看到的,每个子类都有一个函数area()的独立实现,这就是多态的一般使用方式,有了多态,可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。
# 虚函数
虚函数是在基类中使用关键字virtual声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数,我们需要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
# 纯虚函数
如果希望在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用与对象,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
class Shape
{
protected:
int width, height;
public:
Shape(int a = 0, int b = 0)
{
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0;
};
2
3
4
5
6
7
8
9
10
11
12
13
=0告诉编译器,函数没有主体,上面的虚函数为纯虚函数。
# C++ 数据抽象
数据抽象是指,指向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。数据抽象是一种依赖于接口和实现分离的编程技术,比如: 一台电视机,可以打开和关闭、切换频道、调整音量、添加外部组件(如喇叭、录像机、DVD播放器),但是用户并不知道它的内部实现细节,也就是说,用户并不知道它是如何通过线缆接收信号,如何转换信号,并最终显示在屏幕上。
因此,可以说电视把它的内部实现和外部接口分离开了,用户无需知道它的内部实现原理,直接通过它的外部接口(比如电源按钮、遥控器、声量控制器)就可以操控电视。
言归正传,就C++编程而言,C++类为数据抽象提供了可能,它向外界提供了大量用于操作对象数据的公共方法,外界实际上并不清楚类的内部实现。如: 程序可以调用sort()函数,而不需要直到函数中排序数据所用到的算法,实际上,函数排序的底层实现会因库的版本不同而有所差异,只要接口不变,函数调用就可以正常工作。
在C++中,我们使用类来定义自己的抽象数据类型(ADT),可以使用类iostream的cout对象来输出数据到标准输出,如下:
#include <iostream>
using namespace std;
int main()
{
cout << "Hello C++" << endl;
return 0;
}
2
3
4
5
6
7
8
在这里,不需要理解cout是如何在用户的屏幕上显示文本,只需要直到公共接口即可,cout的底层实现可以自由改变。
# 访问标签强制抽象
在C++中,可以使用访问标签来定义类的抽象接口,一个类可以包含零个或多个访问标签:
- 使用公共标签定义的成员都可以访问该程序的所有部分,一个类型的数据抽象视图是由它的公共成员来定义的
- 使用私有标签定义的成员无法访问到使用类的代码,私有部分对使用类型的代码隐藏了实现细节
访问标签出现的频率没有限制,每个访问标签指定了紧随其后的成员定义的访问级别,指定的访问级别会一直有效,直到遇到下一个访问标签或者遇到类主体的关闭右括号为止。
# 数据抽象的好处
数据抽象有两个重要的优势:
- 类的内部收到保护,不会因无意的用户级错误导致对象状态受损
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
如果只在类的私有部分定义数据成员,编写该类的作者就可以随意更改数据,如果实现发生改变,则只需要检查类的代码,确定这个改变会导致哪些影响,如果数据是公有的,则任何直接访问旧表示形式的数据成员的函数都可能受到影响。
# 数据抽象的实例
C++程序中,任何带有公有和私有成员的类都可以作为数据抽象的实例,如下:
#include <iostream>
using namespace std;
class Adder
{
private:
// 对外隐藏的数据
int total;
public:
// 构造函数
Adder(int i = 0)
{
total = i;
}
// 对外的接口
void addNum(int number)
{
total += number;
}
// 对外的接口
int getTotal()
{
return total;
}
};
int main()
{
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total: " << a.getTotal() << endl;
return 0;
}
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
上面的类把数字相加,并返回总和,公有成员addNum和getTotal是对外的接口,用户需要直到它们以便使用类,私有成员total是用户不需要了解的,但又是类能正常工作所必需的。
# 设计策略
抽象把代码分离为接口和实现,所以在设计组件时,必须保持接口独立于实现,这样,如果改变底层实现,接口也可保持不变。在这种情况下,不管任何程序使用接口,接口都不会收到影响,只需要将最新的实现重新编译即可。
# C++ 数据封装
数据封装(Data Encapsulation)是面向对象编程(OOP)的一个基本概念,它通过将数据和操作数据的函数封装在一个类中来实现。这种封装确保了数据的私有和完整,防止外部代码对其直接访问和修改。所有C++程序都有以下两个基本要素:
- 程序语句(代码): 这是程序中执行动作的部分,称为函数
- 程序数据: 数据是程序的信息,会收到程序函数的影响
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全,数据封装引申出了另一个重要的OOP概念,即数据隐藏。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。C++通过创建类来支持封装和数据隐藏(public、protected、private),前面已经介绍,类包含私有成员(private)、保护成员(protected)、公有成员(public),默认情况,在类中定义的所有项目都是私有的,如:
class Box
{
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
public:
double getVolume(void)
{
return length * breadth * height;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
变量length、breadth和height都是私有的,这意味着它们只能被Box类中的其他成员访问,而不能被程序中其他部分访问,这是实现封装的一种方式。为了使类中的成员变成共有的(即,程序中的其他部分也能访问),必须在这些成员前使用public关键字进行声明,所有定义在public标识符后边的变量或函数可以被程序中所有其他的函数访问。
把一个类定义为另一个类的友元类,会暴露实现细节,从而降低封装性,理想的做法是尽可能对外隐藏每个类的实现细节。
访问修饰符:
private: 私有成员只能在类的内部访问,不能被类的外部代码直接访问public: 公有成员可以被类的外部代码直接访问protected: 受保护成员可以被类和其派生类访问
# 数据封装的实例
C++程序中,任何带有公有和私有成员的类都可以作为数据封装和数据抽象的实例,如下:
#include <iostream>
using namespace std;
class Student
{
private:
string name;
int age;
public:
// 构造函数
Student(string studentName, int studentAge)
{
name = studentName;
age = studentAge;
}
// 访问器函数 getter
string getName()
{
return name;
}
int getAge()
{
return age;
}
// 修改器函数 setter
void setName(string studentName)
{
name = studentName;
}
void setAge(int studentAge)
{
if (studentAge > 0)
{
age = studentAge;
}
else
{
cout << "Invalid age!" << endl;
}
}
// 打印学生信息
void printInfo()
{
cout << "Name: " << name << ", Age: " << age << endl;
}
};
int main()
{
// 创建一个 Student 对象
Student student1("Alice", 20);
// 访问和修改数据
student1.printInfo();
student1.setName("Bob");
student1.setAge(22);
student1.printInfo();
return 0;
}
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
62
63
64
65
66
# 设计策略
通常情况下,都会设置类成员状态为私有,除非真的需要将其暴露,这样才能保证良好的封装性。
# 数据封装的优点
- 数据隐藏: 通过将数据成员声明为私有,防止外部代码直接访问这些数据
- 提高代码可维护性: 提供公共方法来访问和修改数据,使得可以在不影响外部代码的情况下修改类的内部实现
- 增强安全性: 防止不合法的数据输入和不当的修改操作
- 实现抽象: 提供了一种机制,使得用户不需要了解类的内部实现细节,只需要了解如何使用类的公共接口即可。
# C++ 接口 (抽象类)
接口描述了类的行为和功能,而不需要完成类的特定实现。C++接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念,如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类,纯虚函数是通过在声明中使用= 0来指定的,如下:
class Box
{
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
public:
// 纯虚函数
virtual double getVolume() = 0;
};
2
3
4
5
6
7
8
9
10
设计抽象类(通常称为ABC)的目的,是为了给其他类提供一个可以继承的适当的基类,抽象类不能被用于实例化对象,它只能作为接口使用,如果试图实例化一个抽象类的对象,会导致编译错误。
因此,如果一个ABC的子类需要被实例化,则必须实现每个纯虚函数,这也意味着C++支持使用ABC声明接口,如果没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误。可用于实例化对象的类被称为具体类。
# 抽象类的实例
#include <iostream>
using namespace std;
// 基类
class Shape
{
public:
// 提供接口框架的纯虚函数
virtual int getArea() = 0;
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);
}
};
class Triangle: public Shape
{
public:
int getArea()
{
return (width * height)/2;
}
};
int main(void)
{
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
// 输出对象的面积
cout << "Total Rectangle area: " << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
// 输出对象的面积
cout << "Total Triangle area: " << Tri.getArea() << endl;
return 0;
}
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
# 设计策略
面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口,然后派生类通过继承抽象基类,就把所有类似的操作都继承下来,外部应用程序提供的功能(即公有函数)在抽象基类中是以纯虚函数的形式存在的,这些纯虚函数在响应的派生类中被实现,这个架构也使得新的应用程序可以很容易地被添加到系统中,即使在系统被定义之后依然可以如此。
- C++ 类 & 对象
- C++ 类定义
- 定义 C++ 对象
- 访问数据成员
- 类 & 对象详解
- 类成员函数
- 类访问修饰符
- 公有(public)成员
- 私有(private)成员
- protected(受保护)成员
- 继承中的特点
- 构造函数 & 析构函数
- 类的构造函数
- 带参数的构造函数
- 使用初始化列表来初始化字段
- 类的析构函数
- C++ 拷贝构造函数
- C++ 友元函数
- C++ 内联函数
- C++ 中的 this 指针
- C++ 中指向类的指针
- C++ 类的静态成员
- 静态成员函数
- C++ 继承
- 基类 & 派生类
- 访问控制和继承
- 继承类型
- 多继承
- C++ 重载运算符和重载函数
- C++ 中的函数重载
- C++ 中的运算符重载
- 可重载运算符/不可重载运算符
- 一元运算符重载
- 二元运算符重载
- 关系运算符重载
- 输入/输出运算符重载
- ++ 和 -- 运算符重载
- 赋值运算符重载
- 函数调用运算符()重载
- 下标运算符[]重载
- 类成员访问运算符->重载
- C++ 多态
- 虚函数
- 纯虚函数
- C++ 数据抽象
- 访问标签强制抽象
- 数据抽象的好处
- 数据抽象的实例
- 设计策略
- C++ 数据封装
- 数据封装的实例
- 设计策略
- 数据封装的优点
- C++ 接口 (抽象类)
- 抽象类的实例
- 设计策略