当前位置: 首页
编程语言
C++面向对象编程中对象的赋值操作详解

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

热心网友 时间: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。

同类文章
更多
C++面向对象编程中对象的赋值操作详解

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

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

时间:2026-05-08 16:54
Spring Boot中ConfigurationProperties配置绑定详解与使用教程

Spring Boot中ConfigurationProperties配置绑定详解与使用教程

@ConfigurationProperties是SpringBoot中用于批量绑定配置的强大工具。它通过指定前缀,将配置文件中的属性自动映射到实体类的对应字段上,并支持短横线与驼峰命名法的自动转换。这种方式集中管理配置,提升了代码的类型安全性和可维护性,适合处理一组相关的复杂属性。

时间:2026-05-08 16:22
Java LocalDate.plusMonths 方法详解 自动处理跨年与月份天数计算

Java LocalDate.plusMonths 方法详解 自动处理跨年与月份天数计算

Java的LocalDate plusMonths()方法基于日历月进行日期运算,能自动处理跨年及月份天数差异。它会在目标月份天数不足时,将日期智能调整至月末,例如1月31日加1个月得到2月28日。该方法简化了日期计算,但需注意其静默调整特性可能影响特定业务逻辑,此时可结合其他方法确保准确性。

时间:2026-05-08 14:48
Laravel Eloquent模型数据库查询进阶指南

Laravel Eloquent模型数据库查询进阶指南

Eloquent模型使用中需注意数据类型匹配,避免whereIn因类型不匹配静默失败。预加载嵌套关系时可能仍产生多余查询,需检查日志或拆分加载。updateOrCreate不支持关联字段作为查找条件,需手动分步查询。toArray与$casts对JSON字段处理不一致,API返回时应显式处理。数据库类型宽容不等于ORM类型安全,需严格遵循类型约定。

时间:2026-05-08 14:17
ThinkPHP多语言缓存设置与读取加速方法详解

ThinkPHP多语言缓存设置与读取加速方法详解

ThinkPHP多语言性能瓶颈在于语言包未被真正缓存。需手动执行命令生成缓存文件,并关闭浏览器语言自动检测以减少开销。模板中应减少lang()调用频次,可改用预加载变量。优化语言包文件结构,合并小型文件并避免深层嵌套,确保缓存机制有效运行以提升性能。

时间:2026-05-08 14:17
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜
热门教程
更多
  • 游戏攻略
  • 安卓教程
  • 苹果教程
  • 电脑教程