C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材

目录

  • 1.类
      • 1.1 C++中可以使用struct、class来定义一个类
          • 1.1.1 struct 和 class的区别
          • 1.1.2 内存的栈空间,自动分配和回收
          • 1.1.3 class与struct的反汇编比较
          • 1.1.4 创建的对象内存情况
          • 1.1.5 函数内存布局:成员函数,不占用创建对象的内存
          • 1.1.6 成员变量内存布局:类中的成员变量,以创建对象的成员变量多少开辟内存
          • 1.1.7 C++编程规范
  • 2. 对象
      • 1.1 对象的内存布局
          • 1.1.1 类中多个成员变量的内存布局
          • 1.1.2 内存中(4个区域)的栈空间
  • 3. this
      • 3.1 this 就是指向当前对象的指针
      • 3.2 对象在调用成员函数的时候,会自动传入当前对象的内存地址
      • 3.3 可以利用 this.m_age 来访问成员变量么?
      • 3.4 指针访问对象成员的本质
      • 3.5 类中多成员变量的内存分配
      • 3.6 如何利用指针间接访问所指向对象的成员变量?
          • 3.6.1 从指针中取出对象的地址
          • 3.6.2 利用 对象的地址+成员变量的偏移量计算出成员变量地址
          • 3.6.3 根据成员变量的地址访问成员变量的存储空间
      • 3.7 一道题检测指针与内存
          • 3.7.1 请问person.display();打印结果是?
          • 3.7.3 偏移量
          • 3.7.4 如果 p->display(); 结果和 person.display();又有什么区别呢?
          • 3.7.5 cc -> int3 指令
  • 4. 封装
      • 4.1 封装就是:成员变量私有化
  • 5. 内存空间的布局
      • 5.1 代码段(代码区)
      • 5.2 数据段(全局区)
      • 5.3 栈空间
      • 5.4 堆空间
          • 5.4.1 使用场景
          • 5.4.2 需要主动去申请和释放
          • 5.4.3 x86环境(32bit)剖析 malloc
            • 1)malloc
          • 5.4.4 new \ delete
          • 5.4.5 memset 较大数据处理函数
          • 5.4.6 堆空间的初始化
          • 5.4.7 结论
  • 6. 对象的内存
      • 6.1 对象的内存可以存放在3个地方
          • 6.1.1 全局区(数据段):全局变量
          • 6.1.2 栈空间:函数里面的局部变量
          • 6.1.3 堆空间:动态申请内存(malloc、new等)
  • 7. 构造函数(Constructor)
      • 7.1 引入构造函数的使用场景
      • 7.2 特点
          • 7.2.1 函数名与类同名,无返回值(void不能写),可以有参数,可以重载,可以有多个构造函数
          • 7.2.2 一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象
      • 7.3 通过malloc分配的对象不会调用构造函数
          • 7.3.1 不要用malloc搞对象(要去new)
      • 7.4 构造函数的谬论
          • 7.4.1 一个广为流传的、很多教程、书籍都推崇的错误结论
          • 7.4.2 谬论的正确理解
      • 7.5 构造函数的调用
          • 7.5.1 全局区、栈空间、堆空间 创建对象时构造函数的调用
  • 8. 成员变量的初始化
      • 8.1 全局区、栈空间、堆空间,无构造函数时 初始化成员变量区别
          • 8.1.1 全局区:成员变量初始化为0(int类型)
          • 8.1.2 栈空间:没有初始化成员变量
          • 8.1.3 堆空间
      • 8.2 全局区、栈空间、堆空间,有构造函数时 初始化成员变量区别
          • 8.2.1 全局区:成员变量初始化为0(int类型)
          • 8.2.2 栈空间:没有初始化成员变量
          • 8.2.3 堆空间
      • 8.3 结论
  • 9. 析构函数(Destructor)
      • 9.1 引入析构函数的使用场景
      • 9.2 特点
          • 9.2.1 函数与析构函数
          • 9.2.2 指针与析构函数
      • 9.3 malloc 与 free 无关 构造函数与析构函数
      • 9.4 全局区与析构函数
      • 9.4 构造与析构函数必须为public

1.类

1.1 C++中可以使用struct、class来定义一个类

1.1.1 struct 和 class的区别

1)struct 的默认成员权限是 public
2)class 的默认权限是 private (实际开发中使用class)

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第1张图片

使用class创建一个类

#include 
using namespace std;

class Person {
public:
	//成员变量(属性)
	int age;
	//成员函数( 方法)
	void run() {
		cout << "Person::run()-" << age << endl;
	}
};
int main() {
	//实例化-利用类创建对象
	Person person;
	person.age = 20;
	person.run();
	return  0;
}

使用struct 创建一个类

#include 
using namespace std;

struct Person {
	//成员变量(属性)
	int age;
	//成员函数( 方法)
	void run() {
		cout << "Person::run()-" << age << endl;
	}
};
int main() {
	//实例化-利用类创建对象
	Person person;
	person.age = 20;
	person.run();
	return  0;
}
1.1.2 内存的栈空间,自动分配和回收

下面的person对象和p指针的内存都是在函数的栈空间,自动分配和回收

#include 
using namespace std;

class Person {
public:
	//成员变量(属性)
	int age;
	//成员函数( 方法)
	void run() {
		cout << "Person::run()-" << age << endl;
	}
};
int main() {
	//实例化-利用类创建对象
	Person person;
	person.age = 20;
	person.run();

	Person* p = &person;
	p->age = 30;
	p->run();

	return  0;
}

person对象和p指针一共占用12个字节(4字节person+8字节*p)

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第2张图片
1.1.3 class与struct的反汇编比较

class 编译的反汇编,如下
mov dword ptr [ebp-0Ch],14h

    15: 	Person person;
    16: 	person.age = 20;
01202672  mov         dword ptr [ebp-0Ch],14h 

struct 编译的反汇编,如下
mov dword ptr [ebp-0Ch],14h

    15: 	Person person;
    16: 	person.age = 20;
01202672  mov         dword ptr [ebp-0Ch],14h 

结论:可见class 和 struct反汇编是一致的,也就是说除了 1.1.1的性质区别,class 和 struct本质上是没区别的

1.1.4 创建的对象内存情况
#include 
using namespace std;

struct Car {
	int price;
	void run() {
		cout << "run()-" << price << endl;
	}
};

int main() {

	Car car1;
	car1.price = 10;
	car1.run();

	Car car2;
	car2.price = 20;
	car2.run();

	Car car3;
	car3.price = 30;
	car3.run();

	return  0;
}

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第3张图片
上面代码的反汇编:
1)每一个 car 对象都在栈空间开辟了4个字节的存储空间
2)可以看出所有的run() 都都被编译成:call 函数地址
3)3个 run() 方法都是call一样的地址,可以看出编译后的run() 方法不与对象产生在同一块内存

1.1.5 函数内存布局:成员函数,不占用创建对象的内存

成员函数(类中的函数) 在创建对象时不会被塞入对象总内存中,而是被编译后单独在内存中(如果不被调用的函数,在编译优化后将不会有内存存储此函数),即创建100个对象,只会有一份函数的内存

调用 成员函数(类中的函数)反汇编为:call 这个函数地址
成员函数(类中的函数)存储的内存与对象存储内存不同

1.1.6 成员变量内存布局:类中的成员变量,以创建对象的成员变量多少开辟内存

类中的成员变量在创建对象时会在内存中开辟成员变量对应类型大小的连续内存(暂不考虑:内存对齐),如int在64位系统占用4个字节,对象的地址量即类中按顺序第一成员变量的地址

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第4张图片

下面的对象 car1~car3总计占用 12个字节

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第5张图片
1.1.7 C++编程规范

变量名规范参考

使用场景 方式 实例
全局变量 g_ g_count
成员变量 m_ m_count
静态变量 s_ s_count
常量 c_ c_count

2. 对象

1.1 对象的内存布局

1.1.1 类中多个成员变量的内存布局

类中成员变量在内存中是连续的存储

#include 
using namespace std;

struct Date {
	int year;
	int month;
	int day;
	void display() {
		cout << "year=" << year 
			<< "month=" << month 
			<< "day=" << day << endl;
	}
};

int main() {
	Date date;
	cout << sizeof(date) << endl;
	return  0;
}

输出:12
一个 int 占用4个字节(x64、x86相同)

1.1.2 内存中(4个区域)的栈空间

创建类的实例 date(对象)存储在栈空间,且由类内成员变量多少决定

#include 
using namespace std;

struct Date {
	//这个 person 对象内存在栈空间
	//栈空间内存会自动回收
	int year;
	int month;
	int day;
	void display() {
		cout << "year=" << year 
			<< "month=" << month 
			<< "day=" << day << endl;
	}
};

int main() {
	Date date;
	date.year = 1;
	date.month = 3;
	date.day = 5;
	cout << "&date=" << &date << endl;
	cout << "&date.year="<< &date .year<< endl;
	cout << "&date.month=" << &date.month << endl;
	cout << "&date.day=" << &date.day << endl;

	return  0;
}
俩张图看懂内存布局
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第6张图片
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第7张图片

3. this

3.1 this 就是指向当前对象的指针

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第8张图片

3.2 对象在调用成员函数的时候,会自动传入当前对象的内存地址

可以看到编译器已经帮我们优化好了,将*person指针 演化成this 再到默认为this

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第9张图片

3.3 可以利用 this.m_age 来访问成员变量么?

不可以,因为 this 是指针,必须用this->m_age 来访问
1). 点的左边只能是对象
2)-> 箭头的左边只能是指针

3.4 指针访问对象成员的本质

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第10张图片
由上图可以看出 ebp-0Ch 为person1对象的地址值
注意这里2个概念地址值 如:ebp-0Ch 地址的存储空间 如:ecx、eax 、[ebp-0Ch]

// ebp-8 是 this指针 的地址
mov dword ptr [ebp-8],ecx 就是将person1对象的地址值存入this地址的存储空间
mov eax,dword ptr [ebp-8] 就是将this地址的存储空间(person1对象的地址值)存入eax
mov dword ptr [eax],3 就是将3赋值给this实际就是person对象地址的存储空间
========================== 汇编代码 ==============================
00A51FA0 mov dword ptr [ebp-8],ecx
19: this->m_age = 3;
00A51FAD mov eax,dword ptr [ebp-8]
00A51FB0 mov dword ptr [eax],3

这里比较绕,大家一定要把代码和反汇编,结合文中的描述一起理解

3.5 类中多成员变量的内存分配

#include 
using namespace std;

struct Person
{
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id
			<< ",m_age = " << m_age
			<< ",m_height = " << m_height << endl;
	}
};

int main() {
	Person person;
	person.m_id = 10;
	person.m_age = 20;
	person.m_height = 30;
	person.display();

	Person* p  = &person;
	p->m_id = 10;
	p->m_age = 20;
	p->m_height = 30;
	p->display();
	return 0;
}

上面代码对应的汇编

========================== 		对象访问		==============================
    18: 	Person person;
    19: 	person.m_id = 10;
009B26D2  mov         dword ptr [ebp-14h],0Ah  
    20: 	person.m_age = 20;
009B26D9  mov         dword ptr [ebp-10h],14h  
    21: 	person.m_height = 30;
009B26E0  mov         dword ptr [ebp-0Ch],1Eh  
    22: 	person.display();
009B26E7  lea         ecx,[ebp-14h]  
009B26EA  call        009B104B  
========================== 		指针访问		==============================
    24: 	Person* p  = &person;
009B26EF  lea         eax,[ebp-14h]  
009B26F2  mov         dword ptr [ebp-20h],eax  
    25: 	p->m_id = 10;
009B26F5  mov         eax,dword ptr [ebp-20h]  
009B26F8  mov         dword ptr [eax],0Ah  
    26: 	p->m_age = 20;
009B26FE  mov         eax,dword ptr [ebp-20h]  
009B2701  mov         dword ptr [eax+4],14h  
    27: 	p->m_height = 30;
009B2708  mov         eax,dword ptr [ebp-20h]  
009B270B  mov         dword ptr [eax+8],1Eh  
    28: 	p->display();
009B2712  mov         ecx,dword ptr [ebp-20h]  
009B2715  call        009B104B 

可以看出多成员变量在对象调用中:
1)对象的地址,就是类下第一成员变量的首地址 即如下:ebp-14h
2)多个成员变量在内存中是连续分配的(根据代码区的类下成员代码顺序,且当前为小端模式)
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第11张图片

还是要明白2个概念:存储空间 和 地址值(上文提到)

将 ebp-14h 地址值存入eax的存储空间,ebp-14h就是&person
009B26EF lea eax,[ebp-14h]
将eax的存储空间 赋值给 ebp-20h地址指向的存储空间,ebp-20h是p的地址
009B26F2 mov dword ptr [ebp-20h],eax
上面2句就是Person* p = &person;
======= 分割========================================
25: p->m_id = 10;
p指针 存储空间 赋值 eax,eax 存储的就是 person对象的地址值(也是person.m_age的地址值)
009B26F5 mov eax,dword ptr [ebp-20h]
person.m_age =10
009B26F8 mov dword ptr [eax],0Ah
====== 分割========================================
26: p->m_age = 20;
009B26FE mov eax,dword ptr [ebp-20h]
009B2701 mov dword ptr [eax+4],14h
27: p->m_height = 30;
009B2708 mov eax,dword ptr [ebp-20h]
009B270B mov dword ptr [eax+8],1Eh
28: p->display();
009B2712 mov ecx,dword ptr [ebp-20h]
009B2715 call 009B104B

3.6 如何利用指针间接访问所指向对象的成员变量?

3.6.1 从指针中取出对象的地址

看到如下的汇编代码,一定是指针间接访问成员变量
mov eax,dword ptr [ebp-20h]
mov dword ptr [eax],14h
[ [ebp-20h] ]将值作为地址,取其存储空间

3.6.2 利用 对象的地址+成员变量的偏移量计算出成员变量地址
3.6.3 根据成员变量的地址访问成员变量的存储空间

3.7 一道题检测指针与内存

3.7.1 请问person.display();打印结果是?
#include 
using namespace std;

struct Person
{
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id
			<< ",m_age = " << m_age
			<< ",m_height = " << m_height << endl;
	}
};

int main() {
	Person person;
	person.m_id = 10;
	person.m_age = 20;
	person.m_height = 30;

	Person* p  = (Person * )&person.m_age;
	p->m_id = 40;
	p->m_age = 50;
	person.display();

	return 0;
}

思路2次偏移量寻找
1)第一次查看当前,成员变量的偏移量

p->m_id = 40;
汇编:mov eax,dword ptr [p]
	  mov dowrd ptr [eax+0],40  // m_id 相对于person地址偏移量为0
p->m_age = 50;
汇编:mov dword ptr [eax+4],50  // m_age 相对于person地址偏移量为 4

2)第二次查看当前,p指针的偏移量

Person* p  = (Person * )&person.m_age;
// &person.m_age 当前p指向的是&person.m_age,也就是 &person +4的偏移

3)重新整理

p->m_id = 40;
汇编:mov eax,dword ptr [p]
	  mov dowrd ptr [eax+0],40 
	  mov dowrd ptr [&person+4+0],40  // 等价于 person.m_age =40
p->m_age = 50;
汇编:mov dword ptr [eax+4],50  
	  mov dowrd ptr [&person+4+4],50  // 等价于 person.m_height =50

最后输出结果
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第12张图片

3.7.3 偏移量

这里所指的偏移量,全部是以类下的成员变量书写顺序,也就是类的机器码存放在代码区的顺序

class Person {
public:
	int age;  // 顺序开始
	int height;
	...
	int phone;// 顺序结束
};
3.7.4 如果 p->display(); 结果和 person.display();又有什么区别呢?
// person 调用 display() 函数 传入的为&person
// 即:display(&person)  &person传给this
person.display();

// p 调用 display() 函数 传入的为&person.m_age,因为age为&person偏移4个字节
// 即:display(&person+4) &person+4传给this
p->display();

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第13张图片

m_height = -858993460 这个结果是 cc

3.7.5 cc -> int3 指令

0xccccccc
cc->int3:起到断点的作用 (interrupt 中断)
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第14张图片

4. 封装

4.1 封装就是:成员变量私有化

提供公共的getter和setter给外界去访问成员变量

#include 
using namespace std;

struct Person{
private:
	int m_age;
public:
	void setAge(int age) {
		if (age < 0) return;
		m_age = age;
	}
	int getAge() {
		return m_age;
	}
};

int main() {
	Person person;
	person.setAge(4);
	cout << person.getAge() << endl;
	
	return  0;
}

输出结果:4

5. 内存空间的布局

每个内存都应有自己独立的内存空间,其内存空间一般都有以下几大区域
(下图有误“其中一个 “栈空间” 为 “堆空间”)

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第15张图片

5.1 代码段(代码区)

用于存放代码(机器码),只读

5.2 数据段(全局区)

用于存放全局变量等(static)
整个程序运行中都存在,除非关闭进程

#include 
using namespace std;

//全局变量
int g_age = 10;

int main() {
	return  0;
}

g_age 就是全局变量

5.3 栈空间

每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间(注意:如果申请了堆空间,堆空间需要手动销毁)

#include 
using namespace std;


int g_age = 10;
// 调用test 开辟连续的栈空间
void test() {
	int a = 1;
	int b = 2;
	int c = a + b;
}

int main() {
	// 调用时自动分配,调用后自动回收
	test();
	return  0;
}

5.4 堆空间

5.4.1 使用场景

在程序运行过程,为了能够自由控制内存的生命周期、大小会经常使用堆空间的内存

5.4.2 需要主动去申请和释放

malloc \ free
new \ delete
new [ ] \ delete [ ]

5.4.3 x86环境(32bit)剖析 malloc
1)malloc

malloc 向堆空间申请内存,因为返回值为void *,需要根据类型强制转换

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第16张图片

malloc 只是单纯的向堆空间申请内存,和栈空间的指针类型没关系
free 只看malloc了多少,不能少回收,不会多回收,malloc(n) => free(n)

如下代码:
int为4个字节,刚好等于malloc(4)的大小

#include 
using namespace std;

int main() {
	int* p = (int *)malloc(4);
	*p = 10;
	free(p);
	//free(p); 不要多次回收,一次malloc 一次free
	return  0;
}

如下代码:
char 为1个字节,而malloc(4)为4个字节的大小

#include 
using namespace std;

int main() {
	char* p = (char*)malloc(4);
	*p = '1';
	*(p+1) = '2';
	*(p+2) = '3';
	*(p+3) = '4';
	/*
	p[0] =  '1';
	p[1] ='2';
	p[2] ='3';
	p[3] ='4';
	*/

	cout << *(p) << endl;
	cout << *(p + 1) << endl;
	cout << *(p + 2) << endl;
	cout << *(p + 3) << endl;
	free(p);
	return  0;
}

*(p+1) = ‘2’; 等价于 p[1] =‘2’;
此时 free§; 销毁的为4个字节(因为malloc(4)),不会因为char是一个字节,只回收一个字节

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第17张图片
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第18张图片
5.4.4 new \ delete

new 和 delete 的一一对应关系

void test2() {
	int* p = new int; // 等价于 (int*)malloc(4);
	*p = 10;
	delete p;

	char* p1 = new char;// 等价于 (char*)malloc(1); 因为char占1个字节
	*p1 = '10';
	delete p1;

	char* p2 = new  char[4];// 等价于 (char*)malloc(4);
	delete[] p2;

}

delete 只会销毁指针指向的堆空间的内存,不会删除 指针的地址(内容)
在下一章《单例模式》中会进一步说明

5.4.5 memset 较大数据处理函数

1)memset(首地址, 要赋的值,长度);
2)memset 是将每一个字节赋值

void test3() {
	int* p1 = (int*)malloc(sizeof(int));// *p1未初始化
	int* p2 = (int*)malloc(sizeof(int));

	//初始化的2种情况
	// 从p1 地址开始,将4个字节中的每一个字节都设置为1
	// 00000001	00000001 00000001 00000001
	memset(p2, 1, 4);

	//将4个字节设置为1
	// 00000000	00000000 00000000 00000001
}
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第19张图片

3)memset 函数是将较大的数据结构(比如对象、数组等)内存清零的比较快的方法

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第20张图片

4)对象初始化
构造函数

struct Person {
	int m_age;
	Person() {
		memset(this, 0, sizeof(Person));
	}
};
5.4.6 堆空间的初始化
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第21张图片
int* p0 = new int;
int* p1 = new int(); // 有小括号的会初始化 (不使用memset的初始化 )
int* p2 = new int(5);// 有小括号的会初始化(不使用memset的初始化 )

如下代码:
可以看出被初始化的是将4个字节设置为5(这里不是用的memset

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第22张图片
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第23张图片

这里 -842150451 对应内存为 cd cd cd cd
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第24张图片

5.4.7 结论

1) 申请堆空间成功后,会返回那一段内存空间的地址
2) 申请和释放必须是1对1的关系,不然可能会存在内存泄露
3) 管理内存->利:提高开发效率,避免内存使用不当或泄露
4) 管理内存->弊:不利于开发人员了解本质,永远停留在API调用和表层语法糖,对性能优化无从下手

6. 对象的内存

6.1 对象的内存可以存放在3个地方

6.1.1 全局区(数据段):全局变量
6.1.2 栈空间:函数里面的局部变量

栈空间对象初始化时,成员变量默认为 cc cc cc cc

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第25张图片

所以:person.m_age 报错

6.1.3 堆空间:动态申请内存(malloc、new等)
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第26张图片

7. 构造函数(Constructor)

构造函数(构造器):在对象创建的时候自动调用,一般用于完成对象的初始化工作

构造函数私有化:可以类不可访问(不可创建对象)

7.1 引入构造函数的使用场景

#include 
using namespace std;

struct Person{
	int m_age;

};

int main() {
	//引入构造函数的场景
	Person person;
	person.m_age = 0;

	// 创建多个对象时,需要给每一个成员变量初始值
	Person person1;
	person1.m_age = 0;
	Person person2;
	person2.m_age = 0;
	Person person3;
	person3.m_age = 0;

	return  0;
}

7.2 特点

7.2.1 函数名与类同名,无返回值(void不能写),可以有参数,可以重载,可以有多个构造函数
7.2.2 一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象

1)验证:一旦自定义了构造函数,必须用其中一个

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第27张图片
#include 
using namespace std;

struct Person{
	int m_age;
	Person() {
		m_age = 0;
		cout << "Person()" << endl;
	}
	Person(int age) {
		m_age = age;
		cout << "Person(int age)" << m_age<< endl;
	}
	void display() {
		cout << "age is " << m_age << endl;
	}
};

int main() {
	Person person1;	// 使用构造函数 Person()
	person1.display();
	Person person2(20);// 使用构造函数 Person(int age) 
	person2.display();
	Person person3(30);// 使用构造函数 Person(int age) 
	person3.display();
	return  0;
}
输出结果:
Person()
age is 0
Person(int age)20
age is 20
Person(int age)30
age is 30

如下代码:违反了自定义函数必须使用其一的原则

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第28张图片

纠正图片错误:“午餐”=》“无参”

7.3 通过malloc分配的对象不会调用构造函数

7.3.1 不要用malloc搞对象(要去new)

malloc 从C语言沿用过来,C语言本身没有面向对象的概念,所以不会调用构造函数

#include 
using namespace std;

struct Person{
	int m_age;
	Person(int age) {
		m_age = age;
		cout << "Person::Person(int age)-" << m_age<< endl;
	}
	void display() {
		cout << "Person::display()-" << m_age << endl;
	}
};

int main() {

	Person* p = (Person*)malloc(sizeof(Person));
	p->m_age = 44;
	p->display();

	return  0;
}
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第29张图片

7.4 构造函数的谬论

7.4.1 一个广为流传的、很多教程、书籍都推崇的错误结论

默认情况下,编译器会为每一个类生成空的无参的构造函数

1)先来看一下有构造函数的反汇编
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第30张图片

2)再来看一下没有构造函数的反汇编
根本没有调用 call Person::Person(地址) 这句汇编语言
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第31张图片

7.4.2 谬论的正确理解

在某些特殊情况下,编译器才会为类生成空的无参的构造函数
C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第32张图片

7.5 构造函数的调用

7.5.1 全局区、栈空间、堆空间 创建对象时构造函数的调用
#include 
using namespace std;

struct Person {
	int m_age;
	Person() {
		m_age = 0;
		cout << "Person()" << endl;
	}
	Person(int age) {
		m_age = age;
		cout << "Person(int age)" << endl;
	}
};
Person g_person0;//	Person()
//Person g_person1(); 函数声明,没有创建对象 // 直接报错
Person g_person2(10);//	Person(int)

int main() {

	Person person0;//	Person()
	Person person1();//	函数声明,没有创建对象 
	Person person2(20);//	Person(int)
	
	Person* p0 = new Person;//	Person()
	Person* p1 = new Person();//	Person()
	Person* p2 = new Person(30);//	Person(int)
	return  0;
}

1)Person person() 这种写法不是创建对象 的写法,无论在全局区、栈空间
2)Person person() 这种写法都为函数声明(详见C++ 函数声明与实现)

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第33张图片

8. 成员变量的初始化

8.1 全局区、栈空间、堆空间,无构造函数时 初始化成员变量区别

如下代码:

#include 
using namespace std;

struct Person {
	int m_age;
};
//全局区:成员变量初始化为0
Person g_person;

int main() {
	//栈空间:没有初始化成员变量
	Person person;
	//堆空间:没有初始化成员变量
	Person* p0 = new Person;//	Person()
	//堆空间:成员变量初始化为0
	Person* p1 = new Person();//	Person()

	cout << g_person.m_age << endl;
	//cout << person.m_age << endl;
	cout << p0->m_age << endl;
	cout << p1->m_age << endl;

	return  0;
}
输出:
0
-842150451
0
8.1.1 全局区:成员变量初始化为0(int类型)
8.1.2 栈空间:没有初始化成员变量

(可见:6.1.2 栈空间:函数里面的局部变量)

8.1.3 堆空间
//堆空间:没有初始化成员变量
Person* p0 = new Person;//	Person()
//堆空间:成员变量初始化为0
Person* p1 = new Person();//	Person()

8.2 全局区、栈空间、堆空间,有构造函数时 初始化成员变量区别

#include 
using namespace std;

struct Person {
	int m_age;
	Person() {
	}
};

//全局区:成员变量初始化为0
Person g_person;


int main() {
	//栈空间:没有初始化成员变量
	Person person;

	//堆空间:没有初始化成员变量
	Person* p0 = new Person;//	Person()
	//堆空间:成员变量初始化为0
	Person* p1 = new Person();//	Person()

	cout << g_person.m_age << endl;
	//cout << person.m_age << endl;
	cout << p0->m_age << endl;
	cout << p1->m_age << endl;


	return  0;
}
输出:
0
-842150451
-842150451
8.2.1 全局区:成员变量初始化为0(int类型)

有无构造函数不影响全局区
内存空间的一个特点

8.2.2 栈空间:没有初始化成员变量

(可见:6.1.2 栈空间:函数里面的局部变量)

8.2.3 堆空间

因为自定义构造函数,堆空间不再初始化对象。成员变量完全由构造函数决定

8.3 结论

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第34张图片

如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化

9. 析构函数(Destructor)

析构函数(析构器):在对象销毁的时候自动调用,一般用于完成(堆空间)对象的清理工作

9.1 引入析构函数的使用场景

当对象被销毁(回收)自动调用

#include 
using namespace std;

struct Person {
	int m_age;
	//新的 person 对象诞生的象征
	Person() {
		cout << "Person:;Person()" << endl;
	}
	//新的 person 对象销毁(回收)的象征
	~Person() {
		cout << "~Person" << endl;
	}
};

int main() {
	Person person;
	return  0;
}

9.2 特点

函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数(总共6点)

9.2.1 函数与析构函数

如下代码:

#include 
using namespace std;

struct Person {
	int m_age;
	//新的 person 对象诞生的象征
	Person() {
		cout << "Person:;Person()" << endl;
	}
	//新的 person 对象销毁(回收)的象征
	~Person() {
		cout << "~Person" << endl;
	}
};

int main() {
	{
		Person person;
	}
	return  0;
}

运行结果

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第35张图片
9.2.2 指针与析构函数

如下代码:

#include 
using namespace std;

struct Person {
	int m_age;
	//新的 person 对象诞生的象征
	Person() {
		cout << "Person:;Person()" << endl;
	}
	//新的 person 对象销毁(回收)的象征
	~Person() {
		cout << "~Person" << endl;
	}
};

int main() {
	Person*p =new Person;
	delete p;
	return  0;
}

运行结果

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第36张图片

9.3 malloc 与 free 无关 构造函数与析构函数

通过malloc分配的对象free的时候不会调用析构函数
如下代码:

#include 
using namespace std;

struct Person {
	int m_age;
	//新的 person 对象诞生的象征
	Person() {
		cout << "Person:;Person()" << endl;
	}
	//新的 person 对象销毁(回收)的象征
	~Person() {
		cout << "~Person" << endl;
	}
};

int main() {
	Person* p = (Person*)malloc(sizeof(Person));
	free(p);
	return  0;
}

运行结果

C++挖掘程序本质(第二章C++面向对象-上)李明杰-M了个J 配套教材_第37张图片

9.4 全局区与析构函数

只要程序还在运行,就无法看到析构函数,除非杀死进程停止程序
结论:全局区不会看到析构函数的调用

9.4 构造与析构函数必须为public

构造函数和析构函数都要声明为public,才能被外界使用

class Person {
	int m_age;
public:
	//新的 person 对象诞生的象征
	Person() {
		cout << "Person:;Person()" << endl;
	}
	//新的 person 对象销毁(回收)的象征
	~Person() {
		cout << "~Person" << endl;
	}
};

你可能感兴趣的:(C++与汇编挖掘代码本质)