特殊的函数:构造函数

作者:jicanmeng

时间:2014年08月12日


问题:
什么是隐式的默认构造函数?
什么是显式的默认构造函数?
什么是重载构造函数?

我们在定义一个变量的时候,常常会对这个变量进行赋值,称为变量的初始化。类似的,我们在定义一个类的对象的时候,也会对此对象中包含的数据进行赋值,称为类对象的初始化。变量的初始化是通过赋值运算符"="来实现的,而类的对象的初始化是通过构造函数来实现的。

对构造函数的规定:

  1. Constructors should always have the same name as the class (with the same capitalization)
  2. Constructors have no return type (not even void)

来看一个例子:

#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 = 3int 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)。

参考资料

  1. <<C++实用教程>> 电子工业出版社 郑阿奇 主编 丁有和 编著
  2. The C++ Tutorial:
    http://www.learncpp.com/cpp-tutorial/85-constructors/
  3. 构造函数定义为private,protected:
    http://www.cnblogs.com/this-543273659/archive/2011/08/02/2125487.html