0
点赞
收藏
分享

微信扫一扫

【C++】 类、对象内存布局、声明实现分离

小a草 2022-01-23 阅读 50

用 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函数存放在代码区,但运行这个函数的时候会在栈空间分配一段空间来存储函数的变量

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

        1. 从指针取出对象的地址

        2. 利用对象的地址 + 成员变量的偏移量计算出成员变量的地址

        3. 根据成员变量的地址访问成员变量的存储空间

          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文件

举报

相关推荐

0 条评论