C++深度解析(11)—对象的初始化、浅拷贝、深拷贝、数组类的创建

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_22847457/article/details/96424651

1.对象的构造(上)

1.1 问题

  • 对象中成员变量的初始值是多少?

 
 
  1. #include <stdio.h>
  2. class Test
  3. {
  4. private:
  5. int i;
  6. int j;
  7. public:
  8. int getI()
  9. {
  10. return i;
  11. }
  12. int getJ()
  13. {
  14. return j;
  15. }
  16. };
  17. Test gt; // 静态存储区
  18. int main()
  19. {
  20. printf( "gt.i = %d\n", gt.getI());
  21. printf( "gt.j = %d\n", gt.getJ());
  22. Test t1; // 栈
  23. printf( "t1.i = %d\n", t1.getI());
  24. printf( "t1.j = %d\n", t1.getJ());
  25. Test* pt = new Test; // 堆
  26. printf( "pt->i = %d\n", pt->getI());
  27. printf( "pt->j = %d\n", pt->getJ());
  28. delete pt;
  29. getchar();
  30. return 0;
  31. }
  • 运行结果

1.2 对象的初始化

  •  从程序设计的角度,对象只是变量,因此: 
    • 在栈上创建对象时,成员变量初始为随机值 
    • 在堆上创建对象时,成员变量初始为随机值 (gcc会为其赋0)
    • 在静态存储区创建对象时,成员变量初始为0值 
  • 问题: 程序中如何对—个对象进行初始化?
  • 一般而言,对象都需要—个确定的初始状态 
  • 解决方案 :
    • 在类中提供—个public的initialize函数 
    • 对象创建后立即调用initialize函数进行初始化 

 
 
  1. #include <stdio.h>
  2. class Test
  3. {
  4. private:
  5. int i;
  6. int j;
  7. public:
  8. int getI()
  9. {
  10. return i;
  11. }
  12. int getJ()
  13. {
  14. return j;
  15. }
  16. void initialize()
  17. {
  18. i = 1;
  19. j = 2;
  20. }
  21. };
  22. Test gt;
  23. int main()
  24. {
  25. gt.initialize();
  26. printf( "gt.i = %d\n", gt.getI());
  27. printf( "gt.j = %d\n", gt.getJ());
  28. Test t1;
  29. //t1.initialize(); //若没有立即调用
  30. printf( "t1.i = %d\n", t1.getI());
  31. printf( "t1.j = %d\n", t1.getJ());
  32. t1.initialize(); //在此处调用
  33. Test* pt = new Test;
  34. pt->initialize();
  35. printf( "t1.i = %d\n", t1.getI());
  36. printf( "t1.j = %d\n", t1.getJ());
  37. printf( "pt->i = %d\n", pt->getI());
  38. printf( "pt->j = %d\n", pt->getJ());
  39. delete pt;
  40. getchar();
  41. return 0;
  42. }
  • 运行结果

  • 存在的问题 
    <ul><li>initialize只是—个普通函数,必须显示调用&nbsp;</li>
    	<li>如果未调用initialize函数,运行结果是不确定的&nbsp;</li>
    </ul></li>
    

1.3 构造函数

  • C++中可以定义与类名相同的特殊成员函数 ,这种特殊的成员函数叫做构造函数 
    • 构造没有任何返回类型的声明 
    • 构造函数在对象定义时自动被调用 

 
 
  1. #include <stdio.h>
  2. class Test
  3. {
  4. private:
  5. int i;
  6. int j;
  7. public:
  8. int getI()
  9. {
  10. return i;
  11. }
  12. int getJ()
  13. {
  14. return j;
  15. }
  16. Test()
  17. {
  18. printf( "Test() Begin\n");
  19. i = 1;
  20. j = 2;
  21. printf( "Test() End\n");
  22. }
  23. };
  24. Test gt;
  25. int main()
  26. {
  27. printf( "gt.i = %d\n", gt.getI());
  28. printf( "gt.j = %d\n", gt.getJ());
  29. Test t1;
  30. printf( "t1.i = %d\n", t1.getI());
  31. printf( "t1.j = %d\n", t1.getJ());
  32. Test* pt = new Test;
  33. printf( "pt->i = %d\n", pt->getI());
  34. printf( "pt->j = %d\n", pt->getJ());
  35. delete pt;
  36. getchar();
  37. return 0;
  38. }
  • 运行结果

1.4 小结

  • 每个对象在使用之前都应该初始化 
  • 类的构造函数用于对象的初始化 
  • 构造函数与类同名并且没有返回值 
  • 构造函数在对象定义时自动被调用 

2.对象的构造(中)

2.1 对象定义和对象声明不同 

  • 对象定义-申请对象的空间并调用构造函数 
  • 对象声明-告诉编译器存在这样—个对象 

 
 
  1. Test t;  //定义对象并调用构造函数  
  2.   
  3. int main()  
  4. {  
  5.       
  6.      extern Test t;   //告诉编译器存在名为t的Test对象   
  7.       
  8.      return  0;  

2.2 带参数的构造函数

  • 构造函数可以根据需要定义参数 

 
 
  1. #include <stdio.h>
  2. class Test
  3. {
  4. public:
  5. Test()
  6. {
  7. printf( "Test()\n");
  8. }
  9. Test( int v)
  10. {
  11. printf( "Tset(int v), v = %d\n", v);
  12. }
  13. };
  14. int main()
  15. {
  16. Test t; // 调用 Test()
  17. Test t1(1); // 调用Test(int v)
  18. Test t2 = 2; // 调用Test(int v)
  19. int i(100); // 赋值, 注意C++中对象初始化和赋值差距
  20. printf( "i = %d\n", i);
  21. getchar();
  22. return 0;
  23. }
  • 运行结果:

2.3 构造函数的调用 

  •  一般情况下,构造函数在对象定义时被自动调用 
  •  一些特殊情况下,需要手工调用构造函数 

 
 
  1. #include <stdio.h>
  2. class Test
  3. {
  4. private:
  5. int i;
  6. int j;
  7. int k;
  8. /*这种与类名相同的成员函数叫做构造函数
  9. 构造函数在定义时可以有参数,但是没有任何返回类型的声明
  10. */
  11. public:
  12. Test( int v)
  13. {
  14. i = v;
  15. j = v;
  16. k = v;
  17. }
  18. void print()
  19. {
  20. printf( "i = %d, j = %d, k = %d\n", i, j, k);
  21. }
  22. };
  23. int main()
  24. {
  25. Test t1(4); // 自动调用构造函数
  26. Test t2 = 5; // 自动调用构造函数
  27. Test t3 = Test( 6); // 主动调用构造函数
  28. t1.print();
  29. t2.print();
  30. t3.print();
  31. Test tA[ 3] = {Test( 4), Test( 5), Test( 6)}; // 主动调用构造函数
  32. for( int i = 0; i < 3; i++)
  33. {
  34. tA[i].print();
  35. }
  36. printf( "Press any key to continue...");
  37. getchar();
  38. return 0;
  39. }
  • 运行结果

2.4 成员函数的重载

  • 类的成员函数和普通函数一样可以进行重载,并遵守相同的重载规则

 
 
  1. #include <stdio.h>
  2. class Test
  3. {
  4. private:
  5. int i;
  6. int j;
  7. int k;
  8. public:
  9. Test()
  10. {
  11. i = 0;
  12. j = 0;
  13. k = 0;
  14. }
  15. Test( int v)
  16. {
  17. i = v;
  18. j = v;
  19. k = v;
  20. }
  21. void print()
  22. {
  23. printf( "i = %d, j = %d, k = %d\n", i, j, k);
  24. }
  25. void print(int v)
  26. {
  27. printf( "v = %d\n", v);
  28. }
  29. };
  30. int main()
  31. {
  32. Test t1(1);
  33. Test t2 = 2;
  34. Test t3 = Test( 3);
  35. Test t4;
  36. Test t5;
  37. t5.print( 5);
  38. t4.print();
  39. t1.print();
  40. t2.print();
  41. t3.print();
  42. printf( "\n");
  43. Test tA[ 3] = { Test( 5), 6, 7 };
  44. for( int i = 0; i < 3; i++)
  45. {
  46. tA[i].print();
  47. }
  48. printf( "Press any key to continue...");
  49. getchar();
  50. return 0;
  51. }
  • 运行结果

2.5 小结

  • 构造函数可以根据需要定义参数 
  • 构造函数之间可以存在重载关系 
  • 构造函数遵循C++中重载函数的规则 
  • 对象定义时会触发构造函数的调用 
  • 在—些情况下可以手动调用构造函数

3.对象的构造(下)

3.1 两个特殊的构造函数 

  • 无参构造函数:没有参数的构造函数 
    <ul><li>无参构造函数 :当类中没有定义构造函数时,编译器默认提供—个无参构造函数,并且其函数体为空&nbsp;</li>
    </ul></li>
    <li><span style="color:#f33b45;"><strong>拷贝构造函数:参数为const class_name&amp;的构造函数</strong></span>
    <ul><li>当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制</li>
    </ul></li>
    

 
 
  1. #include <stdio.h>
  2. class Test
  3. {
  4. private:
  5. int i;
  6. int j;
  7. int k;
  8. public:
  9. Test() // 当提供拷贝构造而不提供无参构造,编译器报错
  10. {
  11. }
  12. Test( const Test &t) // 不写默认提供
  13. {
  14. i = t.i;
  15. j = t.j;
  16. k = t.k;
  17. }
  18. void print()
  19. {
  20. printf( "i = %d, j = %d, k = %d\n", i, j, k);
  21. }
  22. };
  23. int main()
  24. {
  25. Test t1;
  26. Test t2 = t1;
  27. t1.print();
  28. t2.print();
  29. printf( "Press any key to continue...");
  30. getchar();
  31. return 0;
  32. }
  • 运行结果:
  • 拷贝构造函数的参数不能用值传递,只能用引用。如果用值传递,在函数运行时,函数内部会产生一个新的副本(实参副本)接收实参的值(形参变量的值是实参变量的副本),从而又要调用拷贝构造函数,造成死循环。
  • 如果类中一个构造函数都没有写,则编译器自动提供无参构造函数和拷贝构造函数。但如果已经存在某种构造函数,但编译器不会提供无参构造函数,但任然会提供拷贝构造函数。

3.2 拷贝构造函数

  • 拷贝构造函数的意义 
    • 兼容C语言的初始化方式 
    • 初始化行为能够符合预期的逻辑
  • 浅拷贝 
    • 拷贝后对象的物理状态相同 
  • 深拷贝 
    • 拷贝后对象的逻辑状态相同 
  • 编译器提供的拷贝构造函数只进行浅拷贝! 

 
 
  1. #include <stdio.h>
  2. class Test
  3. {
  4. private:
  5. int i;
  6. int j;
  7. int *p;
  8. public:
  9. int getI()
  10. {
  11. return i;
  12. }
  13. int getJ()
  14. {
  15. return j;
  16. }
  17. int *getP()
  18. {
  19. return p;
  20. }
  21. Test( int v)
  22. {
  23. i = 1;
  24. j = 2;
  25. p = new int;
  26. *p = v;
  27. }
  28. void free()
  29. {
  30. delete p;
  31. }
  32. };
  33. int main()
  34. {
  35. Test t1(3);
  36. Test t2 = t1;
  37. printf( "t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP());
  38. printf( "t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP());
  39. //t1.free();
  40. //t2.free();
  41. getchar();
  42. return 0;
  43. }
  • 运行结果:
  • 调用了默认拷贝构造函数,成员变量值一样,但当释放堆空间 t1.free();  t2.free(); 两次释放了堆空间的内存

  • 解决方案:增加拷贝构造函数,实现深拷贝

 
 
  1. #include <stdio.h>
  2. class Test
  3. {
  4. private:
  5. int i;
  6. int j;
  7. int *p;
  8. public:
  9. int getI()
  10. {
  11. return i;
  12. }
  13. int getJ()
  14. {
  15. return j;
  16. }
  17. int *getP()
  18. {
  19. return p;
  20. }
  21. Test( int v)
  22. {
  23. i = 1;
  24. j = 2;
  25. p = new int;
  26. *p = v;
  27. }
  28. Test( const Test &t)
  29. {
  30. i = t.i;
  31. j = t.j;
  32. p = new int;
  33. *p = *t.p;
  34. }
  35. void free()
  36. {
  37. delete p;
  38. }
  39. };
  40. int main()
  41. {
  42. Test t1(3);
  43. Test t2 = t1;
  44. printf( "t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP());
  45. printf( "t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP());
  46. printf( "t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP());
  47. printf( "t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP());
  48. t1. free();
  49. t2. free();
  50. getchar();
  51. return 0;
  52. }
  • 运行结果:
  • 什么时候需要进行深拷贝? 
    • 对象中有成员指代了系统中的资源 
    • 成员指向了动态内存空间 
    • 成员打开了外存中的文件 
    • 成员使用了系统中的网络端口 

   .......
-

3.3 数组类的创建

  • 需求:开发—个数组类解决原生数组的安全性问题 
    • 提供函数获取数组长度 
    • 提供函数获取数组元素 
    • 提供函数设置数组元素
  • 编程实验
  • Array.h

 
 
  1. #ifndef _ARRAY_H_
  2. #define _ARRAY_H_
  3. class Array
  4. {
  5. private:
  6. int mLength;
  7. int *mSpace;
  8. public:
  9. Array( int length);
  10. Array( const Array &obj);
  11. int length(); // 获取数组长度
  12. void setData(int index, int value); // 设置数组元素
  13. int getData(int index); // 获取数组元素
  14. void destory();
  15. };
  16. #endif
  • Array.cpp

 
 
  1. #include "Array.h"
  2. Array::Array( int length)
  3. {
  4. if (length < 0)
  5. {
  6. length = 0;
  7. }
  8. mLength = length;
  9. mSpace = new int[mLength];
  10. }
  11. Array::Array( const Array &obj)
  12. {
  13. mLength = obj.mLength;
  14. mSpace = new int[mLength];
  15. for ( int i = 0; i < mLength; i++)
  16. {
  17. mSpace[i] = obj.mSpace[i];
  18. }
  19. }
  20. int Array::length()
  21. {
  22. return mLength;
  23. }
  24. void Array::setData( int index, int value)
  25. {
  26. mSpace[index] = value;
  27. }
  28. int Array::getData( int index)
  29. {
  30. return mSpace[index];
  31. }
  32. void Array::destory()
  33. {
  34. mLength = -1;
  35. delete[] mSpace;
  36. }
  • main.cpp

 
 
  1. #include <stdio.h>
  2. #include "Array.h"
  3. int main()
  4. {
  5. Array a1(10); // 自动调用构造函数
  6. for( int i = 0; i < a1.length(); i++)
  7. {
  8. a1.setData(i, i);
  9. }
  10. for( int i = 0; i < a1.length(); i++)
  11. {
  12. printf( "Element %d: %d\n", i, a1.getData(i));
  13. }
  14. printf( "\n");
  15. Array a2 = a1; // // 自动调用拷贝构造函数
  16. for( int i = 0; i < a2.length(); i++)
  17. {
  18. printf( "Element %d: %d\n", i, a2.getData(i));
  19. }
  20. a1.destory();
  21. a2.destory();
  22. printf( "Press any key to continue...");
  23. getchar();
  24. return 0;
  25. }
  • 运行结果

3.4 小结

  • C++编译器会默认提供构造函数 
  • 无参构造函数用于定义对象的默认初始状态 
  • 拷贝构造函数在创建对象时拷贝对象的状态 
  • 对象的拷贝有浅拷贝和深拷贝两种方式 
    • 浅拷贝使得对象的物理状态相同 
    • 深拷贝使得对象的逻辑状态相同 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值