拷贝构造和运算符重载(C++)

拷贝构造

如果一个构造函数的第一个参数是所在类类型的引用,且其它参数都有缺省参数,那么这个构造函数就叫做拷贝构造

定义:拷贝构造函数的第一个参数必须是本类类型的引用(通常为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;
赋值重载函数必须是成员函数,不能以全局函数或友元函数形式重载赋值运算符
1. C++语言规范的强制要求C++明确规定了某些运算符必须作为成员函数重载,包括赋值运算符(=)、下标运算符([])、函数调用运算符(``)和成员访问箭头运算符(->)。若尝试以全局函数或友元函数形式重载这些运算符,编译器会直接报错。

2. 支持链式赋值赋值运算符需要返回当前对象的引用(ClassName&),以实现连续赋值操作(例如 a = b = c)。作为成员函数时,可以通过 return *this; 直接返回当前对象的引用,而全局函数无法隐式获取调用对象的引用,难以实现这一特性。

3. 避免与编译器生成的默认版本冲突
  • 默认赋值运算符:若未显式重载赋值运算符,编译器会自动生成一个默认版本,完成逐字节的浅拷贝(即复制所有成员变量的值)。
  • 冲突问题:如果用户尝试在类外定义全局的赋值运算符重载,会与编译器生成的默认版本冲突,导致编译错误。因此,必须通过成员函数形式覆盖默认行为,尤其是需要深拷贝或特殊逻辑时。

赋值运算符支持连续赋值,从右往左,所以重载的时候需要进行引用返回

使用引用返回的原因:

  1. 提高效率:返回别名可以避免返回对象时的拷贝构造过程。如果不使用引用返回,函数在返回时会创建一个临时对象来复制返回的对象,这会增加额外的开销。而返回引用直接返回对象的地址,避免了不必要的拷贝,提高了效率。
  2. 支持连续赋值:返回对象的引用可以支持链式赋值操作,即允许像 d1 = d2 = d3 这样的连续赋值。在这种情况下,d2 = d3 的返回值(即 d2 的引用)会被用作 d1 = 的操作数。
  3. 符合内置类型的赋值行为:内置类型的赋值运算符返回左操作数的引用,使得赋值后可以继续使用左操作数。重载赋值运算符时返回引用,可以保持与内置类型一致的行为,增强代码的可读性和一致性。

系统会默认给内置类型进行浅拷贝所以像日期类这样没有指向资源的,不需要显式定义赋值运算符,只有像栈这样,指向了一个资源,就不能浅拷贝,必须进行显式定义

流插入,流提取重载

流插入,流提取运算符 允许自定义类型进行输入输出

像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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值