1)struct 的默认成员权限是 public
2)class 的默认权限是 private (实际开发中使用class)
使用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;
}
下面的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)
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本质上是没区别的
#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;
}
上面代码的反汇编:
1)每一个 car 对象都在栈空间开辟了4个字节的存储空间
2)可以看出所有的run() 都都被编译成:call 函数地址 ,
3)3个 run() 方法都是call一样的地址,可以看出编译后的run() 方法不与对象产生在同一块内存
成员函数(类中的函数) 在创建对象时不会被塞入对象总内存中,而是被编译后单独在内存中(如果不被调用的函数,在编译优化后将不会有内存存储此函数),即创建100个对象,只会有一份函数的内存
调用 成员函数(类中的函数)反汇编为:call 这个函数地址
成员函数(类中的函数)存储的内存与对象存储内存不同
类中的成员变量 ,在创建对象时会在内存中开辟成员变量对应类型大小的连续内存(暂不考虑:内存对齐),如int在64位系统占用4个字节,对象的地址量即类中按顺序第一成员变量的地址
下面的对象 car1~car3总计占用 12个字节
变量名规范参考
使用场景 | 方式 | 实例 |
---|---|---|
全局变量 | g_ | g_count |
成员变量 | m_ | m_count |
静态变量 | s_ | s_count |
常量 | c_ | c_count |
类中成员变量在内存中是连续的存储
#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相同)
创建类的实例 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;
}
可以看到编译器已经帮我们优化好了,将*person指针 演化成this 再到默认为this
不可以,因为 this 是指针,必须用this->m_age 来访问
1). 点的左边只能是对象
2)-> 箭头的左边只能是指针
由上图可以看出 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
这里比较绕,大家一定要把代码和反汇编,结合文中的描述一起理解
#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)多个成员变量在内存中是连续分配的(根据代码区的类下成员代码顺序,且当前为小端模式)
还是要明白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
看到如下的汇编代码,一定是指针间接访问成员变量
mov eax,dword ptr [ebp-20h]
mov dword ptr [eax],14h
[ [ebp-20h] ]将值作为地址,取其存储空间
#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
这里所指的偏移量,全部是以类下的成员变量书写顺序,也就是类的机器码存放在代码区的顺序
class Person {
public:
int age; // 顺序开始
int height;
...
int phone;// 顺序结束
};
// 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();
m_height = -858993460 这个结果是 cc
0xccccccc
cc->int3:起到断点的作用 (interrupt 中断)
提供公共的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
每个内存都应有自己独立的内存空间,其内存空间一般都有以下几大区域
(下图有误“其中一个 “栈空间” 为 “堆空间”)
用于存放代码(机器码),只读
用于存放全局变量等(static)
整个程序运行中都存在,除非关闭进程
#include
using namespace std;
//全局变量
int g_age = 10;
int main() {
return 0;
}
g_age 就是全局变量
每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间(注意:如果申请了堆空间,堆空间需要手动销毁)
#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;
}
在程序运行过程,为了能够自由控制内存的生命周期、大小会经常使用堆空间的内存
malloc \ free
new \ delete
new [ ] \ delete [ ]
malloc 向堆空间申请内存,因为返回值为void *,需要根据类型强制转换
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是一个字节,只回收一个字节
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 只会销毁指针指向的堆空间的内存,不会删除 指针的地址(内容)
在下一章《单例模式》中会进一步说明
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
}
3)memset 函数是将较大的数据结构(比如对象、数组等)内存清零的比较快的方法
4)对象初始化
构造函数
struct Person {
int m_age;
Person() {
memset(this, 0, sizeof(Person));
}
};
int* p0 = new int;
int* p1 = new int(); // 有小括号的会初始化 (不使用memset的初始化 )
int* p2 = new int(5);// 有小括号的会初始化(不使用memset的初始化 )
如下代码:
可以看出被初始化的是将4个字节设置为5(这里不是用的memset)
这里 -842150451 对应内存为 cd cd cd cd
1) 申请堆空间成功后,会返回那一段内存空间的地址
2) 申请和释放必须是1对1的关系,不然可能会存在内存泄露
3) 管理内存->利:提高开发效率,避免内存使用不当或泄露
4) 管理内存->弊:不利于开发人员了解本质,永远停留在API调用和表层语法糖,对性能优化无从下手
栈空间对象初始化时,成员变量默认为 cc cc cc cc
所以:person.m_age 报错
构造函数(构造器):在对象创建的时候自动调用,一般用于完成对象的初始化工作
构造函数私有化:可以类不可访问(不可创建对象)
#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;
}
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)" << 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
如下代码:违反了自定义函数必须使用其一的原则
纠正图片错误:“午餐”=》“无参”
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;
}
默认情况下,编译器会为每一个类生成空的无参的构造函数
2)再来看一下没有构造函数的反汇编
根本没有调用 call Person::Person(地址) 这句汇编语言
#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++ 函数声明与实现)
如下代码:
#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
(可见:6.1.2 栈空间:函数里面的局部变量)
//堆空间:没有初始化成员变量
Person* p0 = new Person;// Person()
//堆空间:成员变量初始化为0
Person* p1 = new Person();// Person()
#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
有无构造函数不影响全局区
内存空间的一个特点
(可见:6.1.2 栈空间:函数里面的局部变量)
因为自定义构造函数,堆空间不再初始化对象。成员变量完全由构造函数决定
如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化
析构函数(析构器):在对象销毁的时候自动调用,一般用于完成(堆空间)对象的清理工作
当对象被销毁(回收)自动调用
#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;
}
函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数(总共6点)
如下代码:
#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;
}
运行结果
如下代码:
#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;
}
运行结果
通过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;
}
运行结果
只要程序还在运行,就无法看到析构函数,除非杀死进程停止程序
结论:全局区不会看到析构函数的调用
构造函数和析构函数都要声明为public,才能被外界使用
class Person {
int m_age;
public:
//新的 person 对象诞生的象征
Person() {
cout << "Person:;Person()" << endl;
}
//新的 person 对象销毁(回收)的象征
~Person() {
cout << "~Person" << endl;
}
};