面向对象

在此之前康康引用:

① 作用:给变量起别名。

② 语法:数据类型 &别名 = 原名

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;

int main()
{
//引用基本语法
//数据类型 &别名 = 原名

int a = 10;
//创建引用
int& b = a;

b = 100;

cout << "a= " << a << endl;
cout << "b= " << a << endl;

return 0;

}
a= 100
b= 100
引用必须初始化。

引用在初始化后,不可以改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

int main()
{
//1、引用必须初始化
int a = 10;
int &b = a; // int &b; 是错误的,必须要初始化

//2、引用在初始化后,不可以改变
int c = 20;
b = c; // 赋值操作,而不是更改引用。把 c = 20 的数据20给了 b 指向的内存的数据,而 a、b 的指向的内存是一样的。
// 这里并不是 b 指向 c 的内存。

cout << "a = " << a << endl; //a内存中数据变了
cout << "b = " << b << endl;
cout << "c = " << c << endl;

return 0;

}
a = 20
b = 20
c = 20

引用做函数参数

① 函数传参时,可以利用引用的技术让形参修饰实参。

② 可以简化指针修改实参。

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

//1、值传递
void mySwap01(int a,int b)
{
int temp = a;
a = b;
b = temp;
}

//2、地址传递
void mySwap02(int * a, int * b)
{
int temp = *a;
*a = *b;
*b = temp;
}

//2、引用传递
//这里面的&a的实参为a(恰巧为a,恰巧一样)的别名,对&a中的a操作修改,就是对实参a修改
void mySwap03(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}

int main()
{
int a = 10;
int b = 20;

mySwap01(a, b); //值传递,形参不会修饰实参

cout << "a = " << a << endl;
cout << "b = " << b << endl;

mySwap02(&a, &b); //地址传递,形参会修饰实参

cout << "a = " << a << endl;
cout << "b = " << b << endl;

mySwap03(a, b); //引用传递,形参会修饰实参

cout << "a = " << a << endl;
cout << "b = " << b << endl;

return 0;

}
a = 10
b = 20
a = 20
b = 10
a = 10
b = 20

引用做函数返回值

① 引用是可以作为函数的返回值存在的。

② 不要返回局部变量引用。

③ 函数调用可以作为左值。

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

//引用做函数的返回值
//1、不要返回局部变量的引用
int& test01()
{
int a = 10; //局部变量存放在四区中的栈区
return a;
}

//2、函数的调用可以作为左值
int& test02()
{
static int a = 10; //加上关键字static,变成静态变量,存放在全局区,全局区上的数据在程序结束后释放掉
return a; //函数的返回值是a的一个引用
}

int main()
{
/*
int& ref = test01();
cout << "ref = " << ref << endl; //第一次结果正确,是因为编译器做了保留
cout << "ref = " << ref << endl; //第一次结果正确,是因为栈区a的内存已经释放
*/


int& ref = test02(); //由于返回的是a的引用,所以要用引用来接收,这里用ref来接收,ref为原名a的别名
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;

test02() = 1000; //对a的引用进行操作,相当于原名a赋值赋值为1000
cout << "ref = " << ref << endl; //通过原名a的别名ref访问1000
cout << "ref = " << ref << endl;

return 0;

}
ref = 10
ref = 10
ref = 10
ref = 1000
ref = 1000

① 引用的本质在C++内部实现是一个指针常量

② 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>
using namespace std;

//发现是引用,转换为 int* const ref = &a;
void func(int& ref)
{
ref = 100; //ref是引用,转换为 * ref = 100;
}

int main()
{
int a = 10;

//自动转换为 int * const ref = &a; 指针常量是指针不可改,引用不可更改别名。
//虽然指针常量指向的地址不可以更改,但是地址中的值可以更改。
int& ref = a;

ref = 20; //内部发现ref是引用,自动帮我们转换为 *ref = 20; 解引用找到相应的数据改为20

cout << "a:" << a << endl;
cout << "ref:" << ref << endl;

func(a);

return 0;

}

常量引用

① 作用:常量引用主要用来修饰形参,防止误操作。

② 在函数形参列表中,可以加const修饰形参,防止形参改变实参。

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

void showValue(const int& val)
{
// val = 1000; 报错,不能修改了
cout << "val = " << val << endl;
}

int main()
{
//常量引用
//使用场景:用来修饰形参,防止误操作

/*
int a = 10;
int& ref = 10; //报错,引用必须引一块合法的内存空间
*/

//加上const之后,编译器代码修改为 int temp = 10; const in & ref = temp
const int& ref = 10;
//ref = 20; //加入const之后变为只读,不可以修改

int a = 100;
showValue(a);
cout << "a = " << a << endl;

return 0;

}
val = 100
a = 100

面向对象三大特性

① C++面向对象的三大特性为:封装、继承、多态。

② C++认为万事万物皆为对象,对象上有其属性和行为。

③ 例如:

  1. 人可以作为对象,属性有姓名、年龄、身高、体重......行为有走、跑、跳、吃饭、唱歌....
  2. 车也可以作为对象,属性有轮胎、方向盘、车灯......行为有载人、放音乐、放空调......
  3. 具有相同新值的对象,可以抽象为类,人数人类,车属于车类。

封装属性和行为

① 封装是C++面向对象三大特性之一

② 封装的意义一:

  1. 将属性和行为作为一个整体,表现生活中的事物。
  2. 将属性和行为加以权限控制。
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
#include <iostream>
using namespace std;

const double PI = 3.14;

//class 代表设计一个类,类后面紧跟着的就是类名称
class Circle
{
public:

//属性
//半径
int m_r;

double calculateZC()
{
return 2 * PI * m_r;
}
};

int main()
{

Circle c1;
c1.m_r = 10;

cout << "圆的周长为:" << c1.calculateZC() << '\n';

return 0;

}


// 圆的周长为:62.8

封装权限

① 类在设计时,可以把属性和行为放在不同的权限下,加以控制。

② 封装的意义二:

  1. public 公共权限
  2. protected 保护权限
  3. private 私有权限
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
#include <iostream>
using namespace std;

// 访问权限
// 三种
// 公共权限 public 类内可以访问成员 类外可以访问成员
// 保护权限 protected 类内可以访问成员 类外不可以访问成员 子类可以访问父类中的保护内容
// 私有权限 private 类内可以访问权限 类外不可以访问成员 子类不可以访问父类中的私有内容

class Person
{
// 公共权限
public:
string m_Name; // 姓名

protected:
string m_Car; // 汽车

private:
int m_Password; // 银行卡密码

public:
void func()
{
m_Name = "李四";
m_Car = "奔驰"; // 保护权限内容,在类外访问不到
m_Password = 123;
}
};

int main()
{
// 实例化具体对象
Person p1;
p1.m_Name = "李四";
//p1.m_Car = "奔驰"; // 保护权限内容,在类外访问不到
//p1.m_Password = 123; // 私有权限内容,在类外访问不到

p1.func(); // 公共权限内容,在类可以访问

return 0;
}

struct 和 class 区别

① 在C++中struct和class唯一的区别就在于默认的访问权限不同。

② 区别:

  1. struct 默认权限为公共。
  2. class 默认权限为私有。
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
#include <iostream>
using namespace std;

//访问权限
//三种
//公共权限 public 类内可以访问成员 类外可以访问成员
//保护权限 protected 类内可以访问成员 类外不可以访问成员 子类可以访问父类中的保护内容
//私有权限 private 类内可以访问权限 类外不可以访问成员 子类不可以访问父类中的私有内容

class C1
{
int m_A; // 默认权限是私有
};

struct C2
{
int m_A; //默认权限 是公共
};

int main()
{
//struct 和 class 区别
//struct 默认权限是 公共 public
//class 默认权限是 私有 private
C1 c1;
c1.m_A = 100; //私有权限,不能访问

C2 c2;
c2.m_A = 100; //公共权限,可以访问

system("pause");

return 0;

}

成员属性设置为私有

① 优点1:将所有成员属性设置为私有,可以自己控制读写权限。

② 优点2:可以通过写权限,检测数据的有效性。

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
#include <iostream>
using namespace std;
#include <string>
//成员属性设置为私有
//1、可以自己控制读写权限
//2、对于写可以检测数据的有效性
class Person
{
public:
//设置姓名
void setName(string name)
{
m_Name = name;
}
//获取姓名
string getName()
{
return m_Name;
}
//获取年龄
int getAge()
{
m_Age = 0; //初始化为0岁
return m_Age;
}

void setLover(string lover)
{
m_Lover = lover;
}

private:
//姓名 可读可写
string m_Name;
//年龄 只读
int m_Age;
//情人 只写
string m_Lover;
};

int main()
{
Person p;
p.setName("张三");

cout << "姓名为:" << p.getName() << endl;

cout << "年龄为:" << p.getAge() << endl;

//p.m_Age = 18; //私有权限,不可以改
p.getAge();

p.setLover("小李");

return 0;

}
//姓名为:张三
//年龄为: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
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
#include <iostream>
using namespace std;

//点和圆关系案例

//点类
class Point
{
public:
//设置x
void setX(int x)
{
m_X = x;
}
//获取x
int getX()
{
return m_X;
}
//设置y
void setY(int y)
{
m_Y = y;
}
//获取y
int getY()
{
return m_Y;
}

private:
int m_X;
int m_Y;
};

//圆类
class Circle
{
public:
//设置半径
void setR(int r)
{
m_R = r;
}
//获取半径
int getR()
{
return m_R;
}
//设置圆心
void setCenter(Point center)
{
m_Center = center;
}
//获取圆心
Point getCenter()
{
return m_Center;
}
private:
int m_R; //半径
Point m_Center; //圆心 //在类中可以让另一个类 作为本类中的成员
};

//判断点和圆关系
void isInCircle(Circle& c, Point& p)
{
//计算两点之间距离 平方
int distance =
(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY()); //c.getCenter()返回的是圆心点center类
//可以调用圆心点center类中的方法

//计算半径的平方
int rDistance = c.getR() * c.getR();

//判断关系
if (distance == rDistance)
{
cout << "点在圆上" << endl;
}
else if (distance > rDistance)
{
cout << "点在圆外" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}

int main()
{
//创建圆
Circle c;
c.setR(10);
Point center;
center.setX(10); //设置点的横坐标
center.setY(0); //设置点的纵坐标
c.setCenter(center); //设置点类传入圆类

//创建点
Point p;
p.setX(10);
p.setY(10);

//判断关系
isInCircle(c, p);

return 0;

}

头文件

point.h

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
//这是point.h头文件

#pragma once //防止头文件重复包含,防止头文件冲突
#include <iostream> //标准输入输出
using namespace std; //标准命名空间

//只要函数声明、变量声明
//成员函数只需要声明就好了,末尾有分号。
class Point
{
public:
//设置x
void setX(int x);

//获取x
int getX();

//设置y
void setY(int y);

//获取y
int getY();

private:
int m_X;
int m_Y;
};

point.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//这是point.cpp源文件
#include "point.h"
//定义函数时,要加上作用域,"双冒号::"表示Point作用域下的函数
void Point::setX(int x)
{
m_X = x;
}
//获取x
int Point::getX()
{
return m_X;
}
//设置y
void Point::setY(int y)
{
m_Y = y;
}
//获取y
int Point::getY()
{
return m_Y;
}

circle.h头文件

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
#pragma once 
#include <iostream> //标准输出流
using namespace std; //标准命名空间
#include "Point.h" //一个类中用到另一个类,把另一个类包含的头文件包含进来

//圆类
class Circle
{
public:
//设置半径
void setR(int r);

//获取半径
int getR();

//设置圆心
void setCenter(Point center);

//获取圆心
Point getCenter();

private:
int m_R; //半径
Point m_Center; //圆心 //在类中可以让另一个类 作为本类中的成员
};

circle.cpp

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
//这是circle.cpp头文件

#include "circle.h"

//设置半径
void Circle::setR(int r)
{
m_R = r;
}

//获取半径
int Circle::getR()
{
return m_R;
}

//设置圆心
void Circle::setCenter(Point center)
{
m_Center = center;
}

//获取圆心
Point Circle::getCenter()
{
return m_Center;
}

main.cpp

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
#include <iostream>
using namespace std;
#include "circle.h" //想用点类,就要包含点类的头文件
#include "point.h" //想用圆类,就要包含点类的头文件


//判断点和圆关系
void isInCircle(Circle& c, Point& p)
{
//计算两点之间距离 平方
int distance =
(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY()); //c.getCenter()返回的是圆心点center类
//可以调用圆心点center类中的方法

//计算半径的平方
int rDistance = c.getR() * c.getR();

//判断关系
if (distance == rDistance)
{
cout << "点在圆上" << endl;
}
else if (distance > rDistance)
{
cout << "点在圆外" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}

int main()
{
//创建圆
Circle c;
c.setR(10);
Point center;
center.setX(10); //设置点的横坐标
center.setY(0); //设置点的纵坐标
c.setCenter(center); //设置点类传入圆类

//创建点
Point p;
p.setX(10);
p.setY(10);

//判断关系
isInCircle(c, p);

return 0;

}

对象的初始化和清理:

① 对象的初始化和清理是两个非常重要的安全问题。

② 一个对象或者变量没有初始状态,对其使用后果是未知。

③ 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。

④ C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

⑤ 对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。

构造函数和析构函数

构造函数和析构函数的作用

① 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

② 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

构造函数和析构函数的语法

① 构造函数语法:类名 () {}

  1. 构造函数,没有返回值也不写void。
  2. 函数名称与类名相同。
  3. 构造函数可以有参数,因此可以重载。
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次。

② 析构函数语法:~类名(){}

  1. 析构函数,,没有返回值也不写void。
  2. 函数名称与类名相同,在名称前加上符号。
  3. 析构函数不可以有参数,因此不可以发生重载。
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次。
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
#include <iostream>
using namespace std;
#include <string>

//对象的初始化和清理
//1、构造函数 进行初始化操作

class Person
{
public: //无论是构造函数还是析构函数都是在public作用域下
//1.1、构造函数
//没有返回值 不用写void
//函数名 与类名相同
//构造函数可以有参数,可以发生重载
//创建对象的时候,构造函数会自动调用,而且只调用一次
Person()
{
cout << "Person 构造函数的调用" << endl;
}

/*
如果你不写,编译器会自动创建一个,但是里面是空语句
Person()
{

}
*/

//1. 析构函数,,没有返回值也不写void。
//2. 函数名称与类名相同,在名称前加上符号。
//3. 析构函数不可以有参数,因此不可以发生重载。
//4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次。
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
};

//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现
void test01()
{
Person p; //创建对象的时候,自动调用构造函数
//这个对象p是一个局部变量,是在栈上的数据,test01执行完,释放这个对象
}

int main()
{

//方式一:
test01(); // 析构释放时机在test01运行完前,test01函数运行完后,里面的对象就被释放了


/*
方式二: //创建对象的时候,自动调用构造函数
Person p; //只有构造函数,没有析构函数,只有main函数结束完前,对象要释放掉了,才会调用析构函数
*/

return 0;
}

Person 构造函数的调用
Person 析构函数的调用

构造函数的分类及调用

① 两种分类方式:

  1. 按参数分为:有参构造和无参构造。
  2. 按类型分为:普通构造和拷贝构造。

② 三种调用方式:

  1. 括号法
  2. 显示法
  3. 隐式转换法
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
#include <iostream>
using namespace std;

//1构造函数的分类及调用
//分类
//按照参数分类:无参构造(默认构造) 和 有参构造
class Person
{
public:
//构造函数 编译器默认的构造函数是无参的
Person()
{
cout << "Person 无参构造函数的调用" << endl;
}


Person(int a)
{
age = a;
cout << "Person 有参构造函数的调用" << endl;
}

//拷贝构造函数
Person( const Person &p) //用引用的方式传进来,不能改变原来的对象的属性,所以用const
{
// 将传入的人人身上的所有属性,拷贝到我身上
cout << "Person 拷贝构造函数的调用" << endl;
age = p.age;
}

~Person()
{
cout << "Person 析构函数的调用" << endl;
}

int age;
};

//调用
void test01()
{
/*

//1、括号法
Person p1; //默认构造函数调用
Person p2(10); //有参构造函数
Person p3(p2); //拷贝构造函数

cout << "p2的年龄为:" << p2.age << endl;
cout << "p3的年龄为:" << p3.age << endl;

//注意事项1
//调用默认构造函数的时候,不要加()。
//下面这行代码,编译器会认为是一个函数的声明,像void func(),不会认为在创建对象。
//Person p1();

*/

/*
*
//2、显示法
Person p1; //创建一个对象,这个对象调用的是无参构造
Person p2 = Person(10); //有参构造 将匿名对象起了一个名称p2
Person p3 = Person(p2); //创建一个对象,这个对象调用的是拷贝构造

Person(10); //匿名对象 特点:当前行执行结束后,系统会立即回收匿名对象
cout << "aaaa" << endl; //通过打印时机可以得到:test还没结束,就运行析构函数了

//注意事项2
//不要利用拷贝构造函数 初始化匿名对象 编译器认为 Person(p3) 等价于 Person p3,
//编译器会认为这是一个对象的声明,而上面已经有一个p3了,Person p3 = Person(p2);因此编译器认为重定义了
Person(p3);

*/

//3、隐式转换法
Person p4 = 10; //相当于 写了 Person p4 = Person(10); 调用有参构造
Person p5 = p4; //调用拷贝构造

}
int main()
{
test01();

return 0;
}
Person 有参构造函数的调用
Person 拷贝构造函数的调用
Person 析构函数的调用
Person 析构函数的调用

C++中拷贝构造函数调用时机通常有三种情况。

  1. 使用一个已经创建完毕的对象来初始化一个新对象。
  2. 值传递的方式给函数参数传值。
  3. 以值方式返回局部对象。
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
#include <iostream>
using namespace std;

//拷贝构造函数调用时机

//1、使用一个已经创建完毕的对象来初始化一个新对象

//2、值传递的方式给函数参数传值

//3、值方式返回局部对象

class Person
{
public:
Person()
{
cout << "Person 默认构造函数调用" << endl;
}

Person(int age)
{
m_Age = age;
cout << "Person 有参构造函数调用" << endl;
}

Person(const Person& p)
{
m_Age = p.m_Age;
cout << "Person 拷贝构造函数调用" << endl;
}

~Person()
{
cout << "Person 析构函数调用" << endl;
}

int m_Age;

};

//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(20);
Person p2(p1);

cout << "p2的年龄为:" << p2.m_Age << endl;
}

//2、值传递的方式给函数参数传值
void doWork(Person p)
{

}

void test02()
{
Person p;
doWork(p); //实参传给形参的时候,会调用拷贝构造函数,这个是值传递,是一个临时的副本
//拷贝出去的p和原来的p 不是一个p
}

//3、值方式返回局部对象
Person doWork2() //返回值类型为Person对象
{
Person p1; //局部对象
cout << (int*)&p1 << endl;
return p1; //以值的方式返回一个拷贝的对象给外部,拷贝出一个对象p1'与原对象p1不一样,调用拷贝构造函数

//程序运行结束,释放原p1,调用析构函数
}

void test03()
{
Person p = doWork2(); //这里没有调用拷贝构造函数,直接用p接收拷贝对象p1’
cout << (int*)&p << endl;

//程序运行结束,释放拷贝的对象p1',调用析构函数
}

int main()
{
//test01();
//test02();
test03();

return 0;

}
Person 默认构造函数调用
005DF904
Person 拷贝构造函数调用
Person 析构函数调用
005DF9FC
Person 析构函数调用

构造函数调用规则

① 默认情况下,C++编译器至少给一个类添加3个函数。

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  1. 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造。
  2. 如果用户定义拷贝函数,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
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;

//构造函数的调用规则
//1、创建一个类,C+=编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数 (空实现)
//拷贝构造 (值拷贝)

class Person
{
public:
Person()
{
cout << "Person 默认构造函数调用" << endl;
}

Person(int age)
{
m_Age = age;
cout << "Person 有参构造函数调用" << endl;
}

Person(const Person & p)
{
m_Age = p.m_Age;
cout << "Person 拷贝构造函数调用" << endl;
}

~Person()
{
cout << "Person 析构函数调用" << endl;
}

int m_Age;

};

void test01()
{
Person p;
p.m_Age = 18;

Person p2(p);

cout << "p2的年龄:" << p2.m_Age << endl;
}

int main()
{
test01();

return 0;

}
Person 默认构造函数调用
Person 拷贝构造函数调用
p2的年龄:18
Person 析构函数调用
Person 析构函数调用
请按任意键继续. . .

调用默认的拷贝构造函数

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

//构造函数的调用规则
//1、创建一个类,C+=编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数 (空实现)
//拷贝构造 (值拷贝)

class Person
{
public:
Person()
{
cout << "Person 默认构造函数调用" << endl;
}

Person(int age)
{
m_Age = age;
cout << "Person 有参构造函数调用" << endl;
}

//编译器自动提高拷贝构造函数

~Person()
{
cout << "Person 析构函数调用" << endl;
}

int m_Age;

};

void test01()
{
Person p;
p.m_Age = 18;

Person p2(p); //调用编译器默认的拷贝构造函数会把p的所有属性拷贝过来

cout << "p2的年龄:" << p2.m_Age << endl;
}

int main()
{
test01();

return 0;

}
Person 默认构造函数调用
p2的年龄:18
Person 析构函数调用
Person 析构函数调用

调用定义的有参构造函数

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

//构造函数的调用规则
//1、创建一个类,C+=编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数 (空实现)
//拷贝构造 (值拷贝)

class Person
{
public:

Person(int age)
{
m_Age = age;
cout << "Person 有参构造函数调用" << endl;
}

//编译器自动提高拷贝构造函数

~Person()
{
cout << "Person 析构函数调用" << endl;
}

int m_Age;

};


void test02()
{
Person p; //如果写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造构造
//由于没有默认构造函数,所以报错

Person p2(p);
}

int main()
{
test02();


return 0;

}5.5.5 调用定义的拷贝构造函数#include <iostream>
using namespace std;

//构造函数的调用规则
//1、创建一个类,C+=编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数 (空实现)
//拷贝构造 (值拷贝)

class Person
{
public:
//如果写了拷贝构造函数,编译器就不再提供其他普通构造函数
Person(const Person& p)
{
m_Age = p.m_Age;
cout << "Person 拷贝构造函数调用" << endl;
}

~Person()
{
cout << "Person 析构函数调用" << endl;
}

int m_Age;

};

void test01()
{
Person p; //没有默认构造函数,报错
Person(10); //没有有参构造函数,报错

Person p2(p);

cout << "p2的年龄:" << p2.m_Age << endl;
}

int main()
{
test01();
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
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 Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height = new int(height); //把数据创建在堆区,用指针接收new创建的地址
cout << "Person的有参构造函数调用" << endl;
}

//自己实现拷贝函数 解决浅拷贝带来的问题
Person(const Person& p)
{
cout << "Person 拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height; 编译器默认实现就是这行代码,默认执行的是浅拷贝
//浅拷贝带来的问题就是堆区的内存重复释放

// 深拷贝操作,在堆区自己创建一份内存
m_Height = new int(*p.m_Height);
}

~Person()
{
//析构代码,将堆区开辟数据做释放操作
cout << "Person的析构函数调用" << endl;
if (m_Height != NULL)
{
delete m_Height; //释放堆区数据
//一定要手动释放,由程序员自己管理

}
}
int m_Age;
int * m_Height;
};

void test01()
{
Person p1(18,160);

cout << "p1的年龄为:" << p1.m_Age << "身高为:" << * p1.m_Height << endl; //指针通过解引用获得数据

Person p2(p1);

cout << "p2的年龄为:" << p2.m_Age << "身高为:" << * p2.m_Height << endl;

}

int main()
{
test01();

system("pause");

return 0;

}
Person的有参构造函数调用
p1的年龄为:18身高为:160
Person 拷贝构造函数调用
p2的年龄为:18身高为:160
Person的析构函数调用
Person的析构函数调用

初始化列表

① C++提供了初始化列表语法,用来初始化属性。

② 语法:构造函数(): 属性1(值1),属性2(值2),...,()

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

class Person
{
public:
//传统初始化操作
Person(int a, int b, int c)
{
m_A = a;
m_B = b;
m_C = c;
}
int m_A;
int m_B;
int m_C;
};

void test01()
{

Person p(10, 20, 30);
cout << "m_A:" << p.m_A << endl;
cout << "m_B:" << p.m_B << endl;
cout << "m_C:" << p.m_C << endl;
}

int main()
{
test01();


return 0;

}

m_A:10
m_B:20
m_C:30

灵活初始化操作

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

class Person
{
public:

/*
构造函数型的初始化操作
固定初始化10、30、40
Person():m_A(10),m_B(30),m_C(40)
{

}
int m_A;
int m_B;
int m_C;
*/

//可以灵活的初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c)
{

}
int m_A;
int m_B;
int m_C;
};

void test01()
{

Person p(30, 20, 10);
cout << "m_A:" << p.m_A << endl;
cout << "m_B:" << p.m_B << endl;
cout << "m_C:" << p.m_C << endl;
}

int main()
{
test01();

return 0;

}
m_A:30
m_B:20
m_C:10

类对象作为类成员

① C++类中的属性、方法称为成员。

② C++类中的成员可以是另一个类的对象,称该成员为对象成员。

③ B类中有对象A作为成员,A为对象成员,那么当创建B对象时,A与B的构造和析构的顺序是:

  1. 当其他类对象作为本类成员,构造时候先构造其他类对象,在构造自身。
  2. 当其他类对象作为本类成员,析构的顺序与构造相反,想析构自身,再析构其他类对象。
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
#include <iostream>
using namespace std;

//手机类
class Phone
{
public:
Phone(string pName)
{
cout << "Phone的构造函数调用" << endl;
m_PName = pName;
}

~Phone()
{
cout << "Phone的析构代码函数调用" << endl;
}

string m_PName;
};

//人类
class Person
{
public:
//m_Phone(pName) 中m_Phone为phone对象,此语句类似于隐式转换法 Phone m_Phone = pName
Person(string name, string pName) :m_Name(name), m_Phone(pName) //掉用的是灵活初始化列表
{
cout << "Person的构造函数调用" << endl;
}

~Person()
{
cout << "Person的析构代码函数调用" << endl;
}

//姓名
string m_Name;
//手机
Phone m_Phone;
};

//当其他类对象作为本类成员,构造时候先构造其他类对象,在构造自身。
//当其他类对象作为本类成员,析构的顺序与构造相反,想析构自身,再析构其他类对象
void test01()
{

Person p("张三", "苹果MAX");
cout << p.m_Name << "m_A:" << p.m_Phone.m_PName << endl;
}

int main()
{
test01();

system("pause");

return 0;

}
Phone的构造函数调用
Person的构造函数调用
张三m_A:苹果MAX
Person的析构代码函数调用
Phone的析构代码函数调用
//先构造其他类,再构造自己,先析构自己,再析构其他

静态成员

① 静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。

② 静态成员分为:

  1. 静态成员变量

--所有对象共享同一份数据 --在编译阶段分配内存 --类内声明,类外初始化

  1. 静态成员函数

--所有对象共享同一个函数 --静态成员函数只能访问静态成员变量

③ 调用静态成员函数有两种方法:

  1. 通过对象调用
  2. 通过类名调用

成员变量

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

//静态成员变量
class Person
{
public:
//1、所有对象都共享同一份数据
//2、编译阶段就分配内存
//3、类内声明,类外初始化操作
static int m_A;

//静态成员变量也是有访问权限的
private:
static int m_B;
};

int Person::m_A = 100;

void test01()
{
Person p;
cout << p.m_A << endl;

Person p2;
p2.m_A = 200;

//100 ? 200,共享同一份数据,所以p.m_A为200
cout << p.m_A << endl;
}

void test02()
{
//静态成员变量 不属于某个对象上,所有对象都共享同一份数据
//因此静态成员变量有两种访问方式

//1、通过对象进行访问
Person p;
cout << p.m_A << endl;

//2、通过类名进行访问
cout << Person::m_A << endl;

//cout << Person::m_B << endl; //报错,私有作用域,出了类是不可以访问的
}

int main()
{
test01();

test02();
}
100
200
200
200

静态成员函数

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

//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量
class Person
{
public:
//静态成员函数
static void func()
{
m_A = 100; //静态成员函数可以访问静态成员变量,这个数据是共享的,只有一份,所以不需要区分哪个对象的。
//m_B = 200; //静态成员函数不可以访问非静态成员变量,无法区分到底是哪个对象的m_B属性,非静态成员变量属于特定的对象上面
cout << "static void func调用" << endl;
}

static int m_A; //静态成员变量
int m_B; //非静态成员变量

//静态成员函数也是有访问权限的
private:
static void func2()
{
cout << "static void func2调用" << endl;
}
};

int Person::m_A = 0;

//有两种访问方式
void test01()
{
//1、通过对象访问
Person p;
p.func();

//2、通过类名访问
Person::func(); //静态成员函数,所有对象共享同一个函数,可以直接通过类名访问。

//Person::func2(); //类外访问不到私有静态成员函数
}

int main()
{
test01();

return 0;

}
static void func调用
static void func调用

成员变量和成员函数分开存储

在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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
using namespace std;

class Person01
{
public:

};

class Person02
{
public:
int m_A; //非静态成员变量 属于类的对象上

static int m_B; //静态成员变量 不属于类对象上

//void fun(){} //非静态成员函数 不属于类对象上

static void func2() {} //静态成员函数 不属于类的对象上
};

int Person02::m_B = 0;
//初始化

void test01()
{
Person01 p;

//空对象占用内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存空间
cout << "size of p = " << sizeof(p) << endl;
}

void test02()
{
Person02 p2;

cout << "size of p2 = " << sizeof(p2) << endl; //通过打印内存空间大小,检测静态成员变量、非静态成员函数等在不在对象内存上....
}

int main()
{
test01();
test02();

return 0;

}
size of p = 1
size of p2 = 4

this指针概念

① 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码。

② C++通过提供特殊的对象指针,this指针指向被调用的成员函数所属的对象。

③ this指针是隐含每一个非静态成员函数内的一种指针。

④ this指针不需要定义,直接使用即可。

⑤ this指针的用途:

  1. 当形参和成员变量同名时,可用this指针来区分。
  2. 在类的非静态成员函数中返回对象本身,可使用return * this。
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
#include <iostream>
using namespace std;

class Person
{
public:
Person(int age)
{
//this指针指向的是被调用的成员函数所属的对象
this->age = age;
//当下面实例化对象p1在调用,this就指向p1

//用this指针的时候,可以该变量与形参命名相同,但是编译器会认为两个不同

//如果这里是 age = age;那么编译器会将这两个age和上面的形参age当做同一个age,因此age并没有赋值
//因此体现出第一个用途,就是可以用来区分形参和成员变量同名
}

//如果用值的方式返回,Person PersonAddAge(Person& p){},它返回的是本体拷贝的对象p',而不是本体p
Person& PersonAddAge(Person& p) //要返回本体的时候,要用引用的方式返回
{
this->age += p.age; //this->age为调用对象的age

//this指向p2的指针,而*this指向的就是p2这个对象本体
return *this;
//第二个用途,用来返回本体,需要记住
}
int age;
};

//1、解决名称冲突
void test01()
{
Person p1(18);
cout << "p1的年龄为:" << p1.age << endl;
}

//2、返回对象本身用*this
void test02()
{
Person p1(10);

Person p2(10);

//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);

cout << "p2的年龄为:" << p2.age << endl;
}


int main()
{
test01();
test02();

return 0;
}
p1的年龄为:18
p2的年龄为:40

空指针访问成员函数

① C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针。

② 如果用到this指针,需要加以判断保证代码的健壮性。

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

class Person
{
public:
void showClassName()
{
cout << "this is Person class" << endl;
}

/*
void showPersonAge()
{
//报错原因是传入的指针是为NULL
cout << "age= " << m_Age << endl; //默认m_Age是this->m_Age
}
*/

void showPersonAge()
{
if (this == NULL)
{
return; //为空的时候直接退出
}
cout << "age= " << this->m_Age << endl;
}

int m_Age;
};

void test01()
{
Person* p = NULL;

p->showClassName();

p->showPersonAge();
}


int main()
{
test01();

return 0;

}
this is Person class

就是说注意当传入的是空指针的时候要注意判断一下this究竟是不是空

const修饰成员函数

① 常函数:

  1. 成员函数后加const后我们称这个函数为常函数。
  2. 常函数内不可以修改成员属性。
  3. 成员属性声明时加关键字mutable后,在常函数中依然可以修改。

② 常对象:

  1. 声明对象前加const称该对象为常对象。
  2. 常对象只能调用常函数。
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
#include <iostream>
using namespace std;

class Person
{
public:
//this指针的本质 是指针常量 指针的指向是不可以修改的,即Person * const this
//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改,即void showPerson() const 使得 const Person * const this
void showPerson() const //当加了一个const
{
//m_A = 100; //相当于 this->m_A;,由于加了一个const,所以指针指向的值不可以更改
//this = NULL; //this指针不可以修改指针的指向的
this->m_B = 100; //加了mutable就可以修改this指向的值了
//使得在常函数中能够修改这个值
}

void func()
{
m_A = 100;
}

int m_A;
mutable int m_B; //特殊变量,即使在常函数中,也可以修改这个值,加上关键字mutable
};

void test01()
{
Person p;
p.showPerson();

}

//常对象
void test02()
{
const Person p; //在对象前加const,变为常对象
//p.m_A = 100; //常对象不可以修改普通变量
p.m_B = 100; //m_B是特殊值,在常对象下也可以修改

//常对象只能调用常函数
p.showPerson();
//p.func(); //常对象 不可以调用普通成员函数,因为普通成员函数可以修改属性
}

int main()
{
test01();

return 0;

}

友元

  1. 在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。

  2. 友元的目的就是让一个函数或者类访问另一个类中私有成员。

  3. 友元的关键字为 friend。

  4. 友元的三种实现:

  1. 全局函数做友元。
  2. 类做友元。
  3. 成员函数做友元。
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 Buiding
{
//goodfriend全局函数是Buiding类好朋友,可以访问Buiding中私有成员
friend void goodfriend(Buiding* buiding);

public:
Buiding() //构造函数 赋初值
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}

public:
string m_SittingRoom; //客厅

private:
string m_BedRoom;
};

//全局函数
void goodfriend(Buiding *buiding)
{
cout << "好基友全局函数 正在访问:" << buiding->m_SittingRoom << endl;
cout << "好基友全局函数 正在访问:" << buiding->m_BedRoom << endl;
}

void test01()
{
Buiding building;
goodfriend(&building);
}

int main()
{
test01();

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

//类做友元
class Building; //声明

class GoodGay
{
public:
GoodGay();

void visit(); //参观函数 访问Building中的属性

Building* building;
};

class Building
{
//GoodGay类是本类的好朋友,可以访问本类的私有成员
friend class GoodGay;

public:
Building();

public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};

//类外写成员函数,写类名的作用域

//Building构造函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}

//GoodGay构造函数
GoodGay::GoodGay()
{
//创建建筑物对象
building = new Building; //这里会调用Building的构造函数
//new是创建什么样的数据类型,就返回什么样的数据类型的指针
}

void GoodGay::visit()
{
cout << "好基友类正在访问:" << building->m_SittingRoom << endl;

cout << "好基友类正在访问:" << building->m_BedRoom << endl;

}

void test01()
{
GoodGay gg; //创建一个GoodGay的对象,会调用GoodGay构造函数,会创建一个building,然后调用building构造函数
gg.visit();
}

int main()
{
test01();

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

class Building;
class GoodGay
{
public:
GoodGay();

void visit(); //让visit函数可以访问Building中私有成员
void visit2(); //让visit2函数不可以访问Buildinng中私有成员

Building* building;
};

class Building
{
//告诉编译器,GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
friend void GoodGay::visit();

public:
Building();

public:
string m_SittingRoom; //客厅

private:
string m_BedRoom; //卧室
};

// 类外实现构造函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}

GoodGay::GoodGay()
{
building = new Building;
}

// 类外实现成员函数
void GoodGay::visit()
{
cout << "visit 函数正在访问" << building->m_SittingRoom << endl;
cout << "visit 函数正在访问" << building->m_BedRoom << endl;

}

void GoodGay::visit2()
{
cout << "visit2 函数正在访问" << building->m_SittingRoom << endl;
//cout << "visit 函数正在访问" << building->m_BedRoom << endl;

}

void test01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}

int main()
{
test01();
return 0;

}
输出如下:
visit 函数正在访问客厅
visit 函数正在访问卧室
visit2 函数正在访问客厅

运算符重载简介

① 运算符重载:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

② 对于内置的数据类型的表达式的运算符是不可能改变的。

加号运算符重载

① 加号运算符作用:实现两个自定义数据类型相加的运算。

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
#include <iostream>
using namespace std;
//加号运算符重载
class Person
{
public:

//1、成员函数重载+号
Person operator+(Person& p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};

/*
//全局函数重载+号
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
*/

//函数重载的版本
Person operator+(Person& p1, int num)
{
Person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}

void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;

//成员函数重载本质调用
//Person p3 = p1.operator+(p2);

//全局函数重载本质调用
//Person p3 = operator+(p1,p2);

Person p3 = p1 + p2; //重载本质被简化后的形式

//运算符重载,也可以发生函数重载

Person p4 = p1 + 10; //Person + int

cout << "p3.m_A:" << p3.m_A << endl;
cout << "p3.m_B:" << p3.m_B << endl;

cout << "p4.m_A:" << p4.m_A << endl;
cout << "p4.m_B:" << p4.m_B << endl;

}


int main()
{
test01();

return 0;

}
p3.m_A:20
p3.m_B:20
p4.m_A:20
p4.m_B:20

左移运算符重载

① 左移运算符重载:可以输出自定义数据类型。

② 重载左移运算符配合友元可以实现自定义数据类型。

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

//左移运算符
class Person
{

friend ostream& operator<<(ostream& out, Person& p);
//全局函数作为友元,以方便重载

public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
/*
//利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本 p << cout
//成员函数不能利用重载<<运算符,因为无法实现cout在左侧
//当不知道返回值是什么类型的时候,可以先写个void,以后再改
void operator<<(Person& p)
{

}
*/

private:
int m_A;
int m_B;
};
//只能利用全局函数重载左移运算符
//如果返回类型为void,那么就无法无限追加,也没有办法在后面添加换行符
ostream & operator<<(ostream &cout, Person &p) //本质 operator << (cout , p) , 简化 cout << p
//cout是别名,这里可以取out、kn...
//cout为ostream输出流数据类型
{
cout << "m_A= " << p.m_A << " m_B=" << p.m_B;
return cout;
}

void test01()
{
Person p(10,10);

cout << p << " hello world" << endl;
}


int main()
{
test01();

return 0;

}
m_A= 10 m_B=10 hello world

递增运算符

① 递增运算符重载:通过重载递增运算符,实现自己的整型数据。

② 前置递增返回的是引用,后置递增返回的是值。

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

//重载递增运算符

class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);

public:
MyInteger()
{
m_Num = 0;
}

//重载前置++运算符,返回引用是为了一直对一个数据进行递增操作,而返回值并不是一直对一个数据进行递增操作
MyInteger& operator++()
{
//先进行++运算
m_Num++;

//再将自身做一个返回
return *this; //把自身做一个返回
}

//重载后置++运算符 int代表占位参数,可以用于区分前置和后置递增
//后置递增不能返回引用,因为temp是局部变量,如果返回temp,当程序运行完后变量就释放了,再调用temp就是非法操作了
MyInteger operator++(int)
{
//先记录当时结果
MyInteger temp = *this;
//后 递增
m_Num++;
//最后将记录结果做返回
return temp;
}
private:
int m_Num;
};

//只能利用全局函数重载左移运算符
ostream & operator<<(ostream &cout, MyInteger myint) //本质 operator << (cout , p) , 简化 cout << p
//cout是别名,这里可以取out、kn...
//cout为ostream输出流数据类型
{
cout << myint.m_Num;

return cout;
}

void test01()
{
MyInteger myint;

cout << ++(++myint) << endl;
cout << myint << endl;
}

void test02()
{
MyInteger myint;

cout << myint++ << endl;
cout << myint << endl;

}

int main()
{
test01();
test02();

/*
int a = 0;
cout << ++(++a) << endl; //运行结果为2
cout << a << endl; //运行结果为2,表示一直对一个数据进行递增
*/

system("pause");

return 0;

}

赋值运算符

① C++编译器至少给一个类添加4个函数:

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符operator=,对属性进行值拷贝
  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
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
#include <iostream>
using namespace std;

//重载赋值运算符

class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}

~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}

//重载 赋值运算符
//如果返回的是值,而不是引用,是创建一个拷贝函数,返回的是一个副本,而不是自身
Person& operator=(Person& p)
{
//编译器默认是提供浅拷贝
//m_Age = p.m_Age;

//浅拷贝带来的问题是,当创建数据在堆区时,析构代码导致内存重复释放,报错

//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}

//深拷贝
m_Age = new int(*p.m_Age);

//返回对象本身
return *this;
}

int *m_Age;
};

void test01()
{
Person p1(18);

Person p2(20);

Person p3(23);

p3 = p2 = p1; //赋值操作

cout << "p1的年龄为:" << *p1.m_Age << endl;

cout << "p2的年龄为:" << *p2.m_Age << endl;

cout << "p3的年龄为:" << *p3.m_Age << endl;


}

int main()
{
test01();

/*

int a = 10;
int b = 20;
int c = 30;

c = b = a;

cout << "a= " << a << endl;
cout << "b= " << b << endl;
cout << "c= " << c << endl;
*/
return 0;

}
p1的年龄为:18
p2的年龄为:18
p3的年龄为:18

关系重载运算符

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

//重载关系运算符

class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}

//重载 == 号
bool operator==(Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}

bool operator!=(Person& p)
{
if (this->m_Name != p.m_Name && this->m_Age != p.m_Age)
{
return true;
}
return false;
}

string m_Name;
int m_Age;
};

void test01()
{
Person p1("Tom", 17);

Person p2("Jerry", 18);

if (p1 == p2)
{
cout << "p1和p2是相等的!" << endl;
}
else
{
cout << "p1和p2是不相等的!" << endl;

}

if (p1 != p2)
{
cout << "p1和p2是不相等的!" << endl;
}
else
{
cout << "p1和p2是相等的!" << endl;

}


}

int main()
{
test01();

return 0;

}
p1和p2是不相等的!
p1和p2是不相等的!

函数调用运算符重载

① 函数调用运算符()也可以重载。

② 由于重载后使用的方式非常像函数的调用,因此称为仿函数。

③ 仿函数没有固定写法,非常灵活。

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

//函数调用运算符重载

//打印输出类
class MyPrint
{
public:

//重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}

};

//正常函数
void MyPrint02(string test)
{
cout << test << endl;
}

void test01()
{
MyPrint myPrint;

myPrint("hello world");

}

//仿函数非常灵活,没有固定的写法
//加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};

void test02()
{
MyAdd myadd;
int ret = myadd(100,100);
cout << "ret = " << ret << endl;

//匿名函数对象
cout << MyAdd()(100, 100) << endl;

// MyAdd()为创建一个匿名对象,匿名对象的特点为当前行执行完立即释放
}

int main()
{
test01();

MyPrint02("hello world"); //由于使用起来非常类似于函数调用,因此称为仿函数

test02();

return 0;

}
hello world
hello world
ret = 200
200

继承

① 继承是面向对象的三大特性之一。

② 定义类时,下级别的成员除了拥有上一级的共性,还有自己的特性。这个时候,就可以考虑利用继承技术,减少重复代码。

普通实现

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
#include <iostream>
using namespace std;
#include<string>
//打印输出类
class Java
{
public:
void header()
{
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++....(公共分类列表)" << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};

class Python
{
public:
void header()
{
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++....(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};

class CPP
{
public:
void header()
{
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++....(公共分类列表)" << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};

void test01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();

cout << "........................" << endl;
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();

cout << "........................" << endl;
cout << "C++下载视频页面如下:" << endl;
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}

int main()
{
test01();

return 0;

}
Java下载视频页面如下:
首页、公开课、登陆、注册...(公共头部)
帮助中心、交流合作、站内地图...(公共底部)
Java、Python、C++....(公共分类列表)
Java学科视频
Python下载视频页面如下:
首页、公开课、登陆、注册...(公共头部)
帮助中心、交流合作、站内地图...(公共底部)
Java、Python、C++....(公共分类列表)
Python学科视频
C++下载视频页面如下:
首页、公开课、登陆、注册...(公共头部)
帮助中心、交流合作、站内地图...(公共底部)
Java、Python、C++....(公共分类列表)
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
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
#include <iostream>
using namespace std;
#include<string>

//打印输出类
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++....(公共分类列表)" << endl;
}
};

// 继承的好处:减少重复代码
// 语法:class 子类:继承方式 父类
// 子类 也称为 派生类
// 父类 也称为 基类

//Java页面
class Java:public BasePage //继承了BasePage,把BasePage里面的内容全部拿到手了
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};

//Python页面
class Python :public BasePage //继承了BasePage,把BasePage里面的内容全部拿到手了
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};

//C++页面
class CPP :public BasePage //继承了BasePage,把BasePage里面的内容全部拿到手了
{
public:
void content()
{
cout << "CPP学科视频" << endl;
}
};

void test01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();

cout << "........................" << endl;
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();

cout << "........................" << endl;
cout << "C++下载视频页面如下:" << endl;
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}

int main()
{
test01();
return 0;

}
运行结果一致

三种继承改变权限

① 继承的语法:class 子类:继承方式 父类

② 继承方式一共有三种:

  1. 公共继承
  2. 保护继承
  3. 私有继承

③ 不同的继承方式,父类中的变量被继承后,权限相应的得到了改变。

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

//打印输出类
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};

//公共继承
class Son1:public Base1
{
public:
void func()
{
m_A = 10; //父类中的公共权限成员 到子类中依然是公共权限
m_B = 10; //父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10; //父类中的私有权限成员 子类访问不到

}
};

void test01()
{
Son1 s1;
s1.m_A = 100; //公共权限,类内能访问,类外也能访问
//s1.m_B = 100; //保护权限,类内能访问,类外不能访问
}

//保护继承
class Son2:protected Base1
{
public:
void func()
{
m_A = 10; //父类中的公共权限成员 到子类中变为保护权限
m_B = 10; //父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10; //父类中的私有权限成员 子类访问不到

}
};

void test02()
{
Son2 s2;
//s2.m_A = 100; //保护权限,类内能访问,类外不能访问
//s2.m_B = 100; //保护权限,类内能访问,类外不能访问
}

//私有继承
class Son3:private Base1
{
public:
void func()
{
m_A = 10; //父类中的公共权限成员 到子类中变为私有权限
m_B = 10; //父类中的保护权限成员 到子类中变为私有权限
//m_C = 10; //父类中的私有权限成员 子类访问不到

}
};

void test03()
{
Son3 s3;
//s3.m_A = 100; //私有权限,类内能访问,类外不能访问
//s3.m_B = 100; //私有权限,类内能访问,类外不能访问
}

int main()
{

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
using namespace std;
#include<string>

//继承中的对象模型

class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};


//公共继承
class Son:public Base
{
int m_D;
};

//利用开发人员命令提示工具查看对象模型
//跳转盘符→F:
//跳转文件路径→cd 具体路径下
//查看命令
//cl /d1 reportSingleClassLayout查看的类名 "文件名"

void test01()
{
//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
cout << "size of Son =" << sizeof(Son) << endl; //16个字节,父类3个int一个12个字节,字节1个int4个字节
}

int main()
{
test01();

return 0;

}
size of Son =16

继承构造和析构顺序

① 继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造顺序相反,先调用子类析构函数,再调用父类析构函数。

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

//继承中的构造和析构顺序

class Base
{
public:
Base()
{
cout << "Base构造函数!" << endl;
}

~Base()
{
cout << "Base析构函数!" << endl;
}
};


//
class Son:public Base
{
public:
Son()
{
cout << "Son构造函数!" << endl;
}

~Son()
{
cout << "Son析构函数!" << endl;
}
};


void test01()
{
//Base b; //创建父类对象只有父类的构造函数、析构函数

//继承中的构造和析构顺序如下:
//先构造父类、再构造子类,析构的顺序与构造的顺序相反
Son s;
}


int main()
{
test01();

return 0;

}
Base构造函数!
Son构造函数!
Son析构函数!
Base析构函数!

同名成员处理

① 子类对象可以直接访问到子类中同名成员。

② 子类对象加作用域可以访问到父类同名成员。

③ 当子类与父类拥有同名的成员函数,子类会隐藏父类中所有同名成员函数(有参、无参),加作用域才可以访问到父类中同名函数。

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


//继承中同名成员处理

class Base
{
public:
Base()
{
m_A = 100;
}
int m_A;

void func()
{
cout << "Base - func()调用" << endl;
}

void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
};

class Son:public Base
{
public:
Son()
{
m_A = 200;
}

void func()
{
cout << "Son - func()调用" << endl;
}

int m_A;
};

//同名成员属性处理方式
void test01()
{
Son s;
cout << "Son 下 m_A=" << s.m_A << endl;
//如果通过子类对象访问到父类中同名成员,需要加作用域
cout << "Base 下 m_A=" << s.Base::m_A << endl;

}

//同名成员函数处理方式
void test02()
{
Son s;
s.func(); //直接调用 调用时子类中的同名成员

//调用父类中同名成员函数
s.Base::func();

//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
s.Base::func(100);
}

//同名成员函数处理
int main()
{
test01();
test02();

system("pause");

return 0;

}
Son 下 m_A=200
Base 下 m_A=100
Son - func()调用
Base - func()调用
Base - func(int a)调用
请按任意键继续. . .

同名静态成员处理

① 静态成员和非静态成员出现同名,处理方式一致:

  1. 访问子类同名成员,直接访问
  2. 访问父类同名成员,需要加作用域

② 加上static关键字后,成员发生变化,成员变成静态成员。

③ 静态成员变量特点:

  1. 所有对象都共享同一份数据。
  2. 编译阶段就分配内存。
  3. 类内声明,类外初始化。

④ 静态成员函数特点:

  1. 只能访问静态成员变量,不能访问非静态成员变量。
  2. 所有对象都共享同一份函数实例。
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
#include <iostream>
using namespace std;

//继承中同名静态成员处理方式
class Base
{
public:

static int m_A; //静态成员类内声明,类外初始化

static void func()
{
cout << "Base - static func()" << endl;
}

static void func(int a)
{
cout << "Base - static func(int a)" << endl;
}
};

int Base::m_A=100;

class Son:public Base
{
public:

static int m_A;

static void func()
{
cout << "Son - static void func()" << endl;
}
};

int Son::m_A = 200;

//同名静态成员属性
void test01()
{
//1、通过对象访问
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;

//2、通过类名访问
cout << "Son 下 m_A = "<< Son::m_A << endl;
//第一个::代表通过类名方式访问 第二个::代表访问父类作用域
cout << "Base 下 m_A= "<< Son::Base::m_A << endl;
}

void test02()
{
//1、通过对象访问
Son s;
s.func();
s.Base::func();

//2、通过类名访问
Son::func();
Son::Base::func();

//子类出现和父类同名静态成员函数,也会隐藏掉父类中所有同名成员函数
//如何想访问父类中被隐藏同名成员,需要加作用域
Son::Base::func(100);
}

//同名成员函数处理
int main()
{
test01();
test02();

return 0;

}
Son 下 m_A = 200
Base 下 m_A = 100
Son 下 m_A = 200
Base 下 m_A= 100
Son - static void func()
Base - static func()
Son - static void func()
Base - static func()
Base - static func(int a)

多继承语法

① C++运行一个类继承多个类。

② 语法:class 子类:继承方式 父类1,继承方式 父类2,.....

③ 多继承可能会引发父类中有同名成员出现,需要加作用域区分。

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

class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};

//子类 需要继承Base1和Base2
//语法:class 子类:继承方式 父类1,继承方式 父类2,.....
class Son:public Base1,public Base2
{
public:

Son()
{
m_C = 300;
m_D = 400;
}

int m_C;
int m_D;
};

void test01()
{
Son s;

cout << "sizeof(Son):" << sizeof(s) << endl;
//当父类中出现同名成员,需要加作用域区分
cout << "Base1::m_A = " << s.Base1::m_A << endl;
cout << "Base2::m_A = " << s.Base2::m_A << endl;

}

int main()
{
test01();

return 0;

}
sizeof(Son):16
Base1::m_A = 100
Base2::m_A = 200

菱形继承简介

① 菱形继承概念:

  1. 两个派生类继承同一个基类
  2. 又有某个类同时继承两个派生类
  3. 这种继承被称为菱形继承

② 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时候就会产生二义性。

③ 草泥马继承自动物的数据继承了两份,其实我们应当清楚,这份数据我们只需要一份就可以。

④ 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。

⑤ 利用虚继承可以解决菱形继承问题。

菱形继承普通方式

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 Animal
{
public:
int m_Age;
};

//羊类
class Sheep:public Animal{};

//驼类
class Tuo:public Animal{};

//羊驼类
class SheepTuo:public Sheep,public Tuo{};

void test01()
{
SheepTuo st;

st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;

//当出现菱形继承,两个父类拥有相同数据,需要加以作用域区分
cout << "st.Sheep::m_Age="<< st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age=" << st.Tuo::m_Age << endl;

//这份数据我们知道 只有一份就可以,菱形继承导致数据有两份,资源浪费
}

int main()
{
test01();

return 0;
}
st.Sheep::m_Age=18
st.Tuo::m_Age=28

菱形继承虚继承方式

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
#include <iostream>
using namespace std;
//动物类
class Animal
{
public:
int m_Age;
};

//利用虚继承 解决菱形继承的问题
//继承之前 加上关键字 virtual 变成 虚继承
//虚继承后,Animal类 称为 虚基类

//羊类
class Sheep:virtual public Animal{};

//驼类
class Tuo:virtual public Animal{};

//羊驼类
class SheepTuo:public Sheep,public Tuo{};

void test01()
{
SheepTuo st;

st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;

//当出现菱形继承,两个父类拥有相同数据,需要加以作用域区分
cout << "st.Sheep::m_Age="<< st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age=" << st.Tuo::m_Age << endl;

//虚继承,多产生一种输出方式(因为上面两个是同一份数据,所以可以不加作用域来区分了)
cout << "st.m_Age=" << st.m_Age << endl;
}

int main()
{
test01();

return 0;
}
st.Sheep::m_Age=28
st.Tuo::m_Age=28
st.m_Age=28

多态简介

① 多态是C++面向对象三大特性之一。

② 多态分为两类:

  1. 静态多态:函数重载和运算符重载属于静态多态,复用函数名。
  2. 动态多态:派生类和虚函数实现运行时多态。

③ 静态多态和动态多态区别:

  1. 静态多态的函数地址早绑定,编译阶段确定函数地址。
  2. 动态多态的函数地址晚绑定,运行阶段确定函数地址。

④ 多态满足条件:

  1. 有继承关系
  2. 子类重写父类中的虚函数

④ 多态使用条件:

  1. 父类指针或引用指向子类对象

⑤ 重写:函数返回值类型、函数名、参数列表都完全一致称为重写。

多态地址绑定

地址早绑定

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

//多态

//动物类
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};

//猫类
class Cat:public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};

//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Animal &animal) // Animal & animal = cat
{
animal.speak();
}

void test01()
{
Cat cat;
doSpeak(cat);
}

int main()
{
test01();

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
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
#include <iostream>
using namespace std;
//多态
//动物类
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};

//猫类
class Cat:public Animal
{
public:
//重写 函数返回值类型、函数名、参数列表都完全相同才叫重写
void speak() //子类virtual可写可不写,也可以写 virtual void speak()
{
cout << "小猫在说话" << endl;
}
};

//狗类
class Dog:public Animal
{
public:
virtual void speak()
{
cout << "小狗在说话" << endl;
}
};

//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定

//动态多态满足条件
//1、有继承关系
//2、子类重写父类的虚函数

//动态多态使用
//父类的引用或指针指向子类对象

void doSpeak(Animal &animal) // Animal & animal = cat
{
animal.speak();
}

void test01()
{
Cat cat;
doSpeak(cat);

Dog dog;
doSpeak(dog);
}

int main()
{
test01();

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

//多态

//动物类
class Animal
{
public: //如果是虚函数,那么类里面存了一个指针,类占4个字节
virtual void speak() //如果是非静态成员函数void speak(),那么函数不在类上,空类占1个字节空间
{
cout << "动物在说话" << endl;
}
};

//猫类
class Cat:public Animal
{
public:

void speak()
{
cout << "小猫在说话" << endl;
}
};

//狗类
class Dog:public Animal
{
public:
virtual void speak()
{
cout << "小狗在说话" << endl;
}
};

//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定

//动态多态满足条件
//1、有继承关系
//2、子类重写父类的虚函数

//动态多态使用
//父类的引用或指针指向子类对象

void doSpeak(Animal &animal) // Animal & animal = cat
{
animal.speak();
}

void test01()
{
Cat cat;
doSpeak(cat);

Dog dog;
doSpeak(dog);
}

void test02()
{
cout << "sizeof Animal = " << sizeof(Animal) << endl;
}

int main()
{
//test01();
test02();

return 0;

}
sizeof Animal = 4

纯虚函数和抽象类

① 在多态中,通常父类中虚函数的实现时毫无意义的,主要都是调用子类重写的内容。因此,可以将虚函数改为纯虚函数。

② 纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;

③ 当类中有了纯虚函数,这个类也称为抽象类。

④ 抽象类特点:

  1. 无法实例化对象
  2. 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
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
#include <iostream>
using namespace std;

//纯虚函数和抽象类
class Base
{
public:
//纯虚函数
//只要有一个纯虚函数,这个类称为抽象类
//抽象类特点:
//1、无法实例化对象
//2、抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};

class Son : public Base
{
public:
virtual void func()
{
cout << "func函数调用" << endl;
}
};
void test01()
{
//Base b; //抽象类是无法实例化对象
//new Base; //抽象类是无法实例化对象

//Son s; //子类中必须重写父类中的纯虚函数,否则无法实例化对象

Base* base = new Son;
base->func();
}

int main()
{
test01();

return 0;

}
func函数调用

虚析构和纯虚析构

① 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。

② 解决方式:将父类中的析构函数改为虚析构或者纯虚析构。

③ 虚析构和纯虚析构共性:

  1. 可以解决父类指针释放子类对象
  2. 都需要有具体的函数实现

④ 虚析构语法:virtual.类名(){}

⑤ 纯虚析构语法:

  1. virtual~类名 = 0;
  2. 类名::~类名(){}

⑦ 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象。

⑧ 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构。

⑨ 拥有纯虚析构函数的类也属于抽象类。

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

//纯虚函数和纯虚机构
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}

//纯虚函数
virtual void speak() = 0;

/*
//利用虚析构可以解决,父类指针释放子类对象时不干净的问题
virtual ~Animal()
{
cout << "Animal析构函数调用" << endl;
}
*/

//纯虚析构 需要声明也需要实现
//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;

};

Animal::~Animal()
{
cout << "Animal纯虚析构函数调用" << endl;
}


class Cat : public Animal
{
public:
Cat(string name)
{
cout << "Cat构造函数调用" << endl;
m_Name = new string(name);
}

virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}

~Cat()
{
if (m_Name != NULL)
{
cout << "Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}

string* m_Name;
};

void test01()
{
Animal* animal = new Cat("Tom");
animal->speak();
//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄露
delete animal;
}

int main()
{
test01();

return 0;
}
Animal构造函数调用
Cat构造函数调用
Tom小猫在说话
Cat析构函数调用
Animal纯虚析构函数调用