11 对象的构造
1 对象的初始化
-
问题:对象中成员变量的初始值是多少?
-
Demo
#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()); //0 printf("gt.j = %d\n", gt.getJ()); //0 Test t1; //栈 printf("t1.i = %d\n", t1.getI()); //134513995 printf("t1.j = %d\n", t1.getJ()); //5984244 Test* pt = new Test; //堆 printf("pt->i = %d\n", pt->getI()); // printf("pt->j = %d\n", pt->getJ()); // delete pt; return 0; }
-
-
从程序设计的角度,对象只是变量,因此
- 在栈上创建对象时,成员变量初始为随机值
- 在堆上创建对象时,成员变量初始为随机值
- 在静态存储区上创建对象时,成员变量初始为 0 值
-
问题:程序中如何对一个对象进行初始化,使得不管在什么地方创建类对象,其成员变量的初始值都为固定值?
-
解决方案1
-
在类中提供一个
public
的initialize
函数 -
对象创建后立即调用
initialize
函数进行初始化 -
示例:初始化函数
-
Demo
#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()); //1 printf("gt.j = %d\n", gt.getJ()); //2 Test t1; //没有显式调用初始化函数,运行结果未知 //t1.initialize(); printf("t1.i = %d\n", t1.getI()); //7469952 printf("t1.j = %d\n", t1.getJ()); //134514267 //没有立即调用,运行结果未知 t1.initialize(); Test* pt = new Test; //显式调用初始化函数 pt->initialize(); printf("pt->i = %d\n", pt->getI()); //1 printf("pt->j = %d\n", pt->getJ()); //2 delete pt; return 0; }
-
-
存在的问题
initialize
只是一个普通函数,必须显式调用- 如果未调用
initialize
函数,运行结果是不确定的 - 如果没有在生成类对象后立即调用
initialize
函数 ,运行结果是不确定的
-
-
解决方案2:构造函数
2 构造函数
2.1 构造函数简介
-
C++ 中可以定义与类名相同的特殊的成员函数,这种特殊的成员函数叫做构造函数
- 构造函数没有任何返回类型的声明
- 构造函数在对象定义时自动被调用
-
示例:构造函数
-
Demo
#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 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; }
-
编译运行
Test() Begin Test() End gt.i = 1 gt.j = 2 Test() Begin Test() End t1.i = 1 t1.j = 2 Test() Begin Test() End pt->i = 1 pt->j = 2
-
2.2 带参数的构造函数
-
带有参数的构造函数
-
构造函数可以根据需要定义参数
-
一个类中可以存在多个重载的构造函数
-
构造函数的重载遵循 C++ 重载的规则
class Test { public: Test(int c) { //use v to initialize member } };
-
-
注意:对象定义和对象声明不同
-
对象定义:申请对象的空间并调用构造函数
-
对象声明:告诉编译器存在这样一个对象
Test t; //定义对象并调用构造函数 int main() { extern Test t; //告诉编译器存在名为t的Test对象,通过链接器在各个目标文件中寻找t的定义 return 0; }
-
-
构造函数的自动调用
class Test { public: Test(){} Test(int v){} }; int main() { Test t; //调用Test() Test t1(1); //调用Test(int v) Test t2 = 1; //调用Test(int v) }
-
示例:带参数的构造函数
-
Demo
#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(100); //初始化(如果是类类型,会调用构造函数) int j; j = 2; //赋值操作 printf("i = %d\n", i); return 0; }
-
编译运行
Test() Test(int v), v = 1 Test(int v), v = 2 i = 100
-
-
构造函数的调用
- 一般情况下,构造函数在对象定义时被自动调用
- 一些特殊情况下,需要手工调用构造函数,例如创建一个对象数组
-
示例:构造函数的手动调用 => 创建一个对象数组
-
Demo
#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]; for (int i = 0; i < 3; ++i) { printf("ta[%d].getValue() = %d\n", i, ta[i].getValue()); } return 0; }
-
编译运行:可以看到对象数组
ta
中的每一个类对象的成员变量m_value
值都相同:0Test() Test() Test() ta[0].getValue() = 0 ta[1].getValue() = 0 ta[2].getValue() = 0
-
修改:创建对象值不同的对象数组
#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(100); //手动调用构造函数 => 完成定义对象 printf("t.getValue() = %d\n", t.getValue()); return 0; }
-
编译运行
Test() Test(int v), v = 1 Test(int v), v = 2 ta[0].getValue() = 0 ta[1].getValue() = 1 ta[2].getValue() = 2 Test(int v), v = 100 t.getValue() = 100
-
-
需求:开发一个数组类解决原生数组的安全性问题
-
提供函数获取数组长度
-
提供函数获取数组元素
-
提供函数设置数组元素
-
Demo:
IntArray
类//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; } //main.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; }
-
编译运行
a[0] = 1 a[1] = 2 a[2] = 3 a[3] = 4 a[4] = 5
-
2.3 无参构造函数和拷贝构造函数
-
两个特殊的构造函数
- 无参构造函数
- 没有参数的构造函数
- 当类中没有定义任何构造函数时,编译器会默认提供一个无参构造函数,并且其函数体为空
- 拷贝构造函数
- 参数为
const class_name&
的构造函数 - 当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制(浅拷贝)
- 参数为
- 无参构造函数
-
示例:特殊的构造函数
-
Demo
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } /*Test(const Test& t) { i = t.i; j = t.j; } Test() { }*/ }; 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 语言的初始化方式,形如:
int i = 1; int j = i;
- 初始化行为能够符合预期的逻辑:两个对象的状态一样
- 兼容 C 语言的初始化方式,形如:
-
深浅拷贝
- 浅拷贝:拷贝后对象的物理状态相同,编译器提供的拷贝构造函数只进行浅拷贝
- 深拷贝:拷贝后对象的逻辑状态相同
-
示例:对象的初始化
-
Demo
#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 = new int; *p = *t.p; } */ Test(int v) { i = 1; j = 2; p = new int; *p = v; } 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(); //内存泄漏:0x8e6a008被释放了两次 return 0; }
-
编译运行
t1.i = 1, t1.j = 2, t1.p = 0x8e6a008 t2.i = 1, t2.j = 2, t2.p = 0x8e6a008
-
-
问题:什么时候需要进行深拷贝?
-
对象中有成员指代了系统中的资源
- 成员指向了动态内存空间
- 成员打开了外存中的文件
- 成员使用了系统中的网络端口
- 。。。。。。
-
分析:
t1
和t2
的m_pointer
指向同一块内存,之后会被释放两次
-
-
一般性原则:自定义拷贝构造函数,必然需要实现深拷贝
-
IntArray
类的改进-
Demo
//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; } //main.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); } } 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; }
-
编译运行
1 2 3 4 5 1 2 3 4 5
-