彭召意 赵菁菁 刘建国
【摘 要】C++是一门经典的程序设计语言,目前有着广泛的应用,学习C++语言的难点在于类的构建。为了构建好类,需要根据应用的要求设计合理的构造函数。文章从构造函数的概念出发,总结了其灵活多变的形式和使用方法,对想进一步提高C++编程能力的初学者具有很好的参考价值。
【关键词】C++语言;类;构造函数;教学方法
【中图分类号】G642 【文献标识码】A 【文章编号】1674-0688(2016)10-0050-04
C++面向对象程序设计是一种重要的程序设计语言,在硬件驱动、工业控制、系统软件等方面具有广泛的应用。不少初学者在学习C++的过程都出现过不少困难[1-2],其主要原因是他们没有深刻地理解类的概念。类是面向对象程序设计中最重要的概念,它是构成面向对象程序的基石。在类中,有一个用途广泛的成员函数,即构造函数。设计一个类时,通常都会设计构造函数。根据应用的不同,构造函数会以灵活多变的形式出现[3-4],构造函数在增强程序功能的同时也加大了初学者的难度。为了给初学者提供方便,本文就C++中的构造函数进行了系统的分析和总结,同时介绍了使用方法。
1 构造函数简介
构造函数是类中一种特殊的成员函数,它的作用是用于对象的初始化。与其他自定义的成员函数不同,构造函数不需要用户来调用它,而是在建立对象时自动执行。
构造函数的定义[5]:构造函数的名字必须与类名同名,它不具有任何类型,不返回任何值。
格式如下。
构造函数声明:<类名> (<参数表>);
构造函数定义如下。
(1)<函数名>(参数表)。
{//构造函数功能体}//类内定义函数体
(2)<类名>::<函数名>(参数表) 。
{//构造函数功能体}//类外定义函数体
例如:定义一个包含构造函数的汽车类Car
class Car //定义类
{
public:
Car( ) //类内定义构造函数
{m_strCarname = “default name”;}
private:
string m_strCarname; //数据成员
};
如果在类外定义函数体,则:
Car::Car( ) //类外定义构造函数
{m_strCarname = “default name”;}
构造函数的功能是由用户根据对象初始化需要自定义和设计函数体和函数参数。
使用构造函数的注意事项如下:{1}构造函数名称必须与类名相同;{2}构造函数没有返回值;{3}构造函数由系统自动调用,不需用户调用,也不能被用户调用;{4}在类对象进入其作用域时调用构造函数;{5}其功能是对对象进行初始化,一般由一系列赋值语句构成,但是构造函数中也可以包含其他语句,用于对象初始化时执行的功能;{6}如果用户自己没有定义构造函数,则C++系统会自动生成一个空的构造函数。
根据参数的不同,构造函数可以有不同的形式和使用方法。构造函数的不同形式有无参构造函数、有参构造函数、默认参数的构造函数、拷贝构造函数、转换构造函数等。其中,拷贝构造函数和转换构造函数属于有参构造函数。
2 普通形式的构造函数
普通形式的构造函数主要指带参构造函数(包括默认参数的构造函数)和无参构造函数。构造函数可以带参数,也可以不帶参数。当需要从外面把参数传递给对象时,就需要采用带参数的构造函数。系统默认的构造函数是不带参数的,如果想带参数,必须自定义构造函数。
(1)带参数构造函数。前面例子中,写一个带参数的构造函数如下。
Car::Car(string CName) //类外定义构造函数
{m_strCarname = CName;}
带有参数的构造函数定义对象的格式如下:
类名 对象名(实参1,实参2,…);
无参数时,定义对象的格式如下:
类名 对象名;
例如:Car c1;//建立对象c1,不带参数;Car c2(“pzy car.”);//建立对象c2,带参数。注意:建立无参数的对象时,不能带括号。Car c1( ); //错误!,不要括号,否则该语句是函数声明语句。
(2)有默认参数构造函数。构造函数的参数也可以像普通函数一样带默认参数,带默认参数的构造函数中,默认参数值的构造函数的一般形式如下。
类名:构造函数名(类型 参数1=默认值,类型 参数2=默认值)
{函数体}
例如,前述例子中:
Car::Car(string CName=“pzy car.”)
{m_strCarname=CName;}
3 拷贝构造函数
拷贝构造函数能够将参数的属性值拷贝给新的对象,完成新对象的初始化。它是使用类对象的引用作为参数的构造函数,也称为复制构造函数。
拷贝构造函数的格式如下:
class 类名
{public:
类名(类名&变量名) //定义一个拷贝构造函数
{ 函数体}
};
例如:定义一个带有拷贝构造函数的汽车类Car
class Car{
public:
Car(string con_carname, int con_seats)
{
m_strCarname = con_carname;
m_nSeats = con_seats;
}
Car(Car &con_refcar) //拷贝构造函数
{
m_strCarname = con_refcar.m_strCarname;
m_nSeats = con_refcar.m_nSeats;
}
private:
string m_strCarname;
int m_nSeats;
};
下面3种情况会自动调用拷贝构造函数[6]。
(1)用早已存在的对象初始化新对象的时候。例如:Car c2=c1。
(2)将一个对象以值传递的方式传给形参的时候。例如:void findcar(Carc);调用时:findcar(c)。
(3)函数返回一个对象的时候。例如:return Car(c)。
拷贝构造函数中若只完成数据成员本身的赋值,称为“浅拷贝”。将所有数据都进行复制的拷贝构造函数称之为“深拷贝”[7]。
在“浅拷贝”过程中,如果在构造函数中有新申请的存储空间时(比如用new操作符),由于只是完成数据本身的赋值,并没有新申请空间来赋值,所以在析构函数运行时,会出现错误。为了避免这种错误,需要采用“深拷贝”。
4 转换构造函数
转换构造函数只有一个形参,它的作用是将一个其他类型的数据(可以是基本数据类型,也可以是类对象)转换成一个类的对象。
自定义数据类型与基本数据类型之间的转换,除了类型转换运算符重载,还可以定义转换构造函数。所谓转换构造函数就是当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数。例如:
Complex( double Real ) {real=Real;imag=0;}
其作用是将参数Real转换成Complex类的对象(Real是double型),该对象的实部是Real,对象的虚部为0。
下面完整的例子说明了转换构造函数的使用。
#include
using namespace std;
class Complex
{
public:
Complex(double Real,double Imag){real= Real;imag= Imag;}
Complex( ){real=imag=0;}
Complex(double Real) //轉换构造函数
{imag=0;real= Real;}
friend Complex operator+(Complex cp1,Complex cp2);
private:
double imag; double real;
};
Complex operator + (Complex cp1,Complex cp2)
{ Complex c;
c.real=cp1.real+cp2.real;
c.imag=cp1.imag+cp2.imag;
return c;
}
int main()
{
Complex c1(13,24),c2(5,-10),c3;
c3=c1+2.5; //用到转换构造函数,把2.5转换成类Complex的对象
return 0;
}
该例子是将一个标准类型的数据转换成对象,其实参数类型也可以是其他类的对象。比如:可以将一个教师类对象转换为学生类对象,在Student中定义如下的转换构造函数:
Student (Teacher & t)
{num=t.num; sex=t.sex;name=t.name; }
注意:对象t中的数据成员(num,name和sex等)都要是公用成员,因为要被类外来访问。
5 派生类的构造函数
在定义派生类时,派生类的构造函数要考虑派生类新增的数据成员初始化,并且还要考虑基类的数据成员初始化。解决的方法是在执行派生类的构造函数时,同时要调用基类的构造函数。
(1)派生类构造函数一般形式如下。
派生类构造函数名(派生类参数表列):基类构造函数名(基类参数表列)
{自定义派生类中的初始化语句}
(2)如果有多个基类,应该一一把基类列出,比如有2个基类的派生类构造函数形式如下。
派生类构造函数名(派生类参数表列):基类1构造函数(基类1参数表列),基类2构造函数(基类2参数表列)
{自定义派生类中的初始化语句}
(3)派生类构造函数的执行顺序为首先调用基类的构造函数,然后执行派生类的构造函数体。如果是多个基类,那么调用多个基类构造函数的顺序是按照它们在声明派生类时基类出现的顺序。
6 错误使用构造函数分析
构造函数只能由系统自动调用,不能由用户来调用,否则将出错。下面用一个实例来进行说明[8]。
#include
public:
MyBox() { m_a = 1; }
MyBox(int b)
{
m_b = b;
MyBox();
}
~MyBox() {}
void Display()
{
std::cout << m_a <<" "<}
private:
int m_a;
int m_b;
};
int main()
{
MyBox myBox(2);
myBox.Display();
return 0;
}
上述程序中,在QT環境中,输出结果为
6422400 2
在DEV C++环境中,输出结果为
3 2
显然,m_a是一个不确定的值,m_b等于2,这是因为m_a没有被赋初值。在调用MyBox()函数时,实际上是建立了临时的MyBox类对象,MyBox()中赋值m_a=1也是对对象的赋值,因此在myBox的m_a其实并没有被赋值。
这个例子说明了不能显式调用构造函数,也不能给成员变量赋值,否则结果将出现不确定性。
7 结语
本文首先介绍了C++类的构造函数相关概念及其灵活多变的不同形式,并指出不同的应用场合需要建立适合需求的构造函数;然后,介绍了不同形式的构造函数的学习和使用方法,并指出显式调用构造函数所带来的后果。这些内容有助于大家深刻理解类的构造函数,对于刚刚接触C++语言的编程初学者和想进一步提高C++编程能力的人员都有很大的参考价值。
参 考 文 献
[1]肖菁.高校非计算机专业C/C++教学的探索与实践[J].现代计算机:专业版,2011(30):21-22.
[2]鲁红英,肖思和,孙淑霞.C/C++语言程序设计课程教学改革与实践[J].计算机教育,2013(7):95-98.
[3]王帅,马梦娜.关于C++构造函数的几点探究[J].电脑编程技巧与维护,2013(10):6-7.
[4]李欣然,靳雁霞.C++程序设计中构造函数的探讨[J].计算机时代,2011(12):30-32.
[5]谭浩强.C++面向对象程序设计[M].北京:清华大学出版社,2006.
[6]百度百科.构造函数[EB/OL].http://baike.baidu.com/link?url=uhoOfoj3mULwrmajVpFgRYwfomllKB-1VuO-bbCHnPikWcMMKFOAUqDCWlnTlokl2MH3psipethK-HyAxSfu8qlIv3bPfFOY4gChQ7CGe3KWo4kG1Va-yW-ZEEOQ9GHP1-a,2016-10-03.
[7]传智播客高教产品研发部.C++程序设计教程[M].北京:人民邮电出版社,2015.
[8]Ticktick.显式调用构造函数产生的悲剧[EB/OL].http://ticktick.blog.51cto.com/823160/294573,2016-10-03.
[责任编辑:陈泽琦]