黑马程序员C++核心编程学习笔记

一、内存

1.1 内存四区

C++程序在执行时,将内存大致分为4个区域:代码区,全局区,栈区,堆区

  • 代码区:存放函数体的的二进制代码,操作系统管理。

    🔵特点:①共享 :对于频繁被执行的程序,只需要在内存中有一份代码即可。
    ②只读:目的是防止程序意外地修改了它的指令。

  • 全局区:存放全局变量、静态变量和常量(除了const修饰的局部变量)。程序结束时由操作系统释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//全局变量
int g_a = 10;
const int c_g_a = 10;
int main()
{
int l_a = 10;
//静态变量
static int s_a = 10;

//常量:字符串常量/const修饰的变量(全局/局部变量)
const char* a = "hello";
const int c_l_a = 10;

cout << "局部变量l_a的十进制地址为:" << (int)&l_a << endl;
cout << "全局变量g_a的十进制地址为:" << (int)&g_a << endl;
cout << "静态变量s_a的十进制地址为:" << (int)&s_a << endl;
cout << "字符串常量a的十进制地址为:" << (int)a << endl;
cout << "const修饰的全局变量c_g_a的十进制地址为:" << (int)&c_g_a << endl;
cout << "const修饰的局部变量c_l_a的十进制地址为:" << (int)&c_l_a << endl;
return 0;

}
12345678910111213141516171819202122

在这里插入图片描述

  • 栈区:存放函数的参数值、局部变量。由编译器自动分配和释放。

    🔴注意:不要返回局部变量的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
int* funcation()
{
int a = 10;//存放在栈区,栈区的数据在函数执行完后自动释放
return &a;
}
int main()
{
int* p = funcation();
cout <<"局部变量a的值为:"<< *p << endl; //第一次正常打印,因为编译器做了保留
cout << "局部变量a的值为:" << *p << endl;
return 0;
}
123456789101112

在这里插入图片描述

  • 堆区:由程序员分配(new)和释放(delete),若程序员不释放,程序结束时由操作系统回收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int* funcation()
{
//new关键字,可以将数据开辟到堆区上
//指针本质上也是局部变量,存放在栈区上,但是保存的数据在堆区
int* p = new int(10);
return p;
}
int main()
{
int* p = funcation();
cout << *p << endl;
cout << *p << endl;
return 0;
}
1234567891011121314

在这里插入图片描述

Tip:

  • 程序运行前分为:代码区,全局区
  • 程序运行后分为:栈区,堆区

[注]: 关于内存更详细的知识👉深度剖析数据在内存中的存储👉几分钟让你了解什么是函数栈帧的创建和销毁

1.2 new操作符

1
2
3
4
5
6
//new的基本用法
int* p = new int(10);//在堆区创建整型变量,返回该变量的地址
delete p;//释放
int* parr = new int[10];//在堆区创建一个元素为10的整型数组,返回数组首元素的地址
delete[] arr;//释放一个数组
12345

测试delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int* funcation()
{
int* p = new int(10);
return p;
}
int main()
{
int* p = funcation();
cout << *p << endl;
cout << *p << endl;
delete p;
cout << *p << endl;
return 0;
}
1234567891011121314

img img

二、引用

2.1 引用基本使用和注意事项

作用:给变量起个别名。
语法数据类型 & 别名=原名
本质:指针常量

1
2
3
4
//eg.
int a = 10;
int& b = a;
123

​ 🔴注意
​ ①引用必须初始化。
​ ②引用一旦初始化就不可以更改了(如下的例子相当于int *const b=&a

1
2
3
4
5
6
7
8
9
10
	//①引用必须初始化
int a = 10;
int& b;//错了!!!

//②引用一旦初始化就不可以更改了
int a = 10;
int c = 20;
int& b = a;
int& b = c; //错了!!!
123456789

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
//eg.
/*实现数值交换*/
//1.传址交换
void swap1(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//2.引用
void swap2(int& a, int& b)//起别名可以和原名一样
{
int temp = a;
a = b;
b = temp;
}

int main()
{
int a = 10;
int b = 20;
swap1(&a, &b);
swap2(a, b);
return 0;
}
12345678910111213141516171819202122232425

2.3 引用函数返回值

🔴注意
①不要返回局部变量的引用。函数返回时,局部变量会被释放,引用或指针指向的内容会失效
②函数的调用可以作为左值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//eg.
//①不要返回局部变量的引用
int& test1()
{
int a = 10;
return a;
}
int main()
{
int& ret = test1();
cout << "ret=" << ret << endl;
cout << "ret=" << ret << endl;
test1() = 20; //②如果函数的返回值为引用,函数的调用可以作为左值
cout << "ret=" << ret << endl;
cout << "ret=" << ret << endl;
return 0;
}
1234567891011121314151617

在这里插入图片描述

2.3 常量引用

目的:用来修饰形参,防止误操作。

1
2
3
4
5
6
7
8
int &ret=10; //错了!,引用本身需要一个合法的内存空间。
1
/*
相当于编译器先创建一个临时变量:int temp=10;
然后进行起别名:int& ret=temp;
*/
const int& ret = 10;
12345

🔴注意:用常量引用之后不可以更改数据。

1
2
3
4
5
6
7
8
9
//eg.
int main()
{
const int& ret = 10;
ret = 100;//err
cout << "ret=" << ret << endl;
return 0;
}
12345678

在这里插入图片描述

C++推荐引用,因为语法方便,编译器帮我们做了指针的内容。

三、函数提高

3.1 函数默认参数

语法返回类型 函数名 (参数 =默认值) {}
用法:如果自己传入数据就用自己的,如果没有就用默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int Add(int a = 0, int b = 0)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
int c = Add(a, b);
int e = Add(a);
int f = Add(b);
int g = Add();
cout << "c=" << c << endl;
cout << "e=" << e << endl;
cout << "f=" << f << endl;
cout << "g=" << g << endl;
return 0;
}
123456789101112131415161718

在这里插入图片描述

🔴注意

​ ①默认值必须放在右边。

1
2
int test(int a, int b = 10, int c);//err
1

​ ②声明和实现,有且只能有一个有默认参数。否则可能出现二义。

3.2 函数占位参数

语法返回类型 函数名 (数据类型) {}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//eg.
void test1(int a, int)
{
cout << "haha" << endl;

}
void test2(int a, int b=10)//占位参数可以有默认参数
{
cout << "haha" << endl;

}
int main()
{
test1(1);//err
test1(1, 1);
test2(1);
test2(1, 1);
return 0;
}
12345678910111213141516171819

3.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
//eg.
//在全局作用域
void test()
{
cout << "调用test( )" << endl;
}
void test(int a)
{
cout << "调用test(int a)" << endl;
}
void test(double a)
{
cout << "调用test(double a)" << endl;
}
void test(int a, int b)
{
cout << "调用test(int a, int b)" << endl;
}
void test(int a, double b)
{
cout << "调用test(int a, double b)" << endl;
}
void test(double a, int b)
{
cout << "调用test(double a, int b)" << endl;
}
int main()
{
test();
test(1);
test(3.14);
test(1,3.14);
test(3.14, 1);
return 0;
}
1234567891011121314151617181920212223242526272829303132333435

在这里插入图片描述

🔴注意
①函数的返回值不可以作为函数重载的条件!出现二义。

1
2
3
4
5
6
7
8
9
10
//eg.
void test(int a)
{
cout << "调用test(int a)" << endl;
}
int test(int a)
{
cout << "调用test(int a)" << endl;
}
123456789

在这里插入图片描述
②引用作为重载条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//eg.
//引用作为重载条件
void test(int &a)
{
cout << "调用test(int &a)" << endl;
}
void test(const int& b)
{
cout << "调用test(const int& b)" << endl;
}
int main()
{
int a = 10;
const int b = 10;
test(a);
test(b);
return 0;
}
123456789101112131415161718

在这里插入图片描述
③函数重载碰到默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//eg.
//函数重载碰到默认参数
void test(int a,int b=10)
{
cout << "调用test(int &a)" << endl;
}
void test(int a)
{
cout << "调用test(const int& b)" << endl;
}
int main()
{
test(10);//err,出现二义
return 0;
}
123456789101112131415

在这里插入图片描述

四、类与对象

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

4.1 封装

🟦意义
①将属性和行为作为一个整体。(放在一个class里面)
②将属性和行为加以权限控制。
public公共权限:类内外都可以访问
protected保护权限: 类外不可以访问
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
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
//eg.1定义一个圆类
#define PI 3.14
//class 定义一个类 circle是类的名字
class circle
{
//访问权限:公共权限
public:
//属性
int r;
//行为
double circumference( )
{
return r * PI * 2;
}
};
int main()
{
circle c1;//创建具体的圆(对象)(实例化)
c1.r = 10;//给具体的圆的属性赋值
cout << "圆的周长为:" << c1.circumference() << endl;
return 0;
}
12345678910111213141516171819202122
//eg.2设计一个学生类
class student
{
public:
//属性
string name;//string 类处理起字符串来会方便很多,完全可以代替C语言中的字符数组或字符串指针。
int id;
//行为
void show() {
cout << "姓名:" << name << "学号:" << id << endl;
}
};
int main()
{
student s1;
s1.name = "xiyang";
s1.id = 1;
s1.show();
return 0;
}
1234567891011121314151617181920
//eg.3 公共权限,私有权限,保护权限访问的例子
class person
{
public:
string name;
protected:
string car;
private:
int password;
public:
void test()
{
name = "zyz";
car = "ofo";
password = 123;
}
};
int main()
{
person p1;
p1.name = "xiyang";
p1.car = "ufo"; //err
p1.password = 456;//err
return 0;
}
12345678910111213141516171819202122232425

🔴注意structclass的区别:
struct默认权限为:共有
class默认权限为:私有

4.1.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
//eg.
class person
{
private:
string name;//可读写
int age;//只读
string lover;//只写
public:
void SetName(string s)
{
name = s;
}
string GetName()
{
return name;
}
int GetAge()
{
age = 18;
return age;
}
void SetLover(string s)
{
lover = s;
}
};
int main()
{
person p1;
p1.SetName("xiyang");
p1.SetLover("薇尔莉特·伊芙加登");
cout << "姓名为:" << p1.GetName() << endl;
cout << "年龄为:" << p1.GetAge() << endl;
return 0;
}
1234567891011121314151617181920212223242526272829303132333435

设计案例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
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
/*
要求:
1.设计一个立方体类
2.求出立方体的面积和体积
3.分别用全局函数和成员函数判断两个立方体是否相等
*/

class cube
{
private:
//属性
int L;
int W;
int H;
public:
//行为
//设置 获取长,宽,高
void SetL(int a)
{
L = a;
}
int GetL()
{
return L;
}
void SetW(int a)
{
W = a;
}
int GetW()
{
return W;
}
void SetH(int a)
{
H = a;
}
int GetH()
{
return H;
}
//获得面积
int S()
{
return 2 * ((L * W) + (L * H) + (W * H));
}
//获得体积
int V()
{
return L * W * H;
}
//成员函数判断
bool isSameByClass(cube& c)
{
if (c.GetL() == L && c.GetW() ==W && c.GetH() == H)
return true;
else
return false;
}

};
//全局函数判断
bool isSame(cube& c1, cube& c2)
{
if (c1.GetL() == c2.GetL() && c1.GetW() == c2.GetW() && c1.GetH() == c2.GetH())
return true;
else
return false;
}
int main()
{
cube c1,c2;
c1.SetL(10);
c1.SetW(10);
c1.SetH(10);
c2.SetL(10);
c2.SetW(5);
c2.SetH(5);
cout << "第一个立方体的面积为:" << c1.S() << endl;
cout << "第一个立方体的体积为:" << c1.V() << endl;
bool ret1 = isSame(c1, c2);
if (ret1)
{
cout << "全局函数判断c1 c2相等" << endl;
}
else
cout << "全局函数判断c1 c2不相等" << endl;

bool ret2 = c1.isSameByClass(c2);
if (ret2)
{
cout << "成员函数判断c1 c2相等" << endl;
}
else
cout << "成员函数判断c1 c2不相等" << endl;
return 0;
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697

在这里插入图片描述

设计案例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
86
87
88
89
90
/*要求:
1.设计一个圆形类和一个点类
2.计算点和圆的关系
*/

//由简单的数学可知:一个点(x,y)和圆的(x,y,r)的关系有三种:
//1.点在圆内:点到圆心的距离d < r
//2.点在圆上:d=r
//3.点在圆外:d>r
class circle
{
private:
int r;//写+读
int x;//写+读
int y;//写+读
public:
void SetX(int a)
{
x = a;
}
int GetX()
{
return x;
}
void SetY(int a)
{
y = a;
}
int GetY()
{
return y;
}
void SetR(int a)
{
r = a;
}
int GetR()
{
return r;
}
};
class point
{
private:
int x;//写
int y;//写
public:
void SetX(int a)
{
x = a;
}
void SetY(int a)
{
y = a;
}
int location(circle& c)
{
if ((x - c.GetX()) * (x - c.GetX()) + (y - c.GetY()) * (y - c.GetY()) == c.GetR() * c.GetR())
return 0;
else if ((x - c.GetX()) * (x - c.GetX()) + (y - c.GetY()) * (y - c.GetY()) > c.GetR() * c.GetR())
return 1;
else
return -1;
}
};
int main()
{
circle c;
point p;
c.SetX(0);
c.SetY(0);
c.SetR(1);
p.SetX(0);
p.SetY(0);
int ret = p.location(c);
if (ret == 1)
{
cout << "点在圆外" << endl;
}
else if(ret == -1)
{
cout << "点在圆内" << endl;
}
else
{
cout << "点在圆上" << endl;
}
return 0;
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889

优化:因为圆类里面包含点类(转到目录4.3.2.4类对象作为类的成员)

4.2 对象的初始化和清理

C++利用构造函数和析构函数解决了对象的初始化和清理。对象的初始化和清理工作是编译器强制要求我们做的事情,因此就算我们不提供构造和析构,编译器也会提供,只不过编译器提供的是构造函数和析构函数的空实现。

4.3.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
//构造的分类和调用
class person
{
public:
//无参(普通构造)(默认构造)
person()
{
cout << "无参构造函数调用" << endl;
}
//有参(普通构造)
person(int a)
{
cout << "有参构造函数调用" << endl;
}
//拷贝构造函数
person(const person &p)
{
age = p.age;//克隆数据
cout << "拷贝构造函数的调用" << endl;
}
public:
int age;
};
int main()
{
//括号法
person p1;//叫括号法,但是不能加(),加了()编译器会认为是一个函数声明
person p2(10); //p2的年龄初始化为10
person p3(p2);

//显示法
person p4 = person();
person p5 = person(10);
person p6 = person(p5);
//person()为匿名对象,没有名字,但创建了对象
//不要用拷贝构造函数初始化匿名对象,如person(p3),等价于person p3

//隐式转换法
person p7 = 10;//转换为:person p7=person(10)
}
12345678910111213141516171819202122232425262728293031323334353637383940

构造函数的调用规则
创建一个类,C++至少给每一个类添加4个函数:默认构造(空实现),析构函数(空实现),拷贝构造(值拷贝),赋值运算符Operator=对属性进行值拷贝(4.5.4中介绍)
①如果用户定义一个有参构造函数,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
//eg.①如果用户定义一个有参构造函数,C++不会提供默认构造函数,但是会提供拷贝构造函数
class person
{
public:
person(int a)
{
age = a;
cout << "默认构造函数的调用" << endl;
}
int show()
{
return age;
}
private:
int age;
};
int main()
{
person p1;//err
person p2(18);
person p3(p2);//拷贝构造函数
cout << "p2的年龄为:" << p2.show() << endl;
return 0;
}
123456789101112131415161718192021222324
//②如果用户定义一个拷贝构造函数,C++不会提供别的构造函数
class person
{
public:
person(const person& p)
{
age = p.age;//克隆数据
cout << "拷贝构造函数的调用" << endl;
}
int show()
{
return age;
}
private:
int age;
};
int main()
{
person p1;//err
person p2(18);//err
person p3(p1);
cout << "p2的年龄为:" << p2.show() << endl;
return 0;
}
123456789101112131415161718192021222324

4.3.2 析构函数

定义:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
语法~类名 () { }

🔴注意
①析构函数不可以有参数,因此不可以重载。
②程序在对象销毁前会自动调用析构,无须手动调用,且只用调用一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//eg. 构造函数和析构函数例子
class person
{
public:
person()
{
cout << "构造函数的调用" << endl;
}
~person()
{
cout << "析构函数的调用" << endl;
}
};
int main()
{
person p;//创建在栈上,在对象销毁前自动调用析构函数
return 0;
}
123456789101112131415161718

在这里插入图片描述

4.3.2.1 拷贝函数调用的时机

​ 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
class person
{
public:
person()
{
cout << "默认构造函数的调用" << endl;
}
person(int a)
{
age = a;
cout << "默认构造函数的调用" << endl;
}
person(const person& p)
{
age = p.age;//克隆数据
cout << "拷贝构造函数的调用" << endl;
}
int show()
{
return age;
}
private:
int age;
};
void test1(person p)//仅仅测试
{
}
person test2()
{
person p;
return p;
}
int main()
{
//使用一个已创建完毕的对象来初始化一个新对象
person p1(20);
person p2(p1);
cout << "p2的年龄为:" << p2.show() << endl;
//值传递的方式给函数参数传值
person p3;
test1(p3);
//以值的方式返回局部对象
test2();
return 0;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445

4.3.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
40
41
42
43
44
45
46
47
//eg.
class person
{
public:
person(int a,int h)
{
age = a;
height = new int(h);
cout << "有参构造函数的调用" << endl;
}
person(const person& p)
{
age = p.age;
//height=p.height;//编译器默认实现
height = new int(*(p.height));
cout << "拷贝构造函数的调用" << endl;
}
~person()
{
if (height != NULL)
{
delete height;
height = NULL;
}
cout << "析构函数的调用" << endl;
}
int GetAge()
{
return age;
}
int GetHeight()
{
return *height;
}
private:
int age;
int *height;
};
int main()
{
person p1(18, 160);
cout << "p1的年龄为:" << p1.GetAge() << " p1的身高为:" << p1.GetHeight() << endl;
person p2(p1);
cout << "p2的年龄为:" << p2.GetAge() << " p2的身高为:" << p2.GetHeight() << endl;
return 0;
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546

4.3.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
//eg.
class person
{
public:
//初始化列表初始化属性
person() :age(10), key(123), height(160)
{

}
person(int a, int b, int c) :age(a), key(b), height(c)
{

}
int age;
int key;
int height;
};
int main()
{
person p1;
person p2(18, 456, 180);
cout << "p1的年龄,密码,身高为:\n" << p1.age << p1.key << p1.height<<endl;
cout << "p2的年龄,密码,身高为:\n" << p2.age << p2.key << p2.height<<endl;
return 0;
}
12345678910111213141516171819202122232425

4.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
//eg.
class phone
{
public:
phone(string p):PhoneName(p)
{

}
string PhoneName;
};
class person
{
public:

person(string s,string p) : name(s),Phone(p)
{

}
string name;
phone Phone;
};
int main()
{
person p1("xiyang", "huawei");
cout << "名字:" << p1.name << "手机:" << p1.Phone.PhoneName << endl;
return 0;
}
123456789101112131415161718192021222324252627

🔴注意:若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
//eg
class phone
{
public:
phone(string p):PhoneName(p)
{
cout << "调用phone构造" << endl;
}
~phone()
{
cout << "调用phone析构" << endl;
}
string PhoneName;
};
class person
{
public:

person(string s,string p) : name(s),Phone(p)
{
cout << "调用person构造" << endl;
}
~person()
{
cout << "调用person析构" << endl;
}
string name;
phone Phone;
};
int main()
{
person p1("xiyang", "huawei");
cout << "名字:" << p1.name << "手机:" << p1.Phone.PhoneName << endl;
return 0;
}
1234567891011121314151617181920212223242526272829303132333435

在这里插入图片描述

4.3.5 静态成员

  1. 静态成员变量:
    🔵特点
    ①所有对象共享一份数据。
    ②在编译阶段分配内存。
    ③类内声明,类外初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//eg.
class person
{
public:
static int a;//类内声明
};
int person::a = 100;//类外初始化
int main()
{
person p1;
cout << "p1的值为:" << p1.a << endl;
person p2;
p2.a = 200;
cout << "p1的值为:" << p1.a << endl;
return 0;
}
12345678910111213141516

🔴注意
① 静态成员变量不属于某一个对象。因此有两种访问方式:①类名访问,②对象访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class person
{
public:
static int a;
};
int person::a = 100;
int main()
{
//对象访问
person p1;
cout << p1.a << endl;
//类名访问
cout << person::a << endl;
return 0;
}
123456789101112131415

②静态成员变量也有访问权限。

  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
//静态成员函数的访问
class person
{
public:
static void test()
{
cout << "static void test()调用" << endl;
}
};
int main()
{
//对象访问
person p;
p.test();
//成员访问
person::test();
return 0;
}
123456789101112131415161718
//静态成员函数只能访问静态成员函数
class person
{
public:
static void test()
{
a = 200;
b = 200;//err,对象不明
cout << "static void test()调用" << endl;
}
static int a;//静态成员函数访问静态成员变量
int b;//非静态成员函数
};
int person::a = 100;
int main()
{
person p;
p.test();
return 0;
}
1234567891011121314151617181920

4.3 C++对象模型和this指针

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

在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
//eg.
class person1
{

};
class person2
{
int a;//非静态成员变量
};
class person3
{
int a;
static int b;//静态成员变量
};
class person4
{
int a;
static int b;//静态成员变量
void test()//非静态成员函数
{

}
};
int main()
{
//空对象占用内存是 1
//C++会给每一个空对象分配一个字节的内存空间,为了区分空对象占内存的位置
//每一个空对象也应该有一个独一无二的内存地址
person1 p1;
cout << "sizeof(p)=" << sizeof(p1) << endl;//1

//虽然空对象有一个字节,但是一旦类里面不为空就跟着类中字节走
person2 p2;
cout << "sizeof(p)=" << sizeof(p2) << endl;//4

//静态成员变量不属于类对象上的
person3 p3;
cout << "sizeof(p)=" << sizeof(p3) << endl;//4

//类的成员变量和成员函数分开存储
person4 p4;
cout << "sizeof(p)=" << sizeof(p4) << endl;//4
return 0;
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344

4.4.2 this指针概念

定义:this指针指向被调用的成员函数所属的对象。
🔵特点
​ ①this指针隐含在每一个非静态成员函数内的一种指针。
​ ②this指针不需要定义,可直接使用。
🟦意义
​ ①当形参和成员变量同名时,可以用this指针来区分。
​ ②在类的非静态成员函数中返回对象本身,可以用retrun *this

形参和成员变量名字相同
在这里插入图片描述

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//eg.当形参和成员变量同名时,可以用this指针来区分。
class person
{
public:
person(int age)
{
//this 指向被调用的成员函数所属的对象
this->age = age;
}
int age;
};
int main()
{
person p1(18);
cout << "p1的年龄为:" << p1.age << endl;
return 0;
}

123456789101112131415161718

在这里插入图片描述

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
// 在类的非静态成员函数中返回对象本身,可以用retrun *this
class person
{
public:
person(int age)
{
this->age = age;
}
person& test(person p)//person一定要加&,使用本体
{
this->age += p.age;
return *this;
}
int age;
};
int main()
{
person p1(18);
person p2(18);
//链式编程
p2.test(p1).test(p1);
cout << "p2的年龄为:" << p2.age << endl;
return 0;
}
123456789101112131415161718192021222324

在这里插入图片描述

4.4.3 空指针访问成员函数

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
class person
{
public:
void test1()
{
cout << "test1" << endl;
}
void test2()
{
cout << "age="<<this->age<< endl;//err,传入的指针为空
}
int age;
};
int main()
{
person *p=NULL;
p->test1();
p->test2();
return 0;
}
1234567891011121314151617181920
void test2()
{
if(this->age==NULL)
return;//提高代码的健壮性
cout << "age="<<this->age<< endl;//err,传入的指针为空
}
123456

4.4.4 const修饰成员函数

常函数:成员函数后const
🔵特点
​ ①常函数内不可以修改成员属性。
​ ②成员属性声明时加关键字mutable后,在常函数中依然可以修改。

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
//eg.
class person
{
void test2()
{
a = 100;
b = 200; //err b是常量不可以修改
c = 300;
}
//在成员函数后加const,修饰的是this指向,让指针指向的值不能改变
void test1() const
{
a = 100; //err 相当于this->a=100
b = 200; //err b是常量不可修改
c = 300;
}
int a;
const int b;
mutable int c;
};
int main()
{
person p;
p.test1();
p.test2();
return 0;
}
123456789101112131415161718192021222324252627

常对象:声明对象前加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
//eg.
class person
{
public:
void test1()
{
}
void test2() const
{
}
int a;
const int b;
mutable int c;
};
int main()
{
const person p;//不能修改指针指向的值
p.a = 100;//err
p.b = 200;//err
p.c = 300;
p.test1();//err 常对象只能调用常函数
p.test2();
}
1234567891011121314151617181920212223

4.4 友元

目的:让一个函数或类,访问另一个类中的私有成员。
关键字friend
实现
​ ①全局函数做友元。
​ ②类做友元。
​ ③成员函数做友元。

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
//全局函数做友元
class building
{
friend void test2(building* b);//声明友元函数
private:
string bedroom;
public:
building()
{
bedroom = "卧室";
livingroom = "客厅";
}
string livingroom;
};
void test1(building* b)
{
cout << "访问" << b->livingroom << endl;
cout << "访问" << b->bedroom<< endl;//err 不可访问私有成员
}
void test2(building* b)
{
cout << "访问" << b->livingroom << endl;
cout << "访问" << b->bedroom << endl;
}
int main()
{
building b1;
test1(&b1);
test2(&b1);
return 0;
}
12345678910111213141516171819202122232425262728293031
//类做友元
class building
{
friend class gay2;
public:
building();
string livingroom;
private:
string bedroom;
};
building::building()
{
bedroom = "卧室";
livingroom = "客厅";
}
//非友元
class gay1
{
public:
gay1();
void vist();
building* b;
};
gay1::gay1()
{
b = new building;
}
void gay1::vist()
{
cout << "朋友在访问" << b->livingroom << endl;
cout<< "朋友在访问" << b->bedroom << endl;//err 没有访问权限
}
//友元
class gay2
{
public:
gay2();
void vist();
building* b;
};
gay2::gay2()
{
b = new building;
}
void gay2::vist()
{
cout << "朋友在访问" << b->livingroom << endl;
cout << "朋友在访问" << b->bedroom << endl;
}
int main()
{
gay1 g1;
g1.vist();
gay2 g2;
g2.vist();
return 0;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
//成员函数做友元
class building;//当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。
class gay
{
public:
gay();
void test1();
void test2();
building* b;
};
gay::gay()
{
b = new building;
}
void gay::test1()
{
cout << "正在访问" << b->livingroom <<endl;
cout << "正在访问" << b->bedroom << endl;//err
}
void gay::test2()
{
cout << "正在访问" << b->livingroom << endl;
cout << "正在访问" << b->bedroom << endl;
}
class building
{
public:
building();
string livingroom;
friend void gay::test2();
private:
string bedroom;

};
building::building()
{
bedroom = "卧室";
livingroom = "客厅";
}
int main()
{
gay g1;
g1.test1();
g1.test2();
return 0;
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546

4.5 运算符重载

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

4.5.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
//eg.
//加号运算符重载
class person
{
public:
person(int x, int y) :a(x), b(y)
{

}
//重载运算符operator
//成员函数重载+号
person operator+(person& p)
{
person temp(0, 0);
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
}
int a;
int b;
};
//全局函数重载+号
person operator+(person& p1, person& p2)
{
person temp(0, 0);
temp.a = p1.a + p2.a;
temp.b = p1.b + p2.b;
return temp;
}
int main()
{
person p1(10, 10);
person p2(10, 10);
person p3 = p1+p2;//方法一(若用此方法需要屏蔽上面一种重载方式,避免多个运算符+与操作数匹配)
person p3 = p1.operator+(p2);//方法二
person p3 = operator+(p1, p2);//方法三
}
12345678910111213141516171819202122232425262728293031323334353637

🔴注意:不要滥用运算重载符。

4.5.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
//eg
class person
{
public:
//舍弃成员函数重载,p.operator<<cout相当于p<<cout与期望不相符
//void operator<<(cout)
//{

//}
person(int x) :a(x)
{

}
int a;
int b;
};
//全局函数重载左移运算符
//cout属于标准输出流ostream类型
//返回ostream是链式编程思想,返回后cout<<p<<...后面可以再利用
ostream& operator<<(ostream& cout, person& p)
{
cout <<"p中a的值为:"<< p.a<<"p中b的值为:"<< p.b<<endl;
return cout;
}
int main()
{
person p(10);
cout << p;//方法一
operator<<(cout, p);//方法二
return 0;
}
12345678910111213141516171819202122232425262728293031

4.5.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
//重载递增运算符
class MyInt
{
friend ostream& operator<<(ostream& cout, MyInt m);
public:
MyInt()
{
num = 0;
}
//前置++
//&的作用是对同一个数据进行递增
MyInt& operator++()
{
//先++
num++;
//再返回
return *this;
}
//后置++
//int是占位参数
MyInt operator++(int)
{
//先暂存
MyInt temp = *this;
//再++
num++;
//返回暂存的数据
return temp;
}

private:
int num;
};
ostream& operator<<(ostream& cout, MyInt m)
{
cout << m.num;
return cout;
}
int main()
{
MyInt m;
cout << ++m << endl;
cout <<m++<< endl;
return 0;
}

12345678910111213141516171819202122232425262728293031323334353637383940414243444546

🔴注意:前置++,返回是引用。后置++,返回的是值。

4.5.4 赋值运算符重载

​ 创建一个类,C++至少给每一个类添加4个函数:默认构造(空实现),析构函数(空实现),拷贝构造(值拷贝),赋值运算符Operator=对属性进行值拷贝

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
//赋值运算符的重载
class person
{
public:
person(int a)
{
age=new int(a);
}
~person()
{
if (age != NULL)
{
delete age;
age = NULL;
}
}
//重载赋值运算符以避免浅拷贝带来的问题
person& operator=(person& p)
{
//age=p.age;//编译器默认实现
if (age != NULL)
{
delete age;
age = NULL;
}
age = new int(*p.age);
//返回自身
return *this;
}
int *age;
};
int main()
{
person p1(10);
person p2(20);
person p3(30);
p3=p2 = p1;
cout << *(p2.age) << endl;//10
cout << *(p3.age) << endl;//10
return 0;
}
1234567891011121314151617181920212223242526272829303132333435363738394041

4.5.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
//关系运算符重载 == !=
class person
{
public:
person(string s,int a):name(s),age(a)
{

}
bool operator==(person& p)
{
if (this->name == p.name && this->age == p.age)
{
return true;
}
else
return false;
}
string name;
int age;
};
int main()
{
person p1("xi",10);
person p2("xi",20);
if (p1 == p2)
{
cout << "p1和p2相等" << endl;
}
else
cout << "p1和p2不相等" << endl;
return 0;
}
1234567891011121314151617181920212223242526272829303132

4.5.6 函数调用运算符重载(仿函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//函数调用运算符重载
class MyPrint
{
public:
void operator()(string text)//与函数调用长得差不多,所以被称为仿函数
{
cout << text << endl;
}
};
void print(string text)
{
cout << text << endl;
}
int main()
{
MyPrint m1,m2;
m1("xiyang");
m2("xiyang");
return 0;
}
1234567891011121314151617181920

4.6 继承

4.6.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
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
//普通实现页面
class java
{
public:
void head()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void foot()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "JAVA学科视频" << endl;
}
};
class cpp
{
public:
void head()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void foot()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "CPP学科视频" << endl;
}
};
class python
{
public:
void head()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void foot()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "python学科视频" << endl;
}
};
void test1()
{
cout << "java下载视频页面如下:" << endl;
java j;
j.head();
j.foot();
j.left();
j.content();
cout << "--------------------" << endl;
}
void test2()
{
cout << "cpp下载视频页面如下:" << endl;
cpp c;
c.head();
c.foot();
c.left();
c.content();
cout << "--------------------" << endl;
}
void test3()
{
cout << "python下载视频页面如下:" << endl;
python p;
p.head();
p.foot();
p.left();
p.content();
cout << "--------------------" << endl;
}
int main()
{
test1();
test2();
test3();
return 0;

}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
//继承写法
class BasePage
{
public:
void head()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void foot()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "python学科视频" << endl;
}
};
class java :public BasePage
{
void content()
{
cout << "java学科视频" << endl;
}
};
class cpp :public BasePage
{
void content()
{
cout << "cpp学科视频" << endl;
}
};
class python :public BasePage
{
void content()
{
cout << "python学科视频" << endl;
}
};
123456789101112131415161718192021222324252627282930313233343536373839404142

优势:减少重复代码
语法class 子类 : 继承方式

4.6.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
//eg.
class father
{
public:
int a;
protected:
int b;
private:
int c;
};

//公共继承
class son1 :public father
{
public:
void test1()
{
a = 10;//父类中的公共权限成员,子类也是公共权限
b = 10;//父类中的保护权限成员,子类也是保护权限
//c = 10;//err 父类的私有权限成员,子类无法访问
}
};
void test11()
{
son1 s;
s.a;
//s.b;//err保护权限,类外无法访问
}

//保护继承
class son2 :protected father
{
public:
void test2()
{
a = 10;//父类中的公共权限成员,子类是保护权限
b = 10;//父类中的保护权限成员,子类是保护权限
//c = 10;//err 父类的私有权限成员,子类无法访问
}
};
void test22()
{
son2 s;
//s.a;//err保护权限,类外无法访问
//s.b;//err保护权限,类外无法访问
}

//私有继承
class son3 :private father
{
public:
void test3()
{
a = 10;//父类中的公共权限成员,子类是私有权限
b = 10;//父类中的保护权限成员,子类是私有权限
//c = 10;//err 父类的私有权限成员,子类无法访问
}
};
void test22()
{
son3 s;
//s.a;//err私有权限,类外无法访问
//s.b;//err私有权限,类外无法访问
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364

4.6.3 继承中的对象模型

Q:从父类继承过来的成员,哪些属于子类对象中?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//eg
class father
{
public:
int a;
static int d;//不继承
protected:
int b;
private:
int c;
};
class son :public father
{
public:
int e;
};
int main()
{
//父类中的所有非静态成员属性都会被子类继承
//父类的私有成员被编译器隐藏,访问不到但是被继承
cout << "size of(son)=" << sizeof(son) << endl;//16
return 0;
}
1234567891011121314151617181920212223

用工具查看

在这里插入图片描述
在这里插入图片描述

1
2
cl /d1 reportSingleClassLayout查看的类名 所属文件名
1

在这里插入图片描述
在这里插入图片描述

4.6.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
//eg
class father
{
public:
father()
{
cout << "father的构造函数" << endl;
}
~father()
{
cout << "father的析构函数" << endl;
}
};
class son :public father
{
public:
son()
{
cout << "son的构造函数" << endl;
}
~son()
{
cout << "son的析构函数" << endl;
}
};
int main()
{
son s;
return 0;
}
123456789101112131415161718192021222324252627282930

在这里插入图片描述

4.6.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
//eg
class father
{
public:
father()
{
a = 10;
}
void test()
{
cout << "father中的test()函数调用" << endl;
}
int a;
static int b;
};
int father::b=10;
class son :public father
{
public:
son()
{
a = 20;
}
void test()
{
cout << "son中的test()函数调用" << endl;
}
int a;
static int b;
};
int son::b = 20;
int main()
{
son s;
//同名非静态属性处理方式
cout << "son中a=" << s.a << endl;
cout << "father中a=" << s.father::a << endl;//通过子类对象访问父类对象成员需要加作用域

//同名函数处理方式1hj
s.test();
s.father::test();

//同名静态属性处理方式
//方法一:对象访问
cout << "对象访问son中b=" << s.b << endl;
cout << "对象访问father中b=" << s.father::b << endl;
//方法二:类名访问
cout << "类名访问son中b = " << son::b << endl;
cout << "类名访问father中b = " << father::b << endl;
cout << "类名访问father中b = " << son::father::b << endl;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051

①子类对象访问子类同名成员:直接访问
②子类对象访问父类同名成员:加作用域::

4.6.6 多继承语法

语法class 子类 :继承方式 父类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
//eg .用法与上面的大同小异,多继承可能会引发父类中有同名成员出现,需要加作用域区分
class father1
{
public:
father1()
{
a = 10;
}
int a;
};
class father2
{
public:
father2()
{
b = 10;
}
int b;
};
class son :public father1, public father2
{
public:
son()
{
c = 10;
d = 20;
}
int c;
int d;
};
int main()
{
son s;
cout << "sizeof (son)=" << sizeof(son) << endl;
cout << "father1中a的值" << s.a << endl;

}
12345678910111213141516171819202122232425262728293031323334353637

🔴注意C++实际开发中不建议用多继承

4.6.7 菱形继承

定义:两个派生类继承同一个基类,又有某个类同时继承者两个派生类。在这里插入图片描述
Q:菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
✅:虚继承可以解决菱形继承问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//eg.
class animal
{
public:
int age;
};
//关键字virtual将yang变成虚继承
class yang:virtual public animal{};
class tuo:virtual public animal{};
class YangTuo :public yang, public tuo{};
int main()
{
YangTuo yt;
yt.yang::age = 10;
yt.tuo::age = 20;
//数据只有一份
cout << yt.yang::age << endl;//20
cout << yt.tuo::age << endl;//20
cout << yt.age << endl;//20
}
1234567891011121314151617181920

4.7 多态

4.7.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
//eg
class animal
{
public:
void breathe()
{
cout << "动物呼吸" << endl;
}
//虚函数
virtual void speak()
{
cout << "动物叫" << endl;
}
};
class cat:public animal
{
public:

void breathe()
{
cout << "喵呼吸" << endl;
}
//重写
void speak()
{
cout << "miaomiao~" << endl;
}
};
//地址早绑定 在编译阶段确定函数地址
void DoSpeak(animal &a)
{
a.speak();
}
//地址晚绑定 在函数前加 virtual,在运行阶段确定地址
void DoBreathe(animal& a)
{
a.breathe();
}
int main()
{
cat c;
DoSpeak(c);//miaomiao~
DoBreathe(c);//动物呼吸
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344

动态多态满足条件:
①有继承关系
②子类重写父类中的虚函数(子类的virtual可有可无)

动态多态的使用:父类的指针或引用,执行子类对象。

4.7.2 多态原理剖析(图解)

在这里插入图片描述

4.7.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
//普通写法  + - * /
class calculator
{
public:
int GetResult(string s)
{
if (s == "+")
return a + b;
else if (s == "-")
return a - b;
else if (s == "*")
return a * b;
else if (s == "/")
return a / b;
}
int a;
int b;
};
int main()
{
calculator c;
string s;
cout << "输入两个整型数字的+-*/运算:" << endl;;
cin >> c.a >>s>> c.b;
cout << "=" << c.GetResult(s) << endl;
return 0;
}
123456789101112131415161718192021222324252627

❗局限:扩展新的功能,需要修改源码。
✅:

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
//多态写法
class AbstractCalculator
{
public:
virtual int GetResult()
{
return 0;
}
int a;
int b;
};
//加法计算器类
class Add :public AbstractCalculator
{
public:
int GetResult()
{
return a + b;
}
};
//减法计算器类
class Sub :public AbstractCalculator
{
public:
int GetResult()
{
return a - b;
}
};
//乘法计算器类
class Mul :public AbstractCalculator
{
public:
int GetResult()
{
return a * b;
}
};
//除法计算器类
class Div:public AbstractCalculator
{
public:
int GetResult()
{
return a / b;
}
};

void Do(AbstractCalculator& ac2)
{
ac2.GetResult();
}
int main()
{
//加法
//父类的指针或引用,执行子类对象
AbstractCalculator* ac = new Add;
ac->a = 10;
ac->b = 20;
cout << ac->a << "+" << ac->b << "=" << ac->GetResult() << endl;
//记得销毁
delete ac;

//减法
ac = new Sub;
ac->a = 10;
ac->b = 20;
cout << ac->a << "-" << ac->b << "=" << ac->GetResult() << endl;
//记得销毁
delete ac;

//乘法除法同理

}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374

多态优势
①组织结构清晰,可读性强。
②对于前期和后期扩展及维护性高。

4.7.4 纯虚函数和抽象类

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

纯虚函数定义virtual 返回值类型 函数名 (参数列表)= 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
class base
{
public:
//纯虚函数
virtual void func() = 0;
};
class son1 :public base
{
public:
};
class son2 :public base
{
public:
void func()
{}
};
int main()
{
//base b;//err 抽象类无法实例化对象
//new base;//err

//son1 s;//子类必须重写抽象类中的纯虚函数,否则也属于抽象类
son2 s;
}
123456789101112131415161718192021222324

4.7.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
class AbstractDrinking
{
public:

//煮水
virtual void BoilWater() = 0;
//冲泡
virtual void Brew() = 0 ;
//倒入杯中
virtual void IntoCup() = 0;
//加入辅料
virtual void PutSomeing() = 0;
//制作饮品
void Make()
{
BoilWater();
Brew();
IntoCup();
PutSomeing();
}
};
class Tea :public AbstractDrinking
{
//煮水
virtual void BoilWater()
{
cout << "煮500ml水" << endl;
}
//冲泡
virtual void Brew()
{
cout << "加入10g茶叶" << endl;
}
//倒入杯中
virtual void IntoCup()
{
cout<<"倒入杯子里"<<endl;
}
//加入辅料
virtual void PutSomeing()
{
cout << "加入柠檬" << endl;
}
};
class Coffee :public AbstractDrinking
{
//煮水
virtual void BoilWater()
{
cout << "煮500ml水" << endl;
}
//冲泡
virtual void Brew()
{
cout << "加入一勺咖啡" << endl;
}
//倒入杯中
virtual void IntoCup()
{
cout << "倒入杯子里" << endl;
}
//加入辅料
virtual void PutSomeing()
{
cout << "加入牛奶" << endl;
}
};
void DoMake(AbstractDrinking* ad)
{
ad->Make();
delete ad;//记得销毁
}
int main()
{
//做一杯茶
DoMake(new Tea);
//做一杯咖啡
DoMake(new Coffee);
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879

4.7.6 虚析构函数和抽象类

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,将父类中的析构函数改为虚析构或者纯虚析构就可以解决

虚析构语法virtual ~类名(){}
纯虚析构语法virtual ~类名() = 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
class Animal
{
public:
Animal()
{
cout << "Animal构造函数的调用" << endl;
}
~Animal()
{
cout << "Animal析构函数的调用" << endl;
}

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

//纯虚析构
//virtual ~Animal() = 0;
virtual void speak() = 0;

};
//纯虚析构函数要有具体的函数实现
//Animal::~Animal()
//{
// cout << "Animal纯虚析构函数的调用" << endl;
//}
class Cat:public Animal
{
public:
Cat(string n)
{
cout << "Cat的构造函数的调用" << endl;
name = new string(n);
}
void speak()
{
cout << *name<<"miao~" << endl;
}
~Cat()
{
cout << "Cat的析构函数的调用" << endl;
if (name != NULL)
{
delete name;
name = NULL;
}
}
string *name;
};
int main()
{
Animal* a = new Cat("mimi");
a->speak();
//父类指针在析构时候,不会调用子类中的析构函数,导致子类如果有堆区的属性,会出现内存的泄露
delete a;
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758

虚析构和纯虚析构共性:
①可以解决父类指针释放子类对象
②都需要有具体的函数实现

🔴注意:纯虚析构,该类属于抽象类,无法实例化对象

4.7.7 多态案例三:电脑组装

案例描述
电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作

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
class CPU
{
public:
virtual void calulate() = 0;
};
class VideoCard
{
public:
virtual void display() = 0;
};
class Memory
{
public:
virtual void storage() = 0;
};
//intel 厂商
class IntelCPU :public CPU
{
void calulate()
{
cout << "Intel的CPU工作啦!" << endl;
}
};
class IntelVideoCard :public VideoCard
{
void display()
{
cout << "Intel的VideoCard工作啦!" << endl;
}
};
class IntelMemory :public Memory
{
void storage()
{
cout << "Intel的Memory工作啦!" << endl;
}
};
//XiaoMi厂商
class XiaoMiCPU :public CPU
{
void calulate()
{
cout << "XiaoMi的CPU工作啦!" << endl;
}
};
class XiaoMiVideoCard :public VideoCard
{
void display()
{
cout << "XiaoMi的VideoCard工作啦!" << endl;
}
};
class XiaoMiMemory :public Memory
{
void storage()
{
cout << "XiaoMi的Memory工作啦!" << endl;
}
};
class Computer
{
public:
Computer(CPU* cpu, VideoCard* videpcard, Memory* memory)
{
this->cpu = cpu;
this->videpcard = videpcard;
this->memory = memory;
}
void DoWork()
{
cpu->calulate();
videpcard->display();
memory->storage();
}
~Computer()
{
if (cpu!= NULL)
{
delete cpu;
cpu = NULL;
}
if (videpcard!= NULL)
{
delete videpcard;
videpcard = NULL;
}
if (memory!= NULL)
{
delete memory;
memory = NULL;
}
}
private:
CPU* cpu;
VideoCard* videpcard;
Memory* memory;
};
int main()
{
CPU* intel_cpu = new IntelCPU;
VideoCard* intel_vc = new IntelVideoCard;
Memory* intel_m = new IntelMemory;
Computer c1(intel_cpu, intel_vc, intel_m);
c1.DoWork();

CPU* xiaomi_cpu = new XiaoMiCPU;
VideoCard* xiaomi_vc = new XiaoMiVideoCard;
Memory* xiaomi_m = new XiaoMiMemory;
Computer c2(xiaomi_cpu, xiaomi_vc, xiaomi_m);
c2.DoWork();

}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112

在这里插入图片描述

五、文件

5.1 文本文件

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化
C++中对文件操作需要包含头文件< fstream >

文件类型分为两种:
文本文件 - 文件以文本的ASCII码形式存储在计算机中
二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

操作文件的三大类:
①ofstream:写操作
②ifstream: 读操作
③fstream : 读写操作

5.1.1 写文件

写文件步骤如下

  1. 包含头文件: #include <fstream>
  2. 创建流对象: ofstream ofs;
  3. 打开文件: ofs.open(“文件路径”,打开方式);
  4. 写数据: ofs << "写入的数据";
  5. 关闭文件:ofs.close();

文件打开方式

打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式

🔴注意:文件打开方式可以配合使用,利用|操作符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//eg.
#include <fstream>
int main()
{
//1.头文件
//2.创建流对象
ofstream ofs;
//3.打开文件,方式为:为写
ofs.open("test.txt", ios::out);
//4.写数据
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
//5.关闭文件
ofs.close();
return 0;
}
1234567891011121314151617

在这里插入图片描述

5.1.2 读文件

读文件步骤如下

  1. 包含头文件: #include <fstream>
  2. 创建流对象: ifstream ifs;
  3. 打开文件并判断文件是否打开成功: ifs.open(“文件路径”,打开方式);
  4. 读数据: 四种方式读取
  5. 关闭文件: ifs.close();
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
//eg.
#include <fstream>
#include <string>
void test()
{
//1.包含头文件
//2.创建流对象
ifstream ifs;

//3.打开文件并判断文件是否打开成功
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//4.读数据
//第一种方式
char buf[1024] = { 0 };
while (ifs >> buf)
{
cout << buf << endl;
}

//第二种
char buf[1024] = { 0 };
while (ifs.getline(buf,sizeof(buf)))
{
cout << buf << endl;
}
//第三种
string buf;
while (getline(ifs, buf))
{
cout << buf << endl;
}
//第四种
char c;
while ((c = ifs.get()) != EOF)//end of file
{
cout << c;
}
//5.关闭文件
ifs.close();
}
int main()
{
test();
return 0;
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950

5.2 二进制文件

以二进制的方式对文件进行读写操作打开方式要指定为 ios::binary

5.2.1 写文件

二进制方式写文件主要利用流对象调用成员函数write
函数原型ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

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 <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
//1、包含头文件
//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);
//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);
Person p = { "张三" , 18 };
//4、写文件
ofs.write((const char*)&p, sizeof(p));
//5、关闭文件
ofs.close();
}
int main()
{
test01();
return 0;
}
1234567891011121314151617181920212223242526

在这里插入图片描述

5.2.2 读文件

二进制方式读文件主要利用流对象调用成员函数read
函数原型istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

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 <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
//1.包含头文件
//2.创建流对象
//3.打开文件并判断文件是否打开成功
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//4.读文件(读到的是正常的不是乱码)
Person p;
ifs.read((char*)&p, sizeof(p));
cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
//5.关闭文件
ifs.close();
}
int main()
{
test01();
return 0;
}
12345678910111213141516171819202122232425262728293031

文章知识点与官方知识档案匹配,可进一步学