一、构造函数
主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们,即构造函数的重载。
注意:
构造函数的函数名称与类名同名,其他方法(函数)名称可以自定义。
构造函数没有也不能有返回类型,而其他函数随意。
由于构造函数的作用只是在创建对象时用来初始化成员变量和对象的,因此构造函数不能被继承也不能使用接口。
构造函数仅在对象被创建时系统会根据给定的参数以及类中的构造函数定义进行选择调用,
如果类中没有定义构造函数,系统默认会提供一个无参构造空函数,什么都不会做,只是满足接口要求,构造函数不能被显式调用。其他函数根据程序员需要而调用,且必须显式调用。
全局对象和静态对象的构造函数在main()函数执行之前就被调用,局部静态对象的构造函数是当程序第一次执行到相应语句时才被调用。然而给出一个外部对象的引用性声明时,并不调用相应的构造函数,因为这个外部对象只是引用在其他地方声明的对象,并没有真正地创建一个对象。
二、拷贝构造函数
也称为“复制构造函数”。它是一种特殊的构造函数,它在创建对象时,是使用同一类中已创建的对象来初始化新创建的对象。其形参必须是引用,一般用const修饰,但并不限制为const。
如果用户自己未定义复制构造函数,则编译系统会自动提供一个默认的 复制构造函数,其作用只是简单地复制类中每个数据成员。
拷贝构造函数通常用于:
1. 通过使用另一个同类型的对象来 初始化 新创建的对象。
2. 复制对象把它作为 函数参数 传递给函数。
3. 复制对象把它作为 函数返回值 并从函数返回这个对象。
接下来看一个实例:
#include <iostream>
using namespace std;
class Point{
public:
Point(int _x = 0); //拷贝函数
Point(const Point &p); //拷贝构造函数
int getX() const { return x; }
private:
int x;
};
//拷贝函数定义
Point::Point(int _x)
{
this->x = _x;
}
//拷贝构造函数定义
Point::Point(const Point &p)
{
cout << "Calling the copy constructor" << endl;
x = p.x;
}
//形参为Point类对象 的函数
void fun1(Point p)
{
cout << p.getX() << endl;
}
//返回值为Point类对象 的函数
Point fun2()
{
Point tmp(1);
return tmp;
}
int main()
{
Point a(10);
Point b(a); // 用a去初始化b,第一次调用拷贝构造函数
cout << b.getX() << endl; //10
fun1(b); // 对象b作为fun1的实参,第二次调用拷贝构造函数 //10
b = fun2(); // 函数的返回值是类对象,函数返回时,第三次调用拷贝构造函数
cout << b.getX() << endl; //1
}
在了解了拷贝构造函数ed基本知识后,接下来说说深拷贝和浅拷贝:
1. 深拷贝
在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间。
在完成深拷贝之后,两个对象的动态成员各指向不同的内存空间,但它们指向的空间具有相同的内容。
【有关深浅拷贝的详细分析可移步https://blog.youkuaiyun.com/lwbeyond/article/details/6202256】
2. 浅拷贝
指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。
但是一旦对象存在了动态成员,在完成浅拷贝之后,销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,会出错。此时只能使用深拷贝。
在完成浅拷贝之后,两个对象的动态成员均指向相同的内存空间,共享该空间中的内容。
3. 如何判别一个函数是拷贝构造函数:
对于一个类X, 如果一个构造函数的第一个参数是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数。
实例:
X::X(const X&); //是拷贝构造函数
X::X(X&, int=1); //是拷贝构造函数
X::X(X&, int a=1, int b=2); //当然也是拷贝构造函数
三、析构函数
析构函数与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数,自动的回收该对象占有的资源,或是完成一些清理工作。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。
注意:析构函数既没有返回值也没有函数参数,因此析构函数不能被重载。[构造函数可以重载]
析构函数被显示的调用的情况:
若对象是静态的,存在堆区分配,当程序未结束且我们想释放堆时,就可以显示的调用析构函数来完成。
如果显式地调用了析构函数,此时析构函数和普通成员函数是一样的,并不会造成对象被销毁。
四、综合运用
有了构造函数和析构函数的基本概念之后,我们来看一个有关【使用构造和析构观察不同对象的生命周期】的综合实例
#include <iostream>
using namespace std;
#define MAX 3
class Array{
public:
Array(int len=MAX){ //构造函数
size = len;
addr = new int[len];
cout << this << "; general: size = " << size << endl;
}
Array(const Array &obj){ //拷贝构造函数
size = obj.size;
addr = new int[size];
for(int i = 0; i < size; i++)
addr[i] = obj.addr[i];
cout << this << "; copy: size = " << size << endl;
}
~Array(){ //析构函数
if(addr){
delete [] addr;
addr = NULL;
}
cout << this << " destructor; size = " << size << endl;
}
private:
int *addr;
int size;
};
Array a(1); //全局静态对象,main运行前就存在(先于main构造),进程结束消失(main退出后自动析构)
void test()
{
Array a(2); //动态对象,test调用产生(构造自动被调用),test结束后销毁(析构自动调用)
static Array b = a; //局部静态对象,不论调用test多少次,都只构造一次,进程结束后消失(main退出后自动析构)
Array *p = new Array(3); //动态堆对象,new成功时对象产生,构造被自动调用
delete p; //delete销毁对象,析构被自动调用。若不delete,则对象会一直在,析构也不被自动调用。
}
int main()
{
cout << "-----------1--------------" << endl;
test();
test();
cout << "-----------2--------------" << endl;
}
/**执行结果
0x804a0f8; general: size = 1
-----------1--------------
0xbfef0a44; general: size = 2
0x804a110; copy: size = 2
0x9938038; general: size = 3
0x9938038 destructor; size = 3
0xbfef0a44 destructor; size = 2
0xbfef0a44; general: size = 2
0x9938038; general: size = 3
0x9938038 destructor; size = 3
0xbfef0a44 destructor; size = 2
-----------2--------------
0x804a110 destructor; size = 2
0x804a0f8 destructor; size = 1
**/