类
类
用 struct 和class 来定义一个类
C语言里struct是结构体,不能往里面写函数
C++中struct是类,可以往里面写函数
-
利用类创建对象
struct Person { int age; void run() { cout << "Person::run - " << age << endl; } } int main() { Person person; person.age = 10; person.run(); Person *p = &person; // 使用指针指向对象 p->age = 20; // 使用指针访问对象 p->run(); getchar(); return 0; }
上面代码中的person对象,p指针的内存都是在函数的栈空间,自动分配和回收的。
-
类所占用的内存只是类中定义的变量大小。比如上述的Person类中只有一个int age变量,Person类只占4个字节。
问:类中的函数不占用地址大小嘛?
不占用,因为相同的类不需要重复开辟相同的函数的空间,只需要调用相同的函数即可。但变量是不能相互调用的,所以变量需要每次调用类时开辟新的空间来存放。
-
class 和 struct 的区别(只有这一个
-
struct的默认成员权限是 public :可以直接访问
class 的默认成员权限是 private :不可以直接访问
-
需要注意命名规范问题:- 全局变量:g_
- 成员变量:m_
- 静态变量:s_
- 常量:c_
-
-
对象的内存布局
-
问:如果类中有多个成员变量,对象的内存又是如何布局的呢?
比如
struct Person { int m_id; int m_age; int m_height; void display() { cout << "m_id - " << m_id << endl; } }
Person 中定义的三个变量的内存地址是连续的
如果定义
Person person; person.m_id = 1; person.m_age = 2; person.m_height = 3;
内存地址:
按照类的定义顺序来排列的。
注意:person中第一个变量的地址值,就是person的地址值。
-
面向对象创建的对象内存创建在堆中
但person对象内存在栈空间
如果将对象定义在main函数之外,那么就是全局变量,占用的内存在全局区(数据段)
-
内存空间的布局:
Person person1; Person person2; person1.m_id = 1; person2.m_id = 2; person1.run(); person2.run();
-
问:person1调用的和person2所调用的run地址是一样的,那运行的时候系统是怎么区分的呢?
如果在调用函数时给定参数的地址,那么函数就可以轻易在栈空间寻找到参数的值并调用。
struct Person { int m_age; void run(Person *person) { cout << "Person age - " << person->m_age << endl; } void run() { // this = &person; cout << "Person age - " << m_age << endl; // cout << "Person age - " << this->m_age << endl; } }
这样就可以明确指定传入参数的地址。
但实际上并不会这么写,而是像第二种的方法。
因为编译器自动将类中的函数中加了一个隐式指针
this
,会自动传入函数调用者的地址值,指向函数调用者。
(这里将 person1 的地址值传给了 ecx加上this汇编代码是一样的。
如果时Person1调用,this就会被传入Person1的地址值;Person2一个道理。
函数是以机器码的形式存放在代码区,但在调用函数的时候,需要分配额外的存储空间来存储函数内部的局部变量。
(run函数存放在代码区,但运行这个函数的时候会在栈空间分配一段空间来存储函数的变量
-
问:如何利用指针间接访问所指向对象的成员变量?
-
从指针取出对象的地址
-
利用对象的地址 + 成员变量的偏移量计算出成员变量的地址
-
根据成员变量的地址访问成员变量的存储空间
Person *p = &person; p->m_id = 10; p->m_age = 10; p->m.height = 10; p->display();
[ebp-14h] – person 的地址
[ebp-20h] – 指针变量 p 的地址
如果不用指针直接修改对象中的成员,比如
Person p; p.m_id = 10; p.m_age = 10; p.m.height = 10; p.display();
那么汇编代码就只有
mov [address], 0Ah
**问:**那么如果使用指针来间接修改成员的值,汇编需要两行代码而直接修改只需要一行,是否使用指针的效率比直接修改要低?
从汇编指令数量来说是对的。
但没必要关注这里的效率,因为在某些情况下必须要使用指针而非直接修改,比如堆空间只能通过地址来访问,在只传入地址的情况下只能使用指针。
思考题:
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();
问:
person.display();
打印的是怎样的?按前面使用指针来间接修改成员值的汇编来说:
lea eax, [ebp-14h] // 先将所引用的成员的地址取出传入eax // 但此时传入的是 m_age 的地址 mov dword ptr [ebp-20h],eax // 将eax中的存的地址赋给指针变量 mov eax,dword ptr [ebp-20h] // 后面三段按照eax的偏移量对三个成员赋值 mov dword ptr [eax] ,0Ah // 这个地方eax的地址是person的地址,即第一个成员的地址 // 但在上述思考题代码中,传入给eax的是 m_age 的地址,即eax+4 mov eax,dword ptr [ebp-20h] // 所以修改的是eax+4和eax+8的值,即m_age和m_height的值 mov dword ptr [eax+4],0Ah // 所以最后的输出为 10, 40, 50 mov eax,dword ptr [ebp-20h] mov dword ptr [eax+8], eAh mov ecx, dword ptr [ebp-20h]
Person *p = (Person *) &person.m_age;
这个地方必须加强转(Person *),因为 person.m_age 是int型,不能将一个int型赋给Person *型**问:**如果将
person.display()
换成p->display()
呢?40,50, 越界
p->display()
会将指针p里面存储的地址传递给display函数的this,即&person.m_age
问:为什么全是cc呢?
因为在调用函数分配栈空间的时候,栈空间可能是之前别的函数用过的,所以里面还残留有垃圾数据,所以在下一次使用前,全部用cc来填充空间,相当于清空数据。
那为啥不填0呢?
cc在机器码中相当于int3(interrupt),起到断点的作用,相当于函数中填充的全是int3int3int3int3。
如果在代码执行的时候不小心跳到函数的栈空间,程序可以马上停止,
-
-
-
类的声明和实现分离
class Person {
private:
int m_age;
public:
void setAge(int age);
// 全是声明
int getAge();
Person();
~Person();
}
void Person::setAge(int age) {
m_age = age;
}
int Person::getAge() {
return m_age;
}
Person::Person() {
m_age = 0;
}
Person::~Person() {
}
可以把含有函数声明的class放进.h
文件,将函数实现放在.cpp
文件