首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【c++】类和对象(中)拷贝构造函数、赋值重载、运算符重载

【c++】类和对象(中)拷贝构造函数、赋值重载、运算符重载

作者头像
mosheng
发布2026-01-14 18:04:59
发布2026-01-14 18:04:59
80
举报
文章被收录于专栏:c++c++

hello~ 很高兴见到大家! 这次带来的是C++中关于类和对象这部分的一些知识点,如果对你有所帮助的话,可否留下你的三连呢? 个 人 主 页: 默|笙

续接上文构造函数和析构函数。这次带来的是拷贝构造函数,运算符重载与赋值重载函数以及取地址与const取地址运算符重载。

1.拷贝构造函数

1.1 概念

拷贝构造函数是C++中一种特殊的构造函数,用于用一个已存在的对象来初始化同类型的新对象,默认拷贝构造函数是c++里默认六大函数之一。

1.2 特点

  1. 拷贝构造函数被视为构造函数的一个重载形式,它们的函数名称相同,只不过参数不同。
代码语言:javascript
复制
#include<iostream>

using namespace std;
class Date
{
public:
	//实质:Date(const Date* this)
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	//实质:Date(const Date* this, 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(&d2, d1)
	Date d2(d1);
	return 0;
}
  1. 拷贝构造函数的第一个参数必须是类类型对象的常量 (const) 引用,const 是为了不修改原对象,而引用是为了避免传值情况下会出现的无穷递归调用;拷贝构造函数可以有多个参数,但是除了第一个参数之外,其他的参数必须要有缺省值。但在实际应用中几乎都是单个参数。
  2. 拷贝构造函数什么时候会被调用?
  3. 对象初始化:用已有对象初始化新对象时。
  4. 函数传参:对象以传递的方式传入函数时。
  5. 函数返回:对象以值传递的方式从函数返回时(某些编译器会优化,可能不调用)。

接下来我们来解释一下 2 中传值情况下为什么在语法上会出现无穷递归调用:

传值必然发生拷贝,在c++里: 1.内置类型会直接进行拷贝。 2.对自定义类型对象进行拷贝行为会去调用拷贝构造函数。

在这里插入图片描述
在这里插入图片描述

所以,我们需要传引用,d 就是 d1 的别名,我们可以直接操作原对象,就不用去拷贝。这样也能够节省空间。 除了引用,其实指针也能够解决这个问题,不过,c++规定,传引用才是拷贝构造函数。

  1. 浅拷贝与深拷贝:

浅拷贝:仅复制对象的成员值(包括指针的值),新旧对象共享同一块内存深拷贝:复制对象成员值及其指向的资源,为新对象分配独立的内存空间

  1. 当用户未显式定义拷贝构造函数时,编译器会自动生成拷贝构造函数。自动生成的拷贝构造函数会对内置类型成员变量进行浅拷贝,对自定义类型成员变量会去调用它的拷贝构造函数。

对于浅拷贝,就像 Date 类: 它的成员变量都是内置类型,而且没有指向什么资源,由编译器自动生成的拷贝构造函数就可以完成对应的拷贝工作。

代码语言:javascript
复制
class Date
{
public:
	//实质:Date(const Date* this)
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	//实质:Date d2(&d2, d1)
	Date d2(d1);
	return 0;
}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对于深拷贝,就像 Stack类: 成员变量虽然都是内置类型,但是有指向的资源,编译器自动生成的拷贝构造函数无法达到要求,我们需要显式实现拷贝构造函数,原因在之后会讲到。 还有 MyQueue 类: 成员变量都是自定义类型,编译器自动生成的拷贝构造函数会去调用成员变量它们所对应的拷贝构造函数,它不需要我们显式实现拷贝构造函数,但它也是深拷贝。

代码语言:javascript
复制
typedef int SLDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_arr = (SLDataType*)malloc(sizeof(SLDataType) * n);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			exit(1);
		}
		_top = 0;
		_capacity = n;
	}
	Stack(const Stack& st)
	{
		_arr = (SLDataType*)malloc(sizeof(SLDataType) * st._capacity);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			exit(1);
		}
		_top = st._top;
		_capacity = st._capacity;
	}
	~Stack()
	{
		free(_arr);
		_arr = nullptr;
		_top = _capacity = 0;
	}
private:
	SLDataType* _arr;
	int _top;
	int _capacity;
};
class MyQueue
{
public:

private:
	Stack st1;
	Stack st2;
};
int main()
{
	Stack st1;
	//调用拷贝构造函数还可以写成这种格式,清晰且直观
	Stack st2 = st1;
	return 0;
}

拷贝之前:

在这里插入图片描述
在这里插入图片描述

拷贝之后:

在这里插入图片描述
在这里插入图片描述
  1. 为什么 Stack 类需要我们显式实现拷贝构造函数:
在这里插入图片描述
在这里插入图片描述
  1. 什么时候需要我们主动去写拷贝构造函数?通过上面的 Date 类,Stack 类,MyQueue 类,能知道:类成员变量全部是自定义类型或者全部是内置类型且没有指向资源时,是不用我们显式实现的,如 Date 类,MyQueue 类;有指向资源时就需要我们显式实现,如 Stack 类。再将其与我们之前的析构函数做对比,能够得到:

一般来说,需要显式实现析构函数的也需要显式实现拷贝构造函数

  1. 传值返回会产生一个临时对象调用拷贝构造函数,如果我们使用传引用返回就可以避免,不会产生拷贝。但是如果返回对象是当前函数局部域的局部对象,函数结束它就会销毁,这个时候传引用返回是有大问题的,相当于野引用。所以我们在使用传引用返回的时候需要特别注意这一点。能使用传引用返回就使用传引用返回,这样可以提升程序运行效率。

2.运算符重载

引入:内置类型定义的变量之间可以用运算符实现计算,但是自定义类型对象之间则不能,为了能让自定义类型对象之间也够实现内置类型类似的运算符操作,引出了运算符重载。

2.1 概念

运算符重载 是 C++ 中允许为自定义类型重新定义现有运算符的行为的机制,通过编写特殊形式的成员函数或全局函数(如 operator+),使运算符能够根据操作数的类型执行用户定义的逻辑。其核心目标是让自定义类型的操作语法与内置类型一致,提升代码的直观性和可维护性

2.2 特点

  1. 运算符重载是具有特殊名字的函数,名字由 operator 和将要定义的运算符共同构成,拥有返回值。
  2. 运算符重载的参数和该运算符操作的运算对象一样多,一元是一个,二元两个。

1.对于二元运算符,它左侧的对象会传给第一个参数,而右侧的对象会传给第二个参数。 2.若是作为类的成员函数,那么它的第一个运算对象默认会传给 this 指针,因此参数会比运算对象会少一个

  1. 运算符重载以后,它的优先性和结合性与内置类型运算符保持一致。
  2. 不能通过连接c++语法中没有的操作符来创建新的操作符,如 operator@。
  3. " .*, : :, sizeof, ? :, . "这 5 个运算符不能够被重载。
  4. 重载操作符⾄少有⼀个类类型参数。且不能通过运算符重载改变内置类型运算符已有的运算逻辑,如: int operator+(int x, int y)不可取。
  5. 一个类需要重载哪些运算符,要看重载哪些运算符以及在哪些情况下是有意义的。类型比如 日期 * 日期无意义,而 日期 - 日期 是有意义的;情况比如 日期 + 日期 无意义,而 日期 + 天数 有意义。返回值是什么类型,要看运算结果的类型,比如 日期 - 日期是天数,但 日期 - 天数 是日期。
代码语言:javascript
复制
class Date
{
public:
   Date(int year = 2025, int month = 5, int day = 11)
   {
   	_year = year;
   	_month = month;
   	_day = day;
   }
   //由于定义在类里面,参数比运算对象少一个
   //实质: bool operator==(Date* const this, const Date d2)
   bool operator==(const Date d2)
   {
   	return _year == d2._year &&
   		_month == d2._month &&
   		_day == d2._day;
   }

private:
   int _year;
   int _month;
   int _day;
};

int main()
{
   Date d1;
   Date d2;
   // '<<' 的优先级是要比 '=='高的,要加括号
   cout << (d1 == d2) << endl;
   return 0;
}

2.3 重载++与–运算符

重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,我们应该如何区分? C++规定,后置++重载时,增加⼀个 int 形参,跟前置++构成函数重载,⽅便区分。 即:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 重载<<与>>运算符

重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了对象<<cout,不符合使⽤习惯和可读性。

重载为全局函数把 ostream/istream 放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。 即:

代码语言:javascript
复制
//将类里的私有成员变量公有化
 ostream& operator<<(ostream& out, const Date& d)
{
	 out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	 return out;
}
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

3.赋值运算符重载

3.1 概念

在 C++ 中,赋值运算符重载用于自定义类对象之间的赋值行为(如 obj1 = obj2;)。其核心目标是实现深拷贝,避免默认浅拷贝导致的资源冲突(如重复释放内存)。系统自动生成的默认赋值运算符是c++里六大默认成员函数之一。

3.2 特点

  1. 赋值运算符重载是运算符重载,c++规定,必须重载在类里面,或者是声明在类里面实现在类外面。
  2. 它具有返回值,建议写成当前类类型引用,可以提升效率,有返回值是为了能够实现连续赋值
  3. 用户没有显式实现时,系统会自动生成一个默认赋值重载函数,这个默认赋值重载函数的行为跟默认拷贝构造函数类似。对于内置类型成员变量,它会一个字节一个字节的进行浅拷贝/值拷贝,对于自定义类型成员变量,它会调用它的赋值重载函数。
  4. 什么时候需要我们显式实现?这一点也跟拷贝构造函数类似,也可以简单记为:

一般来说,需要显式实现析构函数的也需要显式实现赋值重载函数

代码语言:javascript
复制
class Date
{
public:
	// friend 是友元函数,可以避免注释掉 private
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
	Date(int year = 2025, int month = 5, int day = 11)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator=(const Date d2)
	{
		if (this != &d2)
		{
			_year = d2._year;
			_month = d2._month;
			_day = d2._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out,const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日";
	return out;
}
istream& operator>>(istream& in,  Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
int main()
{
	Date d1;
	Date d2(2025, 5, 12);
	cout << "赋值之前:" << d1 << endl;
	cout << "赋值之后:" << (d1 = d2) << endl;
	return 0;
}

3.3 区分赋值重载函数和构造函数

代码语言:javascript
复制
int main()
{
	Date d1;
	//拷贝构造函数的隐式调用,约等于 Date d2(d1);
	Date d2 = d1;
	//赋值重载函数的调用
	d2 = d1;
	return 0;
}

区分它们的方式:

  1. 调用拷贝构造函数前面是要加上类类型的,而调用赋值重载函数则不需要。
  2. 拷贝构造函数是为了初始化一个新的即原来不存在的对象,赋值重载函数则是将一个对象的值赋值给一个已经存在的对象。这是本质的不同。

4.const成员函数

  1. 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到员函数参数列表的后⾯
  2. const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。 如:
代码语言:javascript
复制
class Date
{
public:
	Date(int year = 2025, int month = 5, int day = 11)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//即为 void Print(const Date* const this)
	void Print()const
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
  1. 关于权限放大与缩小的问题:

我们知道:权限不能被放大,但能够缩小。 就有:

  1. 被 const 修饰的对象不能够调用未被 const 修饰的成员函数,这是因为前者的权限小于后者,权限不能被放大;而无论是否被 const 修饰的对象都能够去调用 被 const 修饰的成员函数,这是因为前者的权限不小于后者,权限可以缩小。
  2. 同样:被 const 修饰的成员函数函数体内不能够调用未被 const 修饰的成员函数,而无论是否被 const 修饰的成员函数函数体内都能够去调用 被 const 修饰的成员函数。

5.取地址和const取地址运算符重载

  1. 在用户未显式实现时,系统会自动生成,系统自动生成的默认取地址和const取地址运算符重载函数都是 c++六大默认成员函数之一,
  2. ⼀般这两个函数由编译器自动生成的就已经达到预期,能够完成任务,不需要去显式实现。除非⼀些很特殊的场景,比如我们不想让其他人取到当前类对象的地址,就可以自己实现⼀份,胡乱返回⼀个地址。 实现如下:
代码语言:javascript
复制
class Date
{
public:
	Date(int year = 2025, int month = 5, int day = 11)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()
	{
		return this;
		//可以把返回值换成 nullptr,防止其他人取到对象地址
		//或者胡乱返回一个错误地址,那很坏了
	}
	const Date* operator&()const
	{
		return this;
		//同上
	}
private:
	int _year;
	int _month;
	int _day;
};

今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~ 如果可以, 那就让我们共同努力, 一起走下去!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.拷贝构造函数
    • 1.1 概念
    • 1.2 特点
  • 2.运算符重载
    • 2.1 概念
    • 2.2 特点
    • 2.3 重载++与–运算符
    • 2.4 重载<<与>>运算符
  • 3.赋值运算符重载
    • 3.1 概念
    • 3.2 特点
    • 3.3 区分赋值重载函数和构造函数
  • 4.const成员函数
  • 5.取地址和const取地址运算符重载
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档