C/C++语言基础--C++构造函数、析构函数、深拷贝与浅拷贝等等相关知识讲解

news2024/11/14 21:45:12

本专栏目的

  • 更新C/C++的基础语法,包括C++的一些新特性

前言

  • 周末休息了,没有更新,请大家见谅哈;
  • 构造函数、析构函数可以说便随着C++每一个程序,故学构造函数、析构函数是必要的;
  • C语言后面也会继续更新知识点,如内联汇编;
  • 本人现在正在写一个C语言的图书管理系统,1000多行代码,包含之前所学的所有知识点,包括链表和顺序表等数据结构,请大家耐心等待!!预计国庆前写完更新。

文章目录

    • 构造函数
    • 析构函数
    • 构造/析构函数调用机制
        • 析构函数调用时间
    • 构造/析构函数用途展示
    • 构造函数分类
      • 无参构造函数
      • 有参构造函数
      • 拷贝构造函数(赋值构造)
      • 移动构造函数
    • 深拷贝和浅拷贝
    • 构造函数的初始化参数列表
      • 初始化参数列表
      • 类中类如何构造

构造函数

首先我们写一个学生类,定义一个公有函数,用来打印学生信息:

#include <iostream>

class Student
{
public:
    void print() 
    {
    	std::cout << "学号: " << m_uid << " 姓名: " << m_name << " 年龄: " << m_age << std::endl;
    }
private:
    std:string m_uid;
    std::string m_name;
    int m_age;
}

int main() {
    Student stu;
    stu.print();   
}

这个时候,你肯定有一个疑问,创建出的这个学习信息,没有赋值!!!,那怎么赋值呢?这个时候你可能会想到在类中再定义一个API函数进行赋值,如下:

void setMessage(std::string uid, std::string name, int age) {
    m_uid = uid;
    m_name = name;
    m_age = age;
}

这样确实能够解决问题,那如果每次都要这样,不觉得太麻烦了么?,每次创建类都要额外在调用一个函数!!!

因此,C++大叔也考虑到了这一点,及发明出了构造函数这个“简单”的东西,它允许我们再创建对象的时候自动调用该函数,如我们将上面的学生类进行修改:

#include <iostream>

class Student
{
public:
    // 方法一
    Student(std::string uid, std::string name, int age)
    {
        m_uid = uid;
        m_name = name;
        m_age = age;
    }
    
    // 方法二,推荐:参数列表方法
    Student(std::string uid, std::string name, int age)
        :m_uid(uid),
    	m_name(name),
    	m_age(age)
    {
        
    }
    
    void print() 
    {
    	std::cout << "学号: " << m_uid << " 姓名: " << m_name << " 年龄: " << m_age << std::endl;
    }
private:
    std:string m_uid;
    std::string m_name;
    int m_age;
}

int main() {
    Student stu("123456", "wy", 18);   // 创建时候赋值
    stu.print();   
}

这样写无论从逻辑上,还是再写代码简约上,都好很多。

构造函数特点:

  • 构造函数名和类名相同
  • 构造函数可以重载
  • 构造函数没有返回类型声明

调用:

  • 自动调用(隐式),一般默认情况下C++编译器会自动调用构造函数(无参构造)
  • 手动调用(显示),在一些情况下则需要手工调用构造函数(有参构造)

析构函数

当对象释放时,我们可能需释放/清理对象里面的某些资源,如果再类中对某一个变量,如:成员变量申请了一块内存,而在应对稍微复杂一点的项目,就很容易忘记释放内存,为了解决这个问题,C++提供了析构函数来处理对象的清理工作。析构函数和构造函数类似,不需要用户来调用它,而是在释放对象时自动执行

特点:

  • 析构函数名和类名相同,但是得在前面加一个波浪号**~**
  • 析构函数能有一个
  • 构造函数没有返回类型声明

构造/析构函数调用机制

当定义了多个对象时,构造与析构的顺序是怎么样的呢?

#include <iostream>

using namespace std;

class Test
{
public:
	Test(int id)
		:m_id(id)
	{
		cout << m_id << " " << __FUNCTION__ << endl;
	}
	~Test()
	{
		cout << m_id << " " << __FUNCTION__ << endl;
	}
private:
	int m_id;
};


void test()
{
	Test t1(1);

	Test t2(2);
}

int main()
{
	test();

	return 0;
}

结果:

在这里插入图片描述

结论:

  • 先创建的对象先构造,后创建的对象后构造
  • 先创建的对象后析构,后创建的对象先析构

这个原因和函数调用内存有关,函数调用是压栈和出栈的过程,如果就想弄清楚,请看计算机系统相关的书籍,如:csapp

析构函数调用时间
  • 在该对象生命周期结束后调用

构造/析构函数用途展示

构造函数:可以用来初始化对象,而且不需要显式调用,方便,快捷

析构函数:可以用来释放对象, 一次写好,没有后顾之忧(如:经常忘记delete、free)

class Man
{
public:
    Man()
    {
        age = 18;
        name = new char[20]{0};
        strcpy(name,"maye");
    }
    ~Man()
    {
        if(name!=nullptr)
        {
            delete[] name;
            name = nullptr;
        }
    }
    void print()
    {
        cout<<age<<" "<<name<<endl;
    }
private:
    int age;
    char* name;
}

这样就可以避免自己忘记释放内存的情况了。

构造函数分类

构造函数是可以重载的,根据参数类型和作用可以分为以下几类:

无参构造函数

  • 直接创建对象即可自动调用
  • Test te; 注意:不要在对象后面加(),无参构造函数不能显式调用

有参构造函数

  • 有三种调用方法

    //1,括号法
    Test t1(20,"cc");
    t1.print();
    //2,赋值符号
    Test t2 = {18,"wy"};
    t2.print();
    //3,匿名对象
    Test t3 = Test(90,"wy");
    t3.print();
    //注意:
    Test tt;	//error:类Test不存在默认构造函数,因为自己定义了构造函数
    
    //** 匿名对象如果没有值来接收,那么就会被立即释放 **
    Int(2, 3);           //会立即释放
    
    Int f = Int(2,3);   //就不会立即释放
    

如果没有写有参构造函数,那么C++编译器会自动帮我们生成一个无参构造函数,如果写了有参构造函数,那么就不会帮我们生成了,必须自己写一个无惨构造函数,才能直接定义对象。

拷贝构造函数(赋值构造)

  • 用一个对象去初始化另一个对象时(函数传参也会拷贝),需要拷贝构造(如果自己没有写,编译器会自动帮我们生成)

    Test t(1,"2");
    //1,赋值符号
    Test t1 =t;
    //2,参数方法
    Test t2(t);
    
    t2 = t1;	//这个调用的是赋值运算符重载函数
    
  • 注意:定义之后进行赋值不会调用拷贝构造函数,而是调用赋值函数,这是运算符重载,这个涉及到运算符重载的知识,这个我们稍后讲解,注意:拷贝构造与运算符重载很容易搞混

移动构造函数

  • 移动构造函数数用来实现移动语义,转移对象之间的资源(如果自己没有写,编译器会自动帮我们生成),调用std::move()
// 定义一个对象
Test t1("wy", 18);
Test t2(std::move(t1));  //移动构造,这个时候对象t1所有权都转移给了t2,t1没有了资源,这样提高了资源的利用率

移动std::move()这个东西,我感觉很神奇,没有结合实践,感觉就这么回事,但是一结合实际,就会发现他特别伟大,特别好用!!!!

深拷贝和浅拷贝

首先,明确一点深拷贝和浅拷贝是针对类里面有指针的对象的,因为基本数据类型在进行赋值操作时(也就是拷贝)是直接将值赋给了新的变量,也就是该变量是原变量的一个副本,这个时候你修改两者中的任何一个的值都不会影响另一个,而对于对象来说在进行浅拷贝时只是将对象的指针复制了一份,也就内存地址,即两个不同的对象里面的指针指向了同一个内存地址,那么在改变任一个对象的指针指向的内存的值时,都是该变这个内存地址的所存储的值,所以两个变量的值都会改变

简单来说,当数据成员中有指针时,必须要用深拷贝。

  • 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址。
    • 使用浅拷贝,释放内存的时候可能会出现重复释放同一块内存空间的错误
  • 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。
    • 使用深拷贝下,释放内存的时候不会因为出现重复释放同一个内存的错误。

注意

  • C++类中默认提供的拷贝构造函数,是浅拷贝
  • 要想实现深拷贝,必须自己手动实现拷贝构造函数
//自己实现深拷贝
TString(const TString& other)                //普通:右值引用
{
    if(&other != this) {  // 不是自己拷贝自己
        m_size = other.m_size;
    
    	m_str = new cahr[m_size + 1];
    	strcay(m_str,other.m_str);
    }
}

int mian()
{
    TString other = TString hello;     //hello 为TString的一个实例化对象
}

构造函数的初始化参数列表

初始化参数列表

当我们再构造函数进行赋值成员变量的时候,可以有以下两种方法:

class Student
{
public:
    // 方法一
    Student(std::string uid, std::string name, int age)
    {
        m_uid = uid;
        m_name = name;
        m_age = age;
    }
    
    // 方法二,推荐:参数列表方法,不同变量之间用 ‘,’ 隔开 
    Student(std::string uid, std::string name, int age)
        :m_uid(uid),
    	m_name(name),
    	m_age(age)
    {
        
    }

private:
    std:string m_uid;
    std::string m_name;
    int m_age;
}

两种方法都可,但是我比较喜欢第二种。

类中类如何构造

类的组合:组合(有时候叫聚合)是将一个对象放到另一个对象里)。它是一种 has-a 的关系。

简单来说,就是一个类的对象作为另一个类的成员,这就叫做类的组合。

那这个时候这么对每一个对象值赋值呢?

假设我们再一个类B中创建了一个类A作为成员变量,而且A类中成员变量中,它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数

class A
{
public:
    A(int a) 
    {
        int m_a = a;
    }
private:
    int m_a;
}

// 定义类B
class B
{
public:
    B(int b, int a)
    	:a1(a),
    	m_b1 = b
    {
            
    }
    
private:
    int m_b1;
    A a1;					// 创建A对象
}

本类和对象成都需要执行构造函数,那么谁先执行呢?有什么样的顺序呢?

  • 先指针被组合对象的构造函数,如果组合对象有多个,按照定义顺序,而不是按照初始化列表的顺序
  • 析构和构造顺序相反,这个再上面将构造和析构函数有讲解,如果大家忘了,可以回去看一下哦🤠🤠🤠

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

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

相关文章

Linux 环境(rhel6.4)oracle11.2.0.1升级到11.2.0.4

停止监听 [oraclerhel64 ~]$ lsnrctl stop 关闭数据库 [oraclerhel64 ~]$ sqlplus / as sysdba SYSNKYYDB>shutdown immediate; 上传软件包并解压 [rootrhel64 ~]# mkdir /u01/upgrade [rootrhel64 ~]# chown -R oracle:oinstall /u01/upgrade/ [oraclerhel64 upgrad…

MySQL(学习笔记)(02)(进阶篇)

P1 存储引擎 MySQL的体系结构 存储引擎简介 存储引擎的选择 P2 索引&#xff08;重要&#xff09; 索引概述 索引结构 二叉树 B树&#xff08;多路平衡查找&#xff09; B树 hash 总结 索引分类 思考题 索引语法 SOL性能分析 索引使用 索引设计原则 P3 SQL优化 P4 视图/存储过…

前端vue-3种生命周期,只能在各自的领域使用

上面的表格可以简化为下面的两句话&#xff1a; setup是语法糖&#xff0c;下面的两个import导入是vue3和vue2的区别&#xff0c;现在的vue3直接导入&#xff0c;比之前vue2简单 还可以是导入两个生命周期函数

基于Nginx搭建点播直播服务器

实现直播和点播离不开服务器⽀持&#xff0c;可以使用开源的NGINX服务器搭建直播和点播服务。 当然&#xff0c;NGINX本身是不⽀持视频的&#xff0c;需要为NGINX增加相应的RTMP模块进行支持。 1、下载nginx和rtmp模块 # nginx wget ht tp://nginx.org/download/nginx-1.18.…

一篇讲完HTML核心内容

一、HTML 1、 HTML概念 网页&#xff0c;是网站中的一个页面&#xff0c;通常是网页是构成网站的基本元素&#xff0c;是承载各种网站应用的平台。通俗的说&#xff0c;网站就是由网页组成的。通常我们看到的网页都是以htm或html后缀结尾的文件&#xff0c;俗称 HTML文件。 2、…

公安局党建平台建设方案和必要性-———未来之窗行业应用跨平台架构

一、建设必要性 1. 适应时代发展需求 - 利用信息技术提升党建工作的效率和覆盖面&#xff0c;符合数字化时代的发展趋势。 2. 提高学习教育效果 - 打破时间和空间限制&#xff0c;让党员能够随时随地获取学习资源&#xff0c;进行自主学习。 3. 加强党组织管理 …

黑马智数Day3

渲染基础Table列表 封装接口&#xff1a; export function getCardListAPI(params) {return request({url: /parking/card/list,params}) } 具体实现&#xff1a; import { getCardListAPI } from /apis/cardexport default {data() {return {// 请求参数params: {page: 1,pa…

【计算机网络 - 基础问题】每日 3 题(十九)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

基于Spark框架实现LightGBM模型

基于Spark框架实现LightGBM模型 原生的Spark MLlib并不支持LightGBM算法的实现&#xff0c;但SynapseML提供了一种解决方案&#xff0c;使得我们可以在Spark中调用LightGBM。LightGBM是一种基于梯度提升决策树的高效机器学习框架&#xff0c;它专门用于创建高质量的决策树算法…

计算机毕业设计选题推荐-基于python的养老院数据可视化分析

精彩专栏推荐订阅&#xff1a;在下方主页&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、养老院数据可…

Java后端面试题(微服务相关2)(day13)

目录 Gateway的三大属性Gateway的三大案例组件为什么要用服务网关不同服务之间如何进行通信在微服务中如何监控服务Openfeign如何使用Openfeign自定义拦截器Seata中2PC和3PC的区别项目的几种发布方式和特点MongoDB和mysql区别什么是分布式锁&#xff0c;Redisson有什么用&#…

人工智能面试题(Artificial Intelligence Algorithm Interview Questions)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…

JavaWeb--纯小白笔记06:使用Idea创建Web项目,Servlet生命周期,注解,中文乱码解决

使用Idea创建一个web项目----详细步骤配置&#xff0c;传送门&#xff1a;http://t.csdnimg.cn/RsOs7 src&#xff1a;放class文件 web&#xff1a;放html文件 out&#xff1a;运行过后产生的文件 一创建一个新的web项目(配置好了后)&#xff1a; 在src创建一个文件…

使用【Sa-Token】实现Http Basic 认证

使用Sa-Token开源架构快速实现Http Basic 认证&#xff0c;如上图 1、springboot环境下直接添加starter即可 <!-- Sa-Token 权限认证&#xff0c;在线文档&#xff1a;https://sa-token.cc --> <dependency><groupId>cn.dev33</groupId><artifactI…

基于gorm.io/sharding分表中间件使用案例

项目背景 项目中需要用到mysql的分表场景&#xff0c;调研了一些常用的分库分表中间件&#xff0c;比如&#xff0c;mycat&#xff0c;小米的Gaea&#xff0c;这两个中间件太重了&#xff0c;学习成本较大&#xff0c;另外mycat不是go写的。我们需要一个轻量级的go版本的分表中…

Docker与Kubernetes学习

基本概述 Docker 是一个流行的容器化平台&#xff0c;允许开发人员在容器中创建、部署和运行应用程序。 Docker 提供了一组工具和 API&#xff0c;使开发人员能够构建和管理容器化应用程序&#xff0c;包括 Docker Engine、Docker Hub 和 Docker Compose。 Kubernetes 是一个…

MySQL如何实现并发控制?(上)

前言 最开始学习数据库的时候都会被问到一个问题&#xff1a;“数据库系统相比与文件系统最大的优势是什么&#xff1f;”。具体的优势有很多&#xff0c;其中一个很重要的部分是&#xff1a;数据库系统能够进行更好的并发访问控制。 那么&#xff0c;数据库系统到底是怎么进…

yolov5-7转onnx并推理(包括缩放图推理与原始图片推理)

一、yolov5转onnx 先安装onnx, onnxruntime-gpu, ( pip install 就可以) 1. 静态模型&#xff1a; python export.py --weights yolov5s.pt --include onnx2.动态模型&#xff1a; python export.py --weights yolov5s.pt --include onnx --dynamic3.这里谈谈静态与动态的…

在虚幻引擎中实时显示帧率

引擎自带了显示帧率的功能 但是只能在编辑器中显示 , 在游戏发布后就没有了 , 所以我们要自己做一个 创建一个控件蓝图 创建画布和文本 , 修改文本 文本绑定函数 , 点击创建绑定 添加一个名为 FPS 的变量 格式化文本 用大括号把变量包起来 {FPS Int} FPS 然后转到事件图表…

【html】基础(一)

本专栏内容为&#xff1a;前端专栏 记录学习前端&#xff0c;分为若干个子专栏&#xff0c;html js css vue等 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;js专栏 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &am…