咱们在 C 语言中,每一个变量都有其初始值。那么问题来了,对象中成员变量的初始值是多少呢?从设计的角度来看,对象只是变量,所以:在栈上建立对象时,成员变量初始为随机值;在堆上建立对象时,成员变量初始为随机值;在静态存储区建立对象时,成员变量初识为 0 值。数组
下来咱们以代码为例进行验证,代码以下安全
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } }; Test gt; int main() { printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test at; printf("at.i = %d\n", at.getI()); printf("at.j = %d\n", at.getJ()); Test* pt = new Test; printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); return 0; }
gt 对象是在静态存储区建立的,因此 gt.i 和 gt.j 应该都为 0,;at 对象是在栈上建立的,因此 at.i 和 at.j 应该都为随机值;pt 对象是在堆上建立的,因此 pt->i 和 pt->j 应该也为随机值。咱们来编译下,看看是否如咱们所分析的那样呢?网络
咱们看到前面两个如咱们所分析的那样,最后一个不同。咱们再来看看BCC编译器呢ide
咱们看到BCC编译器是如咱们所分析的那样。因此咱们不能依赖于某种编译器的特性。函数
在生活中的对象都是在初始化后上市的,初识状态(出厂设置)是对象广泛存在的一个状态。那么程序中如何对一个对象进行初始化呢?通常而言,对象都须要一个肯定的初识状态。解决方案即是在类中提供一个 public 的 initialize 函数,对象建立后当即调用 initialize 函数进行初始化。下来咱们以代码为例进行分析,在上面代码基础上加上 initialize 函数
学习
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } void initialize() { i = 1; j = 2; } }; Test gt; int main() { gt.initialize(); printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test at; at.initialize(); printf("at.i = %d\n", at.getI()); printf("at.j = %d\n", at.getJ()); Test* pt = new Test; pt->initialize(); printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); return 0; }
咱们编译,看看结果是否初始化好呢优化
咱们看到已经所有初始化为按照咱们所想要的状态了。可是这个就存在一个问题了,initialize 只是一个普通的函数,必须显示调用才行。若是为调用 initialize 函数的话,结果是不肯定的。若是咱们忘记在 at 对象中调用 initialize 函数,编译结果以下spa
那么这时问题来了,咱们该如何解决这个问题呢?在 C++ 中介意定义与类名相同的特殊成员函数,这种特殊的成员函数叫作构造函数。注意:构造函数没有返回类型的声明;构造函数在对象定义时自动被调用。那么这时咱们就能够将上面的程序改成这样设计
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } Test() { printf("Test() Begin\n"); i = 1; j = 2; printf("Test() End\n"); } }; Test gt; int main() { printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test at; printf("at.i = %d\n", at.getI()); printf("at.j = %d\n", at.getJ()); Test* pt = new Test; printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); return 0; }
咱们编译后结果以下对象
咱们这样是否是就方便不少呢?那确定了。咱们能够明显看到定义了三个对象后,调用了三次构造函数。那么咱们既然知道了有构造函数这一类的函数,它是否能像通常函数那样进行带参数呢?构造函数能够根据须要定义参数;一个类中能够存在多个重载的构造函数;构造函数的重载遵循 C++ 重载的规则。咱们以前说过定义和声明不一样,在对象这块也一样适用。对象定义和对象声明时不一样的:对象定义 -- 申请对象的空间并调用构造函数;对象声明 -- 告诉编译器存在这样一个对象。下来咱们以代码为例进行分析
#include <stdio.h> class Test { public: Test() { printf("Test()\n"); } Test(int v) { printf("Test(int v), v = %d\n", v); } }; int main() { Test t1; // 调用 Test() Test t2(1); // 调用 Test(int v) Test t3 = 2; // 调用 Test(int v) int i(10); printf("i = %d\n", i); return 0; }
咱们看到第 18 行的 t1 对象的构造函数确定调用了 Test(),第 19 和 20 行则是调用了 Test(int v);在 C 语言中还有 int i(10) 这种写法,咱们看看编译是否会经过?
咱们看到编译经过,而且如咱们所分析的那样。那么构造函数的调用是否有什么规则呢?在通常状况下,构造函数在对象定义时被自动调用,一些特殊状况下,须要手工调用构造函数。咱们如何利用构造函数来建立一个数组呢?
#include <stdio.h> class Test { private: int m_value; public: Test() { printf("Test()\n"); m_value = 0; } Test(int v) { printf("Test(int v), v = %d\n", v); m_value = v; } int getValue() { return m_value; } }; int main() { Test ta[3] = {Test(), Test(1), Test(2)}; for(int i=0; i<3; i++) { printf("ta[%d].getValue() = %d\n", i, ta[i].getValue()); } Test t = Test(10); printf("t.getValue() = %d\n", t.getValue()); return 0; }
咱们首先来分析下,数组第一个成员调用的构造函数应该是 Test(),后面两个成员调用的是 Test(int v) 函数,并打印出相应的值。最后定义的对象 t,它会打印出构造函数和获得的值都为 10,咱们来看看编译结果
下来咱们来开发一个数组类解决原生数组的安全性问题:提供函数获取数组长度;提供函数获取数组元素;提供函数设置数组元素。咱们来看看它是怎么实现的
IntArray.h 源码
#ifndef _INTARRAY_H_ #define _INTARRAY_H_ class IntArray { private: int m_length; int* m_pointer; public: IntArray(int len); int length(); bool get(int index, int& value); bool set(int index, int value); void free(); }; #endif
IntArray.cpp 源码
#include "IntArray.h" IntArray::IntArray(int len) { m_pointer = new int[len]; for(int i=0; i<len; i++) { m_pointer[i] = 0; } m_length = len; } int IntArray::length() { return m_length; } bool IntArray::get(int index, int& value) { bool ret = (0 <= index) && (index <= length()); if( ret ) { value = m_pointer[index]; } return ret; } bool IntArray::set(int index, int value) { bool ret = (0 <= index) && (index <= length()); if( ret ) { m_pointer[index] = value; } return ret; } void IntArray::free() { delete[] m_pointer; }
test.cpp 源码
#include <stdio.h> #include "IntArray.h" int main() { IntArray a(5); for(int i=0; i<a.length(); i++) { a.set(i, i+1); } for(int i=0; i<a.length(); i++) { int value = 0; if( a.get(i, value) ) { printf("a[%d] = %d\n", i, value); } } a.free(); return 0; }
咱们编译后获得以下结果
下来咱们来看看特殊的构造函数:无参构造函数和拷贝构造函数。无参构造函数顾名思义就是没有参数的构造函数,而拷贝构造函数则是参数为 const class_name& 的构造函数。那么这两类构造函数有什么区别呢?无参构造函函数是当类中没有定义构造函数时,编译器默认提供一个无参构造函数,而且其函数体为空;拷贝构造函数是当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制。下来咱们以代码为例进行分析
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } /* Test() { printf("Test()\n"); } Test(const Test& t) { printf("Test(const Test& t)\n"); i = t.i; j = t.j; } */ }; int main() { Test t1; Test t2 = t1; printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ()); printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ()); return 0; }
咱们先将本身提供的无参构造函数和拷贝构造函数注释掉,编译下,看编译器是否提供默认的构造函数,是否能够经过
咱们看到编译是经过的,也就是说,编译器经过了默认的构造函数。咱们再来本身提供呢,看看是否会发生冲突
咱们看到打印出了本身定义的语句,证实它是调用了咱们本身写的构造函数。那么这个拷贝构造函数的意义在哪呢?一是兼容 C 语言的初始化方式,二是初始化行为可以符合预期的逻辑。那么这块就牵扯到是浅拷贝仍是深拷贝。浅拷贝是拷贝后对象的物理状态相同,深拷贝是拷贝后对象的逻辑状态相同。注意:编译器提供的拷贝构造函数只进行浅拷贝!
下来咱们以实例代码看看对象的初始化是怎样进行的
#include <stdio.h> class Test { private: int i; int j; int* p; public: int getI() { return i; } int getJ() { return j; } int* getP() { return p; } Test(int v) { i = 1; j = 2; p = new int; *p = v; } Test(const Test& t) { i = t.i; j = t.j; p = new int; *p = *t.p; } void free() { delete p; } }; int main() { Test t1(3); Test t2(t1); printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP()); printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP()); t1.free(); t2.free(); return 0; }
咱们看看 t1 应该进行的是浅拷贝,t2 应该进行的是深拷贝。咱们看看编译结果
咱们若是只有浅拷贝,没有深拷贝的话,看看结果会是怎样的,将第 34 - 41 行的代码注释掉,将第 54 和 55 行的打印 *p 的值改成打印 p 的地址。
咱们看到它运行的时候报段错误了,t1.p 和 t2.p 指向了同一个地址。咱们看看它是怎样进行的
咱们看到将同一个地址释放两次确定是会出问题的,这时咱们就须要进行深拷贝了。那么咱们就要考虑到底何时须要进行深拷贝?当对象中有成员指代了系统中的资源时,如:成员指向了动态内存空间,成员打开了外存中的文件,成员使用了系统中的网络端口...
咱们在实现拷贝构造函数这块有个通常性的原则,自定义拷贝构造函数时,必需要实现深拷贝。那么咱们再来优化下以前的数组类
IntArray.h 源码
#ifndef _INTARRAY_H_ #define _INTARRAY_H_ class IntArray { private: int m_length; int* m_pointer; public: IntArray(int len); IntArray(const IntArray& obj); int length(); bool get(int index, int& value); bool set(int index, int value); void free(); }; #endif
IntArray.cpp 源码
#include "IntArray.h" IntArray::IntArray(int len) { m_pointer = new int[len]; for(int i=0; i<len; i++) { m_pointer[i] = 0; } m_length = len; } IntArray::IntArray(const IntArray& obj) { m_length = obj.m_length; m_pointer = new int[obj.m_length]; for(int i=0; i<obj.m_length; i++) { m_pointer[i] = obj.m_pointer[i]; } } int IntArray::length() { return m_length; } bool IntArray::get(int index, int& value) { bool ret = (0 <= index) && (index <= length()); if( ret ) { value = m_pointer[index]; } return ret; } bool IntArray::set(int index, int value) { bool ret = (0 <= index) && (index <= length()); if( ret ) { m_pointer[index] = value; } return ret; } void IntArray::free() { delete[] m_pointer; }
test.cpp 源码
#include <stdio.h> #include "IntArray.h" int main() { IntArray a(5); for(int i=0; i<5; i++) { a.set(i, i+1); } for(int i=0; i<a.length(); i++) { int value = 0; if( a.get(i, value) ) { printf("a[%d] = %d\n", i, value); } } printf("\n"); IntArray b = a; for(int i=0; i<b.length(); i++) { int value = 0; if( b.get(i, value) ) { printf("b[%d] = %d\n", i, value); } } a.free(); b.free(); return 0; }
咱们看看编译结果是否如咱们代码所写的那样,建立数组并初始化。用数组 a 初始化数组 b。
经过对对象的构造的学习,总结以下:一、每一个对象在使用以前都应该初始化;二、类的构造函数用于对象的初始化,构造函数与类同名而且没有返回值;三、构造函数在对象定义时被自动调用,构造函数能够根据须要定义参数;四、构造函数之间能够存在重载关系,而且构造函数遵循 C++ 中重载函数的规则;五、对象定义时会触发构造函数的调用,在一些状况下能够手动调用构造函数;六、C++ 编译器会默认提供构造函数;七、无参构造函数用于定义对象的默认初识状态,拷贝构造函数在建立对象时拷贝对象的状态;八、对象的拷贝有浅拷贝和深拷贝两种方式:浅拷贝使得对象的物理状态相同,而深拷贝则使得对象的逻辑状态相同。
欢迎你们一块儿来学习 C++ 语言,能够加我QQ:243343083。