第四章 封装、内存布局(malloc、free、new、delete)、构造函数

封装

  1. 引例

     struct Person
     {
     	int m_age;
     };
        
     //使用
     Person person;
     person.m_age = -4;
    
    1. 年龄设置为-4很显然是现实中是不可能的,但是由于m_age这个成员变量是暴露出来的,可以在外部任意修改
    2. 为了防止这种情况,就出现了封装这个概念。
    3. 封装就是为了让外部不能直接访问对象的成员变量,仅仅通过我们封装后的方法才能访问。
    4. 我们可以在封装方法中设置一些过滤,防止非正常数据的出现。
  2. 封装的本质:

    1. 成员变量私有化,提供公共的getter和setter给外界去访问成员变量

       struct Person
       {	
       private:
       	int m_age;
              
       public:
       	//封装
       	void setAge(int age) {
       		if (age<=0)
       		{
       			cout << "设置的值不合理" << endl;
       			return;
       		}
       		this->m_age = age;
       	}
              
       	int getAge() {
       		return this->m_age;
       	}
       };
              
       int main() {
       	Person person;
       	person.setAge(20);
       	cout << "age=" << person.getAge() << endl;
       	person.setAge(-20);
              
       	getchar();
       	return 0;
       }
      

内存空间的布局

  1. 每个应用都有自己独立的内存空间,其内存空间一般都有以下几大区域
    1. 代码段(代码区)
      1. 用于存放代码
    2. 数据段(全局区)
      1. 用于存放全局变量等
    3. 栈空间
      1. 每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间
      2. 自动分配和回收
    4. 堆空间
      1. 需要主动去申请和释放

堆空间

  1. 为什么需要堆空间? 堆空间存在的意义?
    1. 我们知道栈空间分配的内存,一旦函数调用完毕就会被释放
    2. 但是如果我们需要在B函数中使用A函数定义的变量,怎么办呢? 这就需要延长A函数中变量的生命周期
      1. 设置为全局变量:但是一旦成为全局变量,整个应用关闭之前这段内存就一直存在,无法释放,设置多了就消耗内存,因此,不现实
      2. 能不能有一种方法:自己管理内存,使用前延长内存的生命周期,使用后手动释放掉? —堆区就是这种功能
  2. 在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存
  3. 堆空间的申请\释放
    1. malloc \ free
    2. new \ delete
    3. new [] \ delete []
  4. 注意
    1. 申请堆空间成功后,会返回那一段内存空间的地址
    2. 申请和释放必须是1对1的关系,不然可能会存在内存泄露
  5. 现在的很多高级编程语言不需要开发人员去管理内存(比如Java),屏蔽了很多内存细节,利弊同时存在
    1. 利:提高开发效率,避免内存使用不当或泄露
    2. 弊:不利于开发人员了解本质,永远停留在API调用和表层语法糖,对性能优化无从下手
     //c语言
     void test() {
     	//申请4个字节的堆空间
     	int *p = (int *)malloc(4);
     	//上面的p在栈空间,分配一段内存用于存储堆空间的地址
     	//堆中分配4个字节的内存
     	free(p);
     }
        
     //c++语言
     void test2() {
     	//申请4个字节内存
     	int *p = new int;
     	//释放
     	delete p;
        
     	//申请40个字节
     	int *p2 = new int[10];
     	//释放
     	delete[] p2;
     }
    

堆空间的初始化

  1. 案例如下:

     //c语言初始化
     void test3(){
     	int *P1 = (int *)malloc(sizeof(int));//未初始化
     	int *P2 = (int *)malloc(sizeof(int));
     	//将p2指向的4个字节的堆空间都初始化为0;
     	memset(P2, 0, sizeof(int));
     	cout << *P2 << endl;
        	
     	free(P1);
     	free(P2);
     }
     //c++语言初始化
     void test4() {
     	int *p1 = new int; //*p1 未初始化
     	int *p2 = new int(); // *p2 初始化为0
     	int *p3 = new int(5); // *p3 初始化为5
     	int *p4 = new int[3];//*p4 数组元素未被初始化
     	int *P5 = new int[3]();//3个数组元素都被初始化为0
     	int *p6 = new int[3]{};//3个数组元素都被初始化为0
     	int *p7 = new int[3]{ 5 };//数组首元素被初始化为5,其他元素被初始化为0
     }
    

函数补充memset

  1. memset函数是将较大的数据结构(比如对象、数组等)内存清零的比较快的方法 (不管是堆上还是栈上)
    图1

对象的内存

  1. 对象的内存可以存在于3种地方
    1. 全局区(数据段):全局变量
    2. 栈空间:函数里面的局部变量
    3. 堆空间:动态申请内存(malloc、new等)
     //全局区
     Person g_person;
     int main(){
         //栈空间
         Person person;
         //堆空间
         Person *p = new Person;
         return 0;
     }
    

构造函数(Constructor)

  1. 构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作
    1. 顾名思义:就是在创建、构造、初始化一个对象的时候调用的函数
  2. 构造函数特点
    1. 函数名与类同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数

        struct Person
       {
       	int m_age;
       	//构造函数
       	Person() {
       		cout << "执行了person()" << endl;
       		this->m_age = 0;
       	}
       	//重载
       	Person(int age) {
       		cout << "执行了person(int)" << endl;
       		this->m_age = age;
       	}
       };
       int main() {
              
       	//调用构造函数Person();
       	Person person1;
       	//调用构造函数Person(int);
       	Person person(20);
       	cout << person.m_age << endl;
       	getchar();
       	return 0;
       }
      
    2. 一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象

      1. 没有自定义构造函数

         struct Student
         {
         };
                    
         //创建对象
         //正确没有问题
         Student student;
        
      2. 自定义构造函数

         struct Student
         {
         	Student(int age) {
         	}
                    
         };
         int main() {
         //这么调用有问题,因为没有构造函数 Student(){}
         Student student;
         //这么调用没有有问题
         Student student(20);
        
      3. 自定义构造函数

          struct Student
         {
             Student() {
             }
                        
             Student(int age) {
             }
                    
         };
         int main() {
         //这么调用没有问题
         Student student;
          //这么调用没有有问题
         Student student(20);
        
  3. 注意
    1. 通过malloc分配的对象不会调用构造函数

       //通过堆创建构造函数
       //调用构造函数 Person()
       Person *P1 = new Person();
       delete P1;
                  
       //不会调用任何构造函数。
       Person *p2 = (Person *)malloc(sizeof(Person));
       free(p2);
      
  4. 一个广为流传的、很多教程\书籍都推崇的错误结论:
    1. 默认情况下,编译器会为每一个类生成空的无参的构造函数 (错误结论!!!)
    2. 正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数
      1. (哪些特定的情况?以后再提)

构造函数的调用

  1. 写法注意:
    1. C++通过new创建对象的格式是:

       类型 *x =  new 类型
       类型 *x =  new 类型()
       类型 *x =  new 类型(参数)
      
    2. 栈上创建对象的方式是:

       类型 标识符;
       类型 标识符(参数);
      
    3. 函数声明:

       类型 标识符();
      
    4. 举例:

       struct Person
       {
       	int m_age;
       	//构造函数
       	Person() {
       		cout << "执行了person()" << endl;
       		this->m_age = 0;
       	}
       	//重载
       	Person(int age) {
       		cout << "执行了person(int)" << endl;
       		this->m_age = age;
       	}
       };
              
       //全局区
       Person g_perosn1; //Person();
              
       //这是一个函数声明,函数名叫g_perosn2,无参数,返回值为Person类型。
       Person g_perosn2();
              
       Person g_perosn3(10);//Person(int);
              
       int main() {
       	//栈空间
       	Person person; //Person();
       	//同样这也是一个函数声明,函数名叫person2,无参,返回值类型为Person
       	Person person2();
       	Person person3(10);//Person(int);
              
       	//堆空间
       	Person *p1 = new Person; //Person();
       	Person *p2 = new Person(); //Person();
       	Person *p3 = new Person(10); //Person(int);
              
       	return 0;
       }
      
      1. 调用结果,有3次调用有参的构造函数,有4次调用无参的构造函数
      2. 表面看是应该有6次调用无参的构造函数。为什么呢?因为上面有2个是函数声明!

成员变量的初始化

  1. 默认情况下,成员变量的初始化

     struct Person
     {
     	int m_age;
     };
     //全局区
     Person g_person;//成员变量初始化为0;
        
     int main() {
     	//栈空间
     	Person person; //成员变量不会被初始化
        
     	//对空间
     	Person *p1 = new Person;//成员变量不会被初始化
     	Person *p2 = new Person();//成员变量被初始化为0
     	Person *P3 = new Person[3];//成员变量不会被初始化
     	Person *P4 = new Person[3]();//3个person对象的成员变量被初始化为0
     	Person *P5 = new Person[3]{};//3个person对象的成员变量被初始化为0
     	return 0;
     	}
    
  2. 如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化

     struct Person
     {
     	int m_age;
     	Person() {
        
     	}
     };
     //全局区
     Person g_person;//成员变量初始化为0;
        
     int main() {
     	//栈空间
     	Person person; //成员变量不会被初始化
        
     	//对空间
     	Person *p1 = new Person;//成员变量不会被初始化
     	Person *p2 = new Person();//成员变量不会被初始化
     	Person *P3 = new Person[3];//成员变量不会被初始化
     	Person *P4 = new Person[3]();//3个person对象的成员变量不会被初始化
     	Person *P5 = new Person[3]{};//3个person对象的成员变量被初始化为0
     	return 0
     }
    
    1. 需要自己在自定义的构造函数中初始化

       struct Person
       {
       	int m_age;
       	Person() {
       		memset(this, 0, sizeof(Person));
       	}
       };
      
Table of Contents