模式------------即方法
1. 构造函数的回顾
(1)关于构造函数
①类的构造函数用于对象的初始化
②构造函数与类同名并且没有返回值(思考:无返回值如何判断构造函数的执行结果?)
③构造函数在对象定义时自动被调用
问题:
1,如何判断构造函数的执行结果? ---普通函数可以返回值
2,构造函数中执行return会发生什么?------普通函数立即返回,后面的函数不执行
3,构造函数执行结束是否意味着对象构造成功?-------
编程实验以上问题:
1 #include<stdio.h> 2 /* 1,没有办法判断构造函数的执行结果 3 2,构造函数中执行return会发生什么?构造函数执行之后,不意味着对象构造成功 4 */ 5 // 模式即方法 6 class Test 7 { 8 int mi; 9 int mj; 10 11 public: 12 13 Test(int i, int j) //1,构造函数中执行return会发生什么? 14 { //答:直接返回,Test t1(1,2)有问题,初始状态有问题,没有影响构造函数的诞生,只是不能知道t1能不能用 15 //所以没有办法判断构造函数的执行结果 16 mi = i; 17 18 return; 19 20 mj = j; //这行代码没有执行--------mj变为随机数,mj成员未完成初始化 21 } 22 23 int getI() 24 { 25 return mi; 26 } 27 28 int getJ() 29 { 30 return mj; 31 } 32 }; 33 34 int main() 35 { 36 Test t1(1,2); 37 38 printf("t1.mi=%d ",t1.getI()); //1 39 printf("t1.mj=%d ", t1.getJ()); //5689365 40 41 return 0; 42 }
回答:构造函数的真相
①构造函数只提供自动初始化成员变量的机会,但不能保证初始化逻辑一定成功。它只能决定对象的初始状态,而不是对象的诞生!
②构造函数中执行return语句后,构造函数执行结束,但是不意味着对象构造成功。
那么怎么解决?????
1 #include<stdio.h> 2 /* 3 判断对象构造是否成功------方案:类中增加成员-mStatus-----强行让构造函数有个返回值mStatus 4 */ 5 // 模式即方法 6 class Test 7 { 8 int mi; 9 int mj; 10 11 bool mStatus; //成员变量记录类的初始状态 12 13 public: 14 Test(int i, int j) : mStatus(false) //类的成员变量mStatus的初始化 15 { 16 mi = i; 17 18 return; //mj变为随机数,mj成员未完成初始化 19 20 mj = j; 21 22 mStatus = true; //对象构造成功,mStatus返回true 23 } 24 int getI() 25 { 26 return mi; 27 } 28 int getJ() 29 { 30 return mj; 31 } 32 33 int status() //功能函数 34 { 35 return mStatus; 36 } 37 }; 38 39 int main() 40 { 41 Test t1(1, 2); 42 43 if (t1.status())//构造未完成,status为false 44 { 45 printf("t1.mi=%d ", t1.getI());//i被正确初始化 46 printf("t1.mj=%d ", t1.getJ());//j为随机值 47 } 48 return 0; 49 }
2. 半成品对象
(1)初始化操作不能按照预期完成而得到的对象
(2)半成品对象是合法的C++对象,也是bug的重要来源
段错误:指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址(比如0地址)、访问了只读的内存地址等等情况。
内存申请不成功,产生段错误,但是有的错误只产生一次,该怎么解决这种问题???
人为的将构造分为两步
3. 二阶构造---(用于杜绝半成品对象)
(1)工程开发中的构造过程
①资源无关的初始化操作阶段:
----------不会出现异常情况的操作(比如将成员变量的值设置为初始值)
②需要使用系统资源的操作
-------可能出现异常,如内存申请(堆空间),访问文件等。
二阶构造,对象只在堆空间产生,不能在栈上产生,往往实际工程对象是非常大的,不适合放在栈空间(栈空间有限)
为什么要用对象创建函数???
因为构造函数是Private外界不能调用,所以要通过对象创建函数(stati 静态成员函数内部可以访问类的私有成员)
static TwoPhaseCons* NewInstance(); //对象创建函数 -----------返回对象指针
实例:
1 #include<stdio.h> 2 3 4 5 class TwoPhaseCons 6 { 7 private: 8 TwoPhaseCons() //第一阶段构造函(简单的赋值操作) c++构造函数可以担任二阶构造的一阶构造函数 9 { 10 } 11 bool construct() //第二阶段构造函数(系统资源申请,打开文件,网络) 普通成员函数 12 { 13 return true; 14 } 15 public: 16 //构造函数时Private外界不能调用,通过对象创建函数(静态成员函数内部可以访问类的私有成员函数) 17 static TwoPhaseCons* NewInstance(); //对象创建函数 ----------- 返回对象指针 18 19 }; 20 21 //对象创建函数实现 22 TwoPhaseCons* TwoPhaseCons::NewInstance() 23 { 24 TwoPhaseCons* ret = new TwoPhaseCons(); //堆空间创建对象,调用构造函数----执行第一阶段构造--(静态成员函数内部可以访问类的私有成员) 25 26 27 //r若第二阶段构造失败,返回NULL 28 if (!(ret && ret->construct())) //construct----进行资源申请-------执行二阶构造 29 { 30 delete ret; //删除半成品对象 31 ret = NULL; 32 } 33 34 return ret; //返回对象,资源申请成功-----合法对象NewInstance()申请成功 35 } 36 37 int main() 38 { 39 // TwoPhaseCons obj; //error 40 41 // TwoPhaseCons* obj =new NewInstance(); //error 触发构造函数自动调用,但是构造函数是私有的,不能在外部main()自动调用,只能调用创建函数 42 43 TwoPhaseCons* obj= TwoPhaseCons::NewInstance(); //只能调用静态创建函数-----因为要调用私有的构造函数,所以必须定义静态创建函数,实现在类的外部(mian)调用 44 45 printf("obj=%p ",obj); //可以得到合法可用的对象----位于堆空间 46 47 return 0; 48 }
【编程实验】数组类的加强 IntArray
//IntArray.h
1 #ifndef _INTARRAY_H 2 #define _INTARRAY_H 3 4 class IntArray 5 { 6 private: 7 int m_length; //数组长度 8 int* m_pointer; //数组数据 9 10 IntArray(int len); //一阶构造 11 IntArray(const IntArray& obj); //一阶构造 12 bool construct(); //实现第二阶段的构造 13 14 public: 15 static IntArray* NewInstance(int length); //对象创建函数 16 17 int length(); //获取数组长度 18 bool get(int index, int& value); //获取对应位置的数组元素值 19 bool set(int index, int value); //设置对应位置的数组元素为参数value 20 void free(); 21 }; 22 23 #endif 24
////IntArray.cpp
1 #include"IntArray.h" 2 3 //第一阶段存放不存在错误的操作 4 //可能失败的操作放在第二阶段 5 6 IntArray :: IntArray(int len) //作用域分辨符:: 7 { 8 m_length = len; 9 } 10 11 bool IntArray::construct() //构造函数 12 { 13 bool ret = true; 14 15 m_pointer = new int[m_length]; //数据指针指向堆空间里的一段内存:数组作用,保存整形数组 16 17 if (m_pointer) // 要判断内存是否申请成功 18 { 19 for (int i = 0; i < m_length; i++) //将数组所有值设置为0 20 { 21 m_pointer[i] = 0; 22 } 23 } 24 else 25 { 26 ret = false; 27 } 28 return ret; 29 } 30 31 int IntArray :: length() 32 { 33 return m_length; 34 } 35 36 bool IntArray :: get(int index, int& value) //安全性体现----得到对应位置的数组元素值 ---index为指定位置 37 { 38 bool ret = (0 <= index) && (index <= length()); 39 40 if (ret) 41 { 42 value = m_pointer[index]; //通过引用返回值 43 } 44 45 return ret; 46 } 47 48 IntArray* IntArray::NewInstance(int length) ///对象创建函数 49 { 50 IntArray* ret = new IntArray(length); 51 52 //r若第二阶段构造失败,返回NULL 53 if (!(ret && ret->construct())) 54 { 55 delete ret; 56 ret = 0; 57 } 58 59 return ret; 60 } 61 62 63 bool IntArray :: set(int index, int value) //设置对应位置的数组元素为参数value 64 { 65 bool ret = (0 <= index) && (index <= length()); 66 if (ret) 67 { 68 m_pointer[index]=value; //数组成员重新赋值 69 } 70 return ret; 71 } 72 73 void IntArray::free() 74 { 75 delete[] m_pointer; //释放堆空间 76 }
//main.c
1 #include<stdio.h> 2 #include "IntArray.h" 3 4 5 int main() { 6 7 8 //调用创建函数 9 IntArray* a = IntArray::NewInstance(5); 10 printf("a.length=%d ",a->length()); 11 12 a->set(0, 1); 13 14 for (int i = 0; i <a->length() ; i++) 15 { 16 int v = 0; 17 18 a->get(i, v); 19 20 printf("a[%d]=%d ",i,v); 21 } 22 23 24 return 0; 25 }
4. 小结
(1)构造函数只能决定对象的初始化状态
(2)构造函数中初始化操作的失败不影响对象的诞生
(3)初始化不完全的半成品对象是bug的重要来源
(4)二阶构造人为的将初始化过程分为两个部分
(5)二阶构造能够确保创建的对象都是完整初始化的