C++模板总结

news2024/9/20 0:49:59

文章目录

  • 写在前面
  • 1. 函数模板
    • 1.1 函数模板的概念
    • 1.2 函数模板的原理
    • 1.3 函数模板的实例化
    • 1.4 函数模板的实例化模板参数的匹配原则
  • 2. 类模板
  • 3. 非类型模板参数
  • 4. 模板的特化
    • 4.1 概念
    • 4.2 函数模板特化
    • 4.3 类模板特化
  • 5. 模板分离编译
  • 6. 总结

写在前面

进入C++以后,C++支持了函数重载也就是在同一作用域中可以存在功能类似的同名函数,例如swap函数来完成int,double,char等数据类型的交换。

void swap(int& e1, int& e2)
{
	int tmp = e1;
	e1 = e2;
	e2 = tmp;
}
void swap(double& e1, double& e2)
{
	double tmp = e1;
	e1 = e2;
	e2 = tmp;
}
void swap(char& e1, char& e2)
{
	char tmp = e1;
	e1 = e2;
	e2 = tmp;
}

但是函数重载面临的问题是:

  1. 重载的函数仅仅是类型不同,功能是类似,所以代码复用率比较低,而且一旦有新类型出现,就需要用户自己 增加对应的函数。
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

如果在C++中,能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码)。类似于活字印刷术,模板可以帮助我们快速生成我们所需的代码。
在这里插入图片描述

巧的是前人早已将树栽好,我们只需在此乘凉。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础
在这里插入图片描述

1. 函数模板

1.1 函数模板的概念

  1. 函数模板概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时通过传递不同的类型参数来实例化不同类型的函数。这种机制使得代码更加简洁和通用,减少了重复代码的编写
  2. 函数模板格式:定义一个函数模板时,需要使用关键字 template或者class(切记:不能使用struct代替class),后跟一个模板参数列表。
    template<typename T1, typename T2,…,typename Tn>
    返回值类型 函数名(参数列表){}

    以下是一个简单的例子,展示了如何使用模板来创建一个通用的函数模板,它实现了两个数值的交换:
//template <typename T>
template <class T>
void swap(T& a, T& b) 
{
    T temp = a;
    a = b;
    b = temp;
}

1.2 函数模板的原理

大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。机器生产淘汰掉了很多手工产品。本质是什么,重复的工作交给了机器去完成。有人给出了论调:懒人创造世界。马云曾经说过:"懒不是傻懒,如果你想少干,就要想出懒的方法。要懒出风格,懒出境界"。而函数模板是一个蓝图,它本身并不是函数,是编译器根据使用方式产生特定具体类型函数的模具。所以模板就是将本来应该我们做的重复的事情交给了编译器。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码。通过下面例子来理解一下:

#include <iostream>
using namespace std;
//函数模板
template<class T>
void Swap(T& e1, T& e2)
{
	T tmp = e1;
	e1 = e2;
	e2 = tmp;
}

int main()
{

	int a = 10, b = 20;
	double x = 10, y = 20;
	Swap(a, b);
	Swap(x, y);

	return 0;
}

在这里插入图片描述
通过汇编代码再看一下上面的两次Swap函数调用,也能看出调用的函数不是同一个,因为它们call的地址都不同。
在这里插入图片描述

1.3 函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显示实例化

  1. 隐式实例化:让编译器根据实参推演模板参数的实际类型。
    例如上面的Swap(a, b) 和 Swap(x, y),编译器根据传递给模板函数的参数自动推导出模板参数的类型。实际上,在大多数情况下编译器都能够推导出模板参数的类型,但在下面这种情况,编译器推导不出来模板参数的具体类型,导致编译错误:
    在这里插入图片描述
    这是因为当调用 Add(e1, e2) 时,e1 的类型是 int,而 e2 的类型是 double,这种情况下编译器无法自动推导出模板参数 T,因为 int 和 double 是不同的类型。
    解决办法一:自己来强制转化,使得e1和e2类型相同。
    在这里插入图片描述
    解决办法二:显示实例化,下面就来介绍一下什么是显示实例化。

  2. 显示实例化:在函数名后的<>中指定模板参数的实际类型。
    在调用 Add<int> 时,将 e2 转换为 int 类型。
    在调用 Add<double> 时,将 e1 转换为 double 类型。
    通过显式类型转换,确保传递给模板函数的参数类型一致,从而避免编译错误。
    在这里插入图片描述

1.4 函数模板的实例化模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
    在这里插入图片描述
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
    在这里插入图片描述
  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
    在C++中,模板函数不会进行隐式类型转换来匹配参数类型,而普通函数会进行隐式类型转换。如果模板函数不能完全匹配参数类型,编译器将尝试调用非模板函数,这时可能会发生隐式类型转换。在这里插入图片描述

2. 类模板

  1. 类模板的定义格式:
    template<class T1, class T2, …, class Tn>
    class 类模板名
    {
    // 类内成员定义
    };

    这里,typename 是一个关键字,用于指定 T 是一个模板参数。你也可以使用 class 关键字来代替 typename,它们在这里是等价的。
    在类模板中,如果要在类外定义成员函数,则需要在定义成员函数时提供模板参数列表。这是为了让编译器知道这些函数是属于哪个模板类的实例。下面的例子,展示了如何在类外定义类模板的成员函数:


// 类模板定义
template <class T>
class MyClass 
{
private:
    T data;
public:
    MyClass(T d);       // 构造函数声明
    void Print();     // 成员函数声明
};

// 在类外定义构造函数
template <class T>
MyClass<T>::MyClass(T d) : data(d) {}

// 在类外定义成员函数
template <class T>
void MyClass<T>::Print() 
{
    cout << data << endl;
}
  1. 类模板的实例化
    类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。实例化类模板意味着根据模板定义创建具体的类,这些具体类使用特定的数据类型。
int main()
{
	MyClass<int> m1;
	MyClass<double> m1;
	return 0;
}

3. 非类型模板参数

模板参数分类类型形参与非类型形参
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
例如使用非类型形参来定义一个静态数组:

#include <iostream>
#include <assert.h>

using namespace std;
namespace zzb
{

	template<class T, size_t N = 10>
	class array
	{
	public:
		T& operator[](size_t index)
		{
			assert(index >= 0 && index < _size);
			return _nums[index];
		}
		const T& operator[](int index) const
		{
			assert(index >= 0 && index < _size);
			return _nums[index];
		}
		size_t size() const
		{
			return _size;
		}
		size_t empty()const 
		{
			return 0 == _size;
		}

	private:
		T _nums[N];
		size_t _size = N;
	};
}

int main()
{
	zzb::array<int, 10> nums;
	return 0;
}

ps:浮点数、类对象以及字符串是不允许作为非类型模板参数的,也就说只有整数类型(包括枚举)可以作为非类型模板参数
在这里插入图片描述
而且非类型模板参数必须在编译期确定,这意味着它们的值或大小必须在编译时就能确定,而不能依赖于运行时的计算或输入。这样做是为了在编译期间能够生成对应的代码,以便在程序运行时能够直接使用这些参数值

4. 模板的特化

4.1 概念

模板的特化是允许我们为特定类型提供定制的实现,通常情况下使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,在这些情况下是非常有用的。
比如:我们定义一个通用的模板函数来进行小于比较。

// 通用模板函数,用于比较两个值
template <typename T>
bool Less(const T& a, const T& b) 
{
    return a < b;
}

当我们调用函数模板来比较两个整数时,发现可以正常比较。
在这里插入图片描述
当传参传过去的是指针时,它是按照指针的大小来比较的,不是按照指针指向的内容来比比较的,不符合我们的预期。
在这里插入图片描述
可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,x是小于y的,但是Less内部并没有比较a和b指向的对象内容,而按照地址的大小比较的,这就无法达到预期而错误。此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。

4.2 函数模板特化

函数模板的特化步骤:
1.必须要先有一个基础的函数模板,它可以处理大多数情况。

// 通用模板函数,用于比较两个值
template <typename T>
bool Less(const T& a, const T& b) 
{
    return a < b;
}

2.关键字template后面接一对空的尖括号<>。
3.函数名后跟一对尖括号,尖括号中指定需要特化的类型。
4.函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

//函数模板的特化
template <>
bool Less<int*>(const int*& a, const int*& b) 
{
    return *a < *b;
}

此时传参再传过去指针时,它就是按照指针指向的内容来比较的,符合我们的预期。这是因为特化模板在模板实例化时会优先于通用模板
在这里插入图片描述
我们可以看出上面的特化版本看着特别奇怪 const 写在int* 后面,因此一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

bool Less(const int* a, const int* b)
{
	return *a < *b;
}

该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化

4.3 类模板特化

例如有如下专门用来按照小于比较的类模板Less:

template<class T>
struct Less
{
	bool operator()(const T& e1, const T& e2)
	{
		return e1 < e2;
	}
};

调用sort函数排序一个整数数组,发下可以直接排序。
在这里插入图片描述
调用sort函数排序一个整型指针数组,可以直接排序,但是结果错误。
在这里插入图片描述
同理,这里也需要提供int*的特化版本,而特化分为全特化与偏特化。

1.全特化:将模板参数列表中所有的参数都确定化。

template<>
struct Less<int*>
{
	bool operator()(int* e1, int* e2)
	{
		return *e1 < *e2;
	}
};

上面提供的就是int*的全特化版本,模板参数列表中所有的参数都确定化了,此时运行程序,结果正确。
在这里插入图片描述
2.偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
偏特化有以下两种表现方式

  • 参数更进一步的限制:偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
template<class T>
struct Less<T*>
{
	bool operator()(T* e1, T* e2)
	{
		return *e1 < *e2;
	}
};

上面提供的就是T的偏特化版本,对模板参数列表中的参数进行了更进一步的条件限制,不仅能匹配int类型的,是能匹配所有类型的指针。
此时运行程序,结果也是正确的。
在这里插入图片描述

  • 偏特化的另一种表现方式是部分特化:将模板参数类表中的一部分参数特化。
    例如有如下类模板:
template <typename T1, typename T2>
class MyClass 
{
public:
	void print()
	{
		cout << "MyClass <T1, T2>" << endl;
	}
};

对上面类进行部分特化:

template <typename T1>
class MyClass<T1, int>
{
public:
	void print()
	{
		cout << "MyClass <T1, int>" << endl;
	}
};

MyClass<T1, int> 是对第二个模板参数 T2 为 int 的情况进行的特化,在main 函数中,分别调用了基础模板和部分特化模板,可以看到编译器根据传入的类型选择了合适的模板版本。
在这里插入图片描述

5. 模板分离编译

分离编译:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。 关于编译链接的相关知识可以看下之前的写的文章:详解C语言的编译与链接,这里不再赘述。
而模板分离编译的分离编译是将模板的声明和定义分离到不同的文件中,通常是声明放到xxx.h,定义放到xxx.cpp。
假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:
1.我们首先在头文件中声明函数模板,但不包括具体的实现。

//Swap.h
template<class T>
void Swap(T& e1, T& e2);

2.然后创建一个单独的实现文件,包含模板的完整定义。


//Swap.cpp
#include "Swap.h"
template<class T>
void Swap(T& e1, T& e2)
{
	T tmp = e1;
	e1 = e2;
	e2 = tmp;
}

3.在使用模板的源文件test.cpp中,包含头文件Swap.h,然后调用模板Swap来完成两个数的交换。

//test.cpp
#include <iostream>
#include "Swap.h"
using namespace std;

int main()
{
	int x = 10, y = 20;
	cout << "交换前 x: " << x << " " << "y: " << y << endl;

	Swap(x, y);
	cout << "交换后 x: " << x << " " << "y: " << y << endl;
	return 0;
}

运行程序,发现报如下错误:
在这里插入图片描述
这是因为,一个C/C++程序要变成一个可执行程序,要经过如下过程:
在这里插入图片描述
而编译器对工程中的源文件是单独编译的,因此:
在这里插入图片描述

对于上面的问题有如下两个解决办法:
1.在模板定义的位置显式实例化(不推荐)
在这里插入图片描述
这种办法存在一定的弊端,当再有两个double类型的变量调用函数模板完成交换时,又会报错。
在这里插入图片描述
要想解决这个错误,就只能在模板定义的地方再去实例化一份Swap<double>的出来。因此每当出现一个新类型去调用这个模板的时候,都需要去模板定义的地方去显示实例化一份出来。这种显式实例化方式只适用于我们能预先知道所需类型的情况且这在泛型编程中并不常见,下面来介绍另一种解决方式。
2. 将声明和定义放到同一个文件 “xxx.hpp” 里面或者xxx.h(推荐)

//Swap.h
template<class T>
void Swap(T& e1, T& e2)
{
	T tmp = e1;
	e1 = e2;
	e2 = tmp;
}

这也就意味着,当在test.cpp中包含Swap.h的以后,在test.cpp中可以找到函数模板的完整定义,因此可以根据需求实例化出任意需要的函数,就不会报链接错误了。

在这里插入图片描述

6. 总结

关于模板的优缺点总结如下:
优点:
1.代码复用:通过模板,可以编写通用的代码,而不需要为每个数据类型编写单独的代码,实现了代码的高效复用。模板的使用加速了开发过程,因为可以更容易地引入新的数据类型,而无需修改大量现有代码。C++ 标准模板库(STL)的产生就是模板技术的重要应用之一,提供了大量高效的容器、算法和迭代器,极大地提高了开发效率。
增强代码的灵活性:
2.泛型编程:模板允许编写与类型无关的代码,可以处理不同类型的数据,增强了代码的通用性和灵活性。
缺陷:
1.代码膨胀:模板的实例化会导致生成多个实例代码,可能导致二进制文件变大,尤其是在大型项目中。模板实例化需要更多的编译时间,特别是当模板被广泛使用时,编译时间会显著增加。
2. 模板编译错误信息复杂:模板编译错误信息通常非常复杂且冗长,不易理解和调试,定位错误可能需要更多的时间和经验。

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。

创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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

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

相关文章

Unity自定义场景背景图片

Unity自定义场景背景图片 1.更改图片材质 2.新建空对象并进行组件的添加、图层的设置 3.隐藏图层 4.对原有摄像机进行设置 5.新建摄像机&#xff0c;并进行设置 6.对步骤2新建的空物体大小进行设置&#xff0c;直至铺满整个屏幕

自学鸿蒙HarmonyOS的ArkTS语言<六>警告弹窗AlertDialog和列表选择弹窗ActionSheet

一、警告弹窗 ... Button(点击我可以获取一个警告弹窗).onClick(() > {AlertDialog.show({title: 我是弹窗标题,subtitle: 我是副标题,message: 我是弹窗内容,autoCancel: true, // 点击遮罩层是否关闭alignment: DialogAlignment.Center, // 弹窗位置offset: { dx: 0, dy:…

sed -i会破坏软连接

一、【写在前面】 开门见山&#xff0c;通过 sed - i 修改软连接指向的文件会破坏软连接 最近在管理本人的ansible项目的时候&#xff0c;发现了这个问题 二、【问题说明】 这是一个原本的软连接&#xff0c;码有点多&#xff0c;但是可以看出来指向了一个ini文件 然后我们…

Linux-Cgroup V2 初体验

本文主要记录 Linux Cgroup V2 版本基本使用操作&#xff0c;包括 cpu、memory 子系统演示。 1. 开启 Cgroup V2 版本检查 通过下面这条命令来查看当前系统使用的 Cgroups V1 还是 V2 stat -fc %T /sys/fs/cgroup/如果输出是cgroup2fs 那就是 V2&#xff0c;就像这样 roott…

AIGC产品经理学习路径

基础篇&#xff08;课时 2 &#xff09; AIGC 行业视角 AIGC 的行业发展演进&#xff1a;传统模型/深度学习/大模型 AIGC 的产品设计演进&#xff1a;AI Embedded / AI Copilot / AI Agen AIGC 的行业产业全景图 AIGC 的产品应用全景图 AIGC 职业视角 AI 产品经理/ AIGC…

怎么搭建微信商城

在当今这个数字化时代&#xff0c;微信已成为人们日常生活中不可或缺的一部分&#xff0c;它不仅改变了我们的社交方式&#xff0c;更引领了商业营销的新潮流。微信商城作为微信生态内的一个重要组成部分&#xff0c;正以其独特的优势助力商家们实现线上销售的突破。本文将带您…

Seven layers of the metaverse

看到一篇关于元宇宙的文章&#xff0c;分享给大家&#xff0c;供大家参考。 随着物理世界和数字世界的融合&#xff0c;元宇宙正在推动我们数字能力的新边界。从人类身份、个性和声誉到资产、情感和历史&#xff0c;元宇宙的虚拟现实中可以以全新的方式进行交互、控制和体验。因…

华为怎么录屏?分享4个方法,教你轻松录屏

“最近新买了一台华为手机&#xff0c;但是我对华为手机的功能不是很熟练。听身边的朋友说华为手机有很多种录屏的方法&#xff0c;我却找不到&#xff0c;想问一下大家华为怎么录屏呢&#xff1f;麻烦知道的朋友教一下我&#xff0c;先谢谢大家啦&#xff01;” 在数字化飞速…

BGP第二日

上图为今日所用拓扑 &#xff0c;其中R1和R4&#xff0c;R3和R5为EBGP邻居&#xff0c;R1和R3为IBGP邻居&#xff0c;AS200区域做OSPF动态路由 一.BGP建立邻居的六种状态 1.idle 空闲状态&#xff1a;建立邻居最初的状态 2.Connect 连接状态&#xff1a;在…

Cadence23打开与关闭飞线,修改位号丝印大小

打开与关闭所有飞线&#xff1a; 显示部分飞线&#xff1a; 单独显示网络飞线尤为好用&#xff0c;点击上图中的网络&#xff0c;之后鼠标点击器件中你想高亮的网络即可单独打开部分飞线。 这里的关闭部分网络的飞线也很好用&#xff0c;可以临时关闭讨厌的GND飞线&#xff1a…

前端技术学习记录-基础知识(二)JavaScript基本语法

基本语法 变量 JavaScript是一门动态弱类型语言 动态弱类型语言&#xff1a;变量可以存放在不同类型的值&#xff08;动态&#xff09; 例如&#xff1a; <script> var a 100;//数字 a "hahaha";//字符串 </script> 动态弱类型相对应的是静态强类型 …

相机镜头、焦距与视野

随着非标准传感器尺寸的数码相机的出现&#xff0c;人们似乎对焦距、视场和数字倍增器以及它们之间的关系产生了许多困惑。本文旨在尝试消除一些困惑。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程…

2024 年 6 月公链行业研报:市场回调,比特币和以太坊 Layer 2 表现各异

作者&#xff1a;stellafootprint.network 数据来源&#xff1a;公链 Research 页面 六月&#xff0c;加密货币市场经历了显著的挑战。比特币因即将到来的 Mt. Gox 赔偿支付及政府清算的压力&#xff0c;导致市场不确定性加剧。尽管美国现货以太坊 ETF 的推进带来了积极信号…

Doris安装部署

1、 MPP概念 MPP (Massively Parallel Processing)&#xff0c;即大规模并行处理&#xff0c;在数据库非共享集群中&#xff0c;每个节点都有独立的磁盘存储系统和内存系统&#xff0c;业务数据根据数据库模型和应用特点划分到各个节点上&#xff0c;每台数据节点通过专用网络…

代码随想录(day4)-移除链表元素

题目&#xff1a; 注意&#xff1a;因为可能存在头部元素就是val值&#xff0c;所以可以设置虚拟头部元素&#xff0c;且要保证头部元素不为空。如果头部元素为空&#xff0c;那么又怎么指向下一个元素呢。注意是nexthead! 判断是不是等于val时&#xff0c;是current.next.val…

广电影视NAS共享非编存储磁盘阵列

影视制作通常会涉及大量的视频、音频、图像以及各类素材的处理&#xff0c;因此往往需要制作团队来协作完成。那么影视存储能否做到高效、方便、安全的共享&#xff0c;就成为了影视项目按时交付的关键。GS G3影视共享非编存储解决方案文件级性能可达13.5GB/s的读&#xff0c;5…

YOLOv10改进 | 特殊场景检测篇 | 轻量级的低照度图像增强网络IAT改进YOLOv10暗光检测(全网独家首发)

一、本文介绍 本文给大家带来的改进机制是轻量级的变换器模型&#xff1a;Illumination Adaptive Transformer (IAT)&#xff0c;用于图像增强和曝光校正。其基本原理是通过分解图像信号处理器&#xff08;ISP&#xff09;管道到局部和全局图像组件&#xff0c;从而恢复在低光…

ret2csu简单总结

一个比较进阶的rop利用方式。 Why ret to csu&#xff1f; 当程序给的gadget不够&#xff0c;或者输入长度受限时&#xff0c;可以考虑利用csu中的众多gadget以及一个call指令来劫持控制流。 __libc_csu_init 汇编源码: .text:0000000000400790 ; void __fastcall _libc_c…

el-upload 上传多个图片或多个文件,编辑,回显,删除操作

后端查询详情接口数据&#xff1a; [{"id": 91,"name": "Default","sort": 0,"fold": false,"deletable": false,"uniqueId": "machine_cabinet","infoList": [{"id": …

Qt打包软件

打包 基础打包 要配置好qt的环境变量 在编译好的release目录下面执行 windeployqt myapp.exe复杂打包 下载软件:http://www.jrsoftware.org/isdl.php#stable &#xff08;1&#xff09;打开 Inno Setup Complier&#xff0c;点击 【file】→【new】&#xff0c;新建一个脚…