【C++】拷贝构造函数及析构函数

📢博客主页:https://blog.csdn.net/2301_779549673

📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

📢本文由 JohnKi 原创,首发于 CSDN🙉

📢未来很长,值得我们全力奔赴更美好的生活✨

文章目录


📢前言

当谈论C++编程语言的核心概念时,拷贝构造函数析构函数无疑是不可或缺的话题。它们不仅仅是理解对象生命周期和内存管理的关键,更是构建复杂系统和高效程序的基础。拷贝构造函数在对象复制过程中扮演着关键角色,决定了如何正确地复制对象的状态和数据。而析构函数则负责在对象生命周期结束时释放资源,确保程序运行的稳定性和性能。

在本博客系列中,我们将深入探讨C++中拷贝构造函数和析构函数的实现原理、使用场景以及最佳实践。我们将从基础知识入手,逐步扩展到高级应用和实际案例分析,帮助读者建立起对这两个重要概念的全面理解和应用能力。无论您是刚入门的初学者,还是希望深化专业知识的资深开发者,本系列都将为您提供有价值的内容和实用的技能,助力您在C++编程的道路上更进一步。让我们一起探索C++世界中的拷贝构造函数和析构函数,发现它们的力量和魅力!


🏳️‍🌈什么是拷贝构造函数

什么是拷贝构造函数

如果已经存在一个对象,我想对这个对象再复制一份,该怎么做呢?

有两种方法拷贝构造赋值运算符重载·,但显然赋值运算符重载不是这里的重点,这里要讲的是前者。至于后者笔者后续再补充。

拷贝构造函数是类的六大特殊成员函数之一,它是构造函数的一个重载形式

而且由于拷贝并不需要改变参数,所以参数部分还要用 "const"来修饰。

比如下面这样一个类

cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

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

那么它的拷贝构造函数就可以写成这样

cpp 复制代码
	//拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

❤️1. 引用传参

拷贝构造函数 的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。

因为传值传参 是一种拷贝,每次拷贝时又需要先传值传参,就会导致无限递归,导致程序崩溃


顺便提一下传值返回的注意点

传值返回 会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用),没有产生拷贝。这种方式避免了额外的拷贝开销,提升了性能。然而,如果返回的对象是函数内部的局部对象,函数结束时该对象的生命周期也随之结束,引用返回就会出现问题,因为引用将指向一个已经销毁的对象,类似于野指针的问题。

为了避免这种情况,需要注意以下几点:

  • 如果函数返回一个对象,且希望通过引用返回以避免拷贝开销,确保返回的对象是通过 new 创建的(即动态分配内存),这样它的生命周期不会受到函数作用域的限制。
  • 对于本地(即函数内部定义的)局部对象,不应该使用引用返回,因为这些对象在函数结束时会被销毁。在这种情况下,应该选择传值返回或者返回一个静态或全局对象,确保返回的对象在函数结束后仍然有效。

因此,在选择传值返回还是传引用返回时,必须根据返回对象的生命周期来进行合理的选择,以确保程序的正确性和性能的最优化。

🧡2. 自动生成的拷贝构造函数

在C++中,如果您没有显式定义拷贝构造函数,编译器将自动生成一个默认的拷贝构造函数。这个自动生成的拷贝构造函数执行的是浅拷贝,即简单地复制对象的每个成员变量的值,即一个一个字节地复制。这种行为对于大多数简单的类和结构体是合适的,可以有效地复制对象的状态。

自动生成的拷贝构造函数在以下情况下会被调用:

对象初始化:使用一个对象初始化另一个对象时,例如 ClassName obj1 = obj2;,编译器会调用自动生成的拷贝构造函数。

对象传递:将对象作为参数传递给函数,或从函数返回对象时,编译器也会使用拷贝构造函数来创建对象的副本。

尽管自动生成的拷贝构造函数对于许多情况都能正常工作,但在某些情况下可能需要显式地定义自定义的拷贝构造函数,特别是当类包含指针动态分配的资源时,以确保正确的深拷贝行为和资源管理。

就比如的创建往往是要动态内存开辟

cpp 复制代码
typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}

	//拷贝函数
	Stack(const Stack& st)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_a, st._a, sizeof(STDataType) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}

	void Push(STDataType x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a ,sizeof(STDataType) * newcapacity);
			if (nullptr == tmp)
			{
				perror("malloc申请失败");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_top++] = x;
	}
	
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};

🏳️‍🌈什么是析构函数

析构函数(Destructor 是面向对象编程中的一个概念,它是一种特殊的成员函数,用于在对象生命周期结束时执行清理工作和资源释放操作。在许多编程语言中,包括C++和一些类似的语言,析构函数在对象销毁时自动被调用

在C++中,析构函数的名称与类名相同 ,但前面加上一个波浪号(~)。它没有参数,不返回任何值,也没有返回类型。析构函数的作用是在对象销毁时自动执行一些必要的清理步骤,如释放动态分配的内存、关闭文件、释放资源等。

比如说下面这就是一个基本的析构函数

cpp 复制代码
#include <iostream>

class MyClass {
public:
    // 构造函数
    MyClass() {
        std::cout << "构造函数被调用" << std::endl;
    }

    // 析构函数
    ~MyClass() {
        std::cout << "析构函数被调用" << std::endl;
    }
};

int main() {
    MyClass obj; // 创建对象

    // 在main函数结束时,对象obj将销毁,析构函数会被自动调用
    return 0;
}

但是在C++中,如果您没有显式定义析构函数 ,编译器将自动生成一个默认的析构函数。这个自动生成的析构函数会依次销毁对象的每个成员变量,释放它们占用的内存空间。这对于大多数简单的类和结构体来说通常是合适的,因为它确保对象在生命周期结束时能够正确地释放资源。

自动生成的析构函数在以下情况下会被调用:

  • 对象离开作用域:当对象的作用域结束时,比如一个局部变量超出其作用域范围。
  • 动态分配的对象:如果对象是通过 new 运算符动态分配的,那么在调用 delete 释放内存时,也会自动调用析构函数。

尽管默认的析构函数对于大多数情况都能正常工作,但在涉及到动态分配的资源管理、对象组合等复杂情况下,可能需要显式定义自定义的析构函数。这样可以确保在对象销毁时执行额外的清理工作,如释放动态分配的内存或关闭文件等操作,以防止资源泄漏和程序错误。因此,了解和利用析构函数的自动生成特性对于C++程序的健壮性和性能优化至关重要。

就比如说下面这一块栈的析构函数,因为动态开辟了,所以需要手动释放空间,故要自定义析构函数

cpp 复制代码
typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}

	//拷贝函数
	Stack(const Stack& st)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_a, st._a, sizeof(STDataType) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}

	void Push(STDataType x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a ,sizeof(STDataType) * newcapacity);
			if (nullptr == tmp)
			{
				perror("malloc申请失败");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_top++] = x;
	}

	//析构函数
	~Stack()
	{
		//cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};

👥总结

本篇博文对 拷贝构造函数及析构函数 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

相关推荐
时光找茬2 分钟前
【瑞萨AI挑战赛-FPB-RA6E2】+ 从零开始:FPB-RA6E2 开箱测评与 e2 studio 环境配置
c++·单片机·边缘计算
qq_537562672 分钟前
跨语言调用C++接口
开发语言·c++·算法
wjs202412 分钟前
DOM CDATA
开发语言
Tingjct14 分钟前
【初阶数据结构-二叉树】
c语言·开发语言·数据结构·算法
猷咪40 分钟前
C++基础
开发语言·c++
IT·小灰灰42 分钟前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧43 分钟前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q44 分钟前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳044 分钟前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾44 分钟前
php 对接deepseek
android·开发语言·php