文章目录
1.类
1.1 怎么定义:
- 使用class/struct都可以定义一个类
1.2 class和private的区别:
- class 默认权限是私有的 struct 默认权限是public
class Person1 {
// 默认private 只能自己在类内部使用
int age;
void run() {
};
};
class Person2 {
// 默认public 通用private修改为私有权限
private:
int age;
void run() {
};
};
1.3 对象占用大小
- 对象占用几个字节?
- 对象占用的字节数=对象属性总字节的大小
class Person {
public:
int m_id;
int m_age;
int m_gender;
void run() {
};
};
int main() {
// person1 有三个成员 占用3个
Person person1;
person1.m_id = 1;
person1.m_age = 2;
person1.m_gender = 3;
person1.run();
// --------对象2----------
Person person2;
person2.m_age = 5;
person2.m_id = 4;
person2.m_gender = 6;
person1.run();
}
-
以上代码 person对象占用几个字节?
- 类Person有三个int类型的成员变量,所以在程序栈空间开辟连续的12个字节作为类的空间大小。字节的顺序依次按照类中定义的顺序。
- 类Person有三个int类型的成员变量,所以在程序栈空间开辟连续的12个字节作为类的空间大小。字节的顺序依次按照类中定义的顺序。
-
为什么类中的函数不占字节?
- 函数为公共模块在代码区域在只有一份。调用时候开辟函数栈空间,调用结束销毁栈空间。通过反汇编中两次对象调用函数地址一致。
1.4 为什么代码区的run函数可以调用main的栈帧中不同对象的值呢?
run( ) 成员函数在执行的时候是怎么知道,调用main栈帧中 person1的age, 还是person2的age
class Person {
public:
int m_id;
int m_age;
void run() {
cout << person->m_age << endl;
}
};
int main() {
Person person1;
person1.m_age = 10;
// 成员函数 10
person1.run();
-------------------------------------------
Person person2;
person2.m_age = 20;
//20
person2.run();
getchar();
return 0;
}
- 如果run()成员函数在调用时,将对象的地址传过去,即可解决。
class Person {
public:
int m_id;
int m_age;
传输调用者的地址,即可知道是哪个对象
void run(Person *person) {
cout << person->m_age << endl;
}
};
int main() {
Person person1;
person1.m_age = 10;
//成员函数
person1.run(&person1);
Person person2;
person2.m_age = 20;
person2.run(&person2);
getchar();
return 0;
}
- 为了使用方便在,在c++中使用this指针传输地址
class Person {
public:
int m_id;
int m_age;
void run() {
// this 指针 &person1 调用者的地址,
// 语法糖现象可以省略this 直接写为 m_age=5;
this->m_age = 5;
}
};
void fun() {
}
int main() {
Person person1;
//成员函数
person1.run();
// 普通函数
fun();
Person person2;
person2.run();
cout << person1.m_age <<"____________"<< person2.m_age << endl;
return 0;
}
- 通过反汇编窥探this指针的本质
- 通过反汇编发现在调用run函数前,将调用者的地址赋值给ecx寄存器,调用函数时存放ecx的值到this的内存处。
- 函数访问对象属性值的时,eax赋值 this地址的值,即person的地址。通过地址+偏移量计算出成员变量的地址
- 简洁版
lea ecx,[person1]
call Person::run (0C512B7h)
jmp Person::run (0C525A0h)
vodi run()
{
mov dword ptr [this],ecx
//this->m_age = 5;
mov eax,dword ptr [this]
mov dword ptr [eax+4],5
}
完整版
Person person1;
person1.m_id = 20;
mov dword ptr [ebp-10h],14h
//成员函数
person1.run();
lea ecx,[ebp-10h]
call 00AD12BC
:Person::run:
jmp 00AD25B0
mov dword ptr [ebp-8],ecx
mov ecx,0ADF029h
call 00AD1393
// this 指针 &person1 调用者的地址,
// 语法糖现象可以省略this 直接写为 m_age=5;
this->m_age = 5;
mov eax,dword ptr [ebp-8]
mov dword ptr [eax+4],5
}
2.指针访问成员变量和对象访问成员变量
2.1.指针访问成员变量和对象访问成员变量有什么不一样?
Person person1;
person1.m_id = 1;
person1.m_age = 2;
person1.m_height = 3
mov dword ptr [ebp-14h],1
mov dword ptr [ebp-10h],2
mov dword ptr [ebp-0Ch],3
Person* p = &person1;
p->m_id = 4;
p->m_age = 5;
p->m_height = 6;
lea eax,[ebp-14h]
mov dword ptr [ebp-20h],eax
mov eax,dword ptr [ebp-20h]
mov dword ptr [eax],4
mov eax,dword ptr [ebp-20h]
mov dword ptr [eax+4],5
mov eax,dword ptr [ebp-20h]
mov dword ptr [eax+8],6
- 对象访问成员变量: 根据成员变量的地址访问成员变量
- 指针访问成员变量:
- 对象地址赋值指针变量
- 成员变量的地址=指针变量的值+变量的偏移地址
- 根据成员变量的地访问变量空间
2.2 为什么要使用指针访问成员变量的地址?
- 有时候操作更灵活更方便
2.3 思考题:成员变量的属性值是多少?
class Person {
public:
int m_id;
int m_age;
int m_height;
void display() {
cout << this->m_id << "______" << this->m_age << "______" << this->m_height << endl;
}
};
Person person1;
person1.m_id = 10;
person1.m_age = 20;
person1.m_height = 30;
Person* p = (Person*)&person1.m_age;
// eax存储是age的地址值 不是person的地址值
// mov dword ptr[eax+0],40
p->m_id = 40;
// mov dword ptr[eax+4],50
p->m_age = 50;
person1.display();
p->display();
person1.display() 为什么是10,40 ,50?
调用前传入person1的地址
Person* p = (Person*)&person1.m_age;
p->m_id = 40;
p->m_age = 50;
mov eax,&person+4
mov [eax],40 //看起来修改的是id 实际上修改的age
mov [eax+4],50// 同理修改的height
p->display() 为什么是40 50 不确定的值?
p->display()会将指针p里面存储的地址传递给display函数的this 即&person+4,
即打印id,age,height的地址值为 &person+4+0,&person+4+4,&person+4+8
实际上打印的属性为 age height 不确定
3.内存空间的布局
3.1 程序运行内存区域分布
- 每个应用都有自己独立的内存空间,内存空间大致分为四个区域:
- 数据区(全局区):存放全局变量
- 代码区: 存放函数(main 函数或者自定义的函数)
- 堆区:主动申请和释放。自由控制内存的生命周期和大小,但在很多高级语言中,自动申请和释放堆空间。例如:Java
- 栈区:每调用一个函数就会给它分配栈空间,用来存放参数,bp,局部变量,部分寄存器的值。函数调用结束自动销毁栈帧。
3.2 堆空间的申请和释放
- 堆空间的申请和释放方式(1)
void test() {
//申请:申请4个字节的堆空间 返回堆空间的首地址,堆空间当做整形变量来使用
int* p = (int*)malloc(4);
*p = 10;
free(p);
char* p = (char*)malloc(8);
//将8赋值给8个字节的第一个字节
*p = 10;
// 将8赋值给8个字节的第二个字节
*(p+1)=11;
free(p);
}
- 堆空间的申请和释放方式(2)
void test2() {
// 方式一: 向堆空间申请一个int大小的空间 并且把地址空间的首地址赋值给左边的指针
int *p = new int;
*p = 10;
delete p;
// 方式二:申请四个char字节
char* p = new char[4];
delete[] p;
}
- 函数调用完毕 指针变量p回收 但是之前在堆空间申请的四个字节还存在。
3.3 堆空间的初始化
void test3() {
// int *p = (int *) malloc(4);
// *p = 0;
int size = sizeof(int) * 10;
int* p = (int*)malloc(size);
// memory set
memset(p, 0, size);
// 从p地址开始的连续4个字节中的每一个字节都设置为1
// memset(p, 1, 4);
// 将4个字节设置为1
// 00000000 00000000 00000000 00000001
// 将4个字节中的每一个字节都设置为1
// 00000001 00000001 00000001 00000001
}
3.4 对象在内存什么位置存放?
- 全局区:全局区
- 栈空间:函数里面的局部变量
- 堆空间:动态申请内存(malloc,new等) 但是在很多高级语言中(如java),对象都放在堆空间中
// 堆空间的申请和释放.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
class Person {
public:
int id;
int age;
void run() {
};
};
// 全局区域
Person g_person;
int main()
{// 栈空间
Person person;
//堆空间
Person* p = new Person;
return 0;
}