作者:jicanmeng
时间:2014年08月12日
问题:
什么是隐式的默认构造函数?
什么是显式的默认构造函数?
什么是重载构造函数?
我们在定义一个变量的时候,常常会对这个变量进行赋值,称为变量的初始化。类似的,我们在定义一个类的对象的时候,也会对此对象中包含的数据进行赋值,称为类对象的初始化。变量的初始化是通过赋值运算符"="来实现的,而类的对象的初始化是通过构造函数来实现的。
对构造函数的规定:
来看一个例子:
#include <iostream> using namespace std; class C2DPoint { private: int m_xPos; int m_yPos; public: C2DPoint(int x, int y) { m_xPos = x; m_yPos = y; cout << "constructor function called" << endl; } void setAxis(int x, int y) { m_xPos = x; m_yPos = y; } int getXAxis() { return m_xPos; } int getYAxis() { return m_yPos; } }; int main() { C2DPoint ptA(5, 6); cout << "point axis is (" << ptA.getXAxis() << ", " << ptA.getYAxis() << ")" << endl; return 0; }
编译此文件并运行,看一看运行结果:
[jicanmeng@andy tmp]$ g++ test.cpp
[jicanmeng@andy tmp]$ ./a.out
constructor function called
point axis is (5, 6)
[jicanmeng@andy tmp]$
我们在定义一个变量的时候,例如一个int类型的变量,可以进行赋值,也可以不赋值。int a = 3
和int a;
这两种形式都是有效的。那对于类的对象是不是也是这样呢?我们可以在代码的第32行添加一行:C2DPoint ptB;
。然后进行编译:
[jicanmeng@andy tmp]$ g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:32:14: error: no matching function for call to ‘C2DPoint::C2DPoint()’
C2DPoint ptB;
^
test.cpp:32:14: note: candidates are:
test.cpp:11:5: note: C2DPoint::C2DPoint(int, int)
C2DPoint(int x, int y) {
^
test.cpp:11:5: note: candidate expects 2 arguments, 0 provided
test.cpp:5:7: note: C2DPoint::C2DPoint(const C2DPoint&)
class C2DPoint
^
test.cpp:5:7: note: candidate expects 1 argument, 0 provided
[jicanmeng@andy tmp]$
从编译的输出来看,在第32行定义ptB对象的时候,调用了一个C2DPoint::C2DPoint()函数,但是这个函数根本就不存在。有两个候选的可供调用的函数,第11行的C2DPoint::C2DPoint(int,int)构造函数;还有一个C2DPoint::C2DPoint(const C2DPoint&),这个函数叫做拷贝构造函数(copy constructor),后续讲到运算符重载的时候再进行详细讲解,现在只需要知道有这个概念就行了。
既然C2DPoint::C2DPoint()函数不存在,那么我们就定义一个这样的函数,看看结果如何。代码如下:
#include <iostream> using namespace std; class C2DPoint { private: int m_xPos; int m_yPos; public: C2DPoint(int x, int y) { m_xPos = x; m_yPos = y; cout << "constructor function called" << endl; } C2DPoint() { m_xPos = 0; m_yPos = 0; } void setAxis(int x, int y) { m_xPos = x; m_yPos = y; } int getXAxis() { return m_xPos; } int getYAxis() { return m_yPos; } }; int main() { C2DPoint ptA(5, 6); cout << "point axis is (" << ptA.getXAxis() << ", " << ptA.getYAxis() << ")" << endl; C2DPoint ptB; cout << "point axis is (" << ptB.getXAxis() << ", " << ptB.getYAxis() << ")" << endl; return 0; }
再次进行编译,这次没有错误了,说明我们添加的函数起作用了。由此可以得出,我们定义一个类的对象时,如果不赋初值,c++编译器就会去寻找一个构造函数,这个构造函数不带任何参数,在上面的代码中就是C2DPoint::C2DPoint()
,称为默认构造函数。而那个带参数的构造函数C2DPoint::C2DPoint(int,int)
则称为重载构造函数。
其实,在实际的项目当中,常常将重载构造函数和默认构造函数结合起来,写成一个函数。看下面的代码:
#include <iostream> using namespace std; class C2DPoint { private: int m_xPos; int m_yPos; public: C2DPoint(int x = 0, int y = 0) { m_xPos = x; m_yPos = y; cout << "constructor function called" << endl; } void setAxis(int x, int y) { m_xPos = x; m_yPos = y; } int getXAxis() { return m_xPos; } int getYAxis() { return m_yPos; } }; int main() { C2DPoint ptA(5, 6); cout << "point axis is (" << ptA.getXAxis() << ", " << ptA.getYAxis() << ")" << endl; C2DPoint ptB; cout << "point axis is (" << ptB.getXAxis() << ", " << ptB.getYAxis() << ")" << endl; return 0; }
在C++中,可以定义带有默认参数的函数,所以上面的构造函数具有重载构造函数和默认构造函数的双重功能。
但是前面的C++特性一:封装中的示例程序中,并没有定义任何的构造函数,那为什么定义一个类的对象没有报错呢?原因是因为当类的定义中如果不存在任何的构造函数的定义,那么系统会默认的生成一个默认的构造函数,这个默认的构造函数可以保证定义一个类的对象的合法性。为了和上面的区分开,这个系统默认生成的构造函数称为隐式的默认构造函数,而上面的我们定义的则称为显式的默认构造函数。
现在可以总结一下了:
重载构造函数的作用:在定义类的对象的同时进行初始化,当然我们可以在函数中什么也不做,函数体为空。重载构造函数可以有多个。定义几个重载构造函数,就有几种初始化类的对象的方式。
显式的默认构造函数的作用:在定义类的对象的同时进行初始化,当然我们可以在函数中什么也不做,函数体为空。当类的定义中已经存在了一个重载的构造函数时,如果不定义显式的默认构造函数,那么定义一个不带参数的类的对象就会出错。显式的默认构造函数只能有一个。
隐式的默认构造函数的作用:是当类的定义中不存在构造函数时,能够使得定义一个类的对象的表达式合法。但是这个函数什么也不做,函数体为空。当类中存在构造函数的定义时,隐式的默认构造函数就不存在了。
现在可以回答最开始的三个问题了:1.隐式的默认构造函数是当定义一个类时,如果我们没有实现任何和类名相同的构造函数,那么系统自动生成的一个构造函数。2.显式的默认构造函数是我们在类的定义中实现的一个构造函数,不带任何参数。3.重载构造函数是我们在类的定义中实现的带参数的构造函数。
前面提到,有三个关键字:public,private和protected,用于声明类中的成员和类外代码的关系。对于public类型的成员来说,可以在类外访问,当然也可以在类内访问。对于private成员来说,不能在类外访问,数据成员只能由类中的函数使用,函数成员只允许在类中调用。对于protected成员来说,不能在类外访问,只能在类中或子类中访问。构造函数作为类的函数成员,自然也会通过这三个关键字来修饰。
一般情况下,我们都会将构造函数声明为public类型的,这样可以在类外构造一个这样类型的对象。当然构造函数也可以声明为protected或者private类型的,常见于下列情况:
知道了这三个基本概念后,我们来看一个非常重要的知识点:当类中包含有指针类型的变量成员时,应该如何写我们的构造函数?我们将C2DPoint这个类扩充一下,添加一个char *m_pName;
变量成员来看一看。看下面的例子:
#include <iostream> using namespace std; class C2DPoint { private: int m_xPos; int m_yPos; char *m_pName; public: C2DPoint(int x, int y, char *pName) { m_xPos = x; m_yPos = y; m_pName = pName; cout << "overload constructor function called" << endl; } C2DPoint() { m_xPos = 0; m_yPos = 0; m_pName = NULL; cout << "default constructor function called" << endl; } void setAxis(int x, int y) { m_xPos = x; m_yPos = y; } int getXAxis() { return m_xPos; } int getYAxis() { return m_yPos; } char *getName() { return m_pName; } }; int main() { C2DPoint ptA(-2, 2, "left top point"); cout << "pointA axis is (" << ptA.getXAxis() << ", " << ptA.getYAxis() << ")" << ", name is " << ptA.getName() << endl; C2DPoint ptB; cout << "point axis is (" << ptB.getXAxis() << ", " << ptB.getYAxis() << ")" << endl; return 0; }
程序运行结果如下:
[jicanmeng@andy tmp]$ ./a.out
overload constructor function called
pointA axis is (-2, 2), name is left top point
default constructor function called
pointB axis is (0, 0)
[jicanmeng@andy tmp]$
程序运行正常,好像也没有什么问题。但是我们在main()函数的第48行再添加几行代码,看看运行情况。代码如下:
#include <iostream> using namespace std; class C2DPoint { private: int m_xPos; int m_yPos; char *m_pName; public: C2DPoint(int x, int y, char *pName) { m_xPos = x; m_yPos = y; m_pName = pName; cout << "overload constructor function called" << endl; } C2DPoint() { m_xPos = 0; m_yPos = 0; m_pName = NULL; cout << "default constructor function called" << endl; } void setAxis(int x, int y) { m_xPos = x; m_yPos = y; } int getXAxis() { return m_xPos; } int getYAxis() { return m_yPos; } char *getName() { return m_pName; } }; int main() { C2DPoint ptA(-2, 2, "left top point"); cout << "pointA axis is (" << ptA.getXAxis() << ", " << ptA.getYAxis() << ")" << ", name is " << ptA.getName() << endl; C2DPoint ptB; cout << "point axis is (" << ptB.getXAxis() << ", " << ptB.getYAxis() << ")" << endl; char *pString = new char[6]; strncpy(pString, "hello", 5); pString[5] = '\0'; C2DPoint ptC(2, -2, pString); delete pString; cout << "pointC axis is (" << ptC.getXAxis() << ", " << ptC.getYAxis() << ")" << ", name is " << ptC.getName() << endl; return 0; }
程序运行结果如下:
[jicanmeng@andy tmp]$ ./a.out
overload constructor function called
pointA axis is (-2, 2), name is left top point
default constructor function called
pointB axis is (0, 0)
overload constructor function called
pointC axis is (2, -2), name is ▒▒▒
[jicanmeng@andy tmp]$
这次运行出错了。可以看到,ptC这个对象返回了乱码。为什么呢?聪明的你应该能够猜到,ptC中的m_pName变量成员指向的内存已经被delete给释放了,所以我们返回了一堆乱码。
那么我们应该解决这个问题呢?方法就是:我们需要给指针类型的变量成员赋值时,同时也分配好这个指针指向的内存。不多说,上代码:
#include <iostream> using namespace std; class C2DPoint { private: int m_xPos; int m_yPos; char *m_pName; public: C2DPoint(int x, int y, char *pName) { m_xPos = x; m_yPos = y; int length = strlen(pName) + 1; m_pName = new char[length]; strncpy(m_pName, pName, length); m_pName[length - 1] = '\0'; cout << "overload constructor function called" << endl; } C2DPoint() { m_xPos = 0; m_yPos = 0; m_pName = NULL; cout << "default constructor function called" << endl; } void setAxis(int x, int y) { m_xPos = x; m_yPos = y; } int getXAxis() { return m_xPos; } int getYAxis() { return m_yPos; } char *getName() { return m_pName; } }; int main() { C2DPoint ptA(-2, 2, "left top point"); cout << "pointA axis is (" << ptA.getXAxis() << ", " << ptA.getYAxis() << ")" << ", name is " << ptA.getName() << endl; C2DPoint ptB; cout << "point axis is (" << ptB.getXAxis() << ", " << ptB.getYAxis() << ")" << endl; char *pString = new char[6]; strncpy(pString, "hello", 5); pString[5] = '\0'; C2DPoint ptC(2, -2, pString); delete pString; cout << "pointC axis is (" << ptC.getXAxis() << ", " << ptC.getYAxis() << ")" << ", name is " << ptC.getName() << endl; return 0; }
程序运行结果如下:
[jicanmeng@andy tmp]$ ./a.out
overload constructor function called
pointA axis is (-2, 2), name is left top point
default constructor function called
pointB axis is (0, 0)
overload constructor function called
pointC axis is (2, -2), name is hello
[jicanmeng@andy tmp]$
可以看到,我们在重载构造函数中分配了内存,解决了这个问题。但是另外一个问题同时也浮出了水面,我们每次构造一个C2DPoint对象,都会分配一块内存。那么这些内存在什么地方进行释放呢?这就是下一节要讲的内容了:析构函数(destructor)。