📢博客主页:https://blog.csdn.net/2301_779549673
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨
文章目录
- 📢前言
- 🏳️🌈什么是拷贝构造函数
-
- [❤️1. 引用传参](#❤️1. 引用传参)
- [🧡2. 自动生成的拷贝构造函数](#🧡2. 自动生成的拷贝构造函数)
- 🏳️🌈什么是析构函数
- 👥总结
📢前言
当谈论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;
};
👥总结
本篇博文对 拷贝构造函数及析构函数 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~