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

本专栏目的

  • 更新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.xdnf.cn/news/149257.html

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

相关文章

计算机的错误计算(一百零二)

摘要 探讨 的计算精度问题。 从计算机的错误计算&#xff08;九十九&#xff09;可知&#xff0c; 在IEEE 754-2019的列表中。因此&#xff0c;有必要分析其计算准确度。 例1. 已知 计算 若利用 Python的SciPy库中函数计算&#xff0c;则有&#xff1a; 若用Java的pow函…

通过 LabVIEW 正则表达式读取数值(整数或小数)

在LabVIEW开发中&#xff0c;字符串处理是一个非常常见的需求&#xff0c;尤其是在处理包含复杂格式的数字时。本文通过一个具体的例子来说明如何利用 Match Regular Expression Function 和 Match Pattern Function 读取并解析字符串中的数字&#xff0c;并重点探讨这两个函数…

毕业设计选题:基于ssm+vue+uniapp的英语学习激励系统小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

达梦-华为鲲鹏ARM架构下性能测试最佳实践

一、测试综述 1.1 测试目的 本次测试的目的是验证达梦数据库&#xff0c;在鲲鹏服务器下&#xff0c;不同服务器参数基于sysbench性能压力测试的表现。本次参数是根据为华为鲲鹏arm服务器调优十板斧内建议值调整 成长地图-鲲鹏开发套件开发文档-鲲鹏社区 1.2 通用指标 指标…

虚幻蓝图Ai随机点移动

主要函数: AI MoveTo 想要AI移动必须要有 导航网格体边界体积 (Nav Mesh Bounds Volume) , 放到地上放大 , 然后按P键 , 可以查看范围 然后创建一个character类 这样连上 AI就会随机运动了 为了AI移动更自然 , 取消使用控制器旋转Yaw 取消角色移动组件 的 使用控制器所需的…

关于Cursor使用的小白第一视角

最近看破局感觉洋哥总是提到cursor&#xff0c;感觉好火&#xff0c;所以打算学习一下怎么用Cursor&#xff0c;如果可以希望能做一个我自己的网站。 之前从来没用过Cursor。所以&#xff0c;这是一篇小白视角的Cursor使用教程。 如果你也是一个小白&#xff0c;并且对Cursor…

ArcGIS Desktop使用入门(三)图层右键工具——拓扑(上篇:地图拓扑)

系列文章目录 ArcGIS Desktop使用入门&#xff08;一&#xff09;软件初认识 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——标准工具 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——编辑器 ArcGIS Desktop使用入门&#xff08;二&#x…

Maven配置及使用

1. Maven简介和安装 1.1. Maven是一个依赖管理工具 问题&#xff1a; jar包的规模 随着使用框架越来越多&#xff0c;或框架的封装程度越来越高&#xff0c;项目中使用的jar包也越来越多。项目中&#xff0c;一个模块里用到上百个jar包是非常正常的jar包的来源 jar包所属技术…

LeetCode 面试经典150题 201.数字范围按位与

题目&#xff1a;给你两个整数 left 和 right &#xff0c;表示区间 [left, right] &#xff0c;返回此区间内所有数字 按位与 的结果&#xff08;包含 left 、right 端点&#xff09;。 提示&#xff1a;0 < left < right < 2^31 - 1 思路&#xff1a; 位与的特性…

《一本书讲透Elasticsearch》读书笔记(二)

Elasticsearch集群部署 Elastic Stack集群部署基础知识 Elasticsearch、Logstash、Beats、Kibana全部都支持跨平台部署 集群部署平台及操作系统的选型 可供选择的部署平台包括实体服务器、虚拟机&#xff08;VMWare、OpenStack等&#xff09;​、容器化平台&#xff08;Doc…

链式队列操作

文章目录 &#x1f34a;自我介绍&#x1f34a;概述&#x1f34a;链式队列代码linkstack.clinkstack.hmain.c 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff09;哦~ &#x1f34a;自我介绍 Hello,大家好&…

课程表-LeetCode100

现在你总共有 numCourses 门课需要选&#xff0c;记为 0 到 numCourses - 1。给你一个数组 prerequisites &#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示在选修课程 ai 前 必须 先选修 bi 。 例如&#xff0c;想要学习课程 0 &#xff0c;你需要先完成课程 1…

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-23

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-23 本期&#xff0c;我们对大语言模型在表情推荐, 软件安全和 自动化软件漏洞检测等方面如何应用&#xff0c;提供几篇最新的参考文章。 1 Semantics Preserving Emoji Recommendation with Large Language Mod…

机器学习中分类问题的各类评估指标总结

机器学习中分类问题的各类评估指标总结 在机器学习的世界里&#xff0c;分类问题占据了半壁江山。从垃圾邮件检测到疾病诊断&#xff0c;从用户行为分析到市场趋势预测&#xff0c;分类算法的应用无处不在。然而&#xff0c;如何评价一个分类模型的性能&#xff0c;却是一门大…

MovieLife 电影生活

MovieLife 电影生活 今天看到一个很有意思的项目&#xff1a;https://www.lampysecurity.com/post/the-infinite-audio-book “我有一个看似愚蠢的想法。通常&#xff0c;这类想法只是一闪而过&#xff0c;很少会付诸实践。但这次有所不同。假如你的生活是一部电影&#xff0c…

Stable Diffusion 使用详解(13)--- 3D纹理增强

目录 背景 Normal Map 描述 原理 使用心得 例子 描述 原图 参数设置 底模 ​编辑 正负相关性提示词 其他参数 controlnet 效果 还能做点啥 调整 效果 背景 实际上&#xff0c;在stable diffusion 中&#xff0c;你获取发现很多controlnet 其实功能有点类似&…

webview2加载本地页面

加载方式 通过导航到文件 URL 加载本地内容 使用方式&#xff1a; webView->Navigate( L"file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html"); 但是这种方式存在一些问题&#xff0c;比如&#xff1a; 存在跨域问题&#xff08;我加载…

Java语言程序设计基础篇_编程练习题***18.33 (游戏:骑士旅途的动画)

目录 ***18.33 (游戏:骑士旅途的动画) 习题思路 代码示例 动画演示 ***18.33 (游戏:骑士旅途的动画) 为骑士旅途的问题编写一个程序&#xff0c;该程序应该允许用户将骑士放到任何一个起始正方形&#xff0c;并单击Solve按钮&#xff0c;用动画展示骑士沿着路径的移动&…

9.5HSV体系进行颜色分割

基本概念 inRange() 函数是 OpenCV 中用于图像处理的一个非常有用的函数&#xff0c;即从图像中提取出介于指定范围内的像素值。这个函数在图像处理中特别有用&#xff0c;比如颜色检测、背景去除等应用。它主要用于图像的阈值处理&#xff0c;但与其他阈值方法&#xff08;如…

前端——浮动+定位样式

一、浮动float——浮动是会使盒子脱离文档流 添加了浮动的元素 1.原本的位置不占用 脱离文档流 2.设置了浮动 就不支持auto自适应居中 3.文字会感受到浮动 跟着进行文字环绕效果 而不是浮动元素覆盖文字 文字和浮动处于同一层的关系 4.可以使行内元素支持 高…