# C++基础11-类特性

本文是《C++ Primer Plus》的笔记,本文中的案例均自己实践过,如需转发请在转发开头贴上原文地址,谢谢!

# 运算符重载

运算符重载是C++的一种多态,它允许将已有运算符扩展到用户定义的类型。编译器将根据操作数的数目和类型决定使用哪种定义。

实例:

Time.h的内容

#pragma once
class Time
{
private:
    int hours;
    int minutes;
    int seconds;
    int days;

public:
    Time();
    Time(int days, int hour, int minute, int second);
    ~Time();
    Time operator+(const Time & t2) const;
    Time operator*(int mult) const;
    friend Time operator*(double mult,Time t1);
    friend std::ostream & operator<<(std::ostream & os,const Time & t);
    void GetTime() const;
    int totalSeconds;
    void convertToSeconds();
    void GetStandardTime();
};

Time operator - (const Time& t1, const Time& t2);
Time operator * (const int mult, const Time& t1);

Time.cpp的内容

#include "pch.h"
#include "Time.h"


Time::Time()
{
}

Time::Time(int day, int hour, int minute, int second)
{
    days = day;
    hours = hour;
    minutes = minute;
    seconds = second;
    GetTime();
    convertToSeconds();
}


Time::~Time()
{
}

Time Time::operator+(const Time& t2) const
{
    Time sum;
    sum.seconds = seconds + t2.seconds;
    sum.minutes = minutes + t2.minutes + sum.seconds / 60;
    sum.hours = hours + t2.hours + sum.minutes / 60;
    sum.days = days + t2.days + sum.hours / 24;
    sum.hours = sum.hours % 24;
    sum.minutes = sum.minutes % 60;
    sum.seconds = sum.seconds % 60;
    sum.GetTime();
    return sum;
}

Time Time::operator*(int mult) const
{
    Time result;
    result.totalSeconds = totalSeconds * mult;
    result.GetStandardTime();
    return result;
}

void Time::GetTime() const
{
    cout << "时间是:" << days << " 天 " << hours << " 时 " << minutes << " 分 " << seconds << " 秒 " << endl;
}

void Time::convertToSeconds()
{
    totalSeconds = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds;
    cout << "totalSeconds:" << totalSeconds << endl;
}

void Time::GetStandardTime()
{
    days = totalSeconds / 60 / 60 / 24;
    hours = totalSeconds % (24 * 60 * 60);
    minutes = hours % (60 * 60);
    seconds = minutes % 60;
    minutes = minutes / 60;
    hours = hours / 60 / 60;
    GetTime();
}


Time operator*(double mult, Time t1)
{
    //这个函数得是友元函数因为它访问了t1的私有成员变量days,hours,minutes,seconds
    Time result(t1.days,t1.hours,t1.minutes*mult,t1.seconds*mult);
    return result;
}

std::ostream & operator<<(std::ostream & os, const Time & t)
{
    cout << "现在时间是:" << t.days << " 天" << t.hours << " 时" << t.minutes << " 分" << t.seconds << " 秒" << endl;
    return os;
}

Time operator - (const Time& t1, const Time& t2) {
    //这个函数不用是友元函数因为它没有访问t1,t2的私有成员变量
    Time result;
    //cout << t1.totalSeconds << ":" << t2.totalSeconds << endl;
    if (t1.totalSeconds < t2.totalSeconds) {
        cout << "第一个参数小于第二个参数" << endl;
    }
    else {
        result.totalSeconds = t1.totalSeconds - t2.totalSeconds;
        result.GetStandardTime();
    }
    return result;
}

Time operator*(const int mult, const Time & t1)
{
    //这个全局函数可以替代友元函数。
    return t1 * mult;
}

main函数:

int main(int argc,char *argv[])
{
    Time t1(1, 17, 25, 34);
    Time t2(2, 9, 36, 54);
    Time t3 = t1 + t2;
    Time t4 = t1.operator+(t2);
    Time t5 = t2 - t1;
    //Time t5 = t2.operator-(t1);
    Time t6 = operator-(t2,t1);
    Time t7 = t1 * 10;
    Time t8 = 10 * t1;
    Time t9 = 2.5 * t1;
    cout << "t1:" << t1 << "this is all" << 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
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149

注意要点:

  1. 重载后的运算符至少要有一个参数是用户自定义类型,这样是为了防止重载标准类型。

  2. 重载不改变运算符的用法,不改变优先级和结合性,运算符原有几个操作数,重载后还是有几个,操作顺序和原来还是一样,运算符重载函数不能有默认参数,因为这样会改变操作数个数。

  3. 可以重载的运算符:

    + - *  / % ^
    & | ~= ! = <
    > += -= *= /= %=
    ^= &= |= << >> >>=
    <<= == != <= >= &&
    || ++ -- , ->* ->
    () [] new delete new[] delete[]
    
    1
    2
    3
    4
    5
    6
    7
  4. 不能重载下面的运算符:

    1. sizeof
    2. . 成员运算符
    3. :: 作用域解析运算符
    4. ? 条件运算符
    5. typeid RTTI运算符
    6. const_cast dynamic_cast reinterpret_cast static_cast 强制类型转换运算符
  5. 大部分运算符可以通过类成员函数也可以通过全局函数来重载,作为类成员函数时,二元运算符只有一个参数,一元运算符不需要参数。以下几个运算符只能通过类成员函数重载:

    1. = 赋值运算符
    2. () 函数调用运算符
    3. [] 下标运算符
    4. -> 指针访问类成员运算符

# 友元

在C++类中,类的公有成员函数提供了一种访问私有部分的途径。但这种限制有时候并不灵活,引入了友元的概念。友元分为三种:友元函数,友元类,友元成员函数。

# 友元函数

让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。为什么需要这种形式呢?

比如上面的t7是t1*10,它是由成员函数重载*运算符完成的,但是10*t1这个是没有办法用成员函数实现的,这个时候就需要全局函数或者友元函数来实现,如果不用访问类的内部就那么只用全局函数就行,比如t8的实现Time operator*(const int mult, const Time & t1),如果需要访问类的内部则需要友元函数,比如t9的实现Time operator*(double mult, Time t1)。

# 友元函数特点

  1. 友元函数在类声明中声明的,但它不是成员函数,不能用成员运算符来调用。
  2. 友元函数不是成员函数,不能用类的限定符,但它与成员函数的访问权限相同。
  3. friend关键字只是在友元函数在类声明中声明时使用的,不能在定义时使用。

# 重载<<运算符

一般来说重载<<运算符来显示对象,其友元函数有如下固定形式:

ostream & operator<<(ostream & os,const className & obj){
    os << ...;
    return os;
}
1
2
3
4

返回ostream可方便cout等的链式调用,沿用以前的习惯。

# 随机漫步(drunkard walk problem)

假设有一个醉汉,每一步是固定大小,但是每一次行走的方向是完全随机的,试问1000次后他会离原点多远。

# C++中的随机数

C++产生的随机数是伪随机数,由小M多项式产生,会根据一个初始值(随机种子)来生成一个序列,如果这个种子不变,那么程序每次运行产生的这个序列也不会变。

rand()函数便是产生的伪随机数,根据一个随机种子为基准来推算出一系列数。
srand()根据随机种子来初始化rand()产生的随机序列,如果默认没有调用这个函数,那么它跟调用srand(1)是一样的。如果希望程序每次运行都不一样可以把当前的时间作为随机种子比如srand(time(0))。

# 限制随机数的范围

[1,max]之间
rand()%max+1
[min,max]之间
(rand()%(max-min+1))+min
[min,max)之间
(rand()%(max-min))+min
(min,max]之间
(rand()%(max-min))+min+1
[0,1]之间的浮点数
rand()/double(RAND_MAX)
[min,max]之间的随机整数
min+(int)max*rand()/(RAND_MAX+1)

1
2
3
4
5
6
7
8
9
10
11
12
13

# 随机漫步实现

vector2.h 文件的内容

#pragma once

//#define _USE_MATH_DEFINES
//#include <math.h>
//为了兼容性就不使用上面这种宏定义的M_PI,使用下面计算的方法来自定义宏
#include <cmath>
#include <ctime>

#define PI (atan(1.0)*4)
#define Rad2Deg (45.0/atan(1.0))
#define Deg2Rad (atan(1.0)/45.0)

class vector2
{
private:
    double x, y;
    //假设向量均为以原点为起点
    double magnitude;
    double angle;
public:
    vector2();
    ~vector2();
    void operator+(vector2 & vt);
    void randomWalk();
    void angle2rect();
    void rect2angle();
    double getMag();
    friend ostream & operator<<(ostream & os, vector2);
};

vector2.cpp 文件的内容

#include "pch.h"
#include "vector2.h"

void vector2::operator+(vector2 & vt)
{
    x += vt.x;
    y += vt.y;
}

vector2::vector2()
{
    magnitude = 1;
    srand(time(0));
    randomWalk();
    angle2rect();
}


vector2::~vector2()
{
}

void vector2::randomWalk()
{
    angle = rand() % 361;
    //angle *=  M_PI / 180;
    angle = angle * Deg2Rad;
}

void vector2::angle2rect()
{
    x = magnitude * cos(angle);
    y = magnitude * sin(angle);
}

void vector2::rect2angle()
{
    angle = atan2(y,x);
}

double vector2::getMag()
{
    return sqrt(x*x + y * y);
}

ostream & operator<<(ostream & os, vector2 vt) {
    cout << " x: " << vt.x << " y: " << vt.y << endl;
    return os;
}

main 函数的内容

int main(int argc, char *argv[])
{
    vector2 vt1;
    while (vt1.getMag() < 50) {
        vector2 vtTemp;
        vt1 + vtTemp;
        cout << vt1 <<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
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
88
89
90
91
92
93
94
95
96

# 类的类型转换

C++中有隐式自动转换和强制转换,比如把一个int赋值给double,会进行自动转换。但是如果把一个整形赋值给指针就需要强制类型转换。可以将类定义成与基本类型或另一个类相关的,使用只有一个基本类型参数的构造函数可以将该基本类型转换成类,使用基本类型的转换函数可以将该类转换成基本类型。看下面实例:

money.h 的文件内容:
#pragma once
class money
{
private:
    double btc;
    int yuan;
    int jiao;
public:
    money();
    money(double totalJiao);
    explicit money(int yuan,int jiao=3);
    void showInBTC();
    void showInCNY();
    ~money();

explicit operator int();
    operator double();
};

money.cpp 的文件内容:

#include "pch.h"
#include "money.h"


money::money()
{

}

money::money(double totalJiao)
{
    btc  = totalJiao / 500000.0;
    yuan = totalJiao / 10;
    jiao = (int)totalJiao % 10;
}

money::money(int _yuan, int _jiao)
{
    yuan = _yuan/10;
    jiao = _jiao;
    btc = yuan + jiao / 10.0;
    btc /= 50000;
}

void money::showInBTC()
{
    cout << "In BTC value:" << btc << endl;
}

void money::showInCNY()
{
    cout << "In CNY: " << yuan << " yuan, " << jiao << " jiao." << endl;
}


money::~money()
{
}

money::operator int() {
    return btc * 50000;
}

money::operator double() {
    return btc * 60000.0;
}

main 函数:
int main(int argc, char *argv[])
{
    money money1(200006.0);
    money1.showInCNY();
    money money2 = 200006;
    //因为有explicit关键字,那么上面的隐式转换将不能用,继续寻找将200006转换成double,使用只有一个double参数的构造函数。
    //如果把上面的explicit去掉,那么输出将和下面的一样。
    //临时创建一个money对象,然后逐成员赋值过去,这一过程称为隐式转换。
    money2.showInCNY();
    money money3 = money(200006);
    money3.showInCNY();
    //显示转换,可以使用explicit关键字的方法。
    money money4 = (money)200006;
    //显示转换,可以使用explicit关键字的方法。
    money4.showInCNY();

    ////way1
    //long moneyNum = money4;
    //cout << gone << endl;
    ////way2
    //cout << money4 << endl;
    ////有多个转换方法,比如int => long,double => long,会显示错误,如果只有一种的话上面两种方法就不会显示错误。

    //可以使用显式转换
    long moneyNum1 = double (money4);
    long moneyNum2 = (int)money4;
    cout << moneyNum1 << ":" << moneyNum2 <<endl;

    int moneyNum3 = money4;
    cout << moneyNum3 << endl;
    //如果把explicit去掉会调用int转换函数,否则的话则是调用double转换函数。
    cout << (int)money4 << ":" << int(money4) << 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
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

从上面可以看出:

  1. 只有一个参数的类构造函数 用于将参数的类型的值转换为该类类型,并且可以在构造函数声明中使用explicit可防止隐式转换,只允许显示转换。
  2. 被称为转换函数的特殊类成员函数,可以将类转换为其他标准类型,转换函数得是类成员,没有返回类型,没有参数,名为operator typeName()。typeName是对象将被转换的类型,将类对象赋值给typeName类型变量或者将强制转换成typeName类型时,该转换函数自动调用。

# 转换函数和友元函数

现在要实现money类的相加功能。


第一种方法,使用类的成员运算符函数:

money.h 增加:
void operator+(const money & my);

money.cpp 增加:
void money::operator+(const money & my)
{
    btc += my.btc;
    yuan += my.yuan;
    jiao += my.jiao;
}

第二种方法,使用类的友元函数:

money.h 增加:
friend void operator+(money &m1,const money & m2);

money.cpp 增加:
void operator+(money &m1, const money & m2)
{
    m1.btc += m2.btc;
    m1.yuan += m2.yuan;
    m1.jiao += m2.jiao;
}

main函数:
int main(int argc, char *argv[])
{
    money m1(1000000), m2(2000000);
    double d1 = 3000000;
    m1 + m2;
    m1 + d1;
    //如果money类有operator double的话会出现二义性,是d1从double转换成money,还是m1从money转换成double。
    d1 + m1;
    //使用这种方法d1是不会自动转换成meney类的。
}

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