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++编译器会默认提供构造函数 
  • 无参构造函数用于定义对象的默认初始状态 
  • 拷贝构造函数在创建对象时拷贝对象的状态 
  • 对象的拷贝有浅拷贝和深拷贝两种方式 
    • 浅拷贝使得对象的物理状态相同 
    • 深拷贝使得对象的逻辑状态相同 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
设计并实现一个动态整型数组Vect,要求: (1)实现构造函数重载,可以根据指定的元素个数动态创建初始值为0的整型数组,或根据指定的内置整型数组动态创建整型数组。 (2)设计拷贝构造函数和析构函数,注意使用深拷贝。 (3)设计存取指定位置的数组元素的公有成员函数,并进行下标越界,若越界则输出“out of boundary”。 (4)设计获取数组元素个数的公有成员函数。 (5)设计用于输出数组元素的公有成员函数,元素之间以空格分隔,最后以换行符结束。 在main函数中按以下顺序操作: (1)根据内置的静态整型数组{1,2,3,4,5}构造数组对象v1,根据输入的整型数构造数组对象v2。 (2)调用Vect的成员函数依次输出v1和v2的所有元素。 (3)输入指定的下标及对应的整型数,设置数组对象v1的指定元素。 (4)根据数组对象v1拷贝构造数组对象v3。 (5)调用Vect的成员函数依次输出v1和v3的所有元素。 设计并实现一个动态整型数组Vect,要求: (1)实现构造函数重载,可以根据指定的元素个数动态创建初始值为0的整型数组,或根据指定的内置整型数组动态创建整型数组。 (2)设计拷贝构造函数和析构函数,注意使用深拷贝。 (3)设计存取指定位置的数组元素的公有成员函数,并进行下标越界,若越界则输出“out of boundary”。 (4)设计获取数组元素个数的公有成员函数。 (5)设计用于输出数组元素的公有成员函数,元素之间以空格分隔,最后以换行符结束。 在main函数中按以下顺序操作: (1)根据内置的静态整型数组{1,2,3,4,5}构造数组对象v1,根据输入的整型数构造数组对象v2。 (2)调用Vect的成员函数依次输出v1和v2的所有元素。 (3)输入指定的下标及对应的整型数,设置数组对象v1的指定元素。 (4)根据数组对象v1拷贝构造数组对象v3。 (5)调用Vect的成员函数依次输出v1和v3的所有元素。
C++中,可以通过对象数组初始化创建一个由多个对象组成的数组对象数组初始化的方式有多种,下面是其中两种常用的方式: 1. 使用默认构造函数初始化对象数组 如果中有默认构造函数,那么可以使用以下方式初始化对象数组: ```cpp class MyClass { public: MyClass() { // 默认构造函数 } }; int main() { MyClass arr[5]; // 创建由5个MyClass对象组成的数组 return 0; } ``` 在上面的例子中,`MyClass arr[5]` 创建了一个由5个MyClass对象组成的数组,这些对象都是使用默认构造函数初始化的。 2. 使用带参数的构造函数初始化对象数组 如果中有带参数的构造函数,那么可以使用以下方式初始化对象数组: ```cpp class MyClass { public: MyClass(int x) { // 带参数的构造函数 } }; int main() { MyClass arr[5] = {MyClass(1), MyClass(2), MyClass(3), MyClass(4), MyClass(5)}; // 创建由5个MyClass对象组成的数组,并使用带参数的构造函数初始化 return 0; } ``` 在上面的例子中,`MyClass arr[5]` 创建了一个由5个MyClass对象组成的数组,并使用带参数的构造函数初始化。也可以使用的默认构造函数来初始化数组的一部分元素,例如: ```cpp class MyClass { public: MyClass() { // 默认构造函数 } MyClass(int x) { // 带参数的构造函数 } }; int main() { MyClass arr[5] = {MyClass(1), MyClass(), MyClass(3)}; // 创建由5个MyClass对象组成的数组,并使用带参数的构造函数和默认构造函数初始化 return 0; } ``` 在上面的例子中,`MyClass arr[5]` 创建了一个由5个MyClass对象组成的数组,并使用带参数的构造函数和默认构造函数初始化。其中,第一个和第三个元素使用带参数的构造函数初始化,第二个元素使用默认构造函数初始化

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值