C++六大默认成员函数

C++六大默认成员函数

  • 默认构造函数
  • 默认析构函数
    • RAII技术
      • RAII的核心思想
      • 优点
      • 示例
      • 应用场景
  • 默认拷贝构造
    • 深拷贝和浅拷贝
  • 默认拷贝赋值运算符
  • 移动构造函数(C++11起)
  • 默认移动赋值运算符(C++11起)
  • 取地址及const取地址操作符重载
      • 取地址操作符重载
      • 常量取地址操作符重载
      • 示例代码
      • 输出结果
      • 注意事项
  • 扩展:前置++和后置++重载
      • 重载规则

C++中的六大默认成员函数是编译器在特定条件下自动生成的成员函数,用于管理对象的生命周期和资源操作。它们分别是:

默认构造函数

  • 作用:初始化对象,当类没有显式定义任何构造函数时生成。

  • 生成条件:用户未定义任何构造函数。

  • 注意:若类有其他构造函数(如带参数的构造函数),需显式使用 = default 声明默认构造函数。

class Person
{
public:
	//Person()
	//{

	//} 不写的话默认自动生成
	void GetAge()
	{
		std::cout << _age << std::endl;
	}
private:
	int _age;
};

int main()
{
	Person p;
	p.GetAge();
}

其特征如下:

  1. 函数名与类名相同
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
  6. 编译器生成默认的构造函数会对自定类型成员调用的它的默认成员
    函数。C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

默认析构函数

  • 作用:释放对象资源,默认析构函数调用成员变量的析构函数。

  • 生成条件:用户未定义析构函数。

  • 注意:若类管理动态资源(如堆内存),需自定义析构函数以避免内存泄漏

class Person
{
public:
	//Person()
	//{

	//} 不写的话默认自动生成
	void GetAge()
	{
		std::cout << _age << std::endl;
	}

	~Person()
	{

	}
private:
	int _age;
};

int main()
{
	Person p;
	p.GetAge();
}

RAII技术

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++中一种管理资源的编程技术。它通过将资源的生命周期与对象的生命周期绑定在一起,利用C++的构造函数和析构函数来自动管理资源,从而避免了手动分配和释放资源可能带来的问题,如内存泄漏、资源未正确释放等。

RAII的核心思想

  • 资源在对象构造时获取:当一个对象被创建时,它的构造函数负责获取所需的资源(例如,动态内存分配、文件打开、网络连接等)。
  • 资源在对象销毁时释放:当对象离开作用域或被显式删除时,其析构函数会自动释放之前获取的资源。

优点

  1. 异常安全性:由于资源管理由构造和析构函数自动处理,即使程序中抛出了异常,也能确保资源得到正确释放。
  2. 简化代码:开发者不需要手动跟踪每个资源的状态,并且可以在不使用显式的try-finally块的情况下保证资源的释放。
  3. 防止资源泄露:只要对象被正确地创建并最终销毁,资源就会被正确释放。

示例

以下是一个简单的例子,展示了如何使用RAII来管理动态分配的内存:

#include <iostream>

class ResourceHandler {
private:
    int* data;
public:
    // 构造函数:资源获取
    ResourceHandler() {
        data = new int(10); // 分配资源
        std::cout << "Resource acquired." << std::endl;
    }

    // 析构函数:资源释放
    ~ResourceHandler() {
        delete data; // 释放资源
        std::cout << "Resource released." << std::endl;
    }

    void showData() const {
        std::cout << "Data: " << *data << std::endl;
    }
};

void useResource() {
    ResourceHandler handler;
    handler.showData();
    // 不需要手动释放资源,handler离开作用域时会自动调用析构函数
}

int main() {
    useResource();
    return 0;
}

在这个例子中,ResourceHandler类负责管理一个整数类型的动态分配内存。构造函数在对象创建时分配资源,而析构函数在对象销毁时释放这些资源。这样就确保了无论函数useResource如何退出(正常结束或因异常退出),资源都会被正确释放。

应用场景

RAII不仅限于内存管理,还可以应用于其他资源类型,如文件句柄、网络套接字、数据库连接等。标准库中的智能指针(如std::unique_ptrstd::shared_ptr)、锁机制(如std::lock_guardstd::unique_lock)都是RAII原则的实际应用案例。通过使用这些工具,可以有效地减少资源管理错误,提高代码的安全性和可靠性。

默认拷贝构造

  • 声明形式:ClassName(const ClassName&)

  • 作用:通过已有对象初始化新对象,默认执行浅拷贝。

  • 生成条件:用户未定义拷贝构造函数。

  • 注意:若类包含指针或动态资源,需自定义深拷贝防止重复释放。

class Person
{
public:
	Person()
	{

	}

	Person(const Person& person)
	{
		this->_age = person._age;
	}

	~Person()
	{

	}

	void GetAge()
	{
		std::cout << _age << std::endl;
	}
private:
	int _age;
};

int main()
{
	Person p;
	p.GetAge();
}

深拷贝和浅拷贝

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	//默认生成拷贝构造

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2(s1);
}

这里我没有写实际的拷贝构造函数,这里s2调用的默认的拷贝构造,所以s2_array的地址就是s1中_array的地址,这就叫浅拷贝:
在这里插入图片描述
这样代码就会有问题,因为一个地址会被析构两次:
在这里插入图片描述正确的方法应该是给s2的array开辟一块新的空间:

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	Stack(const Stack& st)
	{
		_array = new int[10];
		_size = st._size;
		_capacity = st._capacity;
	}

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2(s1);
}

在这里插入图片描述这样的拷贝我们称为深拷贝,再次运行程序:
在这里插入图片描述

默认拷贝赋值运算符

  • 声明形式:ClassName& operator=(const ClassName&)

  • 作用:将已有对象的值赋给另一个对象,默认浅拷贝。

  • 生成条件:用户未定义拷贝赋值运算符。

  • 注意:需处理自赋值问题,并在资源管理时实现深拷贝。

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	Stack(const Stack& st)
	{
		_array = new int[10];
		_size = st._size;
		_capacity = st._capacity;
	}

	Stack& operator=(const Stack& st)
	{
		if (this != &st)
		{
			_array = new int[10];
			_size = st._size;
			_capacity = st._capacity;
		}

		return *this;

	}

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2;

	s2 = s1;
}

在这里插入图片描述

移动构造函数(C++11起)

  • 声明形式:ClassName(ClassName&&)

  • 作用:通过右值引用“窃取”资源,避免深拷贝开销。

  • 生成条件:用户未定义拷贝操作、移动操作或析构函数。

  • 注意:移动后源对象应处于有效但未定义状态(如空指针)。

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	Stack(const Stack& st)
	{
		_array = new int[10];
		_size = st._size;
		_capacity = st._capacity;
	}

	Stack& operator=(const Stack& st)
	{
		if (this != &st)
		{
			_array = new int[10];
			_size = st._size;
			_capacity = st._capacity;
		}

		return *this;

	}

	void swap(Stack& st)
	{
		std::swap(_array, st._array);
		std::swap(_size, st._size);
		std::swap(_capacity, st._capacity);
	}

	//移动构造函数
	Stack(Stack&& st):_array(nullptr), _size(0), _capacity(0)
	{
		swap(st);
	}

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2(std::move(s1));
}

在这里插入图片描述

默认移动赋值运算符(C++11起)

  • 声明形式:ClassName& operator=(ClassName&&)

  • 作用:通过右值引用转移资源所有权。

  • 生成条件:同移动构造函数。

  • 注意:需正确处理自移动赋值。

class Stack
{
public:
	//初始化
	Stack()
	{
		_array = new int[20];
	}

	Stack(const Stack& st)
	{
		_array = new int[10];
		_size = st._size;
		_capacity = st._capacity;
	}

	Stack& operator=(const Stack& st)
	{
		if (this != &st)
		{
			_array = new int[10];
			_size = st._size;
			_capacity = st._capacity;
		}

		return *this;

	}

	void swap(Stack& st)
	{
		std::swap(_array, st._array);
		std::swap(_size, st._size);
		std::swap(_capacity, st._capacity);
	}

	//移动构造函数
	Stack(Stack&& st):_array(nullptr), _size(0), _capacity(0)
	{
		swap(st);
	}

	//移动复制构造
	Stack& operator=(Stack&& st)
	{
		swap(st);

		st._array = nullptr;
		st._size = 0;
		st._capacity = 0;

		return *this;
	}

	//析构
	~Stack()
	{
		delete[] _array;
	}
private:
	int* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;

	Stack s2;

	s2 = std::move(s1);
}

在C++中,前置++和后置++运算符可以通过成员函数或非成员函数的形式进行重载。两者的主要区别在于参数列表和返回值:

  • 前置++:增加对象的值,并返回增加后的对象引用。
  • 后置++:首先保存当前对象的状态,然后增加对象的值,最后返回之前保存的对象的副本。

取地址及const取地址操作符重载

在C++中,取地址操作符(&)和常量取地址操作符(const &)通常不需要显式地重载,因为编译器提供了默认的实现,它们分别返回对象或常量对象的内存地址。然而,在某些特定情况下,你可能想要自定义这些操作符的行为。

取地址操作符重载

当你重载取地址操作符时,你通常是为了改变其默认行为,例如返回一个代理对象的地址而不是原始对象的地址。不过这种情况非常少见,大多数时候并不需要这样做。

常量取地址操作符重载

类似地,重载常量版本的取地址操作符也是为了提供特定的行为,但同样,这并不是常见的需求。

示例代码

尽管不常见,这里还是给出如何重载这两种操作符的基本示例:

#include <iostream>

class MyClass {
private:
    int value;
public:
    MyClass(int val) : value(val) {}

    // 重载取地址操作符
    int* operator&() {
        std::cout << "非const取地址操作符被调用" << std::endl;
        return &value;
    }

    // 重载const取地址操作符
    const int* operator&() const {
        std::cout << "const取地址操作符被调用" << std::endl;
        return &value;
    }
};

int main() {
    MyClass obj(10);
    const MyClass constObj(20);

    int* addr = &obj;       // 调用非const版本
    const int* constAddr = &constObj; // 调用const版本

    std::cout << "*addr: " << *addr << std::endl;
    std::cout << "*constAddr: " << *constAddr << std::endl;

    return 0;
}

输出结果

非const取地址操作符被调用
const取地址操作符被调用
*addr: 10
*constAddr: 20

在这个例子中,我们为MyClass类重载了取地址操作符和常量取地址操作符。当通过非常量对象调用operator&()时,会调用非常量版本的操作符,并打印一条消息。而当通过常量对象调用operator&()时,则调用常量版本的操作符,并打印另一条不同的消息。

注意事项

  • 谨慎使用:一般情况下,不需要也不建议重载这两个操作符,除非有特别的需求。这是因为它们改变了标准语义,可能会导致混淆或者不可预期的行为。
  • 保持一致性:如果你决定重载这些操作符,请确保它们的行为符合逻辑且一致,避免引入错误。
  • 理解限制:需要注意的是,即使你重载了取地址操作符,也无法阻止使用内置的取地址操作来获取对象的实际地址。例如,&obj总是可以获得obj的实际地址,除非你完全隐藏了对象的访问方式。

总的来说,重载取地址操作符和常量取地址操作符是一种高级技巧,适用于特定场景下的特殊需求。在大多数日常编程任务中,这种重载是不必要的。

扩展:前置++和后置++重载

重载规则

  • 前置++只需要一个参数(即调用该运算符的对象本身),并且通常返回一个指向修改后的对象的引用。
  • 后置++需要两个参数:第一个是调用该运算符的对象本身,第二个是一个int类型的占位参数,用于区分前置和后置形式。后置++返回的是操作前对象的一个副本(通常是通过值返回)。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class Count
{
public:
	//重载后置++
	Count operator++()
	{
		++_count;
		return *this;
	}

	//后置++
	Count operator++(int)
	{
		Count temp = *this;
		++_count;
		return temp;
	}

	int getCount() const {
		return _count;
	}
private:
	int _count = 0;
};

int main()
{
	Count myCounter;

	std::cout << "Initial count: " << myCounter.getCount() << std::endl;

	++myCounter; // 调用前置++
	std::cout << "After prefix increment: " << myCounter.getCount() << std::endl;

	Count d;
	d = myCounter++; // 调用后置++
	std::cout << "After postfix increment: " << d.getCount() << std::endl;

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/964894.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ext文件系统

文件内容属性 被打开的文件在内存中&#xff0c;没有被打开的文件在磁盘里文件系统的工作就是根据路径帮我们找到在磁盘上的文件 磁盘&#xff08;硬件&#xff09; 磁盘的存储结构 磁头在传动臂的运动下共同进退&#xff0c;向磁盘写入的时候是向柱面批量写入的 OS文件系统访…

AURIX TC275学习笔记3 官方例程 (UART LED WDT)

文章目录 参考资料1. ASCLIN_UART_12. GPIO_LED_Button_13. WDT (Watch Dog Timer) 参考资料 AURIX TC275学习笔记1 资料收集Getting Started with AURIX™ Development Studio 官方帮助文档happy hacking for TC275! 硬件平台使用AURIX™ TC275 Lite 套件&#xff0c;按照参…

免费接入DeepSeek等多种大模型

核心代码 import cn.hutool.core.collection.CollUtil; import com.tool4j.pasteshare.entity.params.AiParams; import com.tool4j.pasteshare.entity.params.Message; import com.tool4j.pasteshare.service.AiCompletionService; import com.tool4j.pasteshare.util.deepse…

PostIn简明安装教程(入门级)

PostIn是一款开源免费的接口管理工具&#xff0c;包含接口调试、接口文档设计、数据MOCK等模块&#xff0c;本文将介绍如何快速安装配置&#xff0c;以快速入门上手。 1、服务端安装 私有部署版本支持多种操作系统&#xff0c;包括 Linux、Docker、Windows及macOS&#xff0c;…

java Fx进阶操作

1.java Fx基本介绍 JavaFX是用于构建富互联网应用程序的Java库。 使用此库编写的应用程序可以跨多个平台一致运行。 使用JavaFX开发的应用程序可以在各种设备上运行&#xff0c;如台式计算机&#xff0c;手机&#xff0c;电视&#xff0c;平板电脑等。 要使用Java编程语言开…

java-重载与重写

介绍 在 Java 中&#xff0c;重载&#xff08;Overloading&#xff09; 和 重写&#xff08;Overriding&#xff09; 是两个重要的概念&#xff0c;它们都与方法有关&#xff0c;但它们的应用场景和行为完全不同。 通过理解重载和重写的区别&#xff0c;可以更好地设计类的继承…

数据库系统概念第六版记录 一

1.关系型数据库 关系型数据库&#xff08;Relational Database&#xff0c;简称 RDB&#xff09;是基于关系模型的一种数据库&#xff0c;它通过表格的形式来组织和存储数据。每个表由若干行&#xff08;记录&#xff09;和列&#xff08;字段&#xff09;组成&#xff0c;数据…

嵌入式工程师必学(143):模拟信号链基础

概述: 我们每天使用的许多电子设备,以及我们赖以生存的电子设备,如果不使用电子工程师设计的实际输入信号,就无法运行。 模拟信号链由四个主要元件组成:传感器、放大器、滤波器和模数转换器 (ADC)。这些传感器用于检测、调节模拟信号并将其转换为适合由微控制器或其他数…

2.5学习

misc buuctf-假如给我三天光明 下载附件后得到了一个压缩包和一个图片&#xff0c;压缩包为加密压缩包&#xff0c;需要解出密码&#xff0c;然后注意到这个图片并非简单的一个封面&#xff0c;在下方还有诸多点&#xff0c;有黑有灰。经过搜索&#xff0c;发现这是盲文通过与…

嵌入式八股文面试题(一)C语言部分

1. 变量/函数的声明和定义的区别&#xff1f; &#xff08;1&#xff09;变量 定义不仅告知编译器变量的类型和名字&#xff0c;还会分配内存空间。 int x 10; // 定义并初始化x int x; //同样是定义 声明只是告诉编译器变量的名字和类型&#xff0c;但并不为它分配内存空间…

设备通过国标GB28181接入EasyCVR,显示在线但视频无法播放的原因排查

安防监控EasyCVR平台支持多种视频源接入&#xff0c;包括但不限于IP摄像头、NVR、编码器、流媒体服务器等。平台采用高效的视频流接入技术&#xff0c;支持大规模视频流的并发接入&#xff0c;确保视频流的稳定性和流畅性。 有用户反馈&#xff0c;项目现场使用国标GB28181接入…

Electricity Market Optimization 探索系列(三)

本文参考链接link 电网容量规划是一个寻求最优发电容量的过程&#xff0c;找到的最优发电容量能够可靠地满足未来电网的需求 发电机的容量和发电成本呈正相关关系&#xff0c;一台发电机的发电量不能超过其额定发电容量&#xff0c;结合我之前的博客所说的内容&#xff0c;可…

深入理解和使用定时线程池ScheduledThreadPoolExecutor

文章目录 前言认识定时线程池什么是定时线程池&#xff1f;定时线程池基本API使用定时线程池的应用场景1、定时任务调度2、缓存过期清理3、心跳检测4、延迟任务执行 定时线程池scheduleAtFixedRate与scheduleWithFixedDelay区别scheduleAtFixedRate案例demo&#xff08;period&…

在Mac mini M4上部署DeepSeek R1本地大模型

在Mac mini M4上部署DeepSeek R1本地大模型 安装ollama 本地部署&#xff0c;我们可以通过Ollama来进行安装 Ollama 官方版&#xff1a;【点击前往】 Web UI 控制端【点击安装】 如何在MacOS上更换Ollama的模型位置 默认安装时&#xff0c;OLLAMA_MODELS 位置在"~/.o…

动态规划练习九(完全背包问题)

一、问题介绍与解题心得 完全背包问题与01背包问题很相似&#xff0c;不同点就是每个物品数量有多个&#xff0c;每个物品可以取多个或不取&#xff0c;来达到收益最大&#xff0c;或者收益在某个值。 限制条件&#xff1a;背包容量有限 解决问题&#xff1a;从价值入手&…

百亿大表的实时分析:华安基金 HTAP 数据库的选型历程与 TiDB 使用体验

导读 在金融科技迅猛发展的今天&#xff0c;华安基金作为行业的先行者&#xff0c;面临着数据管理和分析的全新挑战。随着业务的不断扩展和数据量的激增&#xff0c;传统的数据库架构已难以满足系统对实时性、灵活性和分析能力的需求。在这样的背景下&#xff0c;HTAP&#xf…

低代码系统-产品架构案例介绍、蓝凌(十三)

蓝凌低代码系统&#xff0c;依旧是从下到上&#xff0c;从左至右的顺序。 技术平台h/iPaas 指低层使用了哪些技术&#xff0c;例如&#xff1a;微服务架构&#xff0c;MySql数据库。个人认为&#xff0c;如果是市场的主流&#xff0c;就没必要赘述了。 新一代门户 门户设计器&a…

DeepSeek研究员在线爆料:R1训练仅用两到三周,春节期间观察到R1 zero强大进化

内容提要 刚刚我注意到DeepSeek研究员Daya Guo回复了网友有关DeepSeek R1的一些问题&#xff0c;以及接下来的公司的计划&#xff0c;只能说DeepSeek的R1仅仅只是开始&#xff0c;内部研究还在快速推进&#xff0c;DeepSeek 的研究员过年都没歇&#xff0c;一直在爆肝推进研究…

【Rust自学】20.1. 最后的项目:单线程Web服务器

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 20.1.1. 什么是TCP和HTTP Web 服务器涉及的两个主要协议是超文本传输​​协议(Hypertext T…

19.[前端开发]Day19-王者荣项目耀实战(二)

01_(掌握)王者荣耀-main-banner展示实现 完整代码 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewpor…