C++的类中有6个默认的函数,它们分别是:
- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值运算符的重载函数
- 取地址操作符的重载函数
- const修饰的取地址操作符的重载函数
这6个默认的函数有两个特点:共有的;内联的
今天我们先来看看前三个默认的函数:构造函数、析构函数和拷贝构造函数
一、构造函数
我们都知道,C++中一个对象的生成分两个步骤:
- 开辟内存
- 对内存空间进行初始化
而构造函数的作用就是初始化对象的内存空间,所以调用构造函数是生成对象的一环,需要生成对象必须调用构造函数,之后再调用构造函数是通过该对象调用的。
如果类定义中没有给出构造函数,则C++编译器自动产生一个默认的构造函数,只要我们自己定义了一个构造函数,系统就不会自动生成默认的构造函数。构造函数的函数名与类名相同,(本文代码均以商品CGoods为例进行阐述)
系统提供的默认的构造函数形式:
类名()
{}
通常默认的构造函数并不能满足我们的需求,因此就需要,我们自己写一个构造函数进行实现,这就是构造函数的重载
下面让我们来看看,生成对象时若有系统提供的默认构造函数并且还有其他自己写的多个构造函数时,分别在何种情况下调用某一种构造函数呢,我们结合下面的代码分析一下;
#include<iostream>
#pragma warning(disable:4996);
class CGoods
{
public:
CGoods(char* name, float price, int amount)//带有三个参数的构造函数
{
std::cout << this << " :CGoods::CGoods(char*,float,int)" << std::endl;
mname = new char[strlen(name) + 1]();
strcpy(mname, name);
mprice = price;
mamount = amount;
}
CGoods(char* name,float price)//带有两个参数的构造函数
{
std::cout << this << " :CGoods::CGoods(char*,float)" << std::endl;
mname = new char[strlen(name) + 1]();
strcpy(mname, name);
mprice = price;
}
CGoods(int amount)//带有一个整形参数的构造函数
{
std::cout << this << " :CGoods::CGoods(int)" << std::endl;
mname = new char[1]();
mamount = amount;
}
CGoods()//不带参数的构造函数
{
std::cout << this << " :CGoods::CGoods()" << std::endl;
mname = new char[1]();
}
void Destroyed()
{
delete[] mname;
}
~CGoods()
{
std::cout << this << " :CGoods::~CGoods()" << std::endl;
delete[] mname;
}
/*char* GetName();
void GetName(char* name);
float GetPrice();
int GetAmount();
void Sell();
void Buy();*/
private:
char* mname;
float mprice;
int mamount;
};
int main()
{
CGoods good1("car1", 10.1, 10);
CGoods good2("car1",15.1);
CGoods good3(20);
CGoods good4;
return 0;
}
运行结果:
在上述代码中,一共创建了4个CGoods类的对象,在类中一共有4的构造函数,通过上述代码,我们可以清楚地理解调用构造函数时,会优先选择函数形参列表与定义对象时的实参列表相匹配的那个构造函数进行调用,通常用CGoods good4方式来调用系统默认的构造函数。注意:构造函数是不能手动调用的
二、析构函数
系统默认的析构函数形式:
~类名()
{}
与对象的生成相对应,对象的销毁同样包含两个部分:
- 释放其他资源(对象的空间是在栈上开辟的,这里的其他资源是指栈以外的资源,如new()开辟的堆资源等)
- 释放空间
析构函数的作用就是:释放对象所占的其他资源
我们可以用调用如下析构函数来销毁上述对象的其他资源
~CGoods()
{
delete[] mname;
}
所以从第一个代码块中运行结果中,我们会发现,调用的构造函数的个数与析构函数的个数相等,并且,构造与析构的顺序是:先构造的后析构,后构造的先析构
与构造函数的两个特点:1.可重载;2.不可手动调用 相比,析构函数的特点是:1.不可重载;2.可以手动调用
三、拷贝构造函数
默认的拷贝构造函数的作用:用一个已经存在的对象来生成一个相同类型的新对象
即创建对象时使用同类对象来进行初始化,这时所用的构造函数称为拷贝构造函数,拷贝构造函数是特殊的构造函数
例如:
int main()
{
CGoods good1("car1", 10.1, 10);
/*CGoods good2("car1",15.1);
CGoods good3(20);
CGoods good4;*/
CGoods good5=good1;
CGoods good6(good1);//与上一行等效
return 0;
}
系统默认的拷贝构造函数是浅拷贝的,但如果类中含有指针类型的变量(如下图所示),须考虑用深拷贝来实现
所以我们需要自己写一个拷贝构造函数来实现深拷贝:
CGoods(const CGoods& rhs)
{
mname = new char[strlen(rhs.mname) + 1]();
strcpy(mname, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
上述拷贝构造函数的形参为什么要用&呢?能否用*呢?
1.这是为了防止递归构造形参对象导致栈溢出。引用只是给实参对象起的别名,若不用&则会不断地递归去生成新形参对象,最后导致栈溢出,而要做的正事还没做。
2.不能用*,若写成 CGoods(const CGoods* rhs),就会变成一个构造函数而不是拷贝构造函数,CGoods*传的是已存在对象的地址
今天只介绍了前三种默认函数,其余的未完待续......