「地表最强」C++核心编程(四)类和对象—对象初始化和清理

news2024/12/24 2:07:12

环境:
编译器:CLion2021.3;操作系统:macOS Ventura 13.0.1

文章目录

  • 一、构造函数和析构函数
    • 1.1 构造函数
    • 1.2 析构函数
    • 1.3 示例
  • 二、构造函数的分类及调用
    • 1.1 构造函数的分类
    • 1.2 构造函数的调用
  • 三、拷贝构造函数调用时机
    • 3.1 调用时机
    • 3.2 返回值优化
  • 四、构造函数调用规则
  • 五、深拷贝与浅拷贝
    • 5.1 浅拷贝
    • 5.2 深拷贝
  • 六、初始化列表
  • 七、类对象作为类成员
  • 八、静态成员
    • 8.1 静态成员变量
    • 8.2 静态成员函数

地表最强C++系列传送门:
「地表最强」C++核心编程(一)内存分区模型
「地表最强」C++核心编程(二)引用
「地表最强」C++核心编程(三)函数提高
「地表最强」C++核心编程(四)类和对象—封装
「地表最强」C++核心编程(五)文件操作——暂未更新

一、构造函数和析构函数

构造函数和析构函数是必须实现的两个函数,即使自己没有写,编译器也会默认调用默认的,空的构造和析构函数。

1.1 构造函数

构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

语法: 类名(){}

构造函数的特点:

  • 没有返回值,不用写void
  • 函数名与类名相同
  • 构造函数可以有参数,可以发生重载
  • 创建对象时自动调用,且只调用一次

1.2 析构函数

析构函数主要作用在于对象销毁前系统自动调用,执行一些清理工作。

语法: ~类名(){}

析构函数的特点:

  • 没有返回值,不用写void
  • 函数名与类名相同,前面加个~
  • 析构函数不可以有参数,不可以发生重载
  • 销毁对象时自动调用,且只调用一次

1.3 示例

class Person {
public:
    Person() {
        cout << "Person的构造函数" << endl;//自己写了就自动调用自己写的。否则就调用默认的空的构造函数
    }

    ~Person() {
        cout << "Person的析构函数" << endl;
    }
};

//构造和析构函数都必须实现,自己不提供编译器会提供一个空的构造和析构函数
void test01(){
    Person p;
}


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

二、构造函数的分类及调用

1.1 构造函数的分类

构造函数按照不同的标准可以分为不同的类型:
按参数分类可分为无参构造函数(也叫做默认构造函数)有参构造函数
按类型分类可分为普通构造函数拷贝构造函数
⚠️由于创建对象需要调用构造函数,因此构造函数的权限应该是public,否则类外无法调用构造函数,也就无法实例化对象。
⚠️拷贝构造函数的参数需要是常量对象

class Person {

public://要加public作用域,否则默认是private,就无法调用构造析构函数,也就无法创建对象
    int age;

    Person() {
        cout << "Person的无参构造函数" << endl;
    }

    Person(int a) {
        age = a;
        cout << "Person的有参构造函数" << endl;
    }

    Person(const Person &p) {//拷贝构造函数,要用const和&的方式传参
        age = p.age;//将传入的人身上的所有属性,拷贝到自己身上
        cout << "Person的拷贝构造函数" << endl;
    }

    ~Person() {
        cout << "Person的析构函数" << endl;
    }
};

1.2 构造函数的调用

构造函数的调用有三种方法,分别是括号法、显示法和隐式转换法

void test() {
    //括号法
    Person p1;//默认构造函数调用
    Person p2(10);//有参构造函数调用
    Person p3(p2);//拷贝构造函数调用
    //⚠️括号法调用默认构造函数的时候不要加(),否则编译器会认为这是一个函数的声明
//    Person p1();//虽然不报错,但是逻辑错误,编译器会认为有一个返回值类型是Person,名为p1的函数
    cout<<"p2的年龄是:"<<p2.age<<endl;//10
    cout<<"p3的年龄是:"<<p3.age<<endl;//10
    
    
    //显示法
    Person p1;
    Person p2 = Person(10);//有参构造,实际上是将匿名对象赋值给p2
    Person p3 = Person(p2);//拷贝构造
//    Person(10);//这是一个创建匿名对象,但没有办法使用        特点:当前行执行结束后,系统会立即回收掉匿名对象
//    cout<<"匿名对象已经没了"<<endl;

    //⚠️不要用拷贝构造函数初始化匿名对象,编译器会认为这是重定义。   Person(p3); 会被转换为 Person p3;
//    Person(p3);//err,Redefinition of 'p3'


    //隐式转换法
    Person p4 = 10;//相当于Person p4 = Person(10);     有参构造
    Person p5 = p4;//相当于Person P5 = Person(p4);     拷贝构造
}

三、拷贝构造函数调用时机

3.1 调用时机

C++中拷贝构造函数调用时机通常有三种情况:

  • 1使用一个已经创建完毕的对象来初始化一个新对象
  • 2值传递的方式给函数参数传值
  • 3以值方式返回局部对象
//1使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
    Person p1(20);//括号法调用构造函数
    Person p2(p1);//用对象p1来初始化p2,实际上就是调用了拷贝构造函数
    cout << "p2的年龄:" << p1.mAge << endl;
}


//2值传递的方式给函数参数传值
void doWork1(Person p) {}
void test02() {
    Person p;//调用默认构造函数
    doWork1(p);//值传递,doWork函数会调用拷贝构造函数给形参临时开辟空间
}


//3以值方式返回局部对象
Person doWork2() {
    Person p;//调用默认构造函数
    return p;//返回的不是上边的p,因为已经被释放掉了。这里返回的是另一个创建的对象,这个对象是用拷贝构造函数创建的。
}
void test03() {
    Person p = doWork2();//CLion测试下不会调用析构函数,这是因为返回值优化技术。
    cout << &p << endl;
}

3.2 返回值优化

这里解释一下第3点以值的方式返回对象:doWork2函数中首先定义了一个Person类型变量p,我们假设这个对象的地址是add1,然后该函数返回了对象p。这一步的操作实际上是用拷贝构造函数的方法又创建了一个对象p’,我们假设p’的地址是add2,然后把p作为拷贝构造函数的参数传给p’。而实际返回的是p’而不是p,这两个对象的地址是不同的,是两个对象
返回值优化是一项编译优化技术,使得返回对象时不必调用拷贝构造函数,经过测试后返回的就是已经创建好的p的地址而不会产生p’。
关于返回值优化,更多的可以参考一下这里:Return value optimization-Wikipedia

四、构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数:

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
class Person {
public:
    int age;

    //无参(默认)构造函数
    Person() {
        cout << "无参构造函数!" << endl;
    }
    //有参构造函数
  Person(int a) {
        age = a;
        cout << "有参构造函数!" << endl;
    }
    //拷贝构造函数
    Person(const Person &p) {
        age = p.age;
        cout << "拷贝构造函数!" << endl;
    }

    //析构函数
    ~Person() {
        cout << "析构函数!" << endl;
    }
};

void test(){
    Person p;//若只写有参构造或者拷贝构造,不写无参,此处会报错。因为此行代码会调用无参,但是我们写了有参,编译器就不会提供无参
    Person p(28);//若没写无参和有参,只写了拷贝构造,此处会报错。因为写了拷贝构造,编译器就不会提供其他无参和有参
    Person p2(p);
    cout << "p2的年龄:" << p2.age << endl;
}

五、深拷贝与浅拷贝

5.1 浅拷贝

浅拷贝就是简单的赋值操作,默认的拷贝构造函数进行的就是浅拷贝。

class Person{
private:
	string m_name;
	int m_age;
public:
	Person(const Person& p){//这里其实就是浅拷贝
		m_name = p.m_name;
		m_age = p.m_age;
	}

5.2 深拷贝

从下边这个实例说起,注意这里的身高我定义成了指针,指向的内容是身高

class Person {
public:
    int m_age;
    int *m_height;//指针,指向的内容是身高

public:
    //有参构造函数
    Person(int age, int height) {
        m_age = age;
        m_height = new int(height);//用new创建在堆区
    }

    //拷贝构造函数
    Person(const Person &p) {
        m_age = p.m_age;
        m_height = p.m_height;//默认的浅拷贝
    }

    //析构函数
    ~Person() {//堆区开辟的数据在此时可以释放了
        if (m_height != NULL) {
            delete m_height;
            m_height = NULL;
        }
    }
};

void test01() {
    Person p1(18, 180);//调用有参构造
    Person p2(p1);//调用拷贝构造
}

在这里插入图片描述
如图,通过有参构造创建了p1,然后通过浅拷贝的拷贝构造创建了p2。而浅拷贝的只是在机械的赋值,因此p2的所有属性都和p1一样,此时他们的身高的指针都是指向同一堆空间的,那么此时他们中任何一个对这块空间的操作会直接影响到另一个人。在test()调用结束后,这两个对象也会调用自己的析构函数来销毁空间。p2调用了自己的析构函数,将0x003这块儿空间释放掉,然后p1才调用自己的析构函数(这里涉及到析构函数的调用时间,简单来说就是先构造的后析构,想想栈的特性就明白了),但此时此空间已经被释放掉了,还要释放那就是非法的。
想解决这个问题,只需要在构造p2的时候,让他的身高指针指向与p1不同的空间即可。因此只需重写拷贝构造函数即可:

    Person(const Person &p) {
        //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题。若不自己重写拷贝构造函数,拷贝构造出来的对象和被拷贝的对象指向同一块儿空间。
        m_age = p.m_age;
        m_height = new int(*p.m_height);//重新申请一块堆区,属于深拷贝
    }

在这里插入图片描述
这样就不会有重复释放的问题,这就是深拷贝。

六、初始化列表

初始化列表是一种利用构造函数初始化属性的另一种方式。

语法: 构造函数():属性1(值1),属性2(值2)… {}

class Person {
public:
    int m_A;
    int m_B;
    int m_C;

public:
	//传统方式初始化
    Person(int a, int b, int c) {
    	m_A = a;
    	m_B = b;
    	m_C = c;
    }
    //初始化列表方式初始化
    Person() : m_A(10), m_B(20), m_C(30) {}
    Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {}
};

七、类对象作为类成员

假设一个类的对象B是另一个类A的成员,那么在调用构造函数的时候,会先调用B的构造函数:

class Phone {
public:
    string m_PhoneName;

    Phone(string name) {
        m_PhoneName = name;
        cout << "Phone构造" << endl;
    }
    ~Phone() {
        cout << "Phone析构" << endl;
    }
};

class Person {
public:
    string m_Name;
    Phone m_Phone;
    //初始化列表可以告诉编译器调用哪一个构造函数             Phone m_Phone = pName;其实是隐式转换法
    Person(string name, string pName) : m_Name(name), m_Phone(pName) {
        cout << "Person构造" << endl;
    }

    ~Person() {
        cout << "Person析构" << endl;
    }

    void playGame() {
        cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 手机! " << endl;
    }
};

void test01() {
    //当类中成员是其他类对象时,我们称该成员为对象成员
    //构造的顺序是 :先调用对象成员的构造,再调用本类构造,析构顺序与构造相反
    Person p("张三", "苹果1024");//先构造phone
    p.playGame();
}

八、静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。包括静态成员变量静态成员函数

8.1 静态成员变量

特点:
1.所有对象共享同一份数据,因此不属于某个对象
2.在编译阶段分配内存,全局区
3.类内声明,类外初始化

class Person {
public:
    static int m_A; //静态成员变量
private:
    static int m_B; //静态成员变量也是有访问权限的
};

//类外初始化,否则无法正常访问
int Person::m_A = 10;
int Person::m_B = 10;//private也需要类外初始化,这是可以的

void test01() {
    //静态成员变量两种访问方式,这是由于静态成员变量被所有对象共享
    //1、通过对象        非静态成员变量只能这么访问
    Person p1;
    p1.m_A = 100;
    cout << "p1.m_A = " << p1.m_A << endl;//100

    Person p2;
    p2.m_A = 200;
    cout << "p1.m_A = " << p1.m_A << endl; //200 共享同一份数据,不属于某个对象
    cout << "p2.m_A = " << p2.m_A << endl; //200

    //2、通过类名        非静态成员变量不能这么访问
    cout << "m_A = " << Person::m_A << endl;
    //cout << "m_B = " << Person::m_B << endl; //err,私有权限访问不到
}

8.2 静态成员函数

特点:
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量

class Person
{
public:
    static int m_A; //静态成员变量
    int m_B; //只属于对象本身,不能被对象共享

    //静态成员函数
    static void func()
    {
        cout << "func调用" << endl;
        m_A = 100;
//        m_B = 100; //err,不可以访问非静态成员变量,无法区分到底是哪个对象的属性
    }

private:
    //静态成员函数也是有访问权限的
    static void func2()
    {
        cout << "func2调用" << endl;
    }
};

int Person::m_A = 10;


void test01()
{
    //静态成员变量两种访问方式
    //1、通过对象
    Person p1;
    p1.func();

    //2、通过类名
    Person::func();
//    Person::func2(); //err,私有权限访问不到
}

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

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

相关文章

黑*头条_第1章_环境搭建、SpringCloud微服务(注册发现、服务调用、网关)(新版)

黑*头条_第1章_环境搭建、SpringCloud微服务(注册发现、服务调用、网关)&#xff08;新版&#xff09; 文章目录黑*头条_第1章_环境搭建、SpringCloud微服务(注册发现、服务调用、网关)&#xff08;新版&#xff09;1)课程对比2)项目概述2.1)能让你收获什么2.2)项目课程大纲2.3…

计算机毕业设计——基于HTML仿淘宝电商项目的设计与实现管理系统论文源码(21页)

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

毕业设计 stm32便携用电功率统计系统 -物联网 嵌入式 单片机

文章目录0 前言1 简介2 主要器件3 实现效果4 设计原理4.1 降压电路4.2 接口部分4.3 主控4.4 OLED模块5 部分核心代码5 最后0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的…

小黑被劝退了,生活学习依然继续的leetcode之旅:572. 另一棵树的子树

小黑代码 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution:def isSubtree(self, root: Optional[TreeNode],…

PyTorch~cpp_extension

还是pytorch哈~~ 结合 Python 与 C 各自的优点&#xff0c;在 PyTorch 中加入 C / CUDA的扩展&#xff0c;详细解释C/CUDA 算子实现和调用全流程 代码来源&#xff1a;MMCV, PyTorch。 https://github.com/open-mmlab/mmcv https://github.com/pytorch/pytorch 注&#xff1a…

python 编程 函数的返回值

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.函数 1.函数的返回值介绍 2.函数的返回值定义 例子&#xff1a; 3.函…

【图像去噪】均值+中值滤波图像去噪(含PNSR)【含GUI Matlab源码 372期】

⛄一、图像去噪及滤波简介 1 图像去噪 1.1 图像噪声定义 噪声是干扰图像视觉效果的重要因素&#xff0c;图像去噪是指减少图像中噪声的过程。噪声分类有三种&#xff1a;加性噪声&#xff0c;乘性噪声和量化噪声。我们用f(x,y&#xff09;表示图像&#xff0c;g(x,y&#xff0…

面试题:进程 线程 协程

进程&#xff08;Process&#xff09;是计算机中的程序关于某数据集合上的一次运行活动&#xff0c;是系统进行资源分配和调度的基本单位 通俗的讲&#xff1a;进程可以理解为我们在电脑上正在运行的一个个应用&#xff0c;例如&#xff1a;QQ&#xff0c;微信&#xff0c;LOL…

数据结构---寻找一个整数所有数字全排列的下一个数

寻找一个整数所有数字全排列的下一个数储备知识第一步第二步第三步JAVA实现给出一个正整数&#xff0c;找出这个正整数所有数字全排列的下一个数。说通俗点就是在一个整数所包含数字的全部组合中&#xff0c;找到一个大于且仅大于原数的新整数。 例子&#xff1a; 如果输入123…

【翻译】GPT-3是如何工作的

前排提示 这是我补充的内容&#xff0c;仅代表个人观点&#xff0c;和作者本人无关。 主要是意译我的补充&#xff0c;想看原文表达的拖到最底下有链接。 原文翻译 在科技界我们可以看到很多关于GPT-3的新闻。大型语言模型&#xff08;比如GPT-3&#xff09;已经展示出让我们惊…

[附源码]Python计算机毕业设计Django现代诗歌交流平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

一篇ThreadLocal走天下

尺有所短&#xff0c;寸有所长&#xff1b;不忘初心&#xff0c;方得始终。 请关注公众号&#xff1a;星河之码 在面试的时候经常会有人文ThreadLocal是啥&#xff0c;首先明确的一点是&#xff1a;虽然ThreadLocal提供了一种解决多线程环境下成员变量的问题&#xff0c;但是Th…

SSM框架学习记录-SpringMVC_day01

1.SpringMVC概述 SpringMVC功能与优点 SpringMVC是一种基于Java实现MVC模型的轻量级Web框架 SpringMVC技术与Servlet技术功能一样(对Servlet进行了封装)&#xff0c;都属于Web层开发技术 SpringMVC的主要的作用就是用来接收前端发过来的请求和数据然后经过处理并将处理的结果…

自定义注解实现参数校验

个人博客地址&#xff1a; http://xiaohe-blog.top/ 文章目录1. 为什么要进行参数校验2. 如何实现参数校验3. 注解实现参数校验4. 自定义注解实现参数校验1. 为什么要进行参数校验 在后端进行工作时&#xff0c;需要接收前端传来的数据去数据库查询&#xff0c;但是如果有些数…

目前智慧工厂建设面临的急需解决的问题有哪些?

当前国内诸多制造业企业面临着巨大的转型压力。一方面&#xff0c;劳动力成本迅速攀升、产能过剩、竞争激烈、客户个性化需求日益增长等因素&#xff0c;迫使制造企业从低成本竞争策略转向建立差异化竞争优势。具体在工厂层面&#xff0c;制造企业面临着招工难&#xff0c;以及…

去应聘测试管理职位时遇到的面试题

前言&#xff1a; 在测试管理的路上&#xff0c;少不了招聘测试管理的测试人员&#xff0c;或自己去应聘测试管理人员的时候&#xff0c;因此梳理了关于测试管理职位的面试题&#xff1a; 1、请你列举你曾经担任的测试工作职位&#xff1f; 2、你认为项目测试经理的工作职责和…

简单个人网页设计作业 静态HTML个人博客主页 DW个人网站模板下载 大学生简单个人网页作品代码 个人网页制作 学生个人网页设计作业

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

【印刷字符识别】OCR键盘数字+字母识别【含Matlab源码 807期】

⛄一、OCR简介 1 什么是OCR技术&#xff1f; OCR英文全称是Optical Character Recognition&#xff0c;中文叫做光学字符识别。它是利用光学技术和计算机技术把印在或写在纸上的文字读取出来&#xff0c;并转换成一种计算机能够接受、人又可以理解的格式。文字识别是计算机视觉…

Java项目:SSM服装出租服装店租赁服装管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目为后台管理系统&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,用户管理,公告管理,服装类型管理,服装信息管理,客户信息管…

计算机毕设Python+Vue新生报到管理(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…