文章目录
一、构造函数
在程序执行的过程中,当遇到与对声明语句时,程序会向操作系统申请一定的内存空间用于存放新建的对象。但是与普通变量相比,类的对象特别复杂,编译器不知如何产生代码去初始化对象,这便引出了构造函数。
1.1 构造函数是什么?
C++中,构造函数是一种特殊的成员函数,在每次创建一个类的时候编译器都会默认调用构造函数进行初始化。
1.2 为什么要有构造函数?
构造函数的作用就是在对象被创建的时候,利用特定的值构造对象,将对象初始化为一个特定的状态。
1.3 如何使用构造函数?
要学会如何使用构造函数,首先需要了解构造函数的一些特殊性质:构造函数的函数名与类名相同,而且没有返回值;构造函数通常被声明为公有函数。
Notes:只要类中有了构造函数,编译器就会在创建新对象的地方自动插入对构造函数调用的代码。所以构造函数在对象被创建的时候将会被自动调用。
下面分别介绍常见的几种构造函数:
- 默认构造函数:
class Clock
{
public:
Clock() //编译器自动生成的隐含的默认构造函数;
{
}
....
};
默认构造函数调用时无须提供参数。如果类中没有写构造函数,那么编译器会自动生成一个隐含的默认构造参数,该构造函数的参数列表和函数体都为空,这个构造函数不做任何事情。
Notes: 无参数的构造函数与全缺省的构造函数都称为默认构造函数。
Q:既然默认构造函数不做任何事情,那么为什么还要生成这个构造函数?
答:因为在建立对象时自动调用构造函数时C++必然要做的事情。上述例子中,默认构造函数什么都没有做,但是有些函数体为空的构造函数并非什么都不做,因为它还要负责基类的构造和成员对象的构造。
- 有参数的构造函数和无参数的构造函数
class Clock
{
public:
Clock(int NewH,int NewM,int NewS); //有参数的构造函数
Clock() //无参数的构造函数
{
hour = 0;
minute = 0;
second = 0;
}
void SetTime(int NewH,int NewM,int NewS);
void ShowTime();
private:
int hour,minute,second;
};
int main()
{
Clock(0,0,0); //调用有参数的构造函数
Clock my_clock; //调用无参数的构造函数
return 0;
}
上述例子中出现了两种重载的构造函数的形式:有参数的和无参数的(即默认构造函数)
1.4 构造函数的实现
Clock::Clock(int NewH, int NewM, int NewS)
{
hour = NewH;
minute = NewM;
second = NewS;
}
二、复制构造函数
生成一个对象的副本有两种途径,第一种途径是建立一个新的对象,然后将原始对象的数据成员取出来,赋值给新对象。这样做显然太繁琐了,所以为了使得一个类具有自行复制本类对象的能力,复制构造函数被引出。
2.1 什么是复制构造函数?
复制构造函数是一种特殊的构造函数,其具有一般构造函数的所有特性,其形参是本类对象的引用。
2.2 为什么要有复制构造函数?
复制构造函数的作用是使得一个已经存在的对象(由复制构造函数的参数指定),去初始化一个同类的一个新对象。如果程序没有定义类的复制构造函数,系统就会在必要时自动生成一个隐藏的复制构造函数。这个隐藏的复制构造函数的功能是,把初始值对象的每个数据成员复制到新建立的对象中去。这样得到的对象和原本的对象具有相同的数据成员和属性。
2.3 复制构造函数的功能
在说明复制构造函数的功能之前,我们先看一下声明和实现复制构造函数的一般方法:
class 类名
{
public:
类名(形参); //构造函数
类名(类名& 对象名); //复制构造函数
...
};
类名::类名(类名 &对象名) //复制构造函数的实现
{
//函数体
}
前面我们知道,普通的构造函数在对象被创建的时候调用,而复制构造函数在下面3种情况下均会被调用:
例:
class Point
{
public:
Point(int x = 0,int y = 0)
{
_x = x;
_y = y;
}
Point(Point& p); //复制构造函数
int GetX()
{
return _x;
}
int GetY()
{
return _y;
}
private:
int _x,_y;
};
- 当用类的一个对象去初始化另一个对象时:
int main()
{
Point a(1,2);
Point b(a); //用对象a初始化对象b时,复制构造函数被调用
Point c = a; //用对象a初始化对象c时,复制构造函数被调用
return 0;
}
Notes:上面对b和c的初始化都能够调用复制构造函数,两种写法是等价的,执行的操作完全相同。
- 如果函数的形参是类的对象,调用函数时,进行形参和实参结合时:
void Fun(Point p)
{
//函数体
}
int main()
{
Point a(1,2);
Fun(a); //函数的形参为类的对象,当调用对象时,复制构造函数被调用.
return 0;
}
Notes:只有把对象用值传递的时候,才会调用复制构造函数。如果传递的是引用,则不会调用复制构造函数。所以这也是传递引用会比传值的效率高的原因。
- 如果函数的返回值是类的对象,函数执行完成返回调用者时:
Point Fun()
{
Point a(1,2);
return a; //函数Fun的返回值是类的对象,返回函数值的时候,调用复制构造函数
}
int main()
{
Point b;
b = Fun();
return 0;
}
Q:为什么在这种情况下,返回函数值的时候会调用复制构造函数?
答:函数Fun()将a返回给了主函数,但是我们都知道a是Fun()的局部变量,当Fun()函数的生命周期结束的时候,a也就消亡了,不可能在返回主函数后继续生存。所以在这种情况下编译器会在主函数中创建一个无名的临时对象,该临时对象的生命周期仅仅在b = Fun()中。当执行语句return a时,实际上是调用复制构造函数将a的值复制到无名临时对象中。当函数Fun()运行结束时,对象a消失,但是临时对象会存在于b = Fun()中。当计算这个表达式后,临时对象也就完成了它的工作。
三、析构函数
我们刚讨论了当一个局部变量随着它的函数生命周期结束的时候,函数中的对象也会消失,那么在对象,要消失的时候(比如构造对象的时候,在函数中用malloc动态申请了空间)谁来做这些所谓的“善后”工作呢?这样我们就引出了析构函数。
什么是析构函数?
与构造函数一样,析构函数通常也是类的一个公有成员函数,它的名称是由类名前面加一个”~”构成,没有返回值。析构函数与构造函数不同的是,析构函数不接受任何参数!(参数可以是虚函数),如果我们不自行定义析构函数,则编译器同样会自动生成一个隐藏的析构函数,它的函数体为空。
下面我们看一下析构函数的声明:
class Clock
{
public:
Clock(int NewH,int NewM,int NewS); //有参数的构造函数
Clock() //无参数的构造函数
{
hour = 0;
minute = 0;
second = 0;
}
void SetTime(int NewH,int NewM,int NewS);
void ShowTime();
~Clock(); //析构函数
private:
int hour,minute,second;
};
Notes:函数体为空的析构函数并不是什么都不做。
Tips:
类的构造顺序是按照语句的顺序进行构造
类的析构函数调用完全按照构造函数调用的相反顺序进行调用
1.全局对象先于局部对象进行构造
2.静态对象先于普通对象进行构造