C++类和对象详解(2);初识类的默认成员函数

1.类的默认成员函数

默认成员函数就是用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数。一个类我们不写的情况下编译器会默认生成以下的6个默认成员函数。

(1)构造函数:主要完成初始化的工作

(2)析构函数:主要完成销毁清理的工作

(3)拷贝构造函数:使用同类对象初始化创建对象

(4)赋值重载:主要是把一个对象赋值给另一个对象

(5)普通对象取地址重载:主要用于特殊情况,如安全敏感的应用、自定义内存管理或调试目的。在大多数日常编程中,不需要重载这个运算符。

(6)const对象取地址重载:和普通对象取地址重载一样。日常编程中不需要重载这个运算符。

以上六种默认成员函数我们主要学习前四种。

1.1构造函数

构造函数是特殊的成员函数,构造函数虽然名称是构造,但是构造函数的主要任务是实例化时初始化对象,并不是开空间创造对象(我们常使用的局部对象是栈帧创建时,空间就开好了)。

构造函数的语法特点如下

(1)函数名可以和类名相同

class MyClass {
public:MyClass() { // 构造函数与类名相同// 初始化代码}
};

(2)无返回值,无返回类型。甚至不能使用void(C++规定是这样,不用纠结)

(3)对象实例化时系统会自动调用对应的构造函数

MyClass obj; // 构造函数自动调用

(4)构造函数可以重载

一个类可以有多个构造函数,只要它们的参数列表不同。

class MyClass {
public:MyClass() { // 默认构造函数// 初始化代码}MyClass(int value) { // 带参数的构造函数// 使用value初始化}MyClass(int a, double b) { // 另一个重载// 使用a和b初始化}
};

(5)可以有默认参数

class MyClass {
public:MyClass(int a = 0, double b = 0.0) {// 使用默认参数或提供的值初始化}
};

(6)如果类中没有显式定义构造函数,则C++编译器会自动生成⼀个无参的默认构造函数,⼀旦用户显式定义编译器将不再生成。

#include<iostream>
using namespace std;
class Stack {
public:Stack()//无参的默认构造函数{}
private:int* _date;int _top;int _capacity;
};
int main()
{Stack s1;return 0;
}

这里我们来调试观察s1

会发现s1会被编译器自动生成默认构造函数

1.2析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。

析构函数的特点如下:

(1)在类名前加~

class MyClass {
public:~MyClass() { // 析构函数// 清理代码}
};

(2)无参数,无返回值也,不能被重载。一个类只能有一个析构函数 (这里跟构造类似,也不需要加void)

class MyClass {
public:~MyClass() { // 正确// 清理代码}// ~MyClass(int x) { } // 错误:析构函数不能有参数
};

(3)当对象离开其作用域或被显示删除时,析构函数会自动调用。这里跟构造函数类似,我们不写编译器自动生0成的析构函数对内置类型成员不做处理,自定类型成员会调用他的析构函数。

{MyClass obj; // 对象创建// 使用对象...
} // 对象离开作用域,析构函数自动调用MyClass* ptr = new MyClass();
delete ptr; // 析构函数被调用

(4)还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。

(5)对于继承层次中的对象,析构函数的调用顺序与构造函数相反:派生类的析构函数 成员对象的析构函数(按声明逆序)基类的析构函数

class Base {
public:~Base() { std::cout << "Base destructor" << std::endl; }
};class Member {
public:~Member() { std::cout << "Member destructor" << std::endl; }
};class Derived : public Base {
private:Member member;
public:~Derived() { std::cout << "Derived destructor" << std::endl; }
};// 创建Derived对象然后销毁
// 输出顺序:
// Derived destructor
// Member destructor
// Base destructor

1.3拷贝构造函数

拷贝构造是一种特殊的构造函数,用于创建一个新对象来当已存在对象的副本。以下是拷贝构造的主要特点。

(1)函数名与类名相同,没有返回类型。参数必须是同类型对象的引用,通常为const引用。如果不引用可能会出现无限递归的现象。

class MyClass {
public:// 正确的拷贝构造函数MyClass(const MyClass& other) {// 复制逻辑}// 错误的拷贝构造函数示例// MyClass(MyClass other) { } // 错误:值传递会导致无限递归// MyClass(MyClass* other) { } // 错误:这不是拷贝构造函数
};

如果不用引用导致递归,编译器会报错

(2)C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以自定义类型传值传参和传值返回都会调用拷贝构造完成。

MyClass obj1;
MyClass obj2 = obj1;  // 调用拷贝构造函数
MyClass obj3(obj1);   // 调用拷贝构造函数void func(MyClass param); // 值传递参数时调用拷贝构造函数
func(obj1);              // 调用拷贝构造函数MyClass func2() {MyClass local;return local;       // 可能调用拷贝构造函数(取决于编译器优化)
}

(3)若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。

默认拷贝构造函数的陷阱。浅拷贝构造

这对于简单类足够使用,但是遇到指针类型就会出现双重释放的问题

class ShallowCopyExample {
private:int* data;public:ShallowCopyExample(int value) {data = new int(value);}// 默认拷贝构造函数执行浅拷贝:// ShallowCopyExample(const ShallowCopyExample& other) : data(other.data) {}~ShallowCopyExample() {delete data;}
};// 使用示例
int main()
{ShallowCopyExample obj1(42);ShallowCopyExample obj2 = obj1; // 浅拷贝:两个对象的data指向同一内存return 0;
}
// 问题:obj2析构时会释放内存,然后obj1析构时再次释放同一内存 → 双重释放错误!

因为浅拷贝构造的指针指向了同一块内存,我们用编译器调试或者转到反汇编就可以清楚的看到

那么我们遇到带有指针的成员变量,就需要用自定义拷贝构造来实现深拷贝。

#include <iostream>
#include <cstdlib>  // 包含 malloc 和 free
#include <cstring>  // 包含 strcpyclass StringWithMalloc
{
private:char* data;size_t length;public:// 构造函数 - 使用 malloc 分配内存StringWithMalloc(const char* str = ""){length = strlen(str);data = (char*)malloc(length + 1);  // 使用 malloc 分配内存if (data != nullptr) {strcpy(data, str);}else {length = 0;}std::cout << "构造函数: " << (data ? data : "NULL")<< " (地址: " << (void*)data << ")" << std::endl;}// 自定义拷贝构造函数 - 使用 malloc 实现深拷贝StringWithMalloc(const StringWithMalloc& other){length = other.length;data = (char*)malloc(length + 1);  // 使用 malloc 分配新内存if (data != nullptr) {strcpy(data, other.data);}else {length = 0;}std::cout << "拷贝构造函数: " << (data ? data : "NULL")<< " (新地址: " << (void*)data << ")" << std::endl;}// 析构函数 - 使用 free 释放内存~StringWithMalloc(){std::cout << "析构函数: " << (data ? data : "NULL")<< " (地址: " << (void*)data << ")" << std::endl;free(data);  // 使用 free 释放内存data = nullptr;  // 防止悬空指针}
};

(4)传值返回会产生⼀个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用),没有产生拷贝。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于⼀个野引用,类似⼀个野指针⼀样。传引用返回可以减少拷贝,但是⼀定要确保返回对象,在当前函数结束后还在,才能用引用返。

1.4赋值运算符重载

1.4.1运算符重载
(1)当运算符被用于类类型的对象时,C++允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
Class Data//定义一个类
{//......
}
int main()
{Data d1;Data d2;d1-d2//这里编译器没有对应的运算符重载会报错return 0;
}

(2)运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。

基本语法如下

返回类型 operator操作符(参数列表) {// 函数体
}

(3)重载运算符函数的参数个数和该运算符作用的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。

(4)如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个。

(5)运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。

(6)不能通过连接语法中没有的符号来创建新的操作符:比如operator@。

(7).*   ::    sizeof    ?:    . 注意以上5个运算符不能重载。

(8)重载操作符至少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义

int operator+(int x, int y)//里面没有自定义的类,都是内置类型不可重载
1.4.2赋值运算符重载的特点
(1)赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引用,否则会传值传参会有拷贝。
(2)有返回值,且建议写成当前类类型引用,引用返回可以提⾼效率,有返回值目的是为了支持连续赋值场景。
(3)通常需要避免浅拷贝问题,特别是当类中包含指针成员时
(4)应返回对象的引用(*this),以支持连续赋值(如 a = b = c)
(5)一般需要检查自赋值情况(避免自身赋值导致的问题)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PLC通信 Tpc客户端Socket

1.PLC通信 namespace _2.PLC通信 {public partial class Form1 : Form{public Form1(){InitializeComponent();}//连接//1.型号: 跟PLC沟通 使用哪个型号的PLC//2.IP 同上//3.机台号:同上//4.插槽号:同上Plc plc new Plc(CpuType.S71200, "192.168.25.80", 0, 1);pr…

Android 开发实战:从零到一集成 espeak-ng 实现中文离线 TTS(无需账号开箱即用)

简介 在移动应用开发中,语音合成(TTS)技术是提升用户体验的重要工具。然而,许多开发者在集成 TTS 时面临依赖网络、需注册账号、功能受限等问题。本文将带你从零开始,通过开源项目 espeak-ng,实现无需账号、开箱即用的中文离线语音播报。 文章将覆盖以下核心内容: esp…

直播APP集成美颜SDK详解:智能美妆功能的开发实战

在这个“颜值即正义”的时代&#xff0c;用户对直播APP的第一印象&#xff0c;往往来自主播的画面质量。高清的视频固然重要&#xff0c;但如果缺少自然美颜和智能美妆功能&#xff0c;观众体验就会大打折扣。于是&#xff0c;美颜SDK成了直播行业的“标配”。今天&#xff0c;…

C++内存管理:new与delete的深层解析

1. 引言在C的世界里&#xff0c;动态内存管理是一个核心话题。对于从C语言过渡到C的开发者来说&#xff0c;一个常见的困惑是&#xff1a;既然C语言的malloc和free依然可以在C中使用&#xff0c;为什么C还要引入new和delete这两个操作符&#xff1f;本文将深入探讨这两对内存管…

【AI开发】【前后端全栈】[特殊字符] AI 时代的快速开发思维

&#x1f680; AI 时代的快速开发思维 —— 以 Django Vue3 为例的前后端分离快捷开发流程 一、AI 时代的开发新思路 在 AI 的加持下&#xff0c;软件开发不再是“纯体力活”&#xff0c;而是 思维工具自动化 的协作。 过去&#xff1a;需求 → 设计 → 开发 → 测试 → 上…

Day24_【深度学习(3)—PyTorch使用—张量的创建和类型转换】

一、创建张量1.张量基本创建方式torch.tensor 根据指定数据创建张量 &#xff08;最重要&#xff09;torch.Tensor 根据形状创建张量, 其也可用来创建指定数据的张量torch.IntTensor、torch.FloatTensor、torch.DoubleTensor 创建指定类型的张量1.1 torch.tensor# 方式一&…

3-12〔OSCP ◈ 研记〕❘ WEB应用攻击▸利用XSS提权

郑重声明&#xff1a; 本文所有安全知识与技术&#xff0c;仅用于探讨、研究及学习&#xff0c;严禁用于违反国家法律法规的非法活动。对于因不当使用相关内容造成的任何损失或法律责任&#xff0c;本人不承担任何责任。 如需转载&#xff0c;请注明出处且不得用于商业盈利。 …

AI 大模型赋能智慧矿山:从政策到落地的全栈解决方案

矿山行业作为能源与工业原料的核心供给端&#xff0c;长期面临 “安全生产压力大、人工效率低、技术落地难” 等痛点。随着 AI 大模型与工业互联网技术的深度融合&#xff0c;智慧矿山已从 “政策引导” 迈入 “规模化落地” 阶段。本文基于 AI 大模型智慧矿山行业解决方案&…

Node.js 项目依赖包管理

h5打开以查看 一、核心理念&#xff1a;从“能用就行”到“精细化管理” 一个规范的依赖管理体系的目标是&#xff1a; 可复现&#xff1a;在任何机器、任何时间都能安装完全一致的依赖&#xff0c;保证构建结果一致。 清晰可控&#xff1a;明确知道每个依赖为何存在&#x…

洛谷P1835素数密度 详解

题目如下&#xff1a;这里面有部分代码比较有意思&#xff1a;1&#xff0c;为何开始先遍历&#xff0c;最终值小于50000&#xff1f;因为题目要求的右边与左边差小于 10^6 &#xff0c;所以最多有10^3个素数&#xff0c;所以保存里面的素数数量大于1000&#xff0c;而50000的化…

突破限制:FileCodeBox远程文件分享新体验

文章目录【视频教程】1.Docker部署2.简单使用演示3. 安装cpolar内网穿透4. 配置公网地址5. 配置固定公网地址在隐私日益重要的今天&#xff0c;FileCodeBox与cpolar的协同为文件传输提供了安全高效的解决方案。通过消除公网IP限制和隐私顾虑&#xff0c;让每个人都能掌控自己的…

以太网链路聚合实验

一、实验目的掌握使用手动模式配置链路聚合的方法掌握使用静态 LACP 模式配置链路聚合的方法掌握控制静态 LACP 模式下活动链路的方法掌握静态 LACP 的部分特性的配置二、实验环境安装有eNSP模拟器的PC一台&#xff0c;要求PC能联网。三、实验拓扑LSW1与LSW2均为S3700交换机。L…

autMan安装教程

一、安装命令 如果你系统没安装docker&#xff0c;请看往期教程 以下为通用命令 docker run -d --name autman --restart always -p 8080:8080 -p 8081:8081 -v /root/autman:/autMan --log-opt max-size10m --log-opt max-file3 hdbjlizhe/autman:latest解释一下以上命令&…

【无人机】自检arming参数调整选项

检查项目 (英文名)中文含义检查内容四旋翼建议 (新手 → 老手)理由说明All所有检查启用下面所有的检查项目。✅ 强烈建议勾选这是最安全的设置&#xff0c;确保所有关键系统正常。Barometer气压计检查气压计是否健康、数据是否稳定。✅ 必须勾选用于定高模式&#xff0c;数据异…

数字图像处理(1)OpenCV C++ Opencv Python显示图像和视频

Open CV C显示图像#include <iostream> #include <opencv2/opencv.hpp> using namespace cv;//包含cv命名空间 int main() {//imread(path)&#xff1a;从给定路径读取一张图片&#xff0c;储存为Mat变量对象Mat img imread("images/love.jpg");//named…

【芯片设计-信号完整性 SI 学习 1.2.2 -- 时序裕量(Margin)】

文章目录1. 什么是时序裕量&#xff08;Margin&#xff09;1. 背景&#xff1a;为什么需要数字接口时序分析2. 时钟周期方程3. Setup 裕量 (tMARGIN_SETUP)4. Hold 裕量 (tMARGIN_HOLD)5. 设计注意事项6. 实际应用场景2. 时序裕量的来源3. 测试方法(1) 眼图测试 (Eye Diagram)(…

AOP 切面日志详细

在业务方法上打注解package com.lib.service;Service public class BookService {LogExecution(description "查询图书")public Book query(int id) {return repo.findById(id);}LogExecution(description "借阅图书")public void borrow(int id) {// 模…

使用paddlepaddle-Gpu库时的一个小bug!

起初安装的是 paddlepaddle 2.6.1版本。 用的是Taskflow的快速分词以及ner快速识别&#xff1a;​​​​​​​seg_accurate Taskflow("word_segmentation", mode"fast") ner Taskflow("ner", mode"fast")但是使用不了Gpu。想使用Gp…

量子能量泵:一种基于并联电池与电容阵的动态直接升压架构

量子能量泵&#xff1a;一种基于并联电池与电容阵的动态直接升压架构 摘要 本文提出了一种革命性的高效电源解决方案&#xff0c;通过创新性地采用并联电池组与串联高压电容阵相结合的架构&#xff0c;彻底解决了低电压、大功率应用中的升压效率瓶颈与电池一致性难题。该方案摒…

【Linux网络】网络基础概念——带你打开网络的大门

1. 计算机网络背景 文章目录1. 计算机网络背景网络发展2. 初识协议2.1 协议分层软件分层的好处2.2 OSI七层模型2.3 TCP/IP五层(或四层)模型网络发展 独立模式 独立模式是计算机网络发展的最初阶段&#xff0c;主要特点如下&#xff1a; 单机工作环境&#xff1a; 每台计算机完…