[C++]03-面向对象(1)

本文深入探讨了C++中的类定义,包括class与struct的区别,对象占用内存的计算,以及函数调用的实现原理。讲解了指针访问成员变量与对象访问成员变量的不同,并通过实例解释了为何代码区的函数可以调用栈帧中的不同对象值。此外,文章还阐述了内存空间的布局,包括数据区、代码区、堆区和栈区的功能,以及堆空间的申请、释放和初始化。最后,讨论了对象在内存中的存放位置,揭示了this指针的作用。
摘要由CSDN通过智能技术生成

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个字节作为类的空间大小。字节的顺序依次按照类中定义的顺序。
      在这里插入图片描述
  • 为什么类中的函数不占字节?

    • 函数为公共模块在代码区域在只有一份。调用时候开辟函数栈空间,调用结束销毁栈空间。通过反汇编中两次对象调用函数地址一致。

在这里插入图片描述

       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;
  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值