C++特殊类设计类型转换

news2024/11/18 20:38:52

一、特殊类设计

在普通类的设计基础上,提出一些限制条件设计的类就是特殊类。

1、请设计一个类,不能被拷贝 

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 

C++98中的方式:

将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

class CopyBan
{
    // ...
public:
	CopyBan()
	{}
    
private:
    CopyBan(const CopyBan&);//拷贝构造函数声明
    CopyBan& operator=(const CopyBan&);//赋值运算符重载声明
    //...
};

 原因:

  • 1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不 能禁止拷贝了
  • 2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写 反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

 

如上图代码,在对这个特殊类进行拷贝和赋值的时候,因为这两个成员函数私有而无法调用。

  • 拷贝构造以及赋值运算符重载等成员函数,在调用时都是编译器在域外调用,所以必须是公有的。 

 C++11 :

C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 =delete,表示让编译器删除掉该默认成员函数。

	class CopyBan
	{
		// ...
	public:
		CopyBan()
		{}
		CopyBan(const CopyBan&) = delete;
		CopyBan& operator=(const CopyBan&) = delete;
		//...
	};
  • 使用C++11中的给delete新赋予的意义来禁止生产拷贝构造和赋值运算符重载函数。

此时编译器也不会自动生成默认的拷贝构造和赋值运算符重载函数。

2. 请设计一个类,只能在堆上创建对象 

实现方式:

  • 1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
  • 2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建 
class HeapOnly
{
public:
    static HeapOnly* CreateObject()
    {
        return new HeapOnly;
    }
private:
    HeapOnly() {}

    // C++98
    // 1.只声明,不实现。因为实现可能会很麻烦,而你本身不需要
 // 2.声明成私有
    HeapOnly(const HeapOnly&);

        // or

        // C++11    
        HeapOnly(const HeapOnly&) = delete;
};

 定义一个静态成员函数,在该函数内部new一个HeapOnly对象。将构造函数和拷贝构造函数私有,并且禁止生成拷贝构造函数。

原因:

  •     使用静态成员函数new一个HeapOnly对象。

非静态成员函数在调用的时候,必须使用点(.)操作符来调用,这一步是为了传this指针。

这样的前提是先有一个HeapOnly对象,但是构造函数设置成了私有,就无法创建这样一个对象。

而静态成员函数的调用不用传this指针,也就不用必须有HeapOnly对象,只需要类域::静态成员函数即可。
静态成员函数属于HeapOnly域内,所以在new一个对象的时候,可以调用私有的构造函数。

  • 禁止调用拷贝构造函数,并且私有化。

这样的目的是为了禁止拷贝,防止使用堆区上的HeapOnly对象在栈区上拷贝,如下面代码

 

而禁止了拷贝构造就杜绝了这一行为,从而保证了HeapOnly对象只能在堆上创建。

3. 请设计一个类,只能在栈上创建对象

方法一:同上将构造函数私有化,然后设计静态方法创建对象返回即可。 

  • 主要要做到不能在堆上创建类对象。
  • new一个对象的时候,会调用该类的operator new(size_t size)函数,在释放资源的时候又会调用该类的operator delete(void* p)函数。
class StackOnly
{
public:
 static StackOnly CreateObj()
 {
 return StackOnly();
 }
    
    // 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉
 // StackOnly obj = StackOnly::CreateObj();
 // StackOnly* ptr3 = new StackOnly(obj);
 void* operator new(size_t size) = delete;//禁止调用new
 void operator delete(void* p) = delete;//禁止调用delete

private:
 int _a;
};
  •  使用delete来禁止这两个函数的调用,那么在new一个对象的时候,就会产生编译错误,从而无法在堆区上创建类对象。

此时在堆上创建对象时就会报错,尝试引用已经删除的函数。

方法2:

class StackOnly
{
public:
    static StackOnly CreateObj()
    {
        return StackOnly();
    }
private:
    StackOnly()
        :_a(0)
    {}
private:
    int _a;
};

 另一种方式就是和之前一样,通过一个静态成员函数在栈区上创建一个类对象,并且将默认构造函数私有化。

此时new一个对象的时候,由于默认构造函数私有无法调用,所以报错。

但是这俩种设计方法都有一个漏洞,类对象可以在静态区(数据段)上创建:

static StackOnly num = StackOnly::CreateObject();

 设计特殊类的核心点:只能通过静态成员函数来创建类,封掉其他所有创建方式

4 .不能被继承的类

C++98方式

// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
 static NonInherit GetInstance()
 {
     return NonInherit();
 }
private:
 NonInherit()
 {}
};

原因:

  • 基类的构造函数私有,派生类在创建对象的时候,无法调用基类的构造函数。

此时我们就设计出了不能继承的类。

C++11的方式:

inal关键字,final修饰类,表示该类不能被继承。

class A  final
{
    // ....
};

 使用C++11中的final关键字修饰基类,这个基类就无法继承。

5.单例模拟

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

  • 单例模式:一个类只能创建一个对象。
  • 该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

5.1 单例模式的类型

单例模式有两种类型:

  • 懒汉式:在真正需要使用对象时才去创建该单例类对象
  • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用

 懒汉式创建单例对象

懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。否则则先执行实例化操作。

根据上面的流程图,就可以写出下面的这段代码

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		//如果单例对象没有创建则在堆区创建
		if (m_pInstance == nullptr)
		{
			m_pInstance = new Singleton;
		}
		return m_pInstance;
	}
private:
	//构造函数
	Singleton()
	{}
	Singleton(const Singleton& s) = delete;//禁止拷贝
	Singleton& operator=(const Singleton& s) = delete;//禁止赋值

	//静态单例对象指针
	static Singleton* m_pInstance;//单例对象指针
};

Singleton* Singleton::m_pInstance = nullptr;//初始化为空
  • 将构造函数私有,拷贝构造和赋值运算符重载函数禁止调用,用来保证单例模式的唯一性。
  • 增加静态单例对象指针成员变量。

在类外实例化静态指针变量的时候,并没有创建单例对象,而是将其初始化为空。

int main()
{
	Singleton* ps = Singleton::GetInstance();//获取单例对象

	return 0;
}

懒汉模式又叫做延时加载模式,如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

优点:

  •     第一次使用单例对象时才创建对象,进程启动过程中无负载。
  •     多个互相依赖的单例可以控制启动顺序(通过代码顺序)。

饿汉式创建单例对象

饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创

class Singleton
{
public:
	//获取单例对象接口
	static Singleton* GetInstance()
	{
		return &m_instance;
	}
private:
	Singleton()
	{}
	Singleton(const Singleton& s) = delete;//禁止使用拷贝构造
	Singleton& operator=(const Singleton& s) = delete;//禁止使用赋值运算符重载

	//保证单例对象在静态区且只有一个
	static Singleton m_instance;//单例对象
};

Singleton Singleton::m_instance;//在程序入口之前就完成单例对象初始化

 静态成员变量只能在类域外进行定义初始化。

所以在main函数之前就将单例对象定义初始化,此时该单例对象创建在静态区上,而且仅有一个,后面就无法再创建。

  • 想要获取该单例对象只能通过静态成员函数GetInstance()来获取。
  • 静态成员函数可以直接访问静态成员变量m_instance
int main()
{
	Singleton* s = Singleton::GetInstance();//获取单例对象

	return 0;
}

不管将来会不会使用到这个单例对象,但是在程序一启动还没有进入main函数之前就创建一个唯一的实例对象。

就像一个饿汉一样,一上来就创建单例对象。

缺点:

  • 可能会导致进程启动较慢,如过实例对象很复杂,在创建实例对象时就会花费很多时间。
  • 实例顺序不确定,如果有多个单例对象,并且对象之间存在互相依赖关系,由于对象的实例对象不确定(和代码顺序无关,由编译器决定),此时就会发生错误。

线程安全问题:

在C++11之后饿汉模式是没有线程安全问题的(做了相关补丁),因为单例对象是在main函数之前就实例化的,而多线程都是在main函数里面启动的。

但是懒汉模式是存在线程安全问题的,当多个线程使用到单例对象时候,在使用GetInstance()获取对象时,用因为调度问题出现误判,导致new多个单例对象。


  • GetInstance()中判断是否创建单例的时候加锁,判断完后再解锁,让多线程串行访问单例对象的指针。

互斥锁必须放在静态区:

  • 单例对象只有一个,所以全局只使用一把锁即可,放在静态区刚合适。
  • GetInstance()是静态成员函数,没有this指针,如果互斥锁是普通成员变量的化,无法访问。而静态成员函数可以直接访问静态成员变量。

通过加锁的方式就能保证懒汉模式的线程安全问题。

双检查加锁:

  • 此时,每个调用GetInstance()的线程都需要申请锁然后释放锁,对锁的操作也是有开销的,会有效率上的损失。

 单例模式在单例一经创建以后就不会再创建了,无论多少线程在访问已经创建的单例对象时都不会再创建,线程就已经安全了。所以在单例对象创建以后,根本没有必要再去申请锁和释放锁。

GetInstance()原本加锁代码放在一个if(m_pInstacne == nullptr)的代码快中,进行双重检查。

  • 当单例对象已经被new出来以后,就不会再进行加锁检查了,可以直接通过if进行判断,提高了性能。

 new出来对象的释放:

  •      new出来的单例对象如何释放呢?

一般情况下我们选择不释放,因为全局只有一个单例对象,而且会被一直使用,所以就不用释放,释放了反而后面的使用会出问题。

当进程结束的时候,操作系统会回收该进程的所有资源,包括堆区上的资源。

  •     如果就要主动释放呢?在释放的同时将一些信息保存到磁盘。

 

如上图代码所示,在单例模式的类中创建一个内部类,内部类是外部类的友元。

  • 在内部类对象Gabro的析构函数中释放new出来的单例对象,并且进行相关的文件操作。
  • 内部类对象同样设置成静态成员,在类域外进行定义初始化。

 二、类型转换

 在C语言中,如果赋值运算符(=)两边的类型不同,或者形参和实参类型不匹配,或者返回值类型和接收值类型不一致,就需要发生类型转换。

C语言中有两种类型转换:

  • 隐式类型转换:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
  • 显式类型转换:需要用户自己处理。
int main()
{
	int i = 1;
	double d = i;//隐式类型转换
	printf("%d, %.2f\n", i, d);

	int* p = &i;
	int address = (int)p;//显式类型转换
	printf("%x, %d\n", p, address);

	return 0;
}
  •      double d = i是发生了隐式类型转换,将整形转换成了double类型。

隐式类型转换只发生在相近类型,比如整形家族直接,或者这些int,double等表示数值直接的变量类型。

  •     int address = (int)p是发生了显式类型转换,将int*类型的变量转换为int类型。

显式类型需要用户自己维护,在两种类型没有任何关系的时候需要进行显式类型转换,比如将指针类型转换成普通类型等等。

但是C语言的类型转换存在缺陷:

  • 隐式类型转换有些情况下会出现问题,比如数据精度发生丢失(整形提升等)。
  • 显式类型转换将所有情况混合在一起,代码不够清晰。

 所以C++提出了自己的类型转换风格,但是仍然可以使用C语言的转换风格,因为要兼容C语言。

1、static_cast

C语言的隐式类型转换在C++中就可以使用static_cast来转换,但是不能用于两个不相关的类型进行转换。

double d = 3.14;
int a = static_cast<int>(d);

 double类型转int类型,在C语言中是隐式类型转换,在C++中为了更加明确使用了static_cast

  • static_cast后的<>里放要转换的类型,()里放被转换的类型。

如果将static_cast看成是类模板,<int>就是在实例化,(d)就是在拷贝构造,siatic_cast<int>(d)就是在用d构建一个匿名对象。 

2、reinterpret_cast 

C语言的显式类型转换在C++中就可以reinterpret_cast,用于将一种类型转换为另一种不同的类型。

int a = 1;
int* pa = &a;
int address = reinterpret_cast<int>(pa);

 int*类型转换为int类型,在C语言中是显式类型转换,在C++中为了不混乱使用了reinterpret_cast

 

这里如果使用static_cast进行类型转换的话会报错,必须使用reinterpret_cast

3、 const_cast 

用在删除变量的const属性,方便赋值。

const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;

 变量a原本是const属性的,不能被修改,使用了const_cast以后去除了常量属性,可以修改了,如*p = 3

在调试窗口中可以看到,成功修改了原本是const属性的变量a。

const_cast更多的是一种警示,表示去除了const属性,要谨慎操作。

4 、dynamic_cast 

用于将一个父类对象的指针或者引用转换为子类对象的指针或引用(动态转换)。

向上转换:子类对象的指针或引用 → 父类对象的指针或引用。(不发生类型转换,是语法允许的,发生了切片)
向下转换:父类对象的指针或引用 → 子类对象的指针或引用。(用dynamic_cast转换是安全的) 

//父类
class A
{
public:
	virtual void f()
	{}

	int _a = 1;
};
//子类
class B : public A
{
public:
	int _b = 2;
};

 class A是父类,class B是子类,父类中有成员变量int _a,子类中有成员变量_b

 

main函数中,传父类指针&aa给函数,在函数中将A* pa父类指针接收该值,然后将其强转为子类指针B*,使用子类指针访问子类成员,bptr->_b = 4发生运行时错误。

  • 形参A* pa是父类指针,接收的也是父类指针,所以强转成子类指针后访问子类成员_b会发生越界。 

如果传的是子类指针就不会报错,因为即使形参是父类指针,强转成子类以后并不会越界。

 

使用dynamic_cast将父类指针强转为父类指针。

传父类指针,然后强转为子类指针后,打印出来的结构是nullptr,表示该次转换不能进行。 

传子类指针,形参的父类指针接收后再强转为子类,打印出来的结构是强转后的地址,表示该次强转可以成功。

 

  • pa如果指向的是子类对象,那么子类向上转换为父类没有问题。
  • pa如果指向的是父类对象,那么父类向下转换为子类是存在越界风险的。

注意:

  • dynamic_cast只能用于父类含有虚函数的类。
  • dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回nullptr。
  • dynamic_cast是安全的,直接使用C语言的转换方式是不安全的。

C++中的类型转换,尤其是前两种static_castreinterpret_cast是建议用法,可以采用也可以不采用。const_cast是一种新用法,但是存在风险,dynamic_cast是一种安全的类型转换。

三、总结

特殊类的设计中,要掌握好一点,就是只能通过一个接口来获取类,其他的方式不允许,让成员函数或私有或禁掉就可以。特别是单例模式,变成中我们经常用到。

对于类型转换,除了dynamic_cast是在多态转换中必须使用外,其他三种方式建议使用,可以增加代码的规范性。

 感谢阅读!!!!!!!!!!!!!

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

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

相关文章

vue知识-07

ref # 放在组件上&#xff1a; this.$refs.名字 ---组件对象.组件对象.属性 # 在vue项目中使用: // 写在组件上 <HelloWorld ref"my_hello_world"></HelloWorld>handleClick() {console.log(this.$refs)this.$refs.my_hello_world.name # 获取子组件中…

【算法】使用栈解决一系列算法题(匹配、表达式、模拟)(C++)

1. 前言&#xff08;栈适用于解哪些题&#xff1f;&#xff09; 栈适合解决需要后进先出&#xff08;LIFO&#xff09;的结构的算法题&#xff0c;例如&#xff1a; 括号匹配问题&#xff1a;判断给定字符串中括号是否匹配。表达式求值问题&#xff1a;将表达式转换为后缀表达…

for循环判断有几个偶数

num100 count0 for i in range(1,num):if i%20:print("为偶数")count1 print(f"1-100的范围内&#xff0c;有{count}个偶数") 运行结果如下&#xff1a;

全罐喂养一个月多少钱?适合猫咪全罐喂养的性价比猫罐头推荐

许多铲屎官为了防止他们的猫养成挑食的习惯并确保均衡的营养摄入&#xff0c;正考虑采用全罐喂养的方式。然而&#xff0c;他们也担忧全罐喂养会带来高昂的开销。那么&#xff0c;全罐喂养一个月多少钱呢&#xff1f; 放心&#xff0c;作为一位有6年宠物店经营经验的店长&…

基于ArcGIS的晕线制作

在借助ArcGIS进行制图时&#xff0c;我们有时需要为矢量边界添加晕线&#xff0c;今天就来探索一下基于ArcGIS的晕线制作操作。 软件版本&#xff1a;ArcMap10.4.1 方法一&#xff1a;制作多环缓冲区 工具路径&#xff1a;Analysis Tools-Proximity-Mutiple Ring Buffer 思…

C++写二进制文件

源文件 #include <iostream> #include <fstream> #include <sstream> #include <cmath>void convert2() {// 打开输入文本文件std::ifstream inputFile("mask.txt");// 打开输出二进制文件std::ofstream outputFile("mask.dat", …

软件测试|使用Python读写yaml文件,你会了吗?

简介 YAML&#xff08;YAML Aint Markup Language&#xff09;是一种可读的数据序列化格式&#xff0c;它常用于配置文件和数据交换。Python 提供了许多库来处理 YAML 文件。在本文中&#xff0c;我们将探讨如何使用 PyYAML 库来读取和写入 YAML 文件&#xff0c;以及提供一些…

CentOS 7 权限管理实战指南:用户管理相关命令详解

前言 掌握 CentOS 7 用户管理命令&#xff0c;轻松管理系统用户&#xff01;本文详细介绍了在 CentOS 7 系统中常用的用户管理命令&#xff0c;从创建和删除用户、修改用户属性&#xff0c;到密码管理和用户权限设置&#xff0c;一应俱全。无论你是 Linux 新手还是经验丰富的管…

huggingface学习 | 云服务器使用git-lfs下载huggingface上的模型文件

文章目录 一、找到需要下载的huggingface文件二、准备工作&#xff08;一&#xff09;安装git-lfs&#xff08;二&#xff09; 配置git ssh 三、检查ssh连接huggingface是否成功 一、找到需要下载的huggingface文件 huggingface官网链接&#xff1a;https://huggingface.co/ 以…

rime中州韵小狼毫 LaTex输入法配置

教程目录&#xff1a;rime中州韵小狼毫须鼠管安装配置教程 保姆级教程 100增强功能配置教程 本文的分享一种在rime中州韵小狼毫须鼠管输入法中配置LaTex输入方案的方法&#xff0c;并不完美&#xff0c;仅供参考。 先睹为快 LaTex输入方案可以快捷的在公式模式下输入一些基础…

猫咪全罐喂养一个月多少钱?适合给猫咪全罐喂养的猫罐头推荐

不少铲屎官为了防止猫咪挑食和营养吸收不均衡&#xff0c;打算给猫咪进行全罐喂养&#xff0c;但是又担心全罐喂养花费太多钱了。猫咪全罐喂养一个月多少钱&#xff1f;别担心&#xff0c;咱们打工人的养猫攻略&#xff0c;花小钱办大事&#xff01;追求高性价比的猫罐头才是王…

软件测试|使用selenium处理单选框和多选框

简介 我们在web自动化测试工作中&#xff0c;经常会遇到对单选框&#xff08;Radio Buttons&#xff09;或者多选框&#xff08;Checkboxes&#xff09;进行操作的场景&#xff0c;单选框和多选框主要是用于我们做出选择或提交数据。本文将主要介绍selenium对于单选框和多选框…

嵌入式新人要坚持嵌入式吗?

今日话题&#xff0c;嵌入式新人要坚持嵌入式吗?这问题让我想起了我大学时学习嵌入式的经历。当时&#xff0c;学校还发了一些开发板&#xff0c;可惜我自己的学习进展不顺利&#xff0c;最终我决定转向了纯软件开发领域。但是&#xff0c;我大学时有一个同学&#xff0c;他一…

Flink启动Yarn Session报错:Couldn‘t deploy Yarn session cluster

Flink版本&#xff1a;1.1.3 启动Yarn Session的语句&#xff1a;bin/yarn-session.sh -nm test -d 报错截图如下&#xff1a; 仅通过ERROR信息只能知道是yarn session集群未能正常启动&#xff0c;因此继续向下查找&#xff1a; 找到报错信息的Caused by部分&#xff1a; 报…

网络原理--http

目录 一、 DNS&#xff08;应用层协议&#xff09; 1、域名概念 2、维护ip地址和域名之间的映射&#xff08;域名解析系统&#xff09; 3、DNS系统&#xff08;服务器&#xff09; 4、如何解决DNS服务器高并发问题 二、HTTP&#xff08;应用层协议&#xff09; 1、htt…

觉得做文档不被重视?来看看你未来的可能性

▲ 搜索“大龙谈智能内容”关注公众号▲ 在《2023中国技术传播行业调研报告》中&#xff0c;“从业者心中的痛”这项调研的统计如下图。 其中&#xff0c;“企业内高层不够重视”排在第一位。 图1 - “从业者心中的痛”统计 对于行业的发展趋势&#xff0c;大家是这样看的…

海外代理IP怎么用?常见使用问题及解决方案

海外代理IP是指提供全球范围内的代理服务器&#xff0c;代理服务器充当IP与目标网站之间的中介&#xff0c;可以起到安全匿名、提高网速、突破网络壁垒的作用。在使用代理IP的过程中&#xff0c;用户可能会遇到各种挑战&#xff0c;如连接问题、速度慢等。理解这些问题的原因并…

计算机导论06-人机交互

人机交互基础 人机交互概述 人机交互及其发展 人机交互是指人与计算机之间&#xff0c;使用某种对话语言&#xff0c;以一定的交互方式&#xff0c;为完成确定任务的信息交换过程。 从计算机的诞生之日起&#xff0c;人机交互技术的发展已经历了以下阶段&#xff1a; 早期的…

Linux 有哪些搜索方式?5分钟带你搞懂!

5分钟带你掌握 Linux 的三种搜索方式 前言 1.find 命令 find 命令是用来在给定的目录下查找符合给定条件的文件 语法格式&#xff1a;find [查找起始路径] [查找条件] [处理动作] &#xff08;1&#xff09;根据名称查找&#xff1a;find [查找起始路径] -name 文件名 或者…

开源云真机平台-Sonic平台-python自定义脚本-config.ini方式实现全局配置参数的读写操作

【主要功能】 config.ini方式实现全局配置参数的读写操作 使用python实现以下功能&#xff1a; 1、使用将接口获取的变量值&#xff0c;写入到当前目录下的config文件中&#xff0c;如delayTime10&#xff1b; 2、读取当前目录下的config文件中&#xff0c;特定变量的值&…