Drunkmars's Blog

C++

字数统计: 15.7k阅读时长: 68 min
2021/06/03 Share

因为5月份耽误了一段时间导致pe课程拖了很久,接下来进入到C++的学习。

this类指针

封装:

1
2
3
1.将函数体定义到结构体内部就是封装

2.编译器会自动传递结构体的指针给函数

类:

1
带有函数的结构体称为类

成员函数:

1
结构体里面的函数称为成员函数

定义一个名叫Base的结构体和Max的比较函数(用Pb这个指针进行比较)

image-20210603111532314

运行一下没有问题,输出了两个数中的最大值

image-20210603111723853

输出一下当前结构体的大小为8

image-20210603112127532

平栈方式为外平栈

image-20210603112855222

这里把Max函数放到结构体里面发现结构体大小还是为8

image-20210603112714503

Max函数放到结构体里,发现为内平栈,注意这里要用base.Max(&base);进行调用,要声明Max函数在结构体里面

image-20210603113052619

这里再尝试不传指针进去

image-20210603114401910

编译器会自动传递一个参数进去保存变量的首地址,也就是说函数在结构体里的话编译器会自动生成一个指针

image-20210603114723230

放到结构体外可以直接调用,放到结构体里需要调用结构体再调用函数

为了避免参数和变量同名需要使用到this指针

当函数位于结构体内时,若没有传入参数,编译器会自动传入一个指针,称为this指针

this指针的作用

1
2
3
4
5
6
7
8
9
10
1.可以更好的区分哪个是参数,哪个是成员。

2.可以通过this指针返回首地址

int GetAddr()
{
return *(int*)this;
}

3.this指针不能够更改,因为this指针的作用就是指向当前对象的首地址

image-20210603224806177

结构体内进行加减乘除操作

image-20210604142448283

全局函数和结构体函数的区别

1.结构体指针传入this指针

全局函数传入所需参数变量

2.结构体 把 this 指针ecx 然后 又放到 [ebp-8] 里 ,以 [ebp-8] 为首地址 根据偏移进行 参数的传递与计算

全局函数通过之前压入栈的参数进行计算

3.堆栈平衡

结构体函数通过push this指针pop this 指针 达到的堆栈平衡 (可以说内平栈也可以说 不需要平衡堆栈)

全局函数外平栈,通过add esp,8平栈

空结构体的大小

空结构体的大小为1字节,原因是C++编译器会在空类或空结构体中增加一个虚设的字节(有的编译器可能不止一个),以确保不同的对象都具有不同的地址。

image-20210604143651308

两个结构体函数

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
struct Person  					
{
void Fn_1()
{
printf("Person:Fn_1()\n");
}
void Fn_2()
{
printf("Person:Fn_2()%x\n");
}
};

struct Person2
{
int x ;
void Fn_1()
{
printf("Person:Fn_1()\n");
}
void Fn_2()
{
x = 10;
printf("Person:Fn_2()%x\n");
}
};



int main(int argc, char* argv[])
{
Person* p = NULL;
p->Fn_1();
p->Fn_2();

Person2* p = NULL;
p->Fn_1();
p->Fn_2();

return 0;

}

结构体Person1使用Person* P = NULL;调整了this指针的指向。但是在调用函数的时候并不需要this指针直接使用call调用即可。说明了结构体函数在底层情况下,在结构体内和结构体外没有区别

image-20210604144637838

结构体Person2中使用的this指针Nullptr所以无法找到成员变量

继承 构造-析构函数

构造函数用于初始化对象用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Person
{
int age;
int level;

//构造函数
Person(int age, int level)
{
this->age = age;
this->level = level;
}

//成员函数
void Print()
{
printf("%d %d\n",age,level);
}
};

当创建构造函数后如果有参数,必须要传参数进去使用,否则会报错

image-20210604162201326

如果这里我要设置一个构造函数但现在不想使用的话可以再在构造函数后面再加一个空的构造函数即可,这里称为重载

image-20210604162704766

结构体中函数名可以相同,但是参数不能相同,这样定义的好处是能够使一些功能比较相近的函数不用单独起名

image-20210604162949947

重载的概念

1
函数名一样,函数传入的参数不一样

构造函数的特点

析构函数的作用为当对象不使用的时候调用,用来做一些清理的工作

1
2
3
4
5
6
7
8
9
10
11
1.与类同名

2.没有返回值

3.创建对象的时候执行

4.主要用于初始化

5.可以有多个(最好有一个无参的),称为重载,其他函数也可以重载

6.编译器不要求必须提供

之前的一些参数都是栈或者全局变量,用完之后自己会销毁分配过的空间。而如果使用malloc()函数从堆里分配空间的话,使用之后不能够自己销毁分配过的空间,这时候我们就需要手动去销毁空间。

image-20210604170120376

这个地方如果直接用free()malloc()分配的空间进行销毁,在两个函数同时使用的情况下,第一个函数使用过后就把在堆里分配的空间销毁,导致第二个函数没有空间就导致运行不了

image-20210604170305208

析构函数的特点

1
2
3
4
5
6
7
8
9
10
11
1、只能有一个析构函数,不能重载

2、不能带任何参数

3、不能带返回值

4、主要用于清理工作

5、编译器不要求必须提供

6、与类同名并在最前面加~

这里就使用到一个析构函数~Person(),这里可以考虑到对象不使用的时候就使用free()malloc()分配的空间销毁掉

image-20210604171534833

继承

继承本质是一种数据的复制

这里定义三个结构体,用test()进行调用

image-20210604172831323

断点进入Teacher()反汇编

image-20210604173101279

断点进入Student()反汇编

image-20210606203402747

可以发现这里Teacher()结构体和Student()结构体的反汇编是相同的,而且结构体中有共用数据的成员,这里如果每次使用的时候再到结构体里面进行定义的话就会很麻烦。所以这里就会用到一个C++的继承思想

image-20210606203651714

这里我把结构体内可以共用的成员放到一个新的结构体里面,然后后面结构体需要使用这两个变量的时候在结构体名称加上:Person即可调用这个结构体里面的成员

这里Person为父类或基类,Teacher、Student称为子类或派生类,在void test()这个函数里面的t称为对象或者实例

image-20210606203951011

这里用一个pt指针t对象的地址取出来,然后使用pt指针指向Student类里面的成员

image-20210606204927186

可以用父类指针指向子类对象,但是只能指向子类从父类里面继承的成员,而不能指向本来存在于子类对象里面的成员

image-20210606205614180

例如我这里取的是sex这个成员,很明显sex这个成员是存在于Person这个父类里面的,所以编译能够通过

image-20210606205848961

这里我再去一个成员code,可以发现code这个成员是不存在于父类Person里面的,但是存在子类Student里面,所以这里编译是过不了的

image-20210606210635300

这里如果要用父类指针访问灵活运用指针即可

image-20210606210430488

用一个子类指针去指向父类成员会报错不是一个类型

image-20210606211200381

这里可以强转,但是不能这么使用

image-20210606211520368

因为这里子类指针的范围是比父类指针的范围要大的,如果这里强转之后会导致出现一些错误数据

image-20210606211735460

image-20210606211837400

多层继承

定义三个类,Y继承X,Z继承Y,打印一下Z的大小发现为24,说明Z继承的Y包含了X

image-20210606212249439

添加数据观察反汇编可以看到继承了Y和X

image-20210606212755157

image-20210606212744228

这里尝试用X的指针、Y的指针访问Z的成员,都能够访问到,这里三个结构体的指针都是指向开头

image-20210606213111427

image-20210606213038727

这里一个特殊情况,如果子类里面有跟父类同名的成员,分配的空间不变。原因是因为在最后都会转换成二进制代码,编译器不会管这两个成员是否相同,还是会分配空间

image-20210606214222157

如果要对同名成员进行赋值,需要具体指明给哪个成员赋值,如z.X::a = 1;z.Y::a = 3;

image-20210606214632336

但是这里的反汇编还是跟之前的相同

image-20210606214939474

多重继承

这里Z结构体直接继承了X、Y两个类,打印一下Z结构体的大小,发现还是24,而且反汇编跟之前也是没有区别的

image-20210606215725725

image-20210606215928685

一般不建议使用多重继承是因为多重继承的指针并不都是指向开头,所以增加了程序的复杂度,容易出错

微软建议使用单继承,如果需要多重继承可以改为多层继承

image-20210606220513996

析构 继承练习题

题目如下

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
1.编写DateInfo()结构体,内容如下:
(1)有三个成员: int year; int month;int day;
(2)要求有个带参数的构造函数,其参数分别为对应年、月、日。
(3)有一个无参数的构造函数,其初始的年、月、日分别为:201542
(4)要求有一个成员函数实现日期的设置:SetDay(int day)
(5)要求有一个成员函数实现日期的获取: GetDay()
(6)要求有一个成员函数实现年份的设置: SetYear(int year)
(7)要求有一个成员函数实现年份的获取: GetYear()
(8)要求有一个成员函数实现月份的设置: SetMonth(int month)
(9)要求有一个成员函数实现月份的获取: GetMonth()

2.设计一个结构TimeInfo,要求其满足下述要求。
(1)该结构中包含表示时间的时、分、秒。
(2)设置该结构中时、分、秒的函数。
(3)获取该结构中时、分、秒的三个函数:GetHour(),GetMinute()和GetSecond()。

3.让TimeInfo继承DateInfo 分别使用DataInfo和TimeInfo的指针访问TimeInfo对象的成员。

4.设计一个结构叫做MyString,要求该结构能够完成以下功能:
(1)构造函数能够根据实际传入的参数分配实际存储空间;
(2)提供一个无参的构造函数,默认分配大小为1024个字节;
(3)析构函数释放该空间;
(4)编写成员函数SetString,可以将一个字符串赋值给该结构;
(5)编写成员函数PrintString,可以将该结构的内容打印到屏幕上;
(6)编写成员函数AppendString,用于向已有的数据后面添加数据;
(7)编写成员函数Size,用于得到当前数据的真实长度。
编写测试程序,测试这个结构。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>
#include <malloc.h>
#include <memory.h>

struct DateInfo
{
int year;
int month;
int day;

DateInfo (int year,int month,int day)
{
this->year = year;
this->month = month;
this->day = day;

}

DateInfo ()
{
this->year = 2015;
this->month = 4;
this->day = 2;

}

void SetDay(int day)
{
this->day = day;
printf("The date is:%d年%d月%d日\n",year,month,day);
}

void GetDay()
{
printf("The day of date is:%d\n",day);
}

void SetYear(int year)
{
this->year = year;
printf("The date is:%d年%d月%d日\n",year,month,day);
}

void GetYear()
{
printf("The year of date is:%d\n",year);
}

void SetMonth(int month)
{
this->month = month;
printf("The date is:%d年%d月%d日\n",year,month,day);
}

void GetMonth()
{
printf("The month of date is:%d\n",month);

}

void printDateInfo()
{
SetDay(23);
GetDay();

SetYear(2021);
GetYear();

SetMonth(6);
GetMonth();

}

};

//定义TimeInfo结构体继承DateInfo
struct TimeInfo:DateInfo
{
int hour;
int minute;
int second;

TimeInfo (int hour,int minute,int second)
{
this->hour = hour;
this->minute = minute;
this->second = second;
}

TimeInfo()
{
this->hour = 12;
this->minute = 8;
this->second = 28;
}

void GetHour(int hour)
{
this->hour = hour;
}

void GetMinute(int minute)
{
this->minute = minute;
}

void GetSecond(int second)
{
this->second = second;
}

void printTimeinfo()
{
GetHour(10);
GetMinute(25);
GetSecond(55);
}

};

//输出DataInfo和TimeInfo的成员信息
void PrintDataInfo_TimeInfo()
{
DateInfo d;
TimeInfo t;

DateInfo* pd = &d;
TimeInfo* pt = &t;

//使用DateInfo指针指向DateInfo的成员
printf("***************DateInfo指针指向DateInfo成员***************\n\n");
printf("The day is:%d\n",pd->day);
printf("The month is:%d\n",pd->month);
printf("The year is:%d\n\n",pd->year);

//使用TimeInfo指针指向DateInfo的成员
printf("***************TimeInfo指针指向DateInfo成员***************\n\n");
printf("The day is:%d\n", pt->day);
printf("The month is:%d\n", pt->month);
printf("The year is:%d\n\n", pt->year);

//使用TimeInfo指针指向TimeInfo的成员
printf("***************TimeInfo指针指向TimeInfo成员***************\n\n");
printf("The second is:%d\n",pt->second);
printf("The minute is:%d\n",pt->minute);
printf("The hour is:%d\n\n",pt->hour);

}

struct MyString
{
void* MyMalloc;
int temp;

//构造函数能够根据实际传入的参数分配实际存储空间
MyString(int temp)
{
this->temp = temp;
MyMalloc = malloc(this->temp);

if(!MyMalloc)
{
printf("Destructor allocated memory failed,please try again!\n\n");
}
else
{
printf("Destructor allocated memory successfully!\n\n");
}
memset(MyMalloc , 0, temp);
}

//提供一个无参的构造函数,默认分配大小为1024个字节
MyString()
{
MyMalloc = malloc(1024);

if(!MyMalloc)
{
printf("Destructor allocated memory failed,please try again!\n\n");
}
else
{
printf("Destructor allocated memory successfully!\n\n");
}
memset(MyMalloc, 0, 1024);
}

//析构函数释放该空间
~MyString()
{
free(MyMalloc);
printf("Destructor executed successfully!");
}

//编写成员函数SetString,可以将一个字符串赋值给该结构
void SetString(char* str)
{
strcpy((char*)MyMalloc, str);
}

//编写成员函数PrintString,可以将该结构的内容打印到屏幕上
void PrintString()
{
printf("The string is:%s\n\n", MyMalloc);
}

//编写成员函数AppendString,用于向已有的数据后面添加数据
void AppendString(char* addStr)
{
strcat((char*)MyMalloc, addStr);
}

//编写成员函数Size,用于得到当前数据的真实长度
void Size()
{

int length = strlen((char*)MyMalloc);

printf("The size of the data is:%dbytes!\n\n", length);
}

};

//打印MyString信息
void PrintMyStringMessage()
{
printf("***************打印MyString信息***************\n\n");
MyString s;

char str1[] = "Drunk";
s.SetString(str1);
s.PrintString();

char str2[] = "mars";
s.AppendString(str2);
s.PrintString();

s.Size();
}



int main(int argc, char* argv[])
{

PrintDataInfo_TimeInfo();
PrintMyStringMessage();


return 0;

}

最后输出结果如下

image-20210607112229073

权限控制

优化代码

将定义和代码分离,代码会有更好的可读性,但不是必须的

把结构体定义放到xxx.h文件内,把使用成员的函数放到xxx.cpp文件里面。这里有几个注意的点:

1.xxx.h 只是一个文件,可以是任何的后缀名

2.#include的作用是把里面的内容复制过来,如#include "stdaxf.h"

3.xxx.hxxx.cpp不一定同名

这里我把结构体定义放到StdAfx.h头文件里,把函数的使用放到C_4.cpp文件里

image-20210607163202504

image-20210607163257529

权限控制

权限控制这里用到privatepublic两个关键字,在外部调用的时候,private只能自己使用,public可以公用

所以在结构体外调用关键字为private的成员时会报错,而调用public成员则不会报错

image-20210607164837536

image-20210607164810347

但是如果在结构体里调用private成员则可以调用

image-20210607165746482

对于privatepublic关键字的总结:

1
2
3
4
5
6
7
1、对外提供的函数或者变量,发布成public的,但不能随意改动。						

2、可能会变动的函数或者变量,定义成private的 这样编译器会在使用的时候做检测。

3、只有结构体内部的函数才可以访问private的成员。

4public/private可以修饰函数也可以修饰变量。

这里在结构体里面定义的成员一般是不会改变它的值的,但是有一些特殊情况需要改动成员的值的时候,我们就可以给成员函数定义为private,再在结构体里面定义一个使用它的函数即可

这里定义一个SetAge()判断输入的年龄是否合理、GetAge()输出年龄的值

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
struct Person
{
private:
int age;

public:
void SetAge(int age)
{
if(age > 150)
{
this->age = 0;
printf("The age is error,please try again!");
}
else
{
this->age = age;
}
}

int GetAge()
{
return age;
}
};

这里我首先输入一个超过年龄的数字看一下返回值

image-20210607172136245

再输入一个正常的年龄,如图所示

image-20210607172209984

private深入探讨

这里我用设置一个private成员x和一个public成员y,虽然private不能继承到子类,但是可以看到这里还是分配了8字节的空间

image-20210607184301704

class和struct的区别

1
2
3
1.编译器中默认class中的成员为private,默认struct中的成员为public

2.父类中的程序继承后变成private属性

这里我用class就会显示cannot access private member,是因为class的成员默认是private属性

image-20210607185657747

而使用struct的话则不会报错,因为默认为public属性

image-20210607185531793

继承同理,这里先使用struct继承父类是能够访问到父类成员的

image-20210607190218744

但是这里如果直接使用class则会报错,是因为class会默认继承父类时的类型为private

image-20210607190333614

如果不希望改变成员属性需要给成员加上public属性

image-20210607190636541

private是否被继承

1
2
3
1.父类中的私有成员是会被继承的

2.但是编译器不允许直接对父类中的私有成员进行访问

private只能在class类里面定义函数使用,其他继承的子类一律不能使用

这里我定义一个Base的类且成员类型为private,定义一个Sub的类继承Base类,打印Sub大小为16证明父类成员还是会被继承

image-20210607192102481

这里我只调用了子类Sub,但是可以看到Base()函数中的输出语句被执行了,证明Base()也得到了调用

image-20210607193523050

Sub s处设置一个断点进入反汇编,发现这里调用了一个地址

image-20210607193720830

反汇编跟进去,发现这里是sub的构造函数,这个sub的构造函数我们是没有定义的,这里是编译器替我们生成的

image-20210607193826221

继续往下跟发现这里调用了Base的构造函数

image-20210607193912380

F11跟进构造函数

image-20210607193935408

最终发现来到了Base()这个位置

image-20210607193956869

这里说明先运行父类的构造函数,再执行子类的构造函数

为了进一步验证猜想改进代码如下:

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:
Base()
{
printf("This is Base's constructor!\n");
x = 11;
y = 12;
}
private:
int x;
int y;
};

class Sub:public Base
{
public:
Sub()
{
printf("This is Sub's constructor!\n");
}

int a;
int b;
};

运行结果如下

image-20210607194517709

我们上面已经论证了父类是会继承到子类的,所以这里尝试用指针进行访问

image-20210607194919809

C++权限控制练习题

1
2
3
4
5
将上一节课的所有练习改为class实现

1、添加private/public进行权限控制

2、将类的定义与实现分开来写:定义写到xxx.h中 函数实现写在xxx.cpp

实现代码如下:

PS:PrintDateInfo_TimeInfo()函数因为权限问题暂未实现,明日补上

.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
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
//class DateInfo
class DateInfo
{
private:
int year;
int month;
int day;

public:
DateInfo (int year,int month,int day);

DateInfo ();

void SetDay(int day);

void GetDay();

void SetYear(int year);

void GetYear();

void SetMonth(int month);

void GetMonth();

void printDateInfo();
};

//class TimeInfo::public DateInfo
class TimeInfo:public DateInfo
{
private:
int hour;
int minute;
int second;

public:
TimeInfo (int hour,int minute,int second);

TimeInfo();

void GetHour(int hour);

void GetMinute(int minute);

void GetSecond(int second);

void printTimeinfo();
};

void PrintDateInfo_TimeInfo();

//class MyString
class MyString
{
private:
void* MyMalloc;
int temp;

public:
//构造函数能够根据实际传入的参数分配实际存储空间
MyString(int temp);

//提供一个无参的构造函数,默认分配大小为1024个字节
MyString();

//析构函数释放该空间
~MyString();

//编写成员函数SetString,可以将一个字符串赋值给该结构
void SetString(char* str);

//编写成员函数PrintString,可以将该结构的内容打印到屏幕上
void PrintString();

//编写成员函数AppendString,用于向已有的数据后面添加数据
void AppendString(char* addStr);

//编写成员函数Size,用于得到当前数据的真实长度
void Size();
};

//打印MyString信息
void PrintMyStringMessage();

.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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>
#include <malloc.h>
#include <memory.h>

//class DateInfo

DateInfo::DateInfo (int year,int month,int day)
{
this->year = year;
this->month = month;
this->day = day;

}

DateInfo::DateInfo ()
{
this->year = 2015;
this->month = 4;
this->day = 2;

}

void DateInfo::SetDay(int day)
{
this->day = day;
printf("The date is:%d年%d月%d日\n",year,month,day);
}

void DateInfo::GetDay()
{
printf("The day of date is:%d\n",day);
}

void DateInfo::SetYear(int year)
{
this->year = year;
printf("The date is:%d年%d月%d日\n",year,month,day);
}

void DateInfo::GetYear()
{
printf("The year of date is:%d\n",year);
}

void DateInfo::SetMonth(int month)
{
this->month = month;
printf("The date is:%d年%d月%d日\n",year,month,day);
}

void DateInfo::GetMonth()
{
printf("The month of date is:%d\n",month);

}

void DateInfo::printDateInfo()
{
SetDay(23);
GetDay();

SetYear(2021);
GetYear();

SetMonth(6);
GetMonth();
}



//class TimeInfo:DateInfo

TimeInfo::TimeInfo (int hour,int minute,int second)
{
this->hour = hour;
this->minute = minute;
this->second = second;
}

TimeInfo::TimeInfo()
{
this->hour = 12;
this->minute = 8;
this->second = 28;
}

void TimeInfo::GetHour(int hour)
{
this->hour = hour;
}

void TimeInfo::GetMinute(int minute)
{
this->minute = minute;
}

void TimeInfo::GetSecond(int second)
{
this->second = second;
}

void TimeInfo::printTimeinfo()
{
GetHour(10);
GetMinute(25);
GetSecond(55);
}


/*
void PrintDateInfo_TimeInfo()
{
DateInfo d;
TimeInfo t;

DateInfo* pd = &d;
TimeInfo* pt = &t;

//使用DateInfo指针指向DateInfo的成员
printf("***************DateInfo指针指向DateInfo成员***************\n\n");
printf("The day is:%d\n",pd->day);
printf("The month is:%d\n",pd->month);
printf("The year is:%d\n\n",pd->year);

//使用TimeInfo指针指向DateInfo的成员
printf("***************TimeInfo指针指向DateInfo成员***************\n\n");
printf("The day is:%d\n", pt->day);
printf("The month is:%d\n", pt->month);
printf("The year is:%d\n\n", pt->year);

//使用TimeInfo指针指向TimeInfo的成员
printf("***************TimeInfo指针指向TimeInfo成员***************\n\n");
printf("The second is:%d\n",pt->second);
printf("The minute is:%d\n",pt->minute);
printf("The hour is:%d\n\n",pt->hour);

}*/

//class MyString

//构造函数能够根据实际传入的参数分配实际存储空间
MyString::MyString(int temp)
{
this->temp = temp;
MyMalloc = malloc(this->temp);

if(!MyMalloc)
{
printf("Destructor allocated memory failed,please try again!\n\n");
}
else
{
printf("Destructor allocated memory successfully!\n\n");
}
memset(MyMalloc , 0, temp);
}

//提供一个无参的构造函数,默认分配大小为1024个字节
MyString::MyString()
{
MyMalloc = malloc(1024);

if(!MyMalloc)
{
printf("Destructor allocated memory failed,please try again!\n\n");
}
else
{
printf("Destructor allocated memory successfully!\n\n");
}
memset(MyMalloc, 0, 1024);
}

//析构函数释放该空间
MyString::~MyString()
{
free(MyMalloc);
printf("Destructor executed successfully!");
}

//编写成员函数SetString,可以将一个字符串赋值给该结构
void MyString::SetString(char* str)
{
strcpy((char*)MyMalloc, str);
}

//编写成员函数PrintString,可以将该结构的内容打印到屏幕上
void MyString::PrintString()
{
printf("The string is:%s\n\n", MyMalloc);
}

//编写成员函数AppendString,用于向已有的数据后面添加数据
void MyString::AppendString(char* addStr)
{
strcat((char*)MyMalloc, addStr);
}

//编写成员函数Size,用于得到当前数据的真实长度
void MyString::Size()
{
int length = strlen((char*)MyMalloc);

printf("The size of the data is:%dbytes!\n\n", length);
}


//打印MyString信息
void PrintMyStringMessage()
{
printf("***************打印MyString信息***************\n\n");
MyString s;

char str1[] = "Drunk";
s.SetString(str1);
s.PrintString();

char str2[] = "mars";
s.AppendString(str2);
s.PrintString();

s.Size();
}

int main(int argc, char* argv[])
{

//PrintDateInfo_TimeInfo();
PrintMyStringMessage();

return 0;
}

虚函数表

虚函数:间接调用

直接调用:E8 call 地址

间接调用(IAT表):FF call […]

通过对象直接调用函数生成的反汇编代码相同

image-20210608222837038

image-20210608222959586

使用指针调用函数

image-20210608223119163

发现void为E8直接调用,virtual void为FF间接调用

image-20210608223201780

深入虚函数调用

当类中有虚函数时会多一个4字节的属性,无论有多少个virtual都只会增加4字节

image-20210608223817747

定义一个构造函数

image-20210608224147017

查看一下base里面存的数据

image-20210608224308866

在一个对象加一个virtual时,会在首地址多出来4字节,即0012FF74 20 20 42 00,这里对应的virtual的地址为422020

image-20210608224420817

422020这个地址得到1E 10 40 00

image-20210608224632596

这里使用一个Base*指针去访问下Function_1()这个函数,进到反汇编,F10单步往下跟得到ecx分别为第一个virtual成员、x、y

image-20210608225210635

现在ecx为0012FF74这个地址,edx,dword ptr [ecx]即取ecx里面的4个字节(DWORD)赋给edx,即edx=42201C

image-20210608225806286

call dword ptr [edx]这个语句是用来取edx这个地址里面的值,即42201C这个地址里面的值为1E 10 40 00

image-20210608230545746

这里再对两个virtual函数的反汇编进行分析,取ecx地址里的值为42201C

image-20210608231613560

这里Function_1() call dword ptr [edx]取的值为40101EFunction_2() call dword ptr [edx+4]取的值为401023

virtual多出来的四个字节指向一个数组,数组里面存的是当前对象所有虚函数的地址

image-20210608231941365

ecx的首地址存放的是虚函数表

image-20210608232545135

打印虚函数表

打印this指针

image-20210608234136872

虚表的地址是this指针的前4个字节,使用*(int*)转型取前四个字节即可得到虚函数表的地址

image-20210608234229032

通过函数指针调用函数验证正确性,首先定义一个新的函数类型用typedef void(*pFunction)(void),后赋予变量pFn

这里如果i=0时,即int temp = *((int*)(*(int*)&base)+0);,这里就意味着取虚函数表的前4位,所以用一个循环进行遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void TestMethod()					
{
//查看 Sub 的虚函数表
Base base;

//对象的前四个字节就是虚函数表
printf("base 的虚函数表地址为:%x\n",*(int*)&base);

//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);

pFunction pFn;

for(int i=0;i<2;i++)
{
int temp = *((int*)(*(int*)&base)+i);
pFn = (pFunction)temp;
pFn();
}

}

image-20210608235042527

对于虚函数表的总结

image-20210608235230884

C++虚函数表练习

1
2
3
1、单继承无函数覆盖(打印Sub对象的虚函数表)

2、单继承有函数覆盖(打印Sub对象的虚函数表)

对于单继承有函数覆盖继承的研究

结构体如下:

image-20210609111825635

这里取一个Sub1的指针,使用指针的时候发现只能够指向Function1.2.3.6,猜测相同的覆盖的函数只取一个进入虚函数表

image-20210609111742780

这里使用ps指针指向Function_1进入反汇编进行查看,[ecx]对应的值为42329C,跳转到42329C即虚函数表的地址

image-20210609112041133

进入42329C这个虚函数表地址后可以发现,虚函数表里只有4个数组,所以可以证明在子类含有父类的函数时使用自身的函数,子类中不含父类函数时把函数复制到虚函数表里面

image-20210609112147302

总结:

1
2
3
4
5
6
7
8
1.在单继承无函数覆盖的情况下,先继承父类的虚函数表,再是自己虚函数表的地址

2.在单继承有函数覆盖的情况下,base为父类有三个virtual函数,所以在虚函数表里面有3个连续的函数地址

3.在单继承有函数覆盖的情况下,sub为子类继承base有重复名的函数,有以下几个结论
(1)虚函数表里有4个连续的函数地址(复制总数 = 继承父类的函数 + 自己的函数 - 重复的函数 = 4)
(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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include "stdafx.h"
#include <stdio.h>

//1、单继承无函数覆盖(打印Sub对象的虚函数表)
struct Base
{
public:
virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n\n");
}
};
struct Sub:Base
{
public:
virtual void Function_4()
{
printf("Sub:Function_4...\n");
}
virtual void Function_5()
{
printf("Sub:Function_5...\n");
}
virtual void Function_6()
{
printf("Sub:Function_6...\n\n");
}
};


//2、单继承有函数覆盖(打印Sub对象的虚函数表)
struct Base1
{
public:
virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n");
}
};
struct Sub1:Base1
{
public:
virtual void Function_1()
{
printf("Sub:Function_1...\n");
}
virtual void Function_2()
{
printf("Sub:Function_2...\n");
}
virtual void Function_6()
{
printf("Sub:Function_6...\n");
}
};

//打印base的虚函数表
void PrintBaseVT()
{

Base base;
int baseVTaddress = *(int*)&base; //base的虚表地址

printf("***************打印Base对象的虚函数表***************\n\n");

printf("***************base 的虚函数表地址为:%x***************\n\n",baseVTaddress);


typedef void(*pFunction)(void);

pFunction pFn;

for(int i = 0; i < 3; i++)
{
int temp = *((int*)(*(int*)&base)+i);
pFn = (pFunction)temp;
pFn();
}

}

//单继承无函数覆盖(打印sub对象的虚函数表)
void PrintSubVT1()
{
Sub sub;
int subVTaddress = *(int*)&sub; //sub虚表地址

printf("***************单继承无函数覆盖(打印Sub对象的虚函数表)***************\n\n");

printf("***************sub 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;

for(int i = 0 ; i < 6; i++)
{
int temp = *((int*)(*(int*)&sub)+i);
pFn = (pFunction)temp;
pFn();
}
}

//单继承有函数覆盖(打印sub对象的虚函数表)
void PrintSubVT2()
{
Base base;
Sub1 sub1;

int baseVTaddress = *(int*)&base; //base虚表地址
int subVTaddress = *(int*)&sub1; //sub虚表地址




printf("***************单继承有函数覆盖(打印Sub对象的虚函数表)***************\n\n");

printf("***************sub1 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;


for(int i = 0 ; i < 4 ; i++)
{
int temp = *((int*)(*(int*)&sub1)+i);
pFn = (pFunction)temp;
pFn();
}

}



int main(int argc, char* argv[])
{
PrintBaseVT();
PrintSubVT1();
PrintSubVT2();

return 0;
}

实现结果如下:

image-20210609113951029

动态绑定 多态

继承总结

1
2
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
//1、单继承无函数覆盖(打印Sub对象的虚函数表)
struct Base
{
public:
virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n\n");
}
};
struct Sub:Base
{
public:
virtual void Function_4()
{
printf("Sub:Function_4...\n");
}
virtual void Function_5()
{
printf("Sub:Function_5...\n");
}
virtual void Function_6()
{
printf("Sub:Function_6...\n\n");
}
};

//单继承无函数覆盖(打印sub对象的虚函数表)
void PrintSubVT1()
{
Sub sub;
int subVTaddress = *(int*)&sub; //sub虚表地址

printf("***************单继承无函数覆盖(打印Sub对象的虚函数表)***************\n\n");

printf("***************sub 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;

for(int i = 0 ; i < 6; i++)
{
int temp = *((int*)(*(int*)&sub)+i);
pFn = (pFunction)temp;
pFn();
}
}

image-20210609174045934

image-20210609161428609

单继承有函数覆盖

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
//2、单继承有函数覆盖(打印Sub对象的虚函数表)
struct Base1
{
public:
virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n");
}
};
struct Sub1:Base1
{
public:
virtual void Function_1()
{
printf("Sub:Function_1...\n");
}
virtual void Function_2()
{
printf("Sub:Function_2...\n");
}
virtual void Function_6()
{
printf("Sub:Function_6...\n");
}
};

//单继承有函数覆盖(打印sub对象的虚函数表)
void PrintSubVT2()
{
Base base;
Sub1 sub1;

int baseVTaddress = *(int*)&base; //base虚表地址
int subVTaddress = *(int*)&sub1; //sub虚表地址




printf("***************单继承有函数覆盖(打印Sub对象的虚函数表)***************\n\n");

printf("***************sub1 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;


for(int i = 0 ; i < 4 ; i++)
{
int temp = *((int*)(*(int*)&sub1)+i);
pFn = (pFunction)temp;
pFn();
}
}

image-20210609174113045

image-20210609161731230

多继承无函数覆盖

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
//3、多继承无函数覆盖
struct Base2
{
public:
virtual void Fn_1()
{
printf("Base2:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base2:Fn_2...\n");
}
};
struct Base3
{
public:
virtual void Fn_3()
{
printf("Base3:Fn_3...\n");
}
virtual void Fn_4()
{
printf("Base3:Fn_4...\n");
}
};
struct Sub2:Base2,Base3
{
public:
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
virtual void Fn_6()
{
printf("Sub:Fn_6...\n");
}
};

//多继承无函数覆盖
void PrintSubVT3()
{
{
Sub2 sub2;
int subVTaddress = *(int*)&sub2;
int subVTaddress2 = *(int*)&sub2 + 4;

printf("***************多继承无函数覆盖(打印Sub对象的虚函数表)***************\n\n");


printf("***************sub2 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;

for(int i = 0; i < 6; i++)
{
int temp = *((int*)(*(int*)&sub2)+i);
if(temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}

//对象的第二个四字节是Base2的虚表
printf("***************sub2 的虚函数表地址为:%x***************\n\n",subVTaddress2);

pFunction pFn1;

for(int k = 0 ; k < 2 ; k++)
{
int temp = *((int*)(*(int*)((int)&sub2+4))+k);
pFn1 = (pFunction)temp;
pFn1();
}

}

}

这里注意一下在打印的时候使用了两个循环,进入反汇编把sub2拖进去,发现这里指向了两个地址

image-20210609163511707

进入地址查看发现这两个地址都分别对应了一个虚函数表

image-20210609163611639

image-20210609163642893

这里运行一下,发现子类的虚函数表位于第一个结构体的虚函数表里面,第二个结构体单独有一个虚函数表

image-20210609163748465

结构如下:

image-20210609163841925

多继承有函数覆盖

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
//4、多继承有函数覆盖

struct Base4
{
public:
virtual void Fn_1()
{
printf("Base4:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base4:Fn_2...\n");
}
};
struct Base5
{
public:
virtual void Fn_3()
{
printf("Base5:Fn_3...\n");
}
virtual void Fn_4()
{
printf("Base5:Fn_4...\n");
}
};
struct Sub3:Base4,Base5
{
public:
virtual void Fn_1()
{
printf("Sub:Fn_1...\n");
}
virtual void Fn_3()
{
printf("Sub:Fn_3...\n");
}
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
};

//多继承有函数覆盖

void PrintSubVT4()
{

Sub3 sub3;

int subVTaddress = *(int*)&sub3;
int subVTaddress2 = *(int*)&sub3 + 4;


printf("***************多继承有函数覆盖(打印Sub对象的虚函数表)***************\n\n");

printf("***************sub3 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;

for(int i= 0 ; i < 6; i++)
{
int temp = *((int*)(*(int*)&sub3)+i);
if(temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}

//对象的第二个四字节是Base2的虚表
printf("***************sub3 的虚函数表地址为:%x***************\n\n",subVTaddress2);

pFunction pFn1;

for(int k = 0; k < 2; k++)
{
int temp = *((int*)(*(int*)((int)&sub3+4))+k);
pFn1 = (pFunction)temp;
pFn1();
}

}

这里Sub3里面的函数为Function1、3、5,继承Base4Function1、5位于第一个虚表里面,继承Base5Function4位于第二个虚表里面

image-20210609164621314

image-20210609164721636

多重继承无函数覆盖

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
//5、多重继承无函数覆盖

struct Base6
{
public:
virtual void Fn_1()
{
printf("Base1:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base1:Fn_2...\n");
}
};
struct Base7:Base6
{
public:
virtual void Fn_3()
{
printf("Base2:Fn_3...\n");
}
virtual void Fn_4()
{
printf("Base2:Fn_4...\n");
}
};
struct Sub4:Base7
{
public:
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
virtual void Fn_6()
{
printf("Sub:Fn_6...\n");
}
};

//多重继承无函数覆盖

void PrintSubVT5()
{

Sub4 sub4;
int subVTaddress = *(int*)&sub4;

printf("***************多重继承无函数覆盖(打印Sub对象的虚函数表)***************\n\n");

printf("***************This size of sub4 is:%x***************\n\n",sizeof(sub4));

printf("***************sub4 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;

for(int i = 0; i < 6; i++)
{
int temp = *((int*)(*(int*)&sub4)+i);
if(temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}
}

这里看一下打印的结果,在输出虚函数表的函数里我加了一行判断sub4大小的语句,可以看到这里sub4的大小是4,那么就可以说明sub4里面只有一个虚函数表,下面打印出来的虚函数也印证了这一观点

image-20210609170613885

image-20210609170742874

多重继承有函数覆盖

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
//6、多重继承有函数覆盖

struct Base8
{
public:
virtual void Fn_1()
{
printf("Base8:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base8:Fn_2...\n");
}
};
struct Base9:Base8
{
public:
virtual void Fn_1()
{
printf("Base9:Fn_1...\n");
}
virtual void Fn_3()
{
printf("Base9:Fn_3...\n");
}
};
struct Sub5:Base9
{
public:
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
};

//多重继承有函数覆盖

void PrintSubVT6()
{

Sub5 sub5;
int subVTaddress = *(int*)&sub5;

printf("***************多重继承有函数覆盖(打印Sub对象的虚函数表)***************\n\n");

printf("***************This size of sub5 is:%x***************\n\n",sizeof(sub5));

printf("***************sub5 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;

for(int i = 0; i < 4; i++)
{
int temp = *((int*)(*(int*)&sub5)+i);
if(temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}
}

与父类重复的函数不复制父类函数,直接使用自己的函数,不重复的复制父类的函数

image-20210609171940583

image-20210609172144923

多重继承有函数覆盖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
//7、多重继承有函数覆盖2

struct Base10
{
public:
virtual void Fn_1()
{
printf("Base10:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base10:Fn_2...\n");
}
};
struct Base11:Base10
{
public:
virtual void Fn_1()
{
printf("Base11:Fn_1...\n");
}
virtual void Fn_3()
{
printf("Base11:Fn_3...\n");
}
};
struct Sub6:Base11
{
public:
virtual void Fn_1()
{
printf("Sub:Fn_1...\n");
}
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
};

void PrintSubVT7()
{

Sub6 sub6;
int subVTaddress = *(int*)&sub6;

printf("***************多重继承有函数覆盖2(打印Sub对象的虚函数表)***************\n\n");

printf("***************This size of sub6 is:%x***************\n\n",sizeof(sub6));

printf("***************sub6 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;

for(int i = 0; i < 4; i++)
{
int temp = *((int*)(*(int*)&sub6)+i);
if(temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}
}

与父类重复的函数不复制父类函数,直接使用自己的函数,不重复的复制父类的函数

image-20210609172519250

image-20210609172634100

多重继承有函数覆盖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
//8、多重继承有函数覆盖3

struct Base12
{
public:
virtual void Fn_1()
{
printf("Base12:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base12:Fn_2...\n");
}
};
struct Base13:Base12
{
public:
virtual void Fn_3()
{
printf("Base13:Fn_3...\n");
}
};
struct Sub7:Base13
{
public:
virtual void Fn_1()
{
printf("Sub7:Fn_1...\n");
}
virtual void Fn_3()
{
printf("Sub7:Fn_3...\n");
}
};

//多重继承有函数覆盖3

void PrintSubVT8()
{

Sub7 sub7;
int subVTaddress = *(int*)&sub7;

printf("***************多重继承有函数覆盖3(打印Sub对象的虚函数表)***************\n\n");

printf("***************This size of sub7 is:%x***************\n\n",sizeof(sub7));

printf("***************sub7 的虚函数表地址为:%x***************\n\n",subVTaddress);

typedef void(*pFunction)(void);

pFunction pFn;

for(int i = 0; i < 4; i++)
{
int temp = *((int*)(*(int*)&sub7)+i);
if(temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}
}

与父类重复的函数不复制父类函数,直接使用自己的函数,不重复的复制父类的函数

image-20210609173004516

image-20210609173011572

前期绑定和动态绑定&多态

前期绑定即编译期绑定:在函数编译过后的地址就能够确定

动态绑定即运行期绑定(晚绑定):在调用的时候才能够确定函数地址

可以看到这里int n = pb->x这里的地址是能够确定的,所以为前期绑定

image-20210609190137320

这里function1这个函数也是调用了Base类的function1的地址,地址已经绑定完,所以也为前期绑定。注意一下这里Base类的function1不为虚函数

image-20210609190518265

image-20210609190440775

这里function2这个函数调用的是edx地址里存的虚表,这里使用virtual函数使用FF call的原因是因为edx里面的值可能被子类重写

image-20210609190758887

动态绑定探究

这里生成两个class,分别为BaseSub

image-20210609191523303

这里传入指针指向函数的虚表

image-20210609191634571

输出结果如下

image-20210609191436409

改为Sub类传入指针

image-20210609191742684

输出如下所示

image-20210609191712899

这里就可以总结为以下几点:

1
2
3
4
5
6
7
1、只有virtual的函数是动态绑定			

2、动态绑定还有一个名字:多态

3、多态通俗的来说就是同一函数结构体现出了不同的行为

4、C++动态绑定是通过虚表实现的

多态探究

这里在不适用virtual 的情况下只会调用父类的printf()函数,这样就没有显示出多态

image-20210609194004214

加上virtual之后就能够显示出多态的特性

image-20210609194122417

完整代码如下:

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
#include "stdafx.h"
#include <stdio.h>

class Base
{
public:
int x;
int y;
public:
Base()
{
x = 1;
y = 2;
}
virtual void Print()
{
printf("Base:%x %x\n",x,y);
}

};

class Sub:public Base
{
public:
int z;
public:
Sub()
{
x = 4;
y = 5;
z = 6;
}
virtual void Print()
{
printf("Sub:%x %x %x\n",x,y,z);
}

};

class Sub1:public Base
{
public:
int a;
public:
Sub1()
{
x = 7;
y = 8;
a = 9;
}
virtual void Print()
{
printf("Sub1:%x %x %x\n",x,y,a);
}

};

void TestBound()
{
Base b;
Sub s;
Sub1 s1;

Base* arr[] = {&b,&s,&s1};

for(int i = 0 ; i < 3 ; i++)
{
arr[i] ->Print();
}
}

int main(int argc, char* argv[])
{
TestBound();

return 0;
}

总结:

这里分析代码可以发现,这里首先定义了一个Base* arr[]的数组指针指向BaseSubSub1这三个类的地址,通过一个for循环去调用这几个类里面的Print()方法来打印类里面定义的数字。

只有能产生虚表才能拥有多态,因为多态是建立在虚表的基础上的。如果一个父类指针想要访问子类指针只能通过多态的方式进行访问。

析构函数写成virtual的原因

如果不写成virtual,在使用析构函数释放内存的时候,释放的是父类的内存。

模板

冒泡排序

因为这里数组的下标是从0开始,如果k的长度直接写length的话会产生越界的情况出现,所以这里循环条件是k < length - 1;我们知道冒泡排序是把最大的数放到数组的最后面,那么在我们把一个最大的数放到数组最后时,只需要比较剩下几个数的大小即可,所以这里内层函数写i < length - 1 - k

实现代码如下:

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 "stdafx.h"
#include <stdio.h>

void sort(int* arr,int length)
{
for(int k = 0 ; k < length - 1 ; k++)
{
for(int i = 0 ; i < length - 1 - k; i++)
{
if(arr[i] > arr[i+1])
{
int temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
}
}



int main(int argc, char* argv[])
{
int arr[] = {2,6,9,8,3};

sort(arr,5);

for(int i = 0; i < 5; i++)
{
printf("The number of arr is:%d\n",arr[i]);
}

return 0;
}

折半查找

注意折半查找只能使用在已经排好序的数组里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int Find(int* arr,int nLength,int nElement)						
{
int nBegin = 0,nEnd = nLength-1,nIndex;
while(nBegin<=nEnd)
{
nIndex = (nBegin+nEnd)/2;//(nBegin+nEnd)>>1 右移一位,使用位运算
if(nElement > arr[nIndex])
{
nBegin = nIndex+1;
}
else if(nElement < arr[nIndex])
{
nEnd = nIndex-1;
}
else
{
return nIndex;
}
}
return -1;
}

模板

在函数中使用template<class T>即为模板,这里的T可以为任何字母。注意模板需要替换的只是可能要改变数据类型的变量,length的数据类型一定是int,所以可以不使用模板

image-20210610111335687

引用 友元 运算符重载

先看一个程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "stdafx.h"
#include <stdio.h>

void test(int x)
{
x = 1;

}


int main(int argc, char* argv[])
{
int a = 2;

test(a);

printf("The value is : %x",a);

return 0;
}

这里输出的值应该为2,这里进入反汇编论证,可以看到这里把2传入ebp-4当作参数存储,在调用test(a)的时候将ebp-4的值传入eax,再把eax压入堆栈,在使用call调用函数,所以这里只使用了2这个参数进行操作,所以输出的值应该为2

image-20210611162720678

这里修改代码之后如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "stdafx.h"
#include <stdio.h>

void test(int* x)
{
*x = 1;

}


int main(int argc, char* argv[])
{
int a = 2;

test(&a);

printf("The value is : %x",a);

return 0;
}

进入到反汇编查看这里已经变为了取[ebp - 4]这个地址,这里再跟进call函数进行查看

image-20210611163242411

可以看到call这个函数主要实现了两个功能,一是把ebp + 8的地址传入eax,再把1传入eax所在的地址。所以这个地方输出的值应该为1

image-20210611163427718

引用

我们可以看到test()这个函数传入的是int*这个类型,那么这也出现了一个问题,如果这样定义的话就可能出现给指针乱赋值的情况

image-20210611171752250

这里直接传入int&指针的话就不用担心指针的值再被修改

image-20210611172143169

这里分别进入指针参数传递和引用参数传递的反汇编进行查看,可以发现在反汇编层面这两种传参方式都是相同的

image-20210611172309211

所以在c++中test()函数里既有可能是int类型的参数,也有可能是int&类型的引用

image-20210611172532488

对于引用的总结

1
2
3
4
5
6
7
8
9
1、引用类型是C++里面的类型								

2、引用类型只能赋值一次,不能重新赋值

3、引用只是变量的一个别名

4、引用可以理解成是编译器维护的一个指针,但并不占用空间

5、使用引用可以像指针那样去访问、修改对象的内容,但更加安全

指针和引用的区别

初始化阶段

image-20210611213705929

在初始化阶段指针和引用的行为都是一样的,先将x的地址加载到寄存器中,然后将寄存器的值存到内存里。

赋值

image-20210611221844925

指针方面:内存中的值存入edx,edx+4(对应32位下int的size),edx重新写入内存。

引用方面:内存中的值存入eax,找到eax值对应的内存单元中的数字存入ecx,ecx+1,把内存中的值存入edx,ecx的值写入edx所在的地址。

通过对二者赋值,可以发现,引用变量addr的确有自己的地址,它在内存中是占空间的。并且指针变量addr完全具有“主导权”,对指针变量px进行赋值,改变的就是它本身;而引用变量addr则完全没有“主导权”,对引用变量addr进行赋值,改变的并不是它本身,而是通过它所找到的x。

取地址

image-20210611223519161

指针方面:lea取ebp-8的地址存入eax,再把eax的值存入ebp-10h这个内存地址,相当于把ebp-8的这个地址存入了ebp-10h

引用方面:取ebp-0ch的值放入ecx,再把ecx的值存入ebp-14h这个内存地址,相当于把ebp-0ch这个地址的值存入了ebp-14h

由此可以发现,对指针取地址,取出的就是指针变量的地址,而对引用取地址,取出的实际上是引用变量所在内存单元中的值,也就是它引用的变量的地址

总结

1.引用和指针都是占用内存的。引用变量和指针变量各自的内存单元中,存放的都是它们引用或指向的变量的地址;

2.引用实际上是把引用变量本身给隐藏了,表面上对引用变量进行赋值,实际上改变的是它引用的变量的值;表面上是对引用变量取地址,实际上取出来的是它引用的变量的地址;

3.由于引用变量的操作的实际对象都是引用变量所在内存单元中的值,因此引用变量必须和另一个变量关联起来。也就是说,引用必须初始化。原因很简单,如果你不对一个引用进行初始化,那么引用变量的内存单元中存放的都是垃圾数据,后面对引用变量进行操作时就会通过这些垃圾数据找到对应的内存地址,这是非常危险的;

 那么直接初始化的时候对引用赋一个常量值呢?这实际上也是不对的,因为引用的初始化实际是用用来初始化的变量的地址来初始化引用变量它自己的内存单元,而一个常量值哪有地址呢?虽然在C++11中已经可以用一个常量来初始化右值引用,但是通过反汇编可以看到,右值引用的根本,还是先用一个地址去保存这个常量值,然后再用这个地址去初始化引用变量。

 而指针变量则完全不一样了,指针变量的操作的实际对象都是它自己,因此指针变量不像引用那样必须初始化,但是为了安全,也应该初始化。

4.依然是由于引用变量的一切操作实际对象都是它引用的变量,因此从用户角度来说是没有入口让用户去修改引用变量本身的。换句话说,用户层面没有任何办法去改变引用变量内存单元中存放的地址,这也就是为什么引用一旦初始化后就无法再修改。

5.由于引用变量自身对于用户是不可见的,对引用变量取地址得到的也不是引用变量的地址,因此你无法让一个引用变量的内存中存放另一个引用变量的地址,换句话说,不存在引用的引用。而相反,由于指针变量取出来的地址就是它本身的地址,因此你完全可以把一个指针变量的地址存放在另一个指针变量的内存中,这也就是为什么可以存在多级指针,但是多级引用是不允许的。

6.关于“引用是变量的别名”的考虑。感觉这句话既对也不对,说它不对是因为引用变量和引用的变量二者实际上是独立的两块内存单元,只不过前者依托后者而存在,但是这并不能说前者就是后者的别名,这是矛盾的;说它对,是因为对引用变量进行操作时,改变的对象实际上都是引用的变量而不是引用变量,这就感觉引用变量只是一层伪装,真正的还是它引用的变量,从这个角度来说,引用确实可以说是变量的别名。

7.函数传指针与传引用的区别。函数传指针的原型类似于int add(int * a, int * b);这类函数的形参是指针,调用方式为int x = 1, y = 2; add(&x,&y);那么传入的实参则是变量的地址,因此在函数内部,是用地址型实参&x和&y来初始化形参a和b,相当于int *a = &x,int * b = &y的;因此形参a和b内存单元中存放的是x和y的地址。

  而对于函数传引用,函数原型则类似于int add(int & a,int & b),这类函数的形参是引用,调用方式为int x = 1, y = 2; add(x,y);传入的实参就是x和y变量自身,在函数内部,则是用x和y来初始化a和b,相当于int &a = x,int &b = y,因此,形参a和b内存单元中存放的也是x和y的地址,不过它毕竟是引用,如前面所说,当你试图对形参a或b的值进行改变的时候,改变的不是它内存中存放的&x和&y,其实是x或y;当你对形参a或b取地址时,取出来的并不是形参本身的地址,而是它内存中存放的&x和&y。

  而再说一个与本文无关的函数传值,如int add(int a,int b);这类函数的形参只是普通的int型变量,当调用add(x,y)时,在函数内部实际上就是用x和y来初始化a和b,相当于int a = x,int b = y,所以形参和实参实际上只是值相同而已,改变形参并不会影响实参。

友元函数

const关键字定义为只读,当指针指向函数时不能修改

因为这里为private成员,所以不能够访问到父类的x、y成员

image-20210611195552995

使用友元函数进行声明

1
2
3
friend void print(const Person& refPer);	//声明友元函数

void print(const Person& p) //定义函数

image-20210611195916888

友元函数总结:

1
2
3
4
5
6
7
8
9
10
11
什么情况下需要友元函数:				

(1) 运算符重载的某些场合需要使用友元

(2) 两个类要共享数据的时候

友元函数和类的成员函数的区别:

(1) 成员函数有this指针,而友元函数没有this指针

(2) 友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友

运算符重载

首先这里要了解为什么C++要进行运算符重载

C++ 预定义的运算符,只能用于基本数据类型的运算:整型、实型、字符型、逻辑型等等,且不能用于对象的运算。但是我们有时候又很需要在对象之间能用运算符,那么这时我们就要重载运算符,使得运算符能用于对象之间的运算。

比如,在数学上,两个复数可以直接进行+、-等运算,但在C++中,直接将+或-用于复数对象是不允许的。有时会希望,让对象也能通过运算符进行运算。这样代码就更简洁,也容易理解。

例如:

complex_a 和 complex_b 是两个复数对象,求两个复数的和,希望的能直接写成:complex_a + complex_b

这时我们就需要对 + 号运算符进行重载。

这里举一个更加通俗易懂的例子:我写了一个类A,而我要让A定义的a和b相加,因为这个类是自定义的,所以a和b相加,系统是不可能知道你到底希望他得到什么结果,而这时可以重载运算符的话,那就方便的多了,我直接定义a + b的结果为多少即可。

这里实现一个打印类里面的成员的函数

image-20210611202308339

这里我想每执行一次lowValuehighValue能够每次都加1,增加一个Plus函数

image-20210611202235226

使用运算符重载实现,即定义Number operator++();

image-20210611203228056

定义Number operator+()实现自加

image-20210611205522482

总结:

1
2
3
1、运算符重载就是函数替换(operator)		

2、. :: ?: sizeof # 不能重载

运算符重载可实现的符号代码如下:

注意这里在执行其他运算符(++ – + - * / )的时候,因为类型是从intint类型不变,所以还是Number类型;但是执行比较大小运算符(> < ==)的时候,因为判断是否成立,返回的是布尔类型,所以需要写成bool

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
class Number		
{
private:
int lowValue;
int highValue;
public:
Number(int lowValue,int highValue);
void Print();
Number operator++();
Number operator--();
Number operator+(const Number& p);
Number operator-(const Number& p);
Number operator*(const Number& p);
Number operator/(const Number& p);
bool operator>(const Number& p);
bool operator<(const Number& p);
bool operator==(const Number& p);
};
Number::Number(int lowValue,int highValue)
{
this->lowValue = lowValue;
this->highValue = highValue;
}
void Number::Print()
{
printf("%d\n",lowValue);
printf("%d\n",highValue);
}
Number Number::operator++()
{
lowValue++;
highValue++;
return *this;
}
Number Number::operator--()
{
lowValue--;
highValue--;
return *this;
}
Number Number::operator+(const Number& p)
{
this->highValue = this->highValue + p.highValue;
this->lowValue = this->lowValue + p.lowValue;
return *this;
}
Number Number::operator-(const Number& p)
{
this->highValue = this->highValue - p.highValue;
this->lowValue = this->lowValue - p.lowValue;
return *this;
}
Number Number::operator*(const Number& p)
{
this->highValue = this->highValue * p.highValue;
this->lowValue = this->lowValue * p.lowValue;
return *this;
}
Number Number::operator/(const Number& p)
{
this->highValue = this->highValue / p.highValue;
this->lowValue = this->lowValue / p.lowValue;
return *this;
}
bool Number::operator>(const Number& p)
{
if(this->highValue > p.highValue)
{
return true;
}
return false;
}
bool Number::operator<(const Number& p)
{
if(this->highValue < p.highValue)
{
return true;
}
return false;
}
bool Number::operator==(const Number& p)
{
if(this->highValue == p.highValue)
{
return true;
}
return false;
}
void Test()
{
Number p(1,2),p2(3,4);
p++;
p.Print();
p--;
p.Print();

p = p+p2;
p.Print();

}
int main(int argc, char* argv[])
{
Test();
}

new delete vector

malloc探究

malloc()是人为写的一个分配内存空间的函数,探究一下究竟是哪个函数实现了malloc()的功能

手动分配1024个int空间

1
int* pi = (int*)malloc(sizeof(int)*1024);

编译并运行发现pi指针一开始指向的地址是没有值的

image-20210613140748340

F10单步往下走一下发现已经有了值,而且是一个地址,指向372588

image-20210613140854522

跟到372588这个地址进行查看,发现分配了1024个int

image-20210613140937210

为了探究究竟是哪个函数实现了malloc分配内存空间的功能,进入反汇编f10单步走。最终执行到401074这个内存地址的时候地址对应的值变化了,所以这里实现malloc分配内存空间的功能的应该是_nh_malloc_dbg这个函数

image-20210613141110234

f11跟进函数,继续f10单步往下走,这里看到一个E8 call,判断应该是跟之前一样的函数功能,f11跟进_heap_alloc_dbg

image-20210613142228732

继续f11跟进_heap_alloc_base

image-20210613142704718

4033AC处调用_imp_HeapAlloc,这里可以看到这个call是FF,为间接调用,这里指向的就是IAT

image-20210613143248318

跟进去已经发现到dll模块了,这里malloc使用的就是HeapAlloc这个API

image-20210613144817518

总结如下

1
malloc->_nh_malloc_dbg->_heap_alloc_dbg->_heap_alloc_base->HeapAlloc							

free探究

image-20210613150157305

image-20210613150607876

image-20210613150842595

到最后可以发现跟到的是HeapFree这个API

总结如下

1
free->_free_dbg->_free_base->HeapFree			

new&delete

new关键字即在堆里面分配一块空间,注意用new的时候返回的是地址,所以要使用指针

1
int* pi = new int;

也是进入反汇编跟函数,这里直接总结new如下

1
_nh_malloc->_nh_malloc_dbg->_heap_alloc_dbg->_heap_alloc_base->HeapAlloc

在C++里newmalloc没有本质区别,都是分配一块空间,在C++里释放堆的空间对应的关键词为delete

1
2
int* pi = new int;
delete pi;

关于delete的总结如下

1
_free_dbg->_free_base->HeapFree

使用new在堆里创建一个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
class Person
{
private:
int x;
int y;
public:
Person()
{

}
Person(int x,int y)
{
this->x = x;
this->y = y;
}
};

int main(int argc, char* argv[])
{

int* pi = new int;

Person* pa = new Person; //new Person()

int* pk = new int(5);

Person* pb = new Person(1,2);

delete pi,pk;

delete pa,pb;

return 0;
}

这里有一个特例,如果使用new创建数组,在delete的时候就需要使用delete[],代码如下所示

1
2
int* i = new int[5];
delete[] i;

这里定义一个析构函数,f10单步跟一下

image-20210613153816477

可以看到析构函数是在delete执行的时候才执行的

image-20210613153832449

vector

1
2
3
4
5
6
7
1、本质就是一个数组		

2、可以动态扩充容量

3、支持下标方法,查询性能好

4、新增数据和删除数据较差

实现一个vector类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// cplus vector.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#define SUCCESS 1 // 成功
#define ERROR -1 // 失败
#define MALLOC_ERROR -2 // 申请内存失败
#define INDEX_ERROR -3 // 错误的索引号


template <class T_ELE>
class Vector
{
public:
Vector();
Vector(DWORD dwSize);
~Vector();
public:
DWORD at(DWORD dwIndex,OUT T_ELE* pEle); //根据给定的索引得到元素
DWORD push_back(T_ELE Element); //将元素存储到容器最后一个位置
VOID pop_back(); //删除最后一个元素
DWORD insert(DWORD dwIndex, T_ELE Element); //向指定位置新增一个元素
DWORD capacity(); //返回在不增容的情况下,还能存储多少元素
VOID clear(); //清空所有元素
BOOL empty(); //判断Vector是否为空 返回true时为空
VOID erase(DWORD dwIndex); //删除指定元素
DWORD size(); //返回Vector元素数量的大小
private:
BOOL expand();
private:
DWORD m_dwIndex; //下一个可用索引
DWORD m_dwIncrement; //每次增容的大小
DWORD m_dwLen; //当前容器的长度
DWORD m_dwInitSize; //默认初始化大小
T_ELE *m_pVector; //容器指针
};

template <class T_ELE>
Vector<T_ELE>::Vector()
:m_dwInitSize(100),m_dwIncrement(5) //默认初始化大小为100,每次增容大小为5
{
//1.创建长度为m_dwInitSize个T_ELE对象

m_pVector = new T_ELE[m_dwInitSize];

//2.将新创建的空间初始化

memset(m_pVector,0, *m_dwInitSize sizeof(T_ELE));

//3.设置其他值

m_dwLen = m_dwInitSize;
m_dwIndex = 0;
}

template <class T_ELE>
Vector<T_ELE>::Vector(DWORD dwSize)
:m_dwIncrement(5) //增容大小为5
{
//1.创建长度为dwSize个T_ELE对象

m_pVector = new T_ELE[dwSize];

//2.将新创建的空间初始化

memset(m_pVector,0,sizeof(T_ELE) *dwSize);

//3.设置其他值

m_dwLen = dwSize;
m_dwIndex = 0;

}

template <class T_ELE>
Vector<T_ELE>::~Vector() //析构函数
{

//释放空间 delete[]
delete[] m_Vectory;
m_Vectory = NULL;

}

template <class T_ELE>
BOOL Vector<T_ELE>::expand() //空间不够时增容
{
DWORD templen = 0;
T_ELE* ptemp = 0;
// 1. 计算增加后的长度

templen = m_dwLen + m_dwIncrement;

// 2. 申请空间

ptemp = new T_ELE[templen];
memset(ptemp, 0,sizeof(T_ELE) *templen);

// 3. 将数据复制到新的空间

memcpy(ptemp, m_pVector,sizeof(T_ELE) *m_dwLen);

// 4. 释放原来空间

delete[] m_pVector;
m_pVector = ptemp;
ptemp = NULL;

// 5. 为各种属性赋值

m_dwLen = templen;

return SUCCESS;
}

template <class T_ELE>
DWORD Vector<T_ELE>::push_back(T_ELE Element) //将元素存储到容器最后一个位置
{
//1.判断是否需要增容,如果需要就调用增容的函数

if (m_dwIndex >= m_dwLen)
{
expand();
}

//2.将新的元素复制到容器的最后一个位置

memcpy(&m_pVector[m_dwIndex], &Element, sizeof(T_ELE));

//3.修改属性值

m_dwIndex++;
return SUCCESS;

}

template <class T_ELE>
DWORD Vector<T_ELE>::insert(DWORD dwIndex, T_ELE Element) //向指定位置新增一个元素
{
//1.判断索引是否在合理区间

if (dwIndex < 0 || dwIndex > m_dwIndex)
{
printf("Index is not in a reasonable range,please try again!");
return INDEX_ERROR;
}

//2.判断是否需要增容,如果需要就调用增容的函数

if (m_dwIndex >= m_dwLen)
{
expand();
}

//3.将dwIndex之后的元素后移

for (DWORD i = m_dwIndex; i > dwIndex ; i--)
{
memcpy(&m_pVector[i], &m_pVector[i-1], sizeof(T_ELE));
}

//4.将Element元素复制到dwIndex位置

memcpy(&m_pVector[dwIndex], &Element, sizeof(T_ELE));

//5.修改属性值

m_dwIndex++;

return SUCCESS;
}

template <class T_ELE>
DWORD Vector<T_ELE>::at(DWORD dwIndex,T_ELE* pEle) //根据给定的索引得到元素
{
//判断索引是否在合理区间
//将dwIndex的值复制到pEle指定的内存

if(dwIndex >= 0 && dwIndex <= m_dwIndex)
{
memcpy(pEle, &m_pVector[dwIndex], sizeof(T_ELE));
return SUCCESS;
}
else
{
printf("Index is not in a reasonable range,please try again!");
return INDEX_ERROR;
}
}

template <class T_ELE>
VOID Vector<T_ELE>::pop_back() //删除最后一个元素
{
memset(&m_pVector[m_dwIndex], 0 , sizeof(T_ELE));
}

template <class T_ELE>
DWORD Vector<T_ELE>::capacity() //返回在不增容的情况下,还能存储多少元素
{
return m_dwLen - m_dwIndex;
}

template <class T_ELE>
VOID Vector<T_ELE>::clear() //清空所有元素
{
memset(m_pVector, 0, sizeof(T_ELE) *m_dwIndex)
}

template <class T_ELE>
BOOL Vector<T_ELE>::empty() //判断Vector是否为空 返回true时为空
{
for (int i = 0 ; i < m_dwLen ; i++)
{
if (m_pVector[i] = 0)
{
return TRUE;
}
}
return FALSE;
}

template <class T_ELE>
VOID Vector<T_ELE>::erase(DWORD dwIndex) //删除指定元素
{
for (int t= dwIndex ; t < m_dwIndex ; t++)
{
memcpy(&m_pVector[t+1], &m_pVector[t], sizeof(T_ELE));
}
}

template <class T_ELE>
DWORD Vector<T_ELE>::size() //返回Vector元素数量的大小
{
return m_dwIndex;
}

void PrintVector()
{
int x = 0;
int y = 0;

Vector<int>* pVector = new Vector<int>(3);

pVector->push_back(1);
pVector->push_back(2);
pVector->push_back(3);

pVector->insert(0,9);

pVector->at(2,&x);
printf("%d\n", x);

pVector->pop_back();

pVector->capacity();

//pVector->clear();

pVector->empty();

pVector->erase(2);

pVector->size();
}


int main(int argc, char* argv[])
{
PrintVector();

return 0;
}
CATALOG
  1. 1. this类指针
    1. 1.1. this指针的作用
    2. 1.2. 全局函数和结构体函数的区别
    3. 1.3. 空结构体的大小
    4. 1.4. 两个结构体函数
  2. 2. 继承 构造-析构函数
    1. 2.1. 重载的概念
    2. 2.2. 构造函数的特点
    3. 2.3. 析构函数的特点
    4. 2.4. 继承
      1. 2.4.1. 多层继承
      2. 2.4.2. 多重继承
    5. 2.5. 析构 继承练习题
  3. 3. 权限控制
    1. 3.1. 优化代码
    2. 3.2. 权限控制
    3. 3.3. private深入探讨
    4. 3.4. class和struct的区别
    5. 3.5. private是否被继承
    6. 3.6. C++权限控制练习题
  4. 4. 虚函数表
    1. 4.1. 虚函数:间接调用
    2. 4.2. 深入虚函数调用
    3. 4.3. 打印虚函数表
    4. 4.4. C++虚函数表练习
  5. 5. 动态绑定 多态
    1. 5.1. 继承总结
      1. 5.1.1. 单继承无函数覆盖
      2. 5.1.2. 单继承有函数覆盖
      3. 5.1.3. 多继承无函数覆盖
      4. 5.1.4. 多继承有函数覆盖
      5. 5.1.5. 多重继承无函数覆盖
      6. 5.1.6. 多重继承有函数覆盖
      7. 5.1.7. 多重继承有函数覆盖2
      8. 5.1.8. 多重继承有函数覆盖3
    2. 5.2. 前期绑定和动态绑定&多态
      1. 5.2.1. 动态绑定探究
      2. 5.2.2. 多态探究
      3. 5.2.3. 析构函数写成virtual的原因
  6. 6. 模板
    1. 6.1. 冒泡排序
    2. 6.2. 折半查找
    3. 6.3. 模板
  7. 7. 引用 友元 运算符重载
    1. 7.1. 引用
    2. 7.2. 指针和引用的区别
    3. 7.3. 友元函数
    4. 7.4. 运算符重载
  8. 8. new delete vector
    1. 8.1. malloc探究
    2. 8.2. free探究
    3. 8.3. new&delete
    4. 8.4. vector