前面已经介绍过类中6个默认函数的其中三个:构造函数、析构函数、拷贝构造函数;今天来看一下第四个默认的函数:赋值运算符的重载函数。
1、赋值运算符的重载函数的作用:
把一个已存在的对象赋值给相同类型的已存在对象
2、赋值运算符的重载函数的形式(以上篇文章中的商品CGoods类为例):
CGoods& operator=(const CGoods& rhs)
{
if (this != &rhs)
{
delete[] mname;
mname = new char[strlen(rhs.mname) + 1]();
strcpy(mname, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
return *this;
}
3、实现:
由上述赋值运算符的重载函数的例子可以总结出该函数的实现:
(1)判断是否为自赋值
(2)释放旧资源
(3)申请新资源
(4)赋值
4、与拷贝构造函数相似,默认的赋值运算符的重载函数也是浅拷贝的。
为什么赋值运算符的重载函数要像上述代码中那样写呢?我们先来看以下几个概念:
1、临时量:
(1)内置类型生成的临时量为常量
(2)自定义类型生成的临时量为变量
(3)隐式生成的临时量为常量
2、临时对象的生存周期:
表达式结束
3、CGoods& operator=(const CGoods& rhs)中const修饰形参的原因:
(1)防止修改实参
(2)接收隐式生成的临时对象
4、临时对象的优化:
(1)如果临时对象的目的是为了生成新对象,则以生成临时对象的方式来生成新对象
例如: Test test1=10;//只生成了一个对象
已生成临时对象的方式来生成test1,此处的临时对象是调用带有一个整形参数的构造函数来生成的对象,以此方式来生成了test2对象,但并没有真正生成该临时对象。所以省去了临时对象的生成,是代码的一个优化。
(2)引用能提升临时对象的生存周期,把临时对象提升到和引用变量相同的生存周期。
例如:CGoods& rgood12 = CGoods("good11", 10.1, 20);
我们通过下面的代码,分别说明每一行代码有没有生成对象,以什么方式(调用什么函数)生成的对象,以及对象的存储位置,还有对象的生存周期:
#include
#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(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]();
}
~CGoods()
{
std::cout << this << " :CGoods::~CGoods()" << std::endl;
delete[] mname;
mname = NULL;
}
CGoods(const CGoods& rhs)
{
std::cout << this << " :CGoods::CGoods(const CGoods&)" << std::endl;
mname = new char[strlen(rhs.mname) + 1]();
strcpy(mname, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
CGoods& operator=(const CGoods& rhs)
{
std::cout << this << " :CGoods::operator=(const CGoods&)" << std::endl;
if (this != &rhs)
{
delete[] mname;
mname = new char[strlen(rhs.mname) + 1]();
strcpy(mname, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
return *this;
}
private:
char* mname;
float mprice;
int mamount;
};
CGoods ggood1("good1", 10.1, 20);//.data
int main()
{
std::cout << "------------------" << std::endl;
CGoods good3;
CGoods good4(good3);
std::cout << "------------------" << std::endl;
good4 = good3;
static CGoods good5("good5", 10.1, 20);//.data
std::cout << "------------------" << std::endl;
CGoods good6 = 10;
CGoods good7(10);
CGoods good8 = CGoods("good8", 10.1, 20);
good6 = 20;
good7 = CGoods(20);
good8 = (CGoods)("good8",10.1, 20);
std::cout << "------------------" << std::endl;
CGoods* pgood9 = new CGoods("good9", 10.1, 20);//heap
CGoods* pgood10 = new CGoods[2];
std::cout << "------------------" << std::endl;
CGoods* pgood11 = &CGoods("good11", 10.1, 20);
std::cout << "------------------" << std::endl;
//CGoods* pgood12 = 20;
CGoods& rgood12 = CGoods("good11", 10.1, 20);
std::cout << "------------------" << std::endl;
const CGoods& rgood13 = 20;
std::cout << "------------------" << std::endl;
delete pgood9;
delete[] pgood10;
return 0;
}
CGoods ggood2("good2", 10.1, 20);//.data
CGoods ggood1("good1", 10.1, 20);
位于main()外部,调用带有三个参数(char*,float,int)的构造函数来生成对象ggood1,在.data段存储,生存周期为调用点至整个程序结束。
CGoods ggood2("good2", 10.1, 20);
代码位于main()外部,对象ggood2的生成方式、生存周期存储区域与ggood1相同。ggood1与个ggood2在main函数外边,对象生成顺序先于main函数内部的对象。
CGoods good3;
调用默认的构造函数来生成good3对象,在.bss段存储,生存周期为调用点至main函数结束。
CGoods good4(good3);
调用拷贝构造函数函数用一个已存在的对象good3来生成一个相同类型的新对象good4,在栈上存储,生存周期为调用点至main函数结束。
good4 = good3;
调用赋值运算符的重载函数,把一个已存在的对象good3赋值给相同类型的新对象good4,不会生成新对象。
static CGoods good5("good5", 10.1, 20);
调用带有三个参数(char*,float,int)的构造函数来生成对象good5,在.data段存储,用static修饰,生存周期是调用点至整个程序结束。
CGoods good6 = 10;
调用带有一个整形参数的构造函数来生成临时对象,以生成临时对象的方法生成新对象good6,但由于优化作用,省去了临时对象的生成,只生成了一个对象good6,在栈上存储。
CGoods good7(10);
调用带有一个整型参数的构造函数来生成临时对象,在内存上存储,临时对象的生存周期遇表达式结束则结束,又以生成临时对象的方式生成新对象good7,在栈上存储,good7生存周期是调用点至main函数结束。
CGoods good8 = CGoods("good8", 10.1, 20);
调用带有三个参数(char*,float,int)的构造函数来隐式生成临时对象,临时对象的生存周期遇表达式结束则结束,又以生成临时对象的方式生成新对象good8,在栈上存储,goo8生存周期是调用点至main函数结束。
good6 = 20;
调用带有一个整型参数的构造函数来隐式生成临时对象,临时对象的生存周期遇表达式结束则结束,再调用赋值运算符的重载函数把临时对象赋值给相同类型的已存在对象good6。
good7 = CGoods(20);
调用带有一个整型参数的构造函数生成临时对象,再调用赋值运算符的重载函数把临时对象赋值给相同类型的已存在对象good7。
good8 = (CGoods)("good8",10.1, 20);
首先,等号右边CGoods后面跟的括号里边是一个逗号表达式,逗号表达式的值为最后一个值20,调用带有一个整型参数的构造函数生成临时对象,再调用赋值运算符的重载函数把临时对象赋值给相同类型的已存在对象good8。
CGoods* pgood9 = new CGoods("good9", 10.1, 20);//heap
在堆上开辟空间,调用带有三个参数(char*,float,int)的构造函数来生成临时对象,让指针pgood9指向该临时对象的地址,临时对象表达式结束时结束,之后指针pgood9指向无效地址。
CGoods* pgood10 = new CGoods[2];
在堆上开辟了两个连续的临时对象空间,两个临时对象是调用两次默认的构造函数生成的,让指针pgood10指向第一个临时对象的地址。
CGoods* pgood11 = &CGoods("good11", 10.1, 20);
调用带有三个参数(char*,float,int)的构造函数来生成临时对象,让指针pgood11指向该临时对象的地址。
//CGoods* pgood12 = 20;
将一个int类型的数字赋值给指针类型,语法错误。
CGoods& rgood12 = CGoods("good11", 10.1, 20);
调用带有三个参数(char*,float,int)的构造函数来显式生成临时对象,可以用普通引用来引用,rgood12是该临时对象的一个别名。
const CGoods& rgood13 = 20;
调用带有一个整型参数的构造函数来隐式生成临时对象,隐式生成的临时对象是常量,不能用普通引用来引用常量,所以用const修饰的常引用来引用。
上述代码运行结果截图: