• C++类与对象(拷贝与类的内存管理)


    感谢大佬的光临各位,希望和大家一起进步,望得到你的三连,互三支持,一起进步

    个人主页LaNzikinh-CSDN博客

    文章目录

    • 前言
    • 一.对象的动态建立和释放
    • 二.多个对象的构造和析构
    • 三.深拷贝与浅拷贝
    • 四.C++类的内存管理
    • 总结

    前言 

    我们前面讲起了一些关于C++中类与对象的一些语法,构造函数C构函数初始化成员列表等等,也讲了面对对象的程序设计方法和面对过程的程序设计方法有什么区别,我们这次就主要针对类与对象的拷贝和一些存储内存的角度继续了解


    一.对象的动态建立和释放

    我们在C语言中动态开辟内存和释放内存,用用到的就是malloc函数和free函数,当然在C++中也是可用的,但是来到了C++,我们就要用C++的语法,在这个地方我们主要是用new和delete来动态建立和释放。

    new和delete都是运算符,不是库函数,不需要单独添加头文件,而我们malloc和free都需要头文件,而且是函数,有函数的调用就要开辟栈空间,所以而运算符是不需要的,所以说这也体现的C++的好处

    格式:
    new
    1、类型指针 指针变量名 = new 类型
    2、类型指针 指针变量名 = new 类型(初始值)
    3、类型指针 指针变量名 = new 类型[元素个数]
    delete
    1、delete 指针变量名
    2、delete[] 指针变量名

    1. int main()
    2. {
    3. //在堆上申请一个int类型大小的空间,并且将申请的空间初始化为10
    4. int* p1 = new int(10);
    5. delete p1;
    6. //在堆上申请4个int类型大小的空间,并没初始化
    7. int* p2 = new int[4];
    8. delete[4] p2;
    9. //在堆上申请一个Box类型大小的空间,会构造对象出来
    10. Box* p3 = new Box;
    11. delete p3;
    12. //在堆上申请4个Box类型大小的空间,会构造对象出来
    13. Box* p4 = new Box[4];
    14. delete[4] p4;
    15. return 0;
    16. }

    注意

    new和delete是运算符,不是函数,因此执行效率高。虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new和delete运算符。new/delete 和 malloc/free有何取别呢?

    1、malloc/free为C的标准库函数,new、delete则为C++的操作运算符
    2、new能自动计算需要分配的内存空间,而malloc需要手工计算字节数
    3、new与delete直接带具体类型的指针,malloc和free返回void类型的指针。
    4、new类型是安全的,而malloc不是。例如int*p = new float[2];就会报错;
    而int p = malloc(2sizeof(int))编译时编译器就无法指出错误来。
    5、new调用构造函数,malloc不能;delete调用析构函数,而free不能
    6.new/delete是操作符可以重载,malloc/free则不能

    二.多个对象的构造和析构

    我们之前学了析构函数和构造函数,但是有没有想过在多个对象中析构和构造的调用顺序是怎么样的呢?

    注意:1.当类中的成员变量为另一个类的实例化对象时,我们称这个对象为成员对象。2.成员变量虽属的类中没有实现无参的构造函数是需要使用初始化成员列表。

    1. #include
    2. using namespace std;
    3. class ABC
    4. {
    5. public:
    6. ABC(int A, int B, int C)
    7. {
    8. cout << "ABC(int A, int B, int C)" << endl;
    9. }
    10. ~ABC()
    11. {
    12. cout << "~ABC()" << endl;
    13. }
    14. private:
    15. int a;
    16. int b;
    17. int c;
    18. };
    19. class myD
    20. {
    21. public:
    22. myD() :abc1(1, 2, 3), abc2(3, 5, 7)
    23. {
    24. cout << "myD()" << endl;
    25. }
    26. ~myD()
    27. {
    28. cout << "~myD()" << endl;
    29. }
    30. private:
    31. ABC abc1;
    32. ABC abc2;
    33. };
    34. int main()
    35. {
    36. myD a;
    37. return 0;
    38. }

    调用顺序

    最开始先是构造成员对象,所以先调用成员对象所对应的构造函数,然后就是构造函数本身,最后是析构函数,析构函数的调用顺序与构造相反,总之就是先构造成员对象,在构造本身,析构相反

    三.深拷贝与浅拷贝

    3.1拷贝构造函数

    当使用已经构造好的对象t1,初始化一个新的对象就会调用拷贝构造函数

    1. //拷贝构造函数
    2. Test(const Test& t)
    3. {
    4. cout << "Test(const Test& t)" << endl;
    5. }

    3.2对象的赋值

    思考这样的赋值对吗?

    1. #include
    2. using namespace std;
    3. class Test
    4. {
    5. public:
    6. int x;
    7. int y;
    8. int* sum;
    9. Test(int a, int b):x(a),y(b)
    10. {
    11. sum = new int[4];
    12. }
    13. //拷贝构造函数
    14. Test(const Test& t)
    15. {
    16. cout << "Test(const Test& t)" << endl;
    17. x = t.x;
    18. y = t.y;
    19. sum = t.sum;
    20. }
    21. ~Test()
    22. {
    23. delete[4] sum;
    24. }
    25. };
    26. int main()
    27. {
    28. Test t1(10,20);
    29. t1.sum[0] = 10; t1.sum[1] = 11; t1.sum[2] = 12; t1.sum[3] = 13;
    30. Test t2 = t1;
    31. }

    答案是不对的,不可以直接这样赋值会出现问题,为什么呢?因为他们的sum的地址都指向同一个地方。这个赋值并没有开辟两个空间,而是让这两个成员变量都指向了同一个区域。调用析构函数的时候会释放两次,因此就会造成问题

    这个就是拷贝错误,拷贝分为浅拷贝和深拷贝,同一类对象之间的负值一般是没有副作用的,但是类中有指针,并且指针指向的动态分配的内存空间时会导致两个对象的指针指向同一块内存空间,遇到这种情况时浅拷贝,他就不能解决问题,我们就要用深拷贝去解决

    1. class Test
    2. {
    3. public:
    4. int x;
    5. int y;
    6. int* sum;
    7. Test(int a, int b):x(a),y(b)
    8. {
    9. sum = new int[4];
    10. }
    11. //拷贝构造函数
    12. Test(const Test& t)
    13. {
    14. cout << "Test(const Test& t)" << endl;
    15. x = t.x;
    16. y = t.y;
    17. //浅拷贝
    18. //sum = t.sum;
    19. //深拷贝
    20. sum = new int[4];
    21. for (int i = 0; i < 4; i++)
    22. {
    23. sum[i] = t.sum[i];
    24. }
    25. }
    26. ~Test()
    27. {
    28. delete[4] sum;
    29. }
    30. };

    3.3浅拷贝

    1、同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝
    2、一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,将导致两个对象的指针变量向同一块内存空间,当两个对象被销毁时调用析构函数,因为在析构函数中会释放指针所指向的堆空间,造成同一块堆空间被释放两次从而导致程序运行出错。
    3、如果我们没有实现拷贝构造函数,C++编译器会自动实现一个拷贝构造函数,我们称之为默认拷贝构造函数,但是在默认拷贝构造函数中实现的时浅拷贝

    3.4深拷贝

    实现拷贝构造函数,在拷贝构造函数中需要对对象中的指针变量进行单独的内存申请。两个对象中的指针变量不会指向同一块内存空间,然后再将右值对象指针所指向的空间中的内容拷贝到新的对象指针所指向的堆空间中。

    四.C++类的内存管理

    C++类和对象中成员变量和成员函数是分开存储的,成员变量:静态成员变量存储于全局数据区中普通成员变量存储于函数中与结构体变量有相同的字节对其方式。成员函数:存放于代码段

    证明:

    1. #include
    2. using namespace std;
    3. class C1
    4. {
    5. public:
    6. int i;
    7. int j;
    8. int k;
    9. };
    10. class C2
    11. {
    12. public:
    13. int i;
    14. int j;
    15. int k;
    16. int getK()
    17. {
    18. return k;
    19. }
    20. void setK(int val)
    21. {
    22. k = val;
    23. }
    24. };
    25. int main()
    26. {
    27. C1 c1;
    28. C2 c2;
    29. cout << sizeof(c1) << endl;
    30. cout << sizeof(c2) << endl;
    31. }

    4.2this指针

    this指针的本质--指针常量,当形参和成员变量同名时,可用this指针来区分

    1. using namespace std;
    2. class ABC
    3. {
    4. public:
    5. int x, y, z;
    6. ABC(int x, int y, int z)
    7. {
    8. x = x;
    9. y = y;
    10. z = z;
    11. }
    12. };
    13. int main()
    14. {
    15. ABC a(1, 2, 3);
    16. return 0;
    17. }

    经过编译

    this指针指向调用该成员函数的对象

    1. class ABC
    2. {
    3. public:
    4. int x, y, z;
    5. ABC(ABC*const this,int x, int y, int z)
    6. {
    7. this->x = x;
    8. this->y = y;
    9. this->z = z;
    10. }
    11. };
    12. int main()
    13. {
    14. //&a就是this指针
    15. ABC a(&a,1, 2, 3);
    16. return 0;
    17. }

    4.3类的静态成员变量

    如果我要记录一个农场里面羊的数量,我该如何写呢?如果用C语言来写的话,就是面对过程的编程只有有样出生我就++,有羊死去我就减减,但是麻烦的是每个羊他可能会有年龄名字就会非常的繁琐,但是如果你是用c++面对对象的编程的话,我就可以直接构造一个样的类利用构造函数和析构函数来完成这个事情,而静态成员变量可以让这个事情完成的更完美,他是什么意思呢?可以用关键字static用于声明一个类的成员,静态的成员提供了一个同类对象的共享机制

    1. #include
    2. using namespace std;
    3. class sheep
    4. {
    5. public:
    6. int age;
    7. char name[32];
    8. sheep()
    9. {
    10. cnt++;
    11. }
    12. ~sheep()
    13. {
    14. cnt--;
    15. }
    16. static int cnt;
    17. };
    18. int sheep::cnt = 0;
    19. int main()
    20. {
    21. return 0;
    22. }

    static int cnt;只是声明了一个静态成员变量,不是内或者对象的成员变量,但是他的作用与在内和这些类的所有实例化对象中

    int sheep::cnt = 0;定义了sheep这个类中的静态成员变量cnt,并初始化为零,如果不初始化默认为4

    4.4类的静态成员函数

    定义:使用static修饰的成员函数叫做静态成员函数

    在静态成员函数内,不能访问除静态成员函数以外的其他成员变量

    什么时候可以将函数设计成静态成员函数?

    函数的行为跟类的实例无关,只跟类有关

    静态成员函数的用处:

    1.访问被private/protected修饰静态成员变量
    2.可以实现某些特殊的设计模式:如Singleton(单例模式)
    3.可以封装某些算法,比如数学函数,如In,sin,tan等等,这些函数本就没必要属于任何一个对象,所以从类上调用感觉更好,比如定义一个数学函数类Math。


    总结

    这次我们主要讲解了对象的动态开辟和释放对比C语言的不同,和前面所讲到的析构和构造的一个升华,是多对象的析构和构造,还讲了C++独特的浅拷贝和深拷贝以及C++类的一些内存管理如类的静态成员变量静态成员函数和this指针

  • 相关阅读:
    【JAVA】:万字长篇带你了解JAVA并发编程【一】
    Java学习笔记(十六)
    IVariantArray的注意事项:AE 调用GP工具方法介绍及常见错误“对 COM 组件的调用返回了错误 HRESULTE_FAIL
    网络原理之TCP/IP
    Linux下基于Electron的ZeroTier客户端GUI
    Spring注解开发(十)
    LeetCode刷题(python版)——Topic60排列序列
    高德地图旋转功能
    前端学习2——CSS3
    25K测试老鸟7年经验的面试心得,四种公司、四种问题…
  • 原文地址:https://blog.csdn.net/2301_80344616/article/details/139568473