拷贝构造
如果一个构造函数的第一个参数是所在类类型的引用,且其它参数都有缺省参数,那么这个构造函数就叫做拷贝构造
定义:拷贝构造函数的第一个参数必须是本类类型的引用(通常为const引用),可以有其它参数,但这些参数必须有默认值
拷贝构造相当于是构造函数的重载函数,函数名与类名相同,也不具有返回值,区别在于参数
触发时机:拷贝构造函数在以下情况被调用:
- 用一个对象初始化另一个对象时
- 对象作为值传递给函数时(传值调用)
- 函数返回对象时(传值返回)
- 在异常处理中抛出和捕获对象时
拷贝构造的第一个参数必须是引用,不能是普通的值传递
因为对于自定义函数来说,传值调用时一定会调用拷贝构造创建一个副本,当拷贝构造也是值传递时,就会陷入无限循环,导致无穷递归和栈溢出
浅拷贝:
如果不显式定义拷贝构造,编译器会自动生成拷贝构造函数,自动生成的拷贝构造函数会对内置类型进行一个浅拷贝(一个字节一个字节的进行拷贝),对于自定义类型,就会调用它的拷贝构造
对于没有指向资源的自定义类型,可以不进行它的显式拷贝构造,编译器自动生成的拷贝构造就可以完成拷贝,但是对于有指向资源的自定义类型,就需要进行显式拷贝构造(深拷贝),
因为对于有指向资源的自定义函数,浅拷贝只会单纯拷贝指针(指向所申请空间的地址),不会拷贝空间
深拷贝:
不是单纯拷贝指针,而是拷贝指针所指向的空间
#include <iostream>
using namespace std;
class func
{
public:
func(int n)
{
_a = new int[n] {};//_a被分配了空间
_b = n;
}
func(const func& n)
{
_a = new int[n._b] {};
memcpy(_a, n._a, sizeof(int) * n._b);
_b = n._b;
}
int& operator[](const int& n)
{
return _a[n];
}
~func()
{
delete[] _a;
}
private:
int *_a;
int _b = 0;
};
func f1(func b)//传值调用,传值返回
{
b[0] = 5;
return b;
}
int main()
{
func a(5);
func re=f1(a);
return 0;
}
显示的写拷贝构造,就要显式的写析构
运算符重载
运算符重载:重新定义一个运算符的行为
运算符重载的语法
运算符重载通过定义特殊的函数来实现,这些函数的名称以operator关键字开头,后跟要重载的运算符。例如,要重载加法运算符+,可以定义一个名为operator+的函数
注意事项
- 不能创建新运算符:只能重载已有的运算符。
- 不能改变运算符的优先级和结合性:重载运算符的优先级和结合性与内置类型一致。
- 有些运算符不能重载:如
.成员、::(类域指定运算符)、.*(成员函数调用操作符)、?:(三目操作符)、sizeof - 至少一个操作数是用户定义类型:防止修改内置类型的行为,例如重载“+”,1+2 的结果本来应该是3,却重载为 1-2,结果变成了-1,将“+”的运算结果变成了“-”
通过运算符重载,可以使自定义类型的对象像内置类型一样使用运算符
例如设置一个日期类的对象,定义了operator==函数后
class Date
{
private:
int _year;
int _month;
int _day;
public:
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// ==运算符重载
bool operator==(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}
};
int main()
{
Date a(1998, 5, 30);
Date b(2000, 4, 5);
if (a == b)
{
cout << "相等" << endl;
}
//也可以显式调用,但是不常用,可读性不强
if (a.operator==(b))
{
cout << "相等" << endl;
}
本质上还是调用 operator== 这个函数
传参的时候最好把引用加上,减少拷贝构造的时间
还要用const修饰,避免权限被放大,并让传参范围增大
(上面代码没有额外申请空间,因此可以不显式写拷贝构造和析构)
赋值运算符重载
目的是 完成对象的拷贝
跟拷贝构造不一样,拷贝构造核心是构造 ,本质是一个已经存在的对象,去初始化另一个要实例化的对象
如果是两个已经存在的对象进行赋值,调用的就是赋值重载
Date d1(2000,4,23);//直接构造
//拷贝构造
Date d2(d1);
Date d2=d1;
//赋值重载
Date d3(1987,8,8);
d3=d1;
- 默认赋值运算符:若未显式重载赋值运算符,编译器会自动生成一个默认版本,完成逐字节的浅拷贝(即复制所有成员变量的值)。
- 冲突问题:如果用户尝试在类外定义全局的赋值运算符重载,会与编译器生成的默认版本冲突,导致编译错误。因此,必须通过成员函数形式覆盖默认行为,尤其是需要深拷贝或特殊逻辑时。
赋值运算符支持连续赋值,从右往左,所以重载的时候需要进行引用返回
使用引用返回的原因:
- 提高效率:返回别名可以避免返回对象时的拷贝构造过程。如果不使用引用返回,函数在返回时会创建一个临时对象来复制返回的对象,这会增加额外的开销。而返回引用直接返回对象的地址,避免了不必要的拷贝,提高了效率。
- 支持连续赋值:返回对象的引用可以支持链式赋值操作,即允许像 d1 = d2 = d3 这样的连续赋值。在这种情况下,d2 = d3 的返回值(即 d2 的引用)会被用作 d1 = 的操作数。
- 符合内置类型的赋值行为:内置类型的赋值运算符返回左操作数的引用,使得赋值后可以继续使用左操作数。重载赋值运算符时返回引用,可以保持与内置类型一致的行为,增强代码的可读性和一致性。
系统会默认给内置类型进行浅拷贝,所以像日期类这样没有指向资源的,不需要显式定义赋值运算符,只有像栈这样,指向了一个资源,就不能浅拷贝,必须进行显式定义
流插入,流提取重载
流插入,流提取运算符 允许自定义类型进行输入输出
像C语言里的 printf 就是用不了自定义类型的,所以C++里额外引入了流插入,流提取,使其既可以用于内置类型,也可以用于自定义类型
内置类型可以使用流插入,流提取运算符,是因为库里面已经重载好了,不管是什么内置类型的参数都有对应的函数重载
流插入,流提取重载 支持链式访问,所以需要进行 流插入,流提取的引用返回,本质是多次调用重载函数
流插入,流提取的重载函数不能作为类的成员函数,因为隐含的this指针会占据第一个参数的位置,比如当调用 cout<<d1 ,cout会传给第一个参数(this指针),由于cout不是自身类类型(d1所在类类型),编译器将会报错
所以不能重载为成员函数,需要重载为全局函数,但是没有办法调用私有变量,这时就可以使用 友元,在头文件进行友元函数声明
friend ostream& operator<<(ostream& cout, Date& a);
如果是声明和定义分离,就需要再在类外面,放入该(友元函数)声明的函数的声明
const成员函数
比如实例化了一个被const修饰的对象,调用成员函数时的this指针就应该也被const修饰
此时this指针类型 将会从 Date* const this 变成 const Date* const this
由于this的类型不能在形参处进行修改,所以要在函数名后面加const,变成const成员函数,这个const修饰的是 *this
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void print() const//对this指针进行const修饰
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
const Date d1(2004,4,5);
d1.print();
return 0;
}


&spm=1001.2101.3001.5002&articleId=147874644&d=1&t=3&u=607eb840f8c543dfb3e5aa178a16b97c)
9768

被折叠的 条评论
为什么被折叠?



