1 问题:对象中成员变量的初始值是多少?
小实验:下面的类定义中成员变量i和j的初始值是多少?
编程实验:成员变量的初始值
#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 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;
}
- 从程序设计的角度,对象只是变量,因此:
- 在栈上创建对象时,成员变量初始为随机值;
- 在堆上创建变量时,成员变量初始为随机值;
- 在静态存储区创建变量时,成员变量初始为0值。
2 对象的初始化
- 生活中的对象都是在初始化之后上市的。
- 初始状态(出厂设置)是对象普遍存在的一个状态。
问题:程序中如何对一个对象进行初始化?
- 一般而言,对象都需要一个确定的初识状态。
- 解决方案:
- 在类中提供一个public的initialize函数;
- 对象创建后立即调用intialize函数进行初始化。
编程实验:初始化函数
#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());
t1.initialize();
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函数,运行结果是不确定的。
3 构造函数
- 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 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;
}
小结
- 每个对象在使用之前都应该初始化。
- 类的构造函数用于对象的初始化。
- 构造函数与类同名并且没有返回值。
- 构造函数在对象定义时自动被调用。
4 带参构造函数
- 带有参数的构造函数:
- 构造函数可以根据需要定义参数;
- 一个类中可以存在多个重载的构造函数;
- 构造函数的重载遵循C++重载的规则。
友情提醒
- 对象定义和对象声明不同:
- 对象定义:申请对象的空间并调用构造函数;
- 对象声明:告诉编译器存在这样一个对象。
构造函数的自动调用
注意:构造函数必须为public。
编程实验:带参数的构造函数
#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);
printf("i = %d\n", i);
return 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 a; // Test()
- Test a = 1; or Test a(1); // Test(int v)
- Test a = Test(1); // Test(v)
小实例
- 需求:开发一个数组类解决原生数组的安全性问题
- 提供函数获取数组长度;
- 提供函数获取数组元素;
- 提供函数设置数组元素。
编程实验:数组类的实现
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;
}
小结
- 构造函数可以根据需要定义参数。
- 构造函数之间可以存在重载关系。
- 构造函数遵循C++中重载函数的规则。
- 对象定义时会触发构造函数的调用。
- 在一些情况下可以手动调用构造函数。
5 特殊的构造函数
两个特殊的构造函数
- 无参构造函数
- 没有参数的构造函数。
- 当类中没有定义构造函数(包括拷贝构造函数)时,编译器默认提供一个无参构造函数,并且函数体为空。
- 拷贝构造函数
- 参数为cosnt class_name&的构造函数。
- 当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制。
编程实验:特殊的构造函数
#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;
}
6 拷贝构造函数
- 拷贝构造函数的意义
- 兼容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(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();
return 0;
}
什么时候需要进行深拷贝
- 对象中有成员指代了系统中的资源:
- 成员指向了动态内存空间;
- 成员打开了外存中的文件;
- 成员使用了系统中的网络端口;
- ……
问题分析
一般性原则:自定义构造函数,必然要实现深拷贝!!!
数组类的改进
增加深拷贝构造函数
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++编译器会默认提供构造函数。
- 无参构造函数用于定义对象的默认初始状态。
- 拷贝构造函数在创建对象时拷贝对象的状态。
- 对象的拷贝有浅拷贝和深拷贝两种方式:
- 浅拷贝使得对象的物理状态相同;
- 深拷贝使得对象的逻辑状态相同。