构造函数
一、思考
对象中成员变量的初始值是多少?——随机值?还是0?
#include <stdio.h>
class Test
{
private:
int i;
int j;
public:
int getI(){return i;}
int getJ(){return j;}
};
Test t;
int main()
{
Test t1;
printf("t1.i = %d,t1.j = %d\n",t1.getI(),t1.getJ());
printf("t.i = %d,t.j = %d\n",t.getI(),t.getJ());
Test* pt = new Test;
printf("pt->i = %d,pt->j = %d\n",pt->getI(),pt->getJ());
delete pt;
return 0;
}
——全局数据区的全局变量,初始为0;
——栈空间分配的局部变量,初始值为随机值;
——动态分配的堆空间的变量,初始值也为随机值。
小节:
从程序设计角度,对象只是变量,因此:
- 在栈上创建对象时,成员变量初始为随机值
- 在堆上创建对象时,成员变量初始值为随机值
- 在静态存储区创建对象时,成员变量初始值为0值
二、对象的初始化
- 一般而言,对象都需要一个确定的初始状态
- 解决方案
- 在类中提供一个public的initialize函数
- 对象创建后立即调用initialize函数进行初始化
#include <stdio.h>
class Test
{
private:
int i;
int j;
public:
void initialize()
{
i = 1;
j = 2;
}
int getI(){return i;}
int getJ(){return j;}
};
Test t;
int main()
{
t.initialize();
Test t1;
t1.initialize();
printf("t1.i = %d,t1.j = %d\n",t1.getI(),t1.getJ());
printf("t.i = %d,t.j = %d\n",t.getI(),t.getJ());
Test* pt = new Test;
pt->initialize();
printf("pt->i = %d,pt->j = %d\n",pt->getI(),pt->getJ());
delete pt;
return 0;
}
——如果在定义之后,没有立即调用,就使用,会带来什么?能不能自动调用?
三、引入构造函数
C++中可以定义与类名相同的特殊成员函数——构造函数
- 构造函数没有任何返回类型的声明
- 构造函数在对象定义时自动被调用
#include <stdio.h>
class Test
{
private:
int i;
int j;
public:
Test()
{
i = 1;
j = 2;
printf("Test() begin\n");
}
int getI(){return i;}
int getJ(){return j;}
};
Test t;
int main()
{
Test t1;
printf("t1.i = %d,t1.j = %d\n",t1.getI(),t1.getJ());
printf("t.i = %d,t.j = %d\n",t.getI(),t.getJ());
Test* pt = new Test;
printf("pt->i = %d,pt->j = %d\n",pt->getI(),pt->getJ());
delete pt;
return 0;
}
小结:
- 每个对象在使用之前都应该初始化
- 类的构造函数用于对象的初始化
- 构造函数与类同名并且没有返回值
- 构造函数在对象定义时自动被调用
四、带有参数的构造函数
- 构造函数可以根据需要定义参数
- 一个类中可以存在多个重载的构造函数
- 构造函数的重载遵循C++重载的规则
友情提示:对象定义和对象声明不同
-
对象定义——申请对象的空间并调用构造函数
-
对象声明——告诉编译器存在这样一个对象
#include <stdio.h>
class Test
{
public:
Test()
{
printf("Test()\n");
}
Test(int v)
{
printf("Test(int v) = %d\n",v);
}
};
int main()
{
Test t; //调用Test()
Test t1(1); //调用Test(int v)
Test t2 = 1; //调用Test(int v)
t1 = t2; //为赋值
int i(100); //为初始化i
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) = %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;
}
小实例
需求:开发一个数组类解决原生数组的安全性问题
- 提供函数获取数组长度
- 提供函数获取数组元素
- 提供函数设置数组元素
//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_legth = len;
}
//实现深拷贝
IntArray::IntArray(const IntArray& obj)
{
m_legth = obj.m_legth;
m_pointer = new int[obj.m_legth];
for(int i = 0; i < obj.m_legth; i++)
{
m_pointer[i] = obj.m_pointer[i];
}
}
int IntArray::length()
{
return m_legth;
}
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;
}
ret;
}
void IntArray::free()
{
delete[] m_pointer;
}
//IntArray.h
#ifndef _INTARRAY_H_
#define _INTARRAY_H_
class IntArray
{
private:
int m_legth;
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_H_
/*
*越界保护
*构造函数,深层次拷贝
*/
#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 vulue = 0;
if(a.get(i, vulue)) //100越界了
{
printf("a[%d] = %d\n", i, vulue);
}
}
//IntArray b = a;
IntArray b(a);
for(int i = 0; i < b.length(); i++)
{
int vulue = 0;
if(b.get(i, vulue)) //100越界了
{
printf("b[%d] = %d\n", i, vulue);
}
}
//不要忘记释放
a.free();
b.free();
return 0;
}
小结:
- 构造函数可以根据需要定义参数
- 构造函数之间可以存在重载关系
- 构造函数遵循C++中的重载函数规则
- 对象定义时会触发构造函数的调用
- 在一些情况下可以手动调用构造函数
六、特殊的构造函数
- 2个特殊的构造函数
- 无参构造函数——没有参数的构造函数
- 拷贝构造函数——参数为const class_name&的构造函数
1、无参构造函数
- 当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2、拷贝构造函数
-
当类中没有定义拷贝构造函数时,编译器默认一个拷贝构造函数,简单的进行成员变量的值赋值
-
意义
-
==兼容C语言的初始化方式 ==
-
初始化行为能够符合预期的逻辑
-
浅拷贝
- 拷贝后对象的物理状态相同
编译器提供的拷贝构造函数只进行浅拷贝!
-
深拷贝
- 拷贝后对象的逻辑状态相同
-
//19-1.cpp
#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;
}
//19-2.cpp
#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;//此适类似于C语言的赋值
Test t2(t1);
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());
//在内存中的地址不同,但值相同
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,则调用t2.p会失败
t1.free();
t2.free();
return 0;
}
3、什么时候需要进行深拷贝
- 对象中有成员指代了系统中的资源
- 成员指向了动态内存空间
- 成员打开了外存中的文件
- 成员使用了系统中的网络端口
- …
4、一般性原则
自定义拷贝构造函数,必然需要实现深拷贝。
小结:
C++编译器会默认提供构造函数
无参构造函数用于定义对象的默认初始状态
拷贝构造函数在创建对象时拷贝对象的状态
对象的拷贝有浅拷贝和深拷贝
- 浅拷贝,拷贝后对象的物理状态相同
- 深拷贝,拷贝后对象的逻辑状态相同