一个空类(什么成员都没有)中,真的是什么都没有吗?并非如此,任何一个类在我们不写的情况下,都会产生以下 6 个默认成员函数:
今天我们将着重讲述前三个。
在C++中,有一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行。 这种特殊的成员函数就是构造函数(Constructor)。
构造函数的主要任务是用来初始化成员变量的。
class Date
{
int _year;
int _month;
int _day;
public:
Date(int year, int month, int day)//①自定义的构造函数
{
_year = year;
_month = month;
_day = day;
}
};
当我们没有自己定义构造函数的时候,编译器会自己创建一个默认的无参的构造函数。如果我们显示定义了构造函数,编译器将不再生成。
class Date
{
int _year;
int _month;
int _day;
public:
Date()//②编译器自动创建的无参的构造函数
{
}
};
在以上两个构造函数中,构造函数①我们叫全缺省的构造函数,构造函数②叫做无参的构造函数,这两个构造函数都是默认的构造函数,在一个类中,默认的构造函数有且只能有一个。
构造函数有以下特征:
关于构造函数
在我们不实现构造函数的情况下,编译器会生成 默认的构造函数。但是看起来默认构造函数又没什么用?d 对象调用了编译器生成的默认构造函数,但是d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么卵用呀???
其实并非如此,C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如 int/char…,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员 _t 调用的它的默认成员函数。
#include
using namespace std;
class Time
{
int _hour;
int _minute;
int _second;
public:
Time()
{
cout<<"Time()"<<endl;
_hour = 0;
_minute = 0;
_second = 0;
}
};
class Date
{
//内置类型(基本类型)
int _year;
int _month;
int _day;
//自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
构造函数的主要工作是完成初始化,除了上述方式还可以使用初始化列表。
class Date
{
int _year;
int _month;
int _day;
public:
//初始化列表
Date(int year, int month, int day):_year(year),_month(month),_day(day)
{
}
};
【注意】:成员变量的初始化顺序只与成员变量在类中声明的顺序有关。 与初始化列表中列出的变量的顺序无关。
看以下两个程序便知:
程序一:
#include
using namespace std;
class Test
{
int x;
int y;
public:
Test():y(10),x(x+10)
{
cout<<"x = "<<x<<" y ="<<y<<endl;
}
};
int main()
{
Test t;
return 0;
}
程序二:
#include
using namespace std;
class Test
{
int x;
int y;
public:
Test():x(10),y(x+10)
{
cout<<"x = "<<x<<" y ="<<y<<endl;
}
};
int main()
{
Test t;
return 0;
}
不少难以察觉的程序错误是由于变量没有被正确初始化或清除造成的,而初始化和清除工作很容易被人遗忘。构造函数与析构函数的使命非常明确,就象出生与死亡,光溜溜地来光溜溜地去。
因此,析构函数的作用和构造函数的作用是相反的,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
析构函数的特征如下:
析构函数名是在类名前加上字符 ~
没有参数,没有返回值
一个类有且只有一个析构函数(不支持重载),
若未显式定义,系统会自动生成默认的析构函数
对象生命周期结束时,C++编译系统系统自动调用析构函数
typedef int DataType;
class SeqList
{
public :
SeqList (int capacity = 10)//构造函数
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList() //析构函数
{
if (_pData)
{
free(_pData ); // 释放堆上的空间
_pData = NULL; // 将指针置为空
_capacity = 0;
_size = 0;
}
}
private :
int* _pData ;
size_t _size;
size_t _capacity;
};
拷贝构造函数是特殊的构造函数,在创建对象时,使用同类对象来初始化新创建的对象。具有单个形参,该形参(常用const修饰) 是对该类类型的引用。
如果类中没有定义拷贝构造函数,系统会自动提供一个默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
拷贝构造函数的特征如下:
那么什么时候会用到拷贝构造函数呢?
在C++中,有两种初始化的方式:
对于基本类型来说
拷贝初始化 int a = 5;
直接初始化 int a(5);
对于类类型:
(1)直接初始化直接调用实参匹配的构造函数
(2)拷贝初始化总是调用拷贝构造函数
A是一个类。
A x(2); //直接初始化,调用构造函数
A y = x; //拷贝初始化,调用拷贝构造函数
类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
看下面一个程序:
#include
using namespace std;
class Line
{
public:
int getLength();
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line()
{
cout << "释放内存" << endl;
delete ptr;
}
int Line::getLength()
{
return *ptr;
}
void display(Line obj)
{
cout << "line 大小 : " << obj.getLength() <<endl;
}
int main( )
{
Line line1(10);
Line line2 = line1; // 调用拷贝构造函数
display(line1);// 调用拷贝构造函数
display(line2);// 调用拷贝构造函数
return 0;
}
运行结果如下:
经过与运行结果的对比,我们就可以发现,第一个拷贝构造函数的调用是拷贝初始化,第二个、第三个是把一个对象以值传参的方式传入函数体。