构造函数是一种随着对象创建而自动被调用的函数,它的主要用途是为对象作初始化。那么,构造函数到底是什么样子的呢?
在C++中,规定与类同名的成员函数就是构造函数。需要注意的是,构造函数应该是一个公有的成员函数,并且构造函数没有返回值类型。以下是我们为链表结点类编写的一个构造函数:(其他成员函数定义见14.3节)
//node.h
#include <iostream>//如果不包含iostream头文件,这个文件里就不能用cout
using namespace std;
class Node//定义一个链表结点类
{
public:
……
Node();//构造函数的声明,构造函数是公有的成员函数,没有返回值类型
……
private:
int idata;//存储数据保密
char cdata;//存储数据保密
Node *prior;//前驱结点的存储位置保密
Node *next;//后继结点的存储位置保密
};
Node::Node()//构造函数的定义
{
cout <<"Node constructor is running..." <<endl;//提示构造函数运行
idata=0;//初始化idata
cdata='0';//初始化cdata
prior=NULL;//初始化前驱结点指针
next=NULL;//初始化后续结点指针
}
这时,我们创建一个链表结点对象,构造函数随着对象创建而自动被调用,所以这个对象创建之后idata的值为0,cdata的值为'0',prior和next的值都是NULL:(程序15.2.1)
//main.cpp
#include <iostream>
#include "node.h"
using namespace std;
int main()
{
Node a;//创建一个链表结点对象a,调用构造函数
cout <<a.readi() <<endl;
cout <<a.readc() <<endl;
return 0;
}
运行结果:
Node constructor is running...
0
0
可是,这样的构造函数还是不太理想。如果每次初始化的值都是固定的,那么有没有构造函数都是一样的。构造函数变成了一种摆设!我们该怎么办?
函数的特征之一就是能够在调用时带上参数。既然构造函数也是函数,那么我们就能给构造函数带上参数,使用重载或默认参数等方法,从而实现更自由地对对象进行初始化操作。以下便是对链表结点类的进一步修改:(程序15.2.2)
//node.h
#include <iostream>
using namespace std;
class Node//定义一个链表结点类
{
public:
Node();//构造函数0
Node(int i,char c='0');//构造函数重载1,参数c默认为'0'
Node(int i,char c,Node *p,Node *n);//构造函数重载2
int readi() const;//读取idata
char readc() const;//读取cdata
Node * readp() const;//读取上一个结点的位置
Node * readn() const;//读取下一个结点的位置
bool set(int i);//重载,通过该函数修改idata
bool set(char c);//重载,通过该函数修改cdata
bool setp(Node *p);//通过该函数设置前驱结点
bool setn(Node *n);//通过该函数设置后继结点
private:
int idata;//存储数据保密
char cdata;//存储数据保密
Node *prior;//前驱结点的存储位置保密
Node *next;//后继结点的存储位置保密
};
int Node::readi() const//成员函数readi的定义
{
return idata;
}
char Node::readc() const
{
return cdata;
}
Node * Node::readp() const
{
return prior;
}
Node * Node::readn() const
{
return next;
}
bool Node::set(int i)//重载成员函数定义
{
idata=i;
return true;
}
bool Node::set(char c)
{
cdata=c;
return true;
}
bool Node::setp(Node *p)
{
prior=p;
return true;
}
bool Node::setn(Node *n)
{
next=n;
return true;
}
Node::Node()//构造函数0的定义
{
cout <<"Node constructor is running..." <<endl;//提示构造函数运行
idata=0;//初始化idata
cdata='0';//初始化cdata
prior=NULL;//初始化前驱结点指针
next=NULL;//初始化后续结点指针
}
Node::Node(int i,char c)//构造函数重载1,默认参数只需要在函数原型中出现
{
cout <<"Node constructor is running..." <<endl;
idata=i;
cdata=c;
prior=NULL;
next=NULL;
}
Node::Node(int i,char c,Node *p,Node *n)//构造函数重载2
{
cout <<"Node constructor is running..." <<endl;
idata=i;
cdata=c;
prior=p;
next=n;
}
//main.cpp
#include <iostream>
#include "node.h"
using namespace std;
int main()
{
Node a;//创建一个链表结点对象a,调用构造函数0
Node b(8);//创建一个链表结点对象b,调用构造函数重载1,参数c默认为'0'
Node c(8,'F',NULL,NULL);//创建一个链表结点对象c,调用构造函数重载2
cout <<a.readi() <<' ' <<a.readc() <<endl;
cout <<b.readi() <<' ' <<b.readc() <<endl;
cout <<c.readi() <<' ' <<c.readc() <<endl;
return 0;
}
运行结果:
Node constructor is running...
Node constructor is running...
Node constructor is running...
0 0
8 0
8 F
我们看到,在参数和重载的帮助下,我们可以设计出适合各种场合的构造函数。初始化各个对象的成员数据对我们来说已经是小菜一碟了。但是,这时你是否会回想起当初没有编写构造函数时的情形?如果没有编写构造函数,对象的创建是一个怎样的过程呢?
在C++中,每个类都有且必须有构造函数。如果用户没有自行编写构造函数,则C++自动提供一个无参数的构造函数,称为默认构造函数。这个默认构造函数不做任何初始化工作。一旦用户编写了构造函数,则这个无参数的默认构造函数就消失了。如果用户还希望能有一个无参数的构造函数,必须自行编写。