【C++技能树】类的六个成员函数Ⅰ --构造、析构、拷贝构造函数

news2024/11/22 21:09:23

img

Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。

本篇导航

  • 0.this指针
  • 1.Class默认成员函数
  • 2.构造函数
    • 调用规则:
  • 3.析构函数
  • 4.拷贝构造函数

img

0.this指针

在开始本章内容之前,先浅浅的了解一下this指针的概念.这对理解后面的内容有着很大的帮助.

this指针顾名思义就是这个指针,我们先来看看下面这段很简单的代码

class Date{
public:
	void print()
	{
		cout<<date;
	}
private:
	int date;
};
int main()
{
	Date d1;
	d1.print();
}

首先创建了一个Date的日期类(这也是我们今天的主角),写了一个函数:在屏幕上输出date的具体值,那么this指针体现在哪呢?

每一个函数调用的时候都会隐式的传入一个this指针对象也就是作为成员函数的形式参数,他指向函数运行时调用的对象

所以这段代码也可以写成这样:

class Date{
public:
	void print()
	{
		cout<<this->date;
	}
private:
	int date;
};
int main()
{
	Date d1;
	d1.print();
}

this->date = 访问d1里的date,所以当多个对象互相调用的时候,编译器就能分清究竟谁是谁的参数了this的类型是class *const,也就是无法改变this指向的内容(这点也很好理解,如果能轻易改变,那么这个this的定义还有什么用呢?)

那么问题来了 this是存在哪里呢?

  1. 对象里面
  2. 静态区
  3. 常量区

很多uu们会选择第一个选项,对象里面.但我们回顾我们的定义,他是一个 成员函数的形式参数,那么函数的形式参数自然存储在栈当中(忘记的uu们可以看看这篇文章,会让你对函数的创建与销毁有一个更深层次的理解),所以答案是3

那么再来看看下面的代码运行会有什么效果呢?

class Date{
public:
	void print()
	{
		cout<<"Hello world";
	}
private:
	int date;
};
int main()
{
	Date *d1=nullptr;
	d1->print();
}

虽然d1被初始化为空,但调用print的时候并没有访问到this中成员.所以并不会出现访问错误特点

相对于上面的代码,下面的代码有什么问题?

#include<iostream>
using namespace std;
//this
class Date{
public:
	void print()
	{
		cout<<this->date;
	}
private:
	int date;
};
int main()
{
	Date *d1=nullptr;
	d1->print();
}

显而易见输出内容时访问了this里的内容,此时this代表的是d1,d1为空自然this也为空,所以访问其成员变量就会出现越界访问的错误

1.Class默认成员函数

了解这个之前来看看为什么要有成员对象.回顾之前所学的数据结构:链表、栈、队列…这些数据结构使用之前是不是都需要申请内存空间.虽然这是必要的,但是每次申请不是很麻烦嘛?且函数使用完时还需要进行释放内存,但比如我们需要获取这个栈顶的元素,我们就没有办法直接返回(因为返回后程序结束,内存没有释放),那返回这类值之前都需要一个变量来存储嘛?这也太不高级了

所以C++提出了默认成员函数这一个概念:也就是类里会自动生成一个帮你完成上面的创建,销毁的工作的函数,当然他的功能还远远不止这些

C++中一共有六个默认成员函数

本篇博客我们重点介绍前三个默认成员函数:构造函数、析构函数、拷贝构造函数
在这里插入图片描述

2.构造函数

前面提到,如果每次使用一个类型的时候都需要使用一下Init函数未免太过繁琐.所以C++给出了一个构造函数的解决方法先来看看下面这个代码

class Date{
public:
	Date()
	{
		cout<<"Date";
	}
private:
	int date;
	int monthl;
	int year;
};
int main()
{
	Date d1;

}

这段代码仅仅是声明了一个Date对象d1,并没有调用其任何成员函数.但是此时代码运行结果会输出

Date

也就是调用了Date这个与类同名的函数,这个与类同名的函数我们就叫做构造函数.并不是完成内存的创建,仅为对对像属性的初始化.其有以下几个特点

  1. 函数名与类名相同,其在该对象生命周期之间只会被调用一次。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。

前两个就是字面意思没什么不好理解的,我们重点来说下后两个:

调用规则:

写构造函数的方式一般有以下几种

  1. 无参数的构造函数(默认构造函数)

    class Date{
    public:
    	Date()
    	{
    		_date=10;
    		_monthl=12;
    		_year=2023;
    	}
    	void print()
    	{
    		cout<<_date<<endl<<_monthl<<endl<<_year<<endl;
    	}
    private:
    	int _date;
    	int _monthl;
    	int _year;
    };
    int main()
    {
    	Date d1;
    	d1.print();
    }
    

    在这个函数中,构造函数并没有传入参数,所以其叫做无参数的构造函数.,只需要正常声明就会调用这个函数了

  2. 带参数的构造函数

    class Date{
    public:
    	Date(int date,int month,int year)
    	{
    		_date=date;
    		_monthl=month;
    		_year=year;
    	}
    	void print()
    	{
    		cout<<_date<<endl<<_monthl<<endl<<_year<<endl;
    	}
    private:
    	int _date;
    	int _monthl;
    	int _year;
    };
    int main()
    {
    	Date d1(10,20,30);
    	d1.print();
    }
    

    在这个函数中,构造函数传入了参数,所以叫其 带参数的构造函数,像调用函数一样声明即可调用这个函数

  3. 全缺省的构造函数(默认构造函数)

    class Date{
    public:
    	Date(int date=10,int month=10,int year=10)
    	{
    		_date=date;
    		_monthl=month;
    		_year=year;
    	}
    	void print()
    	{
    		cout<<_date<<endl<<_monthl<<endl<<_year<<endl;
    	}
    private:
    	int _date;
    	int _monthl;
    	int _year;
    };
    int main()
    {
    	Date d1(10,20,30);
    	d1.print();
    }
    

    这和带参的析构函数差不多,也是一样的调用 有参数的时候按照方式2来传参,没有参数的时候按照方式1来传参即可,

    但注意这种方法不可与无参数的构造函数同时声明,因为若没传入参数则不知道调用哪一个了

当然 半缺省的构造函数也是可以的,这里就不过多赘述了.

当用户没有自己写构造函数的时候,编译器会自动生成一个构造函数.与1 3统称为默认构造函数默认构造函数只能用有一个

但这个函数将内置类型随机赋值(C++11后打了补丁,允许用户在声明时给内置类型变量赋值),若有自定义类型,则调用自定义类型的构造函数.

这里解释下什么是自定义类型和内置类型,内置类型是语言提供的类型例如:str int double等,而自定义类型则为class struct union等

大多数情况下我们都需要手写构造函数,所以我们什么时候不需要写构造函数呢?

  1. 内置类型成员都有自己的缺省值,且初始化符合要求
  2. 全是自定义类型构造函数,且这些类型都定义了默认构造

3.析构函数

析构函数与构造函数的功能刚好相反,其也不涉及对对像本身的销毁,而是对对象销毁完后"残局的处理"

class Date{
public:
	
	Date()
	{
		a=(int*)malloc(sizeof(int)*4);
	}
	~Date()
	{
        cout<<"~Date";
		free(a);
		a=nullptr;
	}
private:
	int *a=nullptr;
};

当Date创建的对象所在的栈帧被销毁时,会自动调用这个析构函数来处理残局.

析构函数的特点如下:

  1. 其名字为~+类名
  2. 无参数无返回类型,不支持重载
  3. 对象生命周期结束的时候,会自动调用析构函数

当有动态申请空间的时候,我们就需要手写析构函数.那我们什么时候不需要写析构函数呢?

  1. 没有动态申请的资源
  2. 需要释放资源的成员都是自定义类型,

4.拷贝构造函数

当我们想用一个对象的值去初始化另一个对象的时候,我们该如何去做呢?

Date d1=d2;

C++规定了用一个对象去初始化另一个对象的时候.需要调用拷贝构造函数.拷贝构造函数时构造函数的重载,所以其也满足构造函数的规则

class Date{
public:
	
	Date(int date,int month,int year)
	{
		_date=date;
		_monthl=month;
		_year=year;
	}
	Date(const Date &d)
	{
		cout<<"copy Date";
		_date=d._date;
		_monthl=d._monthl;
		_year=d._year;
	}
	
	void print()
	{
		cout<<_date<<endl<<_monthl<<endl<<_year<<endl;
	}
private:
	int _date;
	int _monthl;
	int _year;
};
int main()
{
	Date d1(11,12,123);
	Date d2=d1;
	d2.print();
}

此时d2的初始化调用了拷贝构造函数.为什么拷贝构造函数需要的形参为值的引用呢

因为若不传值的引用,拷贝构造函数的形式参数仍为一个Date类型,传值的过程仍然是在初始化拷贝一个类对象,又会去调用其拷贝构造函数,之后就无限套娃.陷入了循环.

所以形参需要是引用

若没有自己写出拷贝函数,其调用规则为:

  1. 内置类型成员完成值拷贝/浅拷贝
  2. 自定义类型成员会调用他自己的拷贝构造函数

什么是深浅拷贝呢?这里浅浅的提一下

浅拷贝会对值进行复制,若为数组,则会创建一个指针指向原地址.也就是两个指针指向同一块空间

深拷贝则会创建两个一模一样的变量

所以浅拷贝在拷贝空间的时候会出现问题(两个变量共用一块空间),此时我们就需要自己手写拷贝构造实现深拷贝

img

本篇文章结束啦,感兴趣的uu们可以关注一下

诸君,山顶见!

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

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

相关文章

Channel-wise Knowledge Distillation for Dense Prediction(ICCV 2021)原理与代码解析

paper&#xff1a;Channel-wise Knowledge Distillation for Dense Prediction official implementation&#xff1a;https://github.com/irfanICMLL/TorchDistiller/tree/main/SemSeg-distill 摘要 之前大多数用于密集预测dense prediction任务的蒸馏方法在空间域spatial…

(求正数数组的最小不可组成和,养兔子)笔试强训

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 目录 文章目录 一、选择题1 二、[编程题]养兔子 三、[编程题]求正数数组的最小不可组成和 一、选择题1 reflection是如何工作的__牛客网 (nowcoder.com) 考虑下面这个简单的例子&…

大数据Doris(八):Broker部署和集群启停脚本

文章目录 Broker部署和集群启停脚本 一、Broker部署 1、准备Broker 安装包 2、启动 Broker

PyQt6剑指未来-日期和时间

前言 时间和日期是软件开发中非常重要的概念。在PyQt6中&#xff0c;时间和日期模块提供了处理日期、时间和日期时间的类和函数&#xff0c;以及管理时区和夏令时的特性。这些模块提供了可靠和易于使用的工具&#xff0c;使得在PyQt6中处理和呈现时间和日期的操作变得轻松起来…

Java中Lambda表达式(初学到精通)

目录 一、Lambda表达式是什么&#xff1f;什么场景下使用Lambda&#xff1f; 1.Lambda 表达式是什么 2.函数式接口是什么 第二章、怎么用Lambda 1.必须有一个函数式接口 2.省略规则 3.Lambda经常用来和匿名内部类比较 第三章、具体使用举例&#xff08;&#xff09; 1.案…

跳跃游戏类题目 总结篇

一.跳跃游戏类题目简单介绍 跳跃游戏是一种典型的算法题目&#xff0c;经常是给定一数组arr&#xff0c;从数组的某一位置i出发&#xff0c;根据一定的跳跃规则&#xff0c;比如从i位置能跳arr[i]步&#xff0c;或者小于arr[i]步&#xff0c;或者固定步数&#xff0c;直到到达某…

C++ 链表概述

背景 当需要存储大量数据并需要对其进行操作时&#xff0c;常常需要使用到链表这种数据结构。它可以用来存储一系列的元素并支持插入、删除、遍历等操作。 概念 一般来说&#xff0c;链表是由若干个节点组成的&#xff0c;每个节点包含了两个部分的内容&#xff1a;存储的数…

【嵌入式环境下linux内核及驱动学习笔记-(6-内核 I/O)-阻塞与非阻塞】

目录 1、阻塞与非阻塞1.1 以对recvfrom函数的调用及执行过程来说明阻塞的操作。1.2 以对recvfrom函数的不断轮询调用为例&#xff0c;说明非阻塞时进程的行为。1.3 简单介绍内核链表及等待队列1.4 等待队列1.4.1 定义等待队列头部&#xff08;wait_queue_head_t&#xff09;1.4…

vue动态添加多组数据添加正则限制

如图新增多条数据&#xff0c;如果删除其中一条正则校验失败的数据&#xff0c;提示不会随之删除&#xff0c;若想提示删除并不清空数据 delete (item, index) {this.applicationForm.reserveInfo.forEach((v, i) > {if (i index) {this.$refs.formValidate.fields.forEac…

UFT——操作模块

示例一 创建一个可重复利用的登录测试更改Action的名称。使用本地数据表。创建一个主调用测试。建立测试迭代。处理缺失的Action。 分析&#xff1a;就是创建一个只有登录的测试起名为login&#xff0c;然后在创建一个主测试起名字比如main&#xff0c;在main中&#xff0c;调用…

微信小程序定义模板

微信小程序提供模板&#xff08;template&#xff09;功能&#xff0c;把一些可以共用的&#xff0c;复用的代码在模板中定义为代码片段&#xff0c;然后在不同的地方调用&#xff0c;可以实现一次编写&#xff0c;多次引用的效果。 首先我们看一下官网是如何操作的 一般的情…

笔记:对多维torch进行任意维度的多“行”操作

如何取出多维torch指定维度的指定“行” 从二维torch开始新建torch取出某一行取出某一列一次性取出多行取出连续的多行取出不连续的多行 一次取出多列取出连续的多列取出不连续的多列 考虑三维torch取出三维torch的任意两行&#xff08;means 在dim0上操作&#xff09;取出连续…

( 字符串) 9. 回文数 ——【Leetcode每日一题】

❓9. 回文数 难度&#xff1a;简单 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整数。 例如…

Git的安装与使用+Git在IDEA中的使用

文章目录 一、Git概述1、版本控制器的方式2、Git的工作流程图 二、Git的安装与常用命令1、Git环境安装2、Git环境基本配置3、获取本地仓库4、基础操作指令 三、分支1、常用指令2、解决合并冲突 四、Git远程仓库1、创建远程仓库2、远程操作仓库3、冲突处理 四、IDEA中使用Git1、…

数据结构——二叉树

二叉树 1 二叉树的种类 1.1 满二叉树 节点数量为 2^k - 1 (k是树的深度&#xff0c;底层的叶子节点都是满的&#xff09; 1.2 完全二叉树 完全二叉树是指除了下面一层外&#xff0c;其余层的节点都是满的&#xff1b; 且最下面一层的叶子节点是从左到右连续的。 下面这个…

pci总线协议学习笔记——PCI总线基本概念

1、pci总线概述 (1)PCI&#xff0c;外设组件互连标准(Peripheral Component Interconnection)&#xff0c;是一种由英特尔&#xff08;Intel&#xff09;公司1991年推出的用于定义局部总线的标准; (2)最早提出的PCI总线工作在33MHz频率之下&#xff0c;传输带宽达到133MB/s(33M…

【LeetCode】236. 二叉树的最近公共祖先

1.问题 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是…

1992-2022年31省GDP、第一产业增加值、第二产业增加值 第三产业增加值

1992-2022年31省GDP、第一产业增加值、第二产业增加值 第三产业增加值 1、时间&#xff1a;1992-2022年 2、范围&#xff1a;包括31省 3、指标&#xff1a;省GDP、省第一产业增加值、省第二产业增加值、省第三产业增加值 4、缺失情况说明&#xff1a;无缺失 5、来源&#…

【python知识】__init__.py的来龙去脉

一、说明 我们常见__init__.py文件&#xff0c;但说不清楚它的用途&#xff0c;在本文&#xff0c;我将首先把它的来龙去脉说清楚&#xff0c;然后告诉大家&#xff0c;如何编制python工程&#xff0c;培养全局的编程格局。 二、包-模块-函数结构 在Python工程里&#xff0c;当…