首页 游戏 软件 资讯 排行榜 专题
首页
编程语言
C++面向对象编程中对象的赋值操作详解

C++面向对象编程中对象的赋值操作详解

热心网友
66
转载
2026-05-08

对象初始化:构造函数与复制构造函数详解

在C++面向对象编程中,构造函数是类设计的核心环节。常规对象初始化依赖于构造函数,即使未显式定义,编译器也会生成默认版本。然而,还存在一种特殊的初始化方式——通过已有对象创建新对象,这便涉及复制构造函数。本文将以栈(Stack)类为例,系统解析对象初始化、复制构造以及赋值操作的核心机制与常见陷阱。

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

c++面向对象之对象的赋值详解

首先查看栈类的声明。在Stack.hpp头文件中,我们定义了栈的基本结构:

//Stack.hpp
#pragma once
class Stack {
public:
	Stack(int max_size);
	~Stack();
	//复制构造函数
	Stack(const Stack& s);
	bool IsEmpty() const;
	bool IsFull() const;
	void Push(int data);
	void Pop();
	int Top() const;
private:
	int* buffer_;
	int* top_;
	int capacity_;
};

接下来在Stack.cpp中实现成员函数。注意此处内存分配使用std::malloc而非new,释放则对应使用std::free。复制构造函数的实现尤为关键,它需要为新对象分配独立内存空间,并完整拷贝原对象数据。

//Stack.cpp
#include "Stack.hpp"
#include 
Stack::Stack(int max_size) :
	capacity_(max_size), 
	//buffer_(new int[max_size]),
	buffer_(static_cast(std::malloc(sizeof(int) * max_size))), 
	top_(buffer_) {}
Stack::~Stack() {
	std::free(buffer_);
}
Stack::Stack(const Stack& s) :
	capacity_(s.capacity_),
	buffer_(static_cast(std::malloc(sizeof(int) * s.capacity_))),
	top_(buffer_ - s.buffer_ + s.top_) {
	std::memcpy(buffer_, s.buffer_, sizeof(int) * s.capacity_);
}
bool Stack::IsEmpty() const {
	return top_ <= buffer_;
}
bool Stack::IsFull() const {
	return top_ >= buffer_ + capacity_;
}
void Stack::Push(int data) {
	if (this->IsFull())
		throw "Stack is full!";
	*top_++ = data;
}
void Stack::Pop() {
	if (this->IsEmpty())
		throw "Stack is Empty!";
	top_--;
}
int Stack::Top() const {
	if (this->IsEmpty())
		throw "Stack is Empty!";
	return *(top_ - 1);
}

完成类定义后,我们编写测试代码验证功能。首先创建s1对象并压入两个数值,随后通过复制构造创建s2。此时s1s2栈顶元素应相同。接着向s2压入新值,两个栈的栈顶将发生变化——这正是深拷贝(deep copy)的预期效果。

然而问题常隐藏于看似普通的操作中。我们再创建s3对象,并使用默认赋值运算符将s1赋值给它。此时隐患出现:默认赋值操作仅执行浅拷贝(shallow copy),直接将s1buffer_指针地址复制给s3。当s3压入新值时,虽然top_指针位置改变,但s1s3buffer_指向同一内存区域。程序结束时,两个对象的析构函数将先后释放同一内存块,导致典型的“重复释放”(double free)错误。

//OverloadAssignment
#include 
#include "Stack.hpp"
int main() {
	Stack s1(16);
	s1.Push(1);
	s1.Push(2);
	Stack s2 = s1;	//调用复制构造函数
	std::cout << "s1 top: " << s1.Top() << std::endl;
	std::cout << "s2 top: " << s2.Top() << std::endl;
	s2.Push(3);
	std::cout << "s2 pushed 3" << std::endl;
	std::cout << "s1 top: " << s1.Top() << std::endl;
	std::cout << "s2 top: " << s2.Top() << std::endl;
	Stack s3(16);
	s3 = s1;	//调用赋值运算符重载
	std::cout << "s3 top: " << s3.Top() << std::endl;
	//观察报错信息,s3的析构函数被调用了两次,说明s3和s1指向了同一块内存区域,导致内存被重复释放了。
}

运行程序,控制台输出看似正常:

s1 top: 2
s2 top: 2
s2 pushed 3
s1 top: 2
s2 top: 3
s1 top: 2
s3 top: 2
s3 pushed 3
s1 top: 2
s3 top: 3

但程序结束时发生崩溃。根源很明确:我们仅定义了复制构造函数,未自定义拷贝赋值运算符。编译器生成的默认operator=仅执行浅拷贝,导致两个对象共享动态内存所有权。析构时同一内存区域被释放两次,引发堆损坏与程序崩溃。

重载赋值运算符实现深拷贝

因此,当类管理动态资源时,仅实现复制构造函数并不足够,必须显式重载赋值运算符。这是C++资源管理类设计的重要准则。

Stack.hpp中声明赋值运算符重载函数,并在Stack.cpp中实现:

//重载赋值运算符
//函数返回当前对象的引用,支持链式赋值(如s3 = s2 = s1)。s2 = s1返回s2的引用,s3 = s2实际执行s3 = (s2 = s1),最终s3获得s1的值。
Stack& Stack::operator =(const Stack& s) {
	//检查自赋值
	if (this == &s)
		return *this;
	//释放原有资源
	std::free(buffer_);
	capacity_ = s.capacity_;
	buffer_ = static_cast(std::malloc(sizeof(int) * s.capacity_));
	top_ = buffer_ - s.buffer_ + s.top_;
	std::memcpy(buffer_, s.buffer_, sizeof(int) * s.capacity_);
	return *this;
}

实现需注意三个要点:第一,返回Stack&类型以支持链式赋值;第二,起始处检查自赋值情况,避免不必要的资源释放;第三,必须优先释放当前对象原有资源,再分配新空间并执行深拷贝。实现后重新运行测试代码,内存重复释放错误即会消失。

这种参数为自身const引用的赋值函数,常被称为拷贝赋值函数。它与拷贝构造函数共同构成类对象“值语义”的基础。

赋值运算符不仅限于拷贝赋值。以复数类为例,我们还可定义从doubleComplex类型的赋值:

class Complex {
public:
	Complex(double r, double i) : real_(r), imag_(i) {};
	Complex& operator =(double r) {
		real_ = r;
		imag_ = 0.0;
		return *this;
	}
private:
	double real_, imag_;
};

隐式构造函数与explicit关键字

谈及赋值,另一个易混淆概念是隐式构造。若类未声明特定赋值函数但存在单参数构造函数,编译器可能隐式调用该构造函数完成“赋值”。

class Complex {
public:
	Complex(double r) : real_(r), imag_(0.0) {};
	Complex(double r, double i) : real_(r), imag_(i) {};
public:
	double real_, imag_;
};
int main() {
	Complex c1(1, 2);
	//使用隐式构造函数
	Complex c2 = 45.0;
	//使用隐式构造函数
	c1 = 4;
	std::cout << c1.real_ << " + " << c1.imag_ << "i" << std::endl;
	std::cout << c2.real_ << " + " << c2.imag_ << "i" << std::endl;
	return 0;
}

隐式转换虽带来便利,也可能导致意外歧义与错误。若不希望构造函数被隐式调用,可在其前添加explicit关键字。

Stack类为例,构造函数接受int参数表示最大容量。若允许隐式构造,st = 20;语法虽合法但语义模糊——是修改栈容量还是压入数值20?为避免歧义,可为构造函数添加explicit

class Stack {
public:
	explicit Stack(int max_size);
//……
int main (){
    Stack st(16);
    st = 20;    //编译报错
}

回到复数类示例。若存在多个构造函数,隐式构造可能引发重载决议歧义。此时为含默认参数的构造函数添加explicit,可强制调用者明确意图。

class Complex {
public:
	Complex(double r) : real_(r), imag_(0.0) {};
	explicit Complex(double r = 0, double i = 0) : real_(r), imag_(i) {};
public:
	double real_, imag_;
};
int main() {
	Complex c1(1, 2);
	//使用了隐式构造函数
	Complex c2 = 45.0;
	//使用了隐式构造函数
	c1 = 4;
	std::cout << c1.real_ << " + " << c1.imag_ << "i" << std::endl;
	std::cout << c2.real_ << " + " << c2.imag_ << "i" << std::endl;
	return 0;
}

实际工程中是否使用explicit需权衡便利性与安全性。普遍建议是:对于单参数构造函数,除非有充分理由允许隐式转换,否则应声明为explicit。这能避免许多隐蔽错误,使代码意图更清晰。

补充说明:C++11标准后,隐式构造功能更丰富。若构造函数所有参数均有默认值,甚至支持多参数隐式构造(通过初始化列表语法):

class Complex {
public:
    Complex(double r = 0, double i = 0) : real_(r), imag_(i) {};
public:
	double real_, imag_;
};
int main() {
	Complex c1(1, 2);
	//使用了隐式构造函数
	Complex c2 = 45.0;
	//使用了隐式构造函数
	c1 = 4;
	std::cout << c1.real_ << " + " << c1.imag_ << "i" << std::endl;
	std::cout << c2.real_ << " + " << c2.imag_ << "i" << std::endl;
	//使用多参数隐式构造
	c1 = { 45, 56 };
	std::cout << c1.real_ << " + " << c1.imag_ << "i" << std::endl;
	return 0;
}

这再次提醒我们,深入理解构造、赋值与隐式转换的底层机制,对编写健壮、清晰的C++代码至关重要。

来源:https://www.jb51.net/program/363483muf.htm
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

最新APP

宝宝过生日
宝宝过生日
应用辅助 04-07
台球世界
台球世界
体育竞技 04-07
解绳子
解绳子
休闲益智 04-07
骑兵冲突
骑兵冲突
棋牌策略 04-07
三国真龙传
三国真龙传
角色扮演 04-07

热门推荐

迅捷路由器24G和5G设置后网速变慢的解决方法
电脑教程
迅捷路由器24G和5G设置后网速变慢的解决方法

迅捷路由器双频开启后网速变慢?三步系统调优,释放千兆真实性能 很多朋友发现,家里的迅捷路由器明明开启了2 4G和5G双频,可用起来网速反而时快时慢,追剧卡顿、游戏高延迟成了家常便饭。这背后,问题往往出在几个容易被忽视的细节上:默认开启的“双频合一”功能、信道自动选择的“偷懒”逻辑,以及频段配置与使用

热心网友
05.08
2026年如何选择虚拟货币交易所?十大平台特色与适用人群全解析
web3.0
2026年如何选择虚拟货币交易所?十大平台特色与适用人群全解析

选择虚拟币交易所需综合考量安全性、交易对、费用及用户体验。头部平台各具特色:币安适合多元交易者,Coinbase便于新手入门,OKX在衍生品领域领先,Kraken以安全合规著称。新兴平台如Bybit、KuCoin则在特定市场或功能上表现突出。投资者应根据自身需求,优先考虑资产安全与合规性,再结合交易习惯选择合适平台。

热心网友
05.08
荣耀100Pro遥控空调需要购买附加配件吗
电脑教程
荣耀100Pro遥控空调需要购买附加配件吗

荣耀100 Pro不支持红外遥控功能,硬件层面未配备红外发射模块,因此无法直接通过手机发射红外信号控制传统空调。根据荣耀官方技术规格及多轮实测验证,该机型未集成红外硬件,系统设置中亦无“智能遥控”入口,桌面实用工具文件夹内亦未预置相关应用;用户若需实现空调控制,须借助荣耀智慧空间APP接入兼容的智能

热心网友
05.08
华硕主板U盘启动失效问题排查与解决办法
电脑教程
华硕主板U盘启动失效问题排查与解决办法

华硕主板重启后U盘启动失效?系统性排查与精准解决 遇到华硕主板重启后U盘启动失效这事儿,确实挺让人头疼。但你不用焦虑,这通常不是什么玄学问题,根源往往出在引导设置、启动介质或固件兼容性这几个有章可循的技术环节上。咱们一步步来,把问题拆解清楚。 一、确认BIOS启动顺序与设备识别状态 第一步,得先让主

热心网友
05.08
专业U盘数据恢复服务推荐指南
电脑教程
专业U盘数据恢复服务推荐指南

U盘数据恢复:从逻辑故障到物理损坏的全攻略 遇到U盘数据丢失或彻底“罢工”时,别慌,路通常有两条:要么借助靠谱的软件工具自行尝试,要么交给有资质的专业机构处理。如何选?其实关键看故障类型。对于分区丢失、误删除、中毒这类逻辑性故障,市面上的专业恢复工具是主力军,像数据蛙恢复专家、DiskGenius、

热心网友
05.08