#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } }; Test gt; // 全局对象 全局区,统一初始值为0 int main() { printf("gt.i = %d\n", gt.getI()); // 0 printf("gt.j = %d\n", gt.getJ()); // 0 Test t1; // 局部对象 栈区 printf("t1.i = %d\n", t1.getI()); // 随机值 printf("t1.j = %d\n", t1.getJ()); // 随机值 Test* pt = new Test; // 类也是一个数据类型,堆区 printf("pt->i = %d\n", pt->getI()); // 堆区应该也是随机值 printf("pt->j = %d\n", pt->getJ()); delete pt; return 0; }
从程序设计的角度,对象只是变量,所以:c++
静态存储区包括了全局变量和
static
修饰的局部变量
须要解决的问题:使类的成员变量无论在哪一个存储区进行定义,它的初始值都是固定的。数组
对象的初始化:安全
解决方案:网络
public
的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 t1; t1.initialize(); printf("t1.i = %d\n", t1.getI()); printf("t1.j = %d\n", t1.getJ()); Test* pt = new Test; pt->initialize(); printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); delete pt; return 0; }
这种方式存在的问题:函数
initialize
只是一个普通函数,必须显示调用initialize
函数,运行结果是不肯定的这个初始化函数在对象建立之手就必须立刻调用,新建对象之手,须要人工手动添加initialize()
函数,若是能够有一个函数在建立对象后自动调用,初始化成员变量就是极好的。优化
因而C++出现了构造函数来解决这个问题设计
C++中能够定义与类名相同的特殊成员函数:构造函数指针
#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() { i = 1; j = 2; } }; Test gt; int main() { //gt.initialize(); printf("gt.i = %d, gt.j = %d\n", gt.getI(), gt.getJ()); Test t1; //t1.initialize(); printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ()); Test * pt = new Test; //pt->initialize(); printf("pt->i = %d, pt->j = %d\n", pt->getI(), pt->getJ()); return 0; }
构造函数和普通函数的差异:构造函数没有返回值,名字和类型同样code
此时就只剩下参数能够讨论:构造函数也能够带参数对象
带有参数的构造函数:
class Test { public: Test(int v) { // use v to initialize member } };
注意:
对象定义和对象声明不一样:
- 对象定义——申请对象的空间并调用构造函数
- 对象声明——告诉编译器存在这样一个对象
Test t; // 定义对象并调用构造函数 int main() { // 告诉编译器存在名为t的Test对象 extern Test t; return 0; }
构造函数的自动调用
class Test { public: Test(){} Test(int v) { } Test(const int& cv){} // 拷贝构造函数 }; Test t; // 调用构造函数Test() Test t1(1); // 定义了一个对象t1,并调用带有参数的构造函数,传入参数为1,根据重载规则,构造函数为Test(int v) Test t2 = 1; // 用 1 来初始化对象t2,初始化须要借助构造函数,根据重载规则,选择Test(int v) /*这里的过程实际上是: 首先调用构造函数Test(int v)建立一个临时对象,参数为1; 而后就变成了用一个对象初始化另外一个对象,此时应该是要调用拷贝构造函数进行成员变量值的复制,将这个临时对象做为参数用来构造对象t2。 可是编译器发现,能够经过重载的构造函数Test(int v)来直接初始化对象,而达到相同效果,因此将这条语句优化为Test t1(1) */
初始化和赋值:
#include <stdio.h> class Test { public: Test() { printf("Test()\n"); } Test(int v) { printf("Test(int v), v = %d\n", v); } }; int main() { Test t; // 调用 Test() Test t1(1); // 调用 Test(int v) Test t2 = 2; // 调用 Test(int v) int i = 1; // 用1来初始化变量i i = 2; // 用2对变量i进行赋值 t = t2; // 用对象t2对对象t进行赋值 int i(100); // 用100来初始化i printf("i = %d\n", i); return 0; }
初始化和赋值是不同的,C语言中差异不大,C++中差异很大,由于对象的初始化要调用构造函数
构造函数的调用:
#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; } void getValue() { return m_value; } }; int main() { Test ta[3]; // 调用3次Test() ,每一个数组元素中的m_value都按Test()来处理,不必定须要这样的结果 Test ta2[3] = {Test(), Test(1), Test(2)}; // 手工调用构造函数,3个数组元素调用不一样的构造函数 for (int i = 0; i < 3; i++) { printf("ta[%d].getValue() = %d\n", i, ta[i].getValue()); // 手工调用构造函数后,m_value初始化成不一样值 } Test t = Test(100); // 建立对象以后,调用构造函数来初始化对象 return 0; }
需求:开发一个数组类解决原生数组的安全性问题
// 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.c #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; } // main.c #include <stdio.h> #include "IntArray.h" int main() { IntArray a(5); // 定义了一个对象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(){}; // 编译器会提供一个默认的无参构造函数 */ // 拷贝构造函数,这也是一个构造函数,写了这个以后,编译器就不会提供默认的无参构造函数,建立对象就会失败,须要手工再建立一个无参构造函数 Test(const Test& t) { i = t.i; j = t.j; } Test(){}; }; class T { /* 空类:里面至少有一个无参构造函数 */ } int main() { Test t; // 未定义构造函数时,依然能建立对象,由于编译器提供了一个无参的构造函数 int i = 2; int j = i; // 用一个变量初始化另外一个变量 // 同理到 类 对象中 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; }
拷贝构造函数的意义:
初始化和赋值不同的地方在于,初始化涉及到拷贝构造函数的调用,经过拷贝构造函数能够利用一个已知的对象去建立初始化一个新的对象
拷贝构造函数分为:
拷贝后对象的物理状态相同(只进行值的拷贝,成员的复制)
拷贝后对象的逻辑状态相同(逻辑状态的拷贝)
编译器提供的拷贝构造函数只进行浅拷贝
#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(const Test& t) { i = t.i; j = t.j; // p指针的值不直接复制 p = new int; // 指向一个新的堆空间的地址 *p = *t.p; // 将新的堆空间的值进行从新指定 // 将t.p指向的堆空间的值,复制给新的p指向内存 } // 定义一个带参数的构造函数 Test(int v) { i = 1; j = 2; p = new int; *p = v; } void free() { delete p; } }; int main() { Test t; // 没参数,没有默认的无参构造函数,因此会建立失败 Test t1(3); // 提供一个参数,建立对象 /* t1在建立的时候,p指针指向堆空间里面的某个内存地址 用t1去初始化t2的时候,t2.p也应该指向堆空间的某个内存地址,而且和t1.p不是指向同一个内存地址 这个程序只是将参数3传给v,而后在堆空间里存放这个值 拷贝构造的时候也是作这件事,而不是直接将指针地址复制 */ Test t2 = t1; // 或者 Test t2(t1); // 将t1做为参数传入拷贝构造函数 Test t3 = 2; // 以2为参数调用构造函数Test(2), 生成临时对象去初始化对象t3,可是被编译器优化为Test t3(2),不调用拷贝构造函数 printf("t1.i = %d, t1.j = %d, *t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP()); printf("t2.i = %d, t2.j = %d, *t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP()); /* 采用默认的拷贝构造函数: t1.p = 0x8e6a008 指向堆空间的一个地址 t2.p = 0x8e6a008 t1和t2的p指向了堆空间的同一个地址 采用手工构造的拷贝构造函数: t1.p = 0x8528008 t2.p = 0x8528018 两个对象的指针成员指向的堆空间的地址不一致了,状态仍是一致吗?可是这个地址里面存储的int类型的数据确是相同的 */ 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.p = 3, *t2.p = 3 t1.free(); t2.free(); /* 这里会报内存错误 0x8e6a008这个内存地址会被释放两次,t1.free()以后,t2.free()就不能再释放了 */ return 0; } /* t1.i = 1, t1.j = 2, t1.p = 0x8528008 t2.i = 1, t2.j = 2, t2.p = 0x8528018 这就是物理状态,就是对象占据的内存中,他们的每一个字节是否相等 t1和t2这两个对象在内存中占据的空间中的值是不同的 从另外一个角度看 t1.i = 1, t1.j = 2, *t1.p = 3 t2.i = 1, t2.j = 2, *t2.p = 3 这就是逻辑状态,t1和t2是同样的,咱们须要的仅仅是t1和t2的p指针,所指向的空间中的值是同样的 */
何时使用深拷贝:对象中有成员指向了系统资源
通常性原则:自定义拷贝构造函数,必然须要实现深拷贝!!!
// IntArray.C // 构造函数 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]; } }
- 每一个对象在使用以前都应该初始化
类的构造函数用于对象的初始化
构造函数与类同名而且没有返回值
构造函数在定义时自动被调用
- 构造函数能够根据须要定义参数
构造函数之间能够存在重载关系
构造函数遵循C++中重载函数的规则
对象定义时会触发构造函数的调用
在一些状况下能够手动调用构造函数
C++编译器会默认提供构造函数
无参构造函数用于定义对象的默认初始状态
拷贝构造函数在建立对象时拷贝对象的状态
对象的拷贝有浅拷贝和深拷贝两种方式
- 浅拷贝使得对象的物理状态相同
- 深拷贝使得对象的逻辑状态相同