引言
前文介绍了C++中默认成员函数中的构造函数和析构函数,相信已经对它们的功能与用法有了基本认识,本文接着介绍也很常见的拷贝构造函数和赋值重载函数,便于对C++进一步的学习。
拷贝构造函数
补充知识:深浅拷贝
深拷贝和浅拷贝是C++中对象拷贝的两种不同方式。
浅拷贝是指将一个对象的数据成员的值复制到另一个对象中,这样两个对象将共享相同的数据。当其中一个对象修改了数据,另一个对象也会受到影响。
深拷贝是指创建一个新的对象,并将原对象的数据成员的值复制到新对象中。新对象和原对象是完全独立的,互不影响。
拷贝构造函数:
引子
在现实世界里,我们有时候会看到双胞胎,他们通常外貌很相似,那么类比到创建对象时,如果已经存在一个创建好的类对象时,能不能用它创建一个一模一样的新对象呢。答案是可以的,并且在C++中专门设定了一个函数来完成这个功能,也就是构造拷贝函数。
定义
拷贝构造函数是构造函数的重载,用于创建一个对象时,以另一个同类型对象作为参数,从而将参数对象的数据成员的值复制给新创建的对象。函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰)。
使用注意
1.拷贝构造函数的参数只有一个且必须是类类型对象的引用,如果使用传值方式,编译器会直接报错,因为C++规定自定义类型的传值传参会自动调用拷贝构造函数,此时如果传值传参会引发无穷的递归调用。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// Date(const Date d) //错误写法:编译错误,引发无穷递归
Date(const Date& d) // 正确写法
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
递归调用过程如下图所示:
2.未显式定义拷贝构造函数,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,也就是浅拷贝。
所以当类中资源没有涉及到资源申请时,系统默认的拷贝构造函数可以完成拷贝,但是当涉及到资源申请时,需要自己编写拷贝构造函数,因为浅拷贝不能满足要求。
比如说一个类对象s1在堆上申请开辟空间,此时如果用s1默认拷贝构造函数拷贝构造一个新对象s2,此时s1和s2都指向同一块内存空间,当程序退出,s1和s2都要销毁,s2先析构,将该空间释放,但当s1析构时,也会释放该空间,造成堆上同一块内存空间的多次释放,从而使程序崩溃。
3.常见调用场景:
使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date tmp(d);
return tmp;
}
int main()
{
Date d1(2024,10,8);
Test(d1);
return 0;
}
运行结果:
Date(int,int,int):006FFC74
Date(const Date& d):006FFB64
Date(const Date& d):006FFB94
~Date():006FFB64
~Date():006FFB94
~Date():006FFC74
编译器在优化后显示三次构造,三次析构,其实是有4次构造和4次析构:
1.调用构造函数创建d1;
2. Test函数值传递,传参时调用拷贝构造创建d;
3. 调用拷贝函数创建tmp;
4. Test函数以值的方式返回,返回时用tmp拷贝构造临时对象用来返回。
所以为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用就尽量使用引用。
赋值运算符重载函数
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表。
运算符重载:
返回值类型 operator操作符(参数列表)
bool operator==(Date& d1,Date& d2)
注意:
有5个运算符不能重载
.*
::
sizeof
.
?:
重载操作符必须有一个类类型参数,作为类成员函数重载时,第一个参数为隐藏的this,用于内置类型的运算符不可以改变其含义。
赋值运算符重载
class Date
{
public :
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date (const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
注意:
1.重载格式:
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
返回*this :可以连续赋值
2.赋值重载函数只能重载成类的成员函数,不可以重载成全局函数
因为赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了。
3.与拷贝构造类似,用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。内置类型值拷贝,自定义类型调用对应的赋值重载。
同样的,和拷贝构造一样的情况,如果类中涉及到资源的管理,不显示定义,系统默认的赋值运算符重载函数因为只能浅拷贝,可能会造成内存的重复释放,此时必须自己手动实现赋值重载。
前置++和后置++重载
C++为了区分前置++和后置++, 规定后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器会自动传递。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 前置++:返回+1之后的结果
// this指向的对象函数结束后不会销毁,可以用引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
取地址及const取地址操作符重载
const成员函数
const修饰的成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date{
public:
//void Print(const Date* this)
void Print() const
{
cout << “year:” << _year << endl;
cout << “month:” << _month << endl;
cout << “day:” << _day << endl << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
const Date d1(2024,10,8);
d1.Print();
}
注意:const对象只能调用const成员函数。const成员函数内不可以调用其他的非const成员函数。
取地址及const取地址操作符重载
这两个成员函数一般用编译器默认生成的就可以了,除非想修改返回的内容。
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&() const
{
return this ;
}
private :
int _year ;
int _month ;
int _day ;
};
本文到此就结束了,本文主要就C++的拷贝构造函数和运算符重载函数展开叙述,结合上篇文章较为完整地总结了C++的默认成员函数,希望能带给你帮助。