【C++】构造函数与析构函数

写在前面

构造函数与析构函数都是属于类的默认成员函数!
默认成员函数是程序猿不显示声明定义,编译器会中生成。

构造函数和析构函数的知识需要建立在有初步类与对象的基础之上的,关于类与对象不才在前面笔记中有详细的介绍:点我跳转


文章目录

  • 写在前面
  • 一、构造函数的特性
    • 1.1、函数名与类名相同。
    • 1.2、 无返回值。
    • 1.3、 对象实例化时编译器自动调用对应的构造函数。
    • 1.4、构造函数可以重载。
    • 1.5、如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
    • 1.6、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
    • 1.7、构造函数的初始化列表
  • 二、析构函数
    • 2.1、析构函数名是在类名前加上字符 ~
    • 2.2、无参数无返回值类型
    • 2.3、一个类只能有一个析构函数。
    • 2.4、对象生命周期结束时,C++编译系统系统自动调用析构函数。
    • 2.5、编译器生成的默认析构函数,对自定类型成员调用它的析构函数
    • 2.6、如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数


一、构造函数的特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

其特征如下:

1.1、函数名与类名相同。

1.2、 无返回值。

1.3、 对象实例化时编译器自动调用对应的构造函数。

在这里插入图片描述

class stack {
public:stack() {//构造函数cout << "this is stack()" << endl;}void Init(int defintCapacity) {_arr = (int*)calloc(defintCapacity, sizeof(int));if (nullptr == _arr){perror("malloc申请空间失败");return;}_capacity = defintCapacity;_size = 0;}void push(int x) {//....扩容等_arr[_size++] = x;}
private:int* _arr;int _size;int _capacity;
};int main() {stack s1;return 0;
}

程序运行结果:
在这里插入图片描述

  • 在上述代码中,不才创建了一个默认构造函数stack,在构造函数中,我们只让其打印字符串this is stack(),之后,我们在s1对象中,并没有显示的调用构造函数,但是字符串就被打印出来了,这就说明的对象实例化时编译器自动调用对应的构造函数

这时候,我们就可以把stack的初始化函数设置放入构造函数中,每当我们创建一个对象时,通过构造函数自动初始化数据。如下:

class stack {
public:stack(int defintCapacity = 4) {_arr = (int*)calloc(defintCapacity, sizeof(int));if (nullptr == _arr){perror("malloc申请空间失败");return;}_capacity = defintCapacity;_size = 0;}void push(int x) {//....扩容等_arr[_size++] = x;}
private:int* _arr;int _size;int _capacity;
};int main() {stack s1;return 0;
}

运行结果:
在这里插入图片描述

  • 这时候,我们就不用每次都显示的初始化数据了,而且也不怕忘记初始化。

1.4、构造函数可以重载。

构造函数也是函数,是函数就可以重载
在这里插入图片描述

class stack {
public:stack(int defintCapacity = 4) {_arr = (int*)calloc(defintCapacity, sizeof(int));if (nullptr == _arr){perror("malloc申请空间失败");return;}_capacity = defintCapacity;_size = 0;}stack(int* arr, int defintCapacity) {if (nullptr == arr) {perror("malloc申请空间失败");return;}_arr = arr;_capacity = defintCapacity;_size = 0;}void push(int x) {//....扩容等_arr[_size++] = x;}
private:int* _arr;int _size;int _capacity;
};int main() {int* arr = (int*)calloc(2, sizeof(int));stack s1(arr, 2);return 0;
}

程序运行结果:
在这里插入图片描述
和函数重载一样的逻辑,编译器会根据符号名去调用对应的构造函数。
需要注意,调用默认构造函数不需要加括号,因为加上括号后,编译器会认为是函数

举个栗子:
stack s1:这时s1代表的是调用stack的默认构造函数的对象
stack s1():这时s1就被当做,返回值是stack类且没有形参的函数。
有参调用就和普通函数一样,只不过是对象+参数列表stack s1(arr, 2)


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

默认构造函数在C++中有特殊定义,在C++标准中,默认构造函数不会对内置类型进行处理,自定义类型会调用它的默认构造函数。但是现在有些编译器会对内置类型进行初始化,但这是该编译器自己的行为,C++标准中是不进行处理的。

内置类型/基本类型:语言本身定义的基础类型(如intchar、指针、double等)
自定义类型:使用classstruct等定义的类型

在这里插入图片描述

class stack {
public:stack(int defintCapacity = 4) {_arr = (int*)calloc(defintCapacity, sizeof(int));if (nullptr == _arr){perror("malloc申请空间失败");return;}_capacity = defintCapacity;_size = 0;}stack(int* arr, int defintCapacity) {if (nullptr == arr) {perror("malloc申请空间失败");return;}_arr = arr;_capacity = defintCapacity;_size = 0;}void push(int x) {//....扩容等_arr[_size++] = x;}
private:int* _arr;int _size;int _capacity;
};class Date
{
public:void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};
int main() {Date d1;return 0;
}

程序运行结果:(在vs2022环境下)
在这里插入图片描述

在默认构造函数中,并不会对定义类型进行任何操作,貌似不能证明默认构造函数的存在,但是我们把Date类设置为,下程序时:

class stack {
public:stack(int defintCapacity = 4) {_arr = (int*)calloc(defintCapacity, sizeof(int));if (nullptr == _arr){perror("malloc申请空间失败");return;}_capacity = defintCapacity;_size = 0;}stack(int* arr, int defintCapacity) {if (nullptr == arr) {perror("malloc申请空间失败");return;}_arr = arr;_capacity = defintCapacity;_size = 0;}void push(int x) {//....扩容等_arr[_size++] = x;}
private:int* _arr;int _size;int _capacity;
};class Date
{
public:void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private://内置类型int _year;int _month;int _day;//自定义类型stack _st;
};int main() {Date d1;return 0;
}

运行结果:(在VS2013编译器中)
在这里插入图片描述

  • 在vs2013中,我们可以清晰看出内置类型不会进行处理的,而自定义类型会调用其默认构造函数

但是我们在VS2022中尝试一下

在这里插入图片描述

  • 我们发现在vs2022编译环境下,有自定义类型情况中,内置类型会被初始化为0,在上例中,我们也发现,在没有自定义类型情况中,内置类型是不会处理的

所以,不才推荐在C++中类中,我们默认内置类型是未被处理的,自定义类型是会调用其默认构造函数的,这样不会出现程序运行错误。

C++11后,对成员变量做了一个补丁,可以在声明成员变量时给定一个缺省值。

在这里插入图片描述
这里不才以内置类型为例,

class Date
{
public:Date(){}Date(int year, int month, int day) {_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private://内置类型 //这里不是初始化,而是声明//这里给的是默认缺省值,给编译器生成默认构造函数时使用int _year = 1;int _month = 1;int _day = 1;};int main() {Date d1;d1.Print();return 0;
}

程序运行结果:

在这里插入图片描述
如果我们调用默认构造函数,那么内置类型的值就是程序猿给定的缺省值。如果我们调用不是默认构造函数,那么使用的就是自定义构造函数的值,如下图。
在这里插入图片描述

什么情况下可以直接使用默认构造函数:

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

1.6、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

虽然在语法中,无参的构造函数和全缺省的构造函数形参了函数重载,编译不会有错,但是在对象初始化时,无参调用存在歧义。

在这里插入图片描述

class Date
{
public:Date() {}Date(int year = 2035, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year = 1;int _month = 1;int _day = 1;};int main() {Date d1;d1.Print();return 0;
}

程序运行结果:

在这里插入图片描述

  • 无参构造函数和全缺省的构造函数都是不需要传参调用的,所以在函数调用时,就会报错对重载函数的调用不明确

无参构造函数全缺省构造函数、我们没写编译器默认生成的构造函数,只要不传参就可以调用的,都可以认为是默认构造函数,而默认构造函数只能存在一个!

1.7、构造函数的初始化列表

构造函数体赋值

在上述的构造器中,我们构造函数内进行的操作是构造函数体赋值,这些操作只是赋值,并不是属性的初始化定义。如下

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
  • _year = year; _month = month;_day = day;这些在构造器中只是充当赋值的作用,并不是对属性_year_month_day的初始化定义。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

初始化列表把类中的所有属性进行初始化定义

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个 “成员变量” 后面跟一个放在括号中的初始值或表达式。如下代码

class Date
{
public:Date(int year, int month, int day):_year(year),_month(month),_day(day)//,_day(day) err:最多只能出现一次{}
private:int _year;int _month;int _day;
};
  1. 每个成员变量在初始化列表中最多只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员必须在初始化列表位置进行初始化
    • 引用成员变量
    • const成员变量
    • 自定义类型成员(且该类没有默认构造函数时)

因为引用成员变量const成员变量在初始化时,必须要进行赋值。而自定义类型成员。构造函数编译器只会调用自定义类型的默认构造函数,如果没有默认构造函数的情况编译器就无法找到自定义类型所对应的构造函数。

在这里插入图片描述
引用成员变量const成员变量 不在初始化列表进行初始化。而在构造函数体赋值中进行赋值来达到初始化。

在这里插入图片描述

自定义类型在没有默认构造函数的情况下,让编译器自己去寻找默认构造函数。

在这里插入图片描述
所以我们就必须在属性初始化的时候赋初值。如下代码:

class A {
public:A(int a):_a(a){	}
private:int _a = 0;
};class Date
{
public:Date(int year, int month, int day):_year(year),_month(month),aa(10){}
private:A aa;const int _year;int& _month;int _day;
};

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。 对于内置类型也是在初始化列表中进行初始化。

我们属性都是在初始化列表中进行初始化构造函数体赋值一般是完成初始化列表不能完成的工作。如检查动态开辟内存是否为空等操作。
在这里插入图片描述

class stack {
public:stack(int size):_arr( (int*)malloc(sizeof(int) * size) ),_capacity(0),_size(size){//检查_arr动态开辟的空间是否为空if (_arr == nullptr) {perror("malloc::>");exit(1);}//再把_arr初始化为0memset(_arr, 0, (sizeof(int) * size) );}private:int* _arr;int _capacity;int _size;
};int main() {stack s1(10);return 0;
}

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

在这里插入图片描述
在上述的stack类中,很多小伙伴在动态开辟_arr的初始化时,先在初始化列表时把_size放在了_arr的前面,之后把_arr计算空间大小的size使用了属性的_size这样导致的程序崩溃

class stack {
public:stack(int size): _capacity(0), _size(size),_arr( (int*)malloc(sizeof(int) * _size) ){//检查_arr动态开辟的空间是否为空if (_arr == nullptr) {perror("malloc::>");exit(1);}//再把_arr初始化为0memset(_arr, 0, (sizeof(int) * size) );}private:int* _arr;int _capacity;int _size;
};int main() {stack s1(10);return 0;
}
  • 这时候初始化顺序是先初始化_arr数组,此时_size并没有被初始化,那么这时候_size就是一个随机值。
  • 我们使用一个巨大的随机值去动态开辟一个空间,那么动态开辟出来的空间也是一个巨大的空间,所以程序会崩溃。
  • 只有初始化完了_arr数组,再初始化_capacity,最后初始化_size

为了深刻理解:我们再举个栗子,下面程序运行结果是什么。

class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2;int _a1;
};int main() {A aa(20);aa.Print();
}
  • 根据我们理解的:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,此时程序的运行结果为:20 随机值

不才这里建议:声明的顺序与初始化列表顺序保持一致

二、析构函数

析构函数:与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作

析构函数是特殊的成员函数,其特征如下:

2.1、析构函数名是在类名前加上字符 ~

2.2、无参数无返回值类型

2.3、一个类只能有一个析构函数。

若未显式定义,系统会自动生成默认的析构函数。注意:析构函数没有形参所以不能重载

2.4、对象生命周期结束时,C++编译系统系统自动调用析构函数。

在这里插入图片描述

class Date
{
public:Date() {cout << "Date()" << endl;}Date(int year, int month, int day) {_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}~Date() {cout << "~Date()" << endl;}private:int _year = 1;int _month = 1;int _day = 1;};int main() {Date d1;{//创建了代码块用于验证:对象生命周期结束时,C++编译系统系统是否会自动调用析构函数Date d2;d2.Print();printf("\n");cout << &d2 << endl;}printf("\n");d1.Print();return 0;
}

程序运行结果:

在这里插入图片描述


2.5、编译器生成的默认析构函数,对自定类型成员调用它的析构函数

在这里插入图片描述

class stack {
public:stack(int defintCapacity = 4) {_arr = (int*)calloc(defintCapacity, sizeof(int));if (nullptr == _arr){perror("malloc申请空间失败");return;}_capacity = defintCapacity;_size = 0;cout << "stack()" << endl;}stack(int* arr, int defintCapacity) {if (nullptr == arr) {perror("malloc申请空间失败");return;}_arr = arr;_capacity = defintCapacity;_size = 0;}void push(int x) {//....扩容等_arr[_size++] = x;}~stack() {free(_arr);_arr = nullptr;cout << "~stack()" << endl;}
private:int* _arr;int _size;int _capacity;
};class Date
{
public:Date() {cout << "Date()" << endl;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private://内置类型 给定缺省值int _year = 1;int _month = 1;int _day = 1;//自定义类型stack _st;
};int main() {Date d1;d1.Print();return 0;
}

程序运行结果:

在这里插入图片描述

  • main方法中创建了Date对象d1,而d1中包含4个成员变量,其中_year, _month,_day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可。
  • 但是_ststack类的对象,所以在d1销毁时,要将其内部包含的stack类的_st对象销毁,所以要调用stack类的析构函数
  • main函数中不能直接调用stack类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date生成一个默认的析构函数,目的是在其内部调用stack类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁

2.6、如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数

析构函数的使用:

  1. 一般情况下,有动态申请资源 ,就需要显示写析构函数释放资源
  2. 没有动态申请资源,不需要写析构函数
  3. 需要释放资源的类型都是自定义类型,在该类中就不需要写析构函数。因为默认生成的析构函数遇到自定义类型会自动调用自定义类型的析构函数
  4. 特殊场景特殊使用

以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 如果对大家有用的话,就请多多为我点赞收藏吧~~~💖💖
请添加图片描述

ps:表情包来自网络,侵删🌹

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

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

相关文章

2013年IMO几何预选题第4题

在 △ A B C \triangle ABC △ABC 中, A B < A C AB < AC AB<AC. P P P, Q Q Q 是直线 A C AC AC 上的两个不同的点, 满足 ∠ P B A ∠ Q B A ∠ A C B \angle PBA \angle QBA \angle ACB ∠PBA∠QBA∠ACB, 且 A A A 在 P P P 与 C C C 之间. 已知在线段…

UDP报文格式

UDP是传输层的一个重要协议&#xff0c;他的特性有面向数据报、无连接、不可靠传输、全双工。 下面是UDP报文格式&#xff1a; 1&#xff0c;报头 UDP的报头长度位8个字节&#xff0c;包含源端口、目的端口、长度和校验和&#xff0c;其中每个属性均为两个字节。报头格式为二…

网络科技有限公司网络设计

网络科技有限公司网络设计 摘要&#xff1a;伴随着信息科技发展&#xff0c;上网变得一件必不可少的事情&#xff0c;当然网络安全对我们也是越来越重要。像我们的传统网结构是无法为我们的上网提供一个安全的网络环境。锐雯网络科技有限公司就是以网络安全为基本的对网络惊醒…

【c++】哈希

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 目录 1. unordered系列关联式容器1.1 unordered_map1.2 接口函数 例题 2.哈希概念2.1哈希冲突2.2哈希函数2.3哈希冲突解决2.3.1线性探测二次探测 2.3.2开散列 3. 封装迭代器封装完整代码&a…

@Query(org.springframework.data.jpa.repository.Query)

文章目录 1. findPAProductByAdminId 方法作用&#xff1a;解释&#xff1a; 2. findPaginatedPAProductByAdminId 方法作用&#xff1a;解释&#xff1a; 总结&#xff1a; package com.productQualification.resource.repository.productAuthentication;import com.productQu…

[Collection与数据结构] PriorityQueue与堆

1. 优先级队列 1.1 概念 前面介绍过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队列时&#xff0c;可能需要优先级高的元素先出队列&#xff0c;该中场景下&#xff0c;使用队列显然…

STM32网络通讯之CubeMX实现LWIP项目设计(十五)

STM32F407 系列文章 - ETH-LWIP-CubeMX&#xff08;十五&#xff09; 目录 前言 一、软件设计 二、CubeMX实现 1.配置前准备 2.CubeMX配置 1.ETH模块配置 2.时钟模块配置 3.中断模块配置 4.RCC及SYS配置 5.LWIP模块配置 3.生成代码 1.main文件 2.用户层源文件 3.…

【React】静态组件动态组件

目录 静态组件动态组件创建一个构造函数(类)使用 class 实现组件**使用 function 实现类组件** 静态组件 函数组件是静态组件&#xff1a; 组件第一次渲染完毕后&#xff0c;无法基于内部的某些操作让组件更新「无法实现自更新」&#xff1b;但是&#xff0c;如果调用它的父组…

我的世界-与门、或门、非门等基本门电路实现

一、红石比较器 (1) 红石比较器结构 红石比较器有前端单火把、后端双火把以及两个侧端 其中后端和侧端是输入信号,前端是输出信号 (2) 红石比较器的两种模式 比较模式 前端火把未点亮时处于比较模式 侧端>后端 → 0 当任一侧端强度大于后端强度时,输出…

持续集成 01|Gitee介绍、Pycharm使用Gitee

目录 一、理论 二、 git的简介与安装 三、Gitee 1、注册网易163邮箱 2、注册Gitee账号 3、git和gitee管理代码工作原理 三、PyCharm安装配置Gitee 四、Pycharm使用Gitee插件的五种场景 1、将 Gitee的新仓库 Checkout&#xff08;检出&#xff09;到 Pycharm中 2、推送…

【Qt】03-页面切换

前言一、按键实现界面切换1.1 创建新的类文件1.1.1 创建1.1.2 细节选择 1.2 代码以及需要注意的点mywidget.cppsecondwidget.cppmywidget.hsecondwidget.h 1.3 结果展示 二、signal关键字2.1 代码以及解释mywidget.cppsecondwidget.cppmywidget.hsecondwidget.h解释 2.2 现象 三…

软件授权管理中的软件激活向导示例

软件激活向导示例 在软件许可中&#xff0c;提供许可应该是简单和安全的。这适用于想要在中央许可证服务器上创建新许可证的软件开发人员&#xff0c;也适用于需要在其设备上获得许可证的最终用户。如果所讨论的系统有互联网连接&#xff0c;或是暂时的连接&#xff0c;就可以…

02JavaWeb——JavaScript-Vue(项目实战)

一、JavaScript html完成了架子&#xff0c;css做了美化&#xff0c;但是网页是死的&#xff0c;我们需要给他注入灵魂&#xff0c;所以接下来我们需要学习 JavaScript&#xff0c;这门语言会让我们的页面能够和用户进行交互。 1.1 介绍 通过JS/js效果演示提供资料进行效果演…

【java】java入门

盘符名称冒号---------盘符切换 dir---------------查看当前路径下的内容 cd目录--------进入单级目录 cd..----------回退到上一级目录 cd \----------回退到盘符目录 cls----------清屏 exit 为什么要配环境变量&#xff1f; 在任意的目录下都可以打开指定的软件。把软件的路…

利用爬虫获取某学习软件的考试题库(带源码)

首先要重新进行账号的登陆用来获取cookie 按下F12在控制台输入&#xff1a; // 获取当前页面的 cookies var cookies document.cookie.split(";");// 创建一个数组来存储 cookies 对象 var cookieArray [];// 遍历每个 cookie 并将其转换为对象 cookies.forEach(f…

【C++篇】红黑树的实现

目录 前言&#xff1a; 一&#xff0c;红黑树的概念 1.1&#xff0c;红黑树的规则 1.2&#xff0c;红黑树的最长路径 1.3&#xff0c;红黑树的效率分析 二&#xff0c;红黑树的实现 2.1&#xff0c;红黑树的结构 2.2&#xff0c;红黑树的插入 2.2.1&#xff0c;大致过程…

Autodl转发端口,在本地机器上运行Autodl服务器中的ipynb文件

通过 SSH 隧道将远程端口转发到本地机器 输入服务器示例的SSH指令和密码&#xff0c;将远程的6006端口代理到本地 在服务器终端&#xff0c;激活conda虚拟环境 conda activate posecnnexport PYOPENGL_PLATFORMegljupyter notebook --no-browser --port6006 --allow-root从…

镭速大文件传输视频文件预览实现原理

镭速可以支持视频预览&#xff0c;在测试过程中需要大量不同格式的视频&#xff0c;如果直接去找各种格式的视频不太现实&#xff0c;所以就会用到一个视频格式转换的工具ffmpeg&#xff0c;本文将介绍ffmpeg的基本使用方法。FFmpeg 是一个免费开源的音视频处理工具&#xff0c…

达梦8-DMSQL程序设计学习笔记1-DMSQL程序简介

1、DMSQL程序简介 DMSQL程序是达梦数据库对标准SQL语言的扩展&#xff0c;是一种过程化SQL语言。在DMSQL程序中&#xff0c;包括一整套数据类型、条件结构、循环结构和异常处理结构等&#xff0c;DMSQL程序中可以执行SQL语句&#xff0c;SQL语句中也可以使用DMSQL函数。 DMSQ…

C# 获取PDF文档中的字体信息(字体名、大小、颜色、样式等

在设计和出版行业中&#xff0c;字体的选择和使用对最终作品的质量有着重要影响。然而&#xff0c;有时我们可能会遇到包含未知字体的PDF文件&#xff0c;这使得我们无法准确地复制或修改文档。获取PDF中的字体信息可以解决这个问题&#xff0c;让我们能够更好地处理这些文件。…