0%

16-C++

C++

this指针

汇编中会将对象的基地址通过ecx传递,然后通过ecx+几来计算

练习

习题1

代码原型,分别分析结构体中的Add跟普通的Add 以及结构体中的Sub跟普通的Sub的区别

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
// Test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>

using namespace std;

struct Calc
{
int x;
int y;
int Add()
{
return this->x + this->y;
}
int Sub(int num1)
{
return this->x + this->y -num1;
}
};
int Add()
{
return 1+2;
}
int Sub(int num1)
{
return 1+2-num1;
}


int main(int argc, char* argv[])
{
Calc mycalc = Calc();
mycalc.Add();
mycalc.Sub(1);
Add();
Sub(1);
//cout << "Hello world!" << endl;
return 0;
}

结构体中的Add函数传参

1
2
004012F4 8D 4D F8             lea         ecx,[ebp-8]
004012F7 E8 0E FD FF FF call @ILT+5(Calc::Add) (0040100a)

结构体中的Sub传参

1
2
3
004012FC 6A 01                push        1
004012FE 8D 4D F8 lea ecx,[ebp-8]
00401301 E8 13 FD FF FF call @ILT+20(Calc::Sub) (00401019)

普通的Add函数

1
00401306 E8 FA FC FF FF       call        @ILT+0(Add) (00401005)

普通的Sub函数

1
2
3
0040130B 6A 01                push        1
0040130D E8 11 FD FF FF call @ILT+30(Sub) (00401023)
00401312 83 C4 04 add esp,4

结构体中的初始化以及结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 初始化
00401340 55 push ebp
00401341 8B EC mov ebp,esp
00401343 83 EC 44 sub esp,44h
00401346 53 push ebx
00401347 56 push esi
00401348 57 push edi
00401349 51 push ecx
0040134A 8D 7D BC lea edi,[ebp-44h]
0040134D B9 11 00 00 00 mov ecx,11h
00401352 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401357 F3 AB rep stos dword ptr [edi]
00401359 59 pop ecx

#结束

004013AB 5F pop edi
004013AC 5E pop esi
004013AD 5B pop ebx
004013AE 8B E5 mov esp,ebp
004013B0 5D pop ebp
004013B1 C2 04 00 ret 4

普通函数的初始化及结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#初始化
004012A0 55 push ebp
004012A1 8B EC mov ebp,esp
004012A3 83 EC 40 sub esp,40h
004012A6 53 push ebx
004012A7 56 push esi
004012A8 57 push edi
004012A9 8D 7D C0 lea edi,[ebp-40h]
004012AC B9 10 00 00 00 mov ecx,10h
004012B1 B8 CC CC CC CC mov eax,0CCCCCCCCh
004012B6 F3 AB rep stos dword ptr [edi]

#结束
004012C0 5F pop edi
004012C1 5E pop esi
004012C2 5B pop ebx
004012C3 8B E5 mov esp,ebp
004012C5 5D pop ebp
004012C6 C3 ret

从这里可以看出不同点

  1. 结构体中函数传参会同时用寄存器以及栈,ecx存储的是基地址
  2. 普通函数栈平衡是外平栈,也就是_cdel 而结构体中的函数是内平栈,传进去一个参数,他就ret 4
  3. 普通函数初始化他不会保存ecx,而结构体中的函数他会保存ecx
  4. 普通函数无参数就是无参数,而结构体中的函数无参数却还有一个通过ecx传递的参数

习题2

观察空结构体跟非空结构体的大小

空结构体: 1
Calc结构体: 8

这里有点奇怪,空结构体居然有一个字节,而我定义的非空结构体却刚好是8个字节,两个int

习题3

以下函数能否执行,原因?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Person  				
{
void Fn_1()
{
printf("Person:Fn_1()\n");
}
void Fn_2()
{
printf("Person:Fn_2()%x\n");
}
};
int main(int argc, char* argv[])
{

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

return 0;
}

可以,因为Fn_1根本不在结构体里面,所以肯定是可以执行的,调试后发觉,他跳到指定函数处执行,简单来说就是数据通过ecx寻找,而代码通过call直接调用

下面这个呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Person  				
{
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();

return 0;
}

不可以,会出错,因为初始化了数据,他会从0处取数据,明显会报错

继承

  1. 继承
  2. 多层继承
  3. 多重继承
  4. 父类与子类指针

多重继承与多层继承区别

多层:

多重:

从图里很好理解,就是起始地址是不一样的,多层跟多重

练习

习题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
// Test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>

using namespace std;

/*
1 设计一个结构 DateInfo,要求其满足下述要求。
(1) 有三个成员: int year; int month;int day;
(2) 要求有个带参数的构造函数,其参数分别为对应年、月、日。
(3) 有一个无参数的构造函数,其初始的年、月、日分别为:2015、4、2。
(4) 要求有一个成员函数实现日期的设置:SetDay(int day)
(5) 要求有一个成员函数实现日期的获取: GetDay()
(6) 要求有一个成员函数实现年份的设置: SetYear(int year)
(7) 要求有一个成员函数实现年份的获取: GetYear()
(8) 要求有一个成员函数实现月份的设置: SetMonth(int month)
(9) 要求有一个成员函数实现月份的获取: GetMonth()
*/

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;
}
void SetMonth(int month)
{
this->month = month;
}
void SetYear(int year)
{
this->year = year;
}
int GetDay()
{
return this->day;
}
int GetMonth()
{
return this->month;
}
int GetYear()
{
return this->year;
}

};
int main(int argc, char* argv[])
{
DateInfo myDate = DateInfo(2015,5,4);
printf("%d", myDate.GetDay());
return 0;
}

习题2

对比上一题跟这一题,我发觉一个问题,就是构造函数的使用,上一题的用法会导致多次初始化,第一次用构造函数初始化,然后在将其复制给main函数栈里的myDate,这样是重复劳动,不建议,建议直接使用第二种初始化方法

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
// Test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>

using namespace std;

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

*/

struct TimeInfo
{
int hour;
int minute;
int second;
void setTime(int hour, int minute, int second)
{
this->hour = hour;
this->minute = minute;
this->second = second;
}
TimeInfo(int hour,int minute, int second)
{
this->hour = hour;
this->minute = minute;
this->second = second;
}
TimeInfo()
{
this->hour = 0;
this->minute = 0;
this->second = 0;
}
int GetHour()
{
return this->hour;
}
int GetMinute()
{
return this->minute;
}
int GetSecond()
{
return this->second;
}

};
int main(int argc, char* argv[])
{
TimeInfo myTime(12,12,12);
myTime.GetHour();
return 0;
}

习题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
// Test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>

using namespace std;

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

*/
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;
}
void SetMonth(int month)
{
this->month = month;
}
void SetYear(int year)
{
this->year = year;
}
int GetDay()
{
return this->day;
}
int GetMonth()
{
return this->month;
}
int GetYear()
{
return this->year;
}

};

struct TimeInfo:DateInfo
{
int hour;
int minute;
int second;
void setTime(int hour, int minute, int second)
{
this->hour = hour;
this->minute = minute;
this->second = second;
}
TimeInfo(int hour,int minute, int second)
{
this->hour = hour;
this->minute = minute;
this->second = second;
}
TimeInfo()
{
this->hour = 0;
this->minute = 0;
this->second = 0;
}
int GetHour()
{
return this->hour;
}
int GetMinute()
{
return this->minute;
}
int GetSecond()
{
return this->second;
}

};
int main(int argc, char* argv[])
{
TimeInfo myTime(12,13,14);
myTime.day = 10;
myTime.year = 2018;
myTime.month = 11;

TimeInfo* pTimeInfo = &myTime;
DateInfo* pDateInfo = &myTime;
int temp = pTimeInfo->hour;
temp = pTimeInfo->minute;
temp = *((int* )((int)pDateInfo + 12));
printf("%d\n", temp);
myTime.GetHour();
return 0;
}

习题4

作为c++基础还是有点的,很快搞定这些作业了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// Test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>

using namespace std;

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

struct MyString
{
void *buf;
int size;
MyString()
{
this->size = 1024;
buf = calloc(1024, 1);
}
MyString(int size)
{
this->size = size;
buf = calloc(size, 1);
}
~MyString()
{
free(buf);
}
void setString(char *str)
{
int length = strlen(str);
if(length > this->size)
{
printf("Too long\n");
return;
}
memcpy(buf, str, strlen(str));
}
void printString()
{
printf("%s\n", buf);
}
void appendString(char *str)
{
int length = strlen((char *)buf);
int newLength = strlen(str);
if(length + newLength > this->size)
{
printf("Too long\n");
return;
}
memcpy((void *)((int)buf+length), str, newLength);
}

int Size()
{
return strlen((char *)buf);
}
};
int main(int argc, char* argv[])
{
MyString Str(1024);
Str.setString("123");
Str.printString();
printf("Size: %d\n", Str.Size());
Str.appendString("456");
Str.printString();
printf("Size: %d\n", Str.Size());
return 0;
}

权限控制

C++是为了更安全的

继承的话,默认以私有继承

变量定义也是默认私有

class 权限最小化原则

1
2
3
4
5
6
7
8
9
class A:class B
{

}

class A:public class B
{

}

不练习了,这个太简单了,就改class就行

虚函数表

  1. 直接用对象调用虚函数跟普通函数一样

  2. 如果用指针的话,虚函数变成间接调用E8->FF

  3. 有虚函数的,this指针指向的空间第一个为虚函数表

  4. 几个直接父类带虚函数就有几个虚表

虚函数图:

练习

习题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
// Test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>

using namespace std;

/*
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");
}
};

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");
}
};




int main(int argc, char* argv[])
{
Sub test;
Sub* pSub = &test;
//pSub->Function_4();
typedef void(*pFunction)(void);
pFunction pVir = NULL;
int i = 0;
for(; i<6; i++)
{
pVir = (pFunction)(((int*)(*(int*)&test))+i);
printf("%x : ", pVir);
pVir = (pFunction)(*(int *)pVir);
printf("%x\n", pVir);
pVir();
}

return 0;
}

打印结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
431028 : 401032
Base:Function_1...
43102c : 40100f
Base:Function_2...
431030 : 401019
Base:Function_3...
431034 : 401023
Sub:Function_4...
431038 : 401005
Sub:Function_5...
43103c : 401014
Sub:Function_6...
Press any key to continue

从这里发现的是Sub的虚函数表里同时存了base的虚函数表,也就是说他们是同一张表

习题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
// Test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>

using namespace std;

/*
2、单继承有函数覆盖(打印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");
}
};

struct Sub:Base
{
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");
}
};




int main(int argc, char* argv[])
{
Sub test;
Sub* pSub = &test;
typedef void(*pFunction)(void);
pFunction pVir = NULL;
int i = 0;
for(; i<6; i++)
{
pVir = (pFunction)(((int*)(*(int*)&test))+i);
printf("%x : ", pVir);
pVir = (pFunction)(*(int *)pVir);
printf("%x\n", pVir);
if(pVir == 0)
break;
pVir();
}

return 0;
}

打印结果

1
2
3
4
5
6
7
8
9
10
431028 : 401023
Sub:Function_1...
43102c : 401014
Sub:Function_2...
431030 : 401019
Base:Function_3...
431034 : 40100f
Sub:Function_6...
431038 : 0
Press any key to continue

发觉只有4个函数,1,2,3,6

重名的直接被覆盖掉了,虚表里,默认用的是Sub的,Sub对象默认用的Sub还是Sub指针默认的是Sub呢,测试下

经过测试Base指针也是默认的Sub,那Base对象呢,Base只有Base的函数,用Sub指针还是Base的调用,一样的

多态

动态绑定运行起来才绑定的,需要实例化,间接call

前期绑定,编译完就有准确的地址的,直接call

virtual后的多态

练习

习题1

思考题:为什么析构函数建议写成virtul的?

写成virtual可以释放子类空间

习题2

写一个例子程序,能体现多态的作用.

狗和猫叫法不一样,这里运用了个抽象类,因为Animal是抽象出来的,他们有共同的方法,叫

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
// test1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

class Animal
{
public:
virtual void cry() = 0;
};

class Dog:public Animal
{
public:
virtual void cry()
{
printf("dog is wang\n");
}
};

class Cat:public Animal
{
public:
virtual void cry()
{
printf("Cat is Miao\n");
}
};

int main(int argc, char* argv[])
{
Dog dog;
Cat cat;
Animal* arr[] = {&dog,&cat};
for(int i=0; i<2; i++)
{
arr[i]->cry();
}
return 0;
}

模板

1
template<class T ……>

练习

习题1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
1、使用模版实现swap(x,y)函数,功能:交换x,y的值.
*/
#include "stdafx.h"

template<class T>
void Swap(T* num1, T* num2)
{
T temp = *num1;
*num1 = *num2;
*num2 = temp;
}

int main(int argc, char* argv[])
{
int int1=1,int2=2;
short short1=1,short2=2;
char char1=1,char2=2;
Swap(&int1,&int2);
Swap(&short1,&short2);
Swap(&char1,&char2);
return 0;
}

发觉编译了3份不同的代码,一份用32位寄存器,一份用16位寄存器,一份用8位寄存器

习题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
/*
2、冒泡排序:对结构体或者类进行排序,如果不能实现,找出问题所在.
*/
// test1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"


struct DateInfo
{
int year;
int month;
int day;
bool operator <(DateInfo &x)const
{
return this->year < x.year;
}
DateInfo()
{
this->year = 2018;
this->month = 10;
this->day = 1;
}
~DateInfo()
{
}
};

template<class T>
class Cmp
{
private:
T num1;
T num2;
public:
Cmp(T num1, T num2)
{
this->num1 = num1;
this->num2 = num2;
}
bool getResult()
{
return this->num1 < this->num2;
}
};

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

DateInfo a,b;
b.year = 2019;
Cmp<DateInfo> test(a,b);
test.getResult();
return 0;
}

习题3

1
2
3
4
5
6
7
8
/*
3、观察下面两个Sort方法的反汇编代码(看内存地址和内容):
int arr[] = {2,6,1,5,4};
char arr1[] = {2,6,1,5,4};
Sort(arr,5);
Sort(arr1,5);
*/
类似第一题

后面的

不学了,全都学过,都忘得差不多了

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/97ad516e/
版权声明:转载请注明出处!