设计模式之单例模式(C++)
作者:翟天保Steven
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

一、单例模式是什么?
单例模式是一种创建型的软件设计模式,在工程项目中非常常见。通过单例模式的设计,使得创建的类在当前进程中只有一个实例,并提供一个全局性的访问点,这样可以规避因频繁创建对象而导致的内存飙升情况。
实现单例模式的三个要点:
1)私有化构造函数:这样外界就无法自由地创建类对象,进而阻止了多个实例的产生。
2)类定义中含有该类的唯一静态私有对象:静态变量存放在全局存储区,且是唯一的,供所有对象使用。
3)用公有的静态函数来获取该实例:提供了访问接口。
单例模式一般分为懒汉式和饿汉式。
1)懒汉式:在使用类对象(单例实例)时才会去创建它,不然就懒得去搞。
2)饿汉式:单例实例在类装载时构建,有可能全局都没使用过,但它占用了空间,就像等着发救济粮的饿汉提前排好队等吃的一样。
二、懒汉式实现
2.1 懒汉基础实现
最基本的懒汉实现方法。
//Singleton.h
/****************************************************/
#include <iostream>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance(){// 若为空则创建if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance = new Singleton();cout << "创建结束。" << endl;}else {cout << "已有实例,返回。" << endl;}return instance;}
private:// 私有构造函数Singleton(){cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton(){cout << "析构函数启动。" << endl;};
private:// 静态私有对象static Singleton* instance;
};// 初始化
Singleton* Singleton::instance = nullptr;
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();Singleton* s3 = Singleton::getInstance();cout << "main结束" << endl;return 0;
}
执行代码,让我们看看结果。

从结果中可以看出这样设计主要有两个问题,一个是线程安全,另一个是内存泄漏。
线程安全是因为在多线程场景下,有可能出现多个线程同时进行new操作的情况,没通过加锁来限制。
内存泄漏是因为使用了new在堆上分配了资源,那么在程序结束时,也应该进行delete,确保堆中数据释放。
接下来,我们先解决线程安全问题,对懒汉式实现进行改进。
2.2 基于双重检测锁的懒汉实现
通过双重检测锁,可以确保线程安全。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance(){// 若为空则创建if (instance == nullptr) {// 加锁保证线程安全// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance = new Singleton();cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;}
private:// 私有构造函数Singleton(){cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton(){cout << "析构函数启动。" << endl;};
private:// 静态私有对象static Singleton* instance;// 锁static mutex m_mutex;
};// 初始化
Singleton* Singleton::instance = nullptr;
mutex Singleton::m_mutex;
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {Singleton* s1 = Singleton::getInstance();});thread t2([] {Singleton* s2 = Singleton::getInstance();});t1.join();t2.join();Singleton* s3 = Singleton::getInstance();cout << "地址为:" << s3 << endl;cout << "main结束" << endl;return 0;
}
执行代码,让我们看看结果。

这样看来没有问题,那如果取消双重检测锁,在多线程下看看会发生什么。将代码部分函数修改为下。把锁注释掉,再查看地址信息。
// 公有接口获取唯一实例
static Singleton* getInstance(){// 若为空则创建if (instance == nullptr) {// 加锁保证线程安全// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕//lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance = new Singleton();cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;
}
此时结果中可以看出,两个线程进行了两次new操作,但是最后只能捕捉到最后一次new的地址信息了,前面的那个丢失了。。。。。

这个测试也是让大家直观地感受下双重检测锁的用处。
接下来,我们再解决内存泄漏(资源释放)问题,对懒汉式实现进行进一步的改进。
2.3 基于双重检测锁和资源管理的懒汉实现
在2.2的基础上,我们加入资源管理机制,以达到对资源的释放的目的,解决方法有两个:智能指针&静态嵌套类。
2.3.1 智能指针方案
将实例指针更换为智能指针,另外智能指针在初始化时,还需要人为添加公有的毁灭函数,因为析构函数私有化了。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static shared_ptr<Singleton> getInstance(){// 若为空则创建if (instance == nullptr) {// 加锁保证线程安全// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance.reset(new Singleton(), destoryInstance);cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;}// 毁灭实例static void destoryInstance(Singleton* x) {cout << "自定义释放实例" << endl;delete x;}
private:// 私有构造函数Singleton(){cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton(){cout << "析构函数启动。" << endl;};
private:// 静态私有对象static shared_ptr<Singleton> instance;// 锁static mutex m_mutex;
};// 初始化
shared_ptr<Singleton> Singleton::instance;
mutex Singleton::m_mutex;
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {shared_ptr<Singleton> s1 = Singleton::getInstance();});thread t2([] {shared_ptr<Singleton> s2 = Singleton::getInstance();});t1.join();t2.join();shared_ptr<Singleton> s3 = Singleton::getInstance();cout << "地址为:" << s3 << endl;cout << "main结束" << endl;return 0;
}
应用智能指针后,在程序结束时,它自动进行资源的释放,解决了内存泄漏的问题。

2.3.2 静态嵌套类方案
类中定义一个嵌套类,初始化该类的静态对象,当程序结束时,该对象进行析构的同时,将单例实例也删除了。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance() {// 若为空则创建if (instance == nullptr) {// 加锁保证线程安全// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance = new Singleton();cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};// 定义一个删除器class Deleter {public:Deleter() {};~Deleter() {if (instance != nullptr) {cout << "删除器启动。" << endl;delete instance;instance = nullptr;}}};// 删除器是嵌套类,当该静态对象销毁的时候,也会将单例实例销毁static Deleter m_deleter;
private:// 静态私有对象static Singleton* instance;// 锁static mutex m_mutex;
};// 初始化
Singleton* Singleton::instance = nullptr;
mutex Singleton::m_mutex;
Singleton::Deleter Singleton::m_deleter;
main.h同2.2中的一致,结果如下,可以看出,当嵌套类Deleter对象销毁时,其析构函数执行的实例删除操作也完成了。

2.4 基于局部静态对象的懒汉实现
C++11后,规定了局部静态对象在多线程场景下的初始化行为,只有在首次访问时才会创建实例,后续不再创建而是获取。若未创建成功,其他的线程在进行到这步时会自动等待。注意C++11前的版本不是这样的。
因为有上述的改动,所以出现了一种更简洁方便优雅的实现方法,基于局部静态对象实现。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton& getInstance() {cout << "获取实例" << endl;static Singleton instance;cout << "地址为:" << &instance << endl;return instance;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};
};
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {Singleton &s1 = Singleton::getInstance();});thread t2([] {Singleton &s2 = Singleton::getInstance();});t1.join();t2.join();cout << "main结束" << endl;return 0;
}
从结果中可以看出,构造函数启动了一次,另一个线程直接获取了地址。并且当程序结束时,进行了自动释放。

三、饿汉式实现
3.1 饿汉基础实现
饿汉和懒汉的差别就在于,饿汉提前进行了创建,所以它的基础实现也不是很复杂,如下所示。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance() {cout << "获取实例" << endl;cout << "地址为:" << instance << endl;return instance;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};
private:// 静态私有对象static Singleton* instance;
};// 初始化
Singleton* Singleton::instance = new Singleton();
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {Singleton* s1 = Singleton::getInstance();});thread t2([] {Singleton* s2 = Singleton::getInstance();});t1.join();t2.join();cout << "main结束" << endl;return 0;
}
输出结果中可知,main还没开始,实例就已经构建完毕,获取实例的函数也不需要进行判空操作,因此也就不用双重检测锁来保证线程安全了,它本身已经是线程安全状态了。

但是内存泄漏的问题还是要解决的,这点同懒汉是一样的。
3.2 基于资源管理的饿汉实现
内存泄漏解决方法有两个:智能指针&静态嵌套类。
3.2.1 智能指针方案
将实例指针更换为智能指针,另外智能指针在初始化时,还需要人为添加公有的毁灭函数,因为析构函数私有化了。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static shared_ptr<Singleton> getInstance() {cout << "获取实例" << endl;cout << "地址为:" << instance << endl;return instance;}// 毁灭实例static void destoryInstance(Singleton* x) {cout << "自定义释放实例" << endl;delete x;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};
private:// 静态私有对象static shared_ptr<Singleton> instance;
};// 初始化
shared_ptr<Singleton> Singleton::instance(new Singleton(), destoryInstance);
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {shared_ptr<Singleton> s1 = Singleton::getInstance();});thread t2([] {shared_ptr<Singleton> s2 = Singleton::getInstance();});t1.join();t2.join();cout << "main结束" << endl;return 0;
}
加入了智能指针后,不出意外地进行了自动的资源释放。

3.2.2 静态嵌套类方案
类中定义一个嵌套类,初始化该类的静态对象,当程序结束时,该对象进行析构的同时,将单例实例也删除了。
//Singleton.h
/****************************************************/
#include <iostream>
#include <mutex>using namespace std;// 单例模式演示类
class Singleton
{
public:// 公有接口获取唯一实例static Singleton* getInstance() {cout << "获取实例" << endl;cout << "地址为:" << instance << endl;return instance;}
private:// 私有构造函数Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数~Singleton() {cout << "析构函数启动。" << endl;};// 定义一个删除器class Deleter {public:Deleter() {};~Deleter() {if (instance != nullptr) {cout << "删除器启动。" << endl;delete instance;instance = nullptr;}}};// 删除器是嵌套类,当该静态对象销毁的时候,也会将单例实例销毁static Deleter m_deleter;
private:// 静态私有对象static Singleton* instance;
};// 初始化
Singleton* Singleton::instance = new Singleton();
Singleton::Deleter Singleton::m_deleter;
//main.h
/****************************************************/
#include <iostream>
#include "Singleton.h"using namespace std;int main()
{cout << "main开始" << endl;thread t1([] {Singleton* s1 = Singleton::getInstance();});thread t2([] {Singleton* s2 = Singleton::getInstance();});t1.join();t2.join();cout << "main结束" << endl;return 0;
}
同懒汉的一样,不多做阐述。

四、总结
上述讲了这么多关于单例模式的内容,我尽可能地将测试的结果也同步展示了,目的就是帮助大家更好地理解。文中所有的代码都是完整的,可以直接复制到自己的项目中测试验证下。
最后,如果说让我选择用什么样的实现,那我选择用局部静态对象的方法,代码简洁,线程安全,内存无泄漏,有什么理由说不呢?除非你是C++11之前的版本。。。。
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!
相关文章:
设计模式之单例模式(C++)
作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 一、单例模式是什么? 单例模式是一种创建型的软件设计模式,在工程项目中非常常见。通过单例模式的设计&am…...
贪心算法(基础)
目录 一、什么是贪心? (一)以教室调度问题为例 1. 问题 2. 具体做法如下 3. 因此将在这间教室上如下三堂课 4. 结论 (二)贪心算法介绍 1. 贪心算法一般解题步骤 二、最优装载问题 (一…...
【九宫格坐标排列 Objective-C语言】
一、这个案例做好之后的效果如图: 1.这个下载是可以点击的,当你点击之后,弹出一个框,过一会儿,框框自动消失,这里变成“已安装” 2.那么,我现在先问大家一句话:大家认为在这一个应用里面,它包含几个控件, 3个,哪3个:一个是图片框,一个是Label,一个是按钮, 这…...
Tomcat简介
目录 一、Tomcat简介 二、下载安装Tomcat 三、利用Tomcat部署静态页面 一、Tomcat简介 Tomcat是一个HTTP服务器,可以按照HTTP的格式来解析请求来调用用户指定的相关代码然后按照HTTP的格式来构造返回数据。 二、下载安装Tomcat 进入Tomcat官网选择与自己电脑…...
Python基础及函数解读(深度学习)
一、语句1.加注释单行注释:(1)在代码上面加注释: # 后面跟一个空格(2)在代码后面加注释:和代码相距两个空格, # 后面再跟一个空格多行注释:按住shift 点击三次"&am…...
车道线检测-PolyLaneNet 论文学习笔记
论文:《PolyLaneNet: Lane Estimation via Deep Polynomial Regression》代码:https://github.com/lucastabelini/PolyLaneNet地址:https://arxiv.org/pdf/2004.10924.pdf参考:https://blog.csdn.net/sinat_17456165/article/deta…...
GO——接口(下)
接口接口值警告:一个包含空指针值的接口不是nil接口sort.Interface接口http.Handler接口类型断言类型分支接口值 接口值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。对于像Go语言这种静态类型的语言&…...
计算机网络之http02| HTTPS HTTP1.1的优化
post与get请求的区别 get 是获取资源,Post是向指定URI提交资源,相关信息放在body里 2.http有哪些优点 (1)简单 报文只有报文首部和报文主体,易于理解 (2)灵活易拓展 URI相应码、首部字段都没有…...
基于matlab使用神经网络清除海杂波
一、前言此示例演示如何使用深度学习工具箱™训练和评估卷积神经网络,以消除海上雷达 PPI 图像中的杂波返回。深度学习工具箱提供了一个框架,用于设计和实现具有算法、预训练模型和应用程序的深度神经网络。二、数据集该数据集包含 84 对合成雷达图像。每…...
每天10个前端小知识 【Day 8】
前端面试基础知识题 1. Javascript中如何实现函数缓存?函数缓存有哪些应用场景? 函数缓存,就是将函数运算过的结果进行缓存。本质上就是用空间(缓存存储)换时间(计算过程), 常用于…...
【项目精选】基于Java的敬老院管理系统的设计和实现
本系统主要是针对敬老院工作人员即管理员和员工设计的。敬老院管理系统 将IT技术为养老院提供一个接口便于管理信息,存储老人个人信息和其他信息,查找 和更新信息的养老院档案,节省了员工的劳动时间,大大降低了成本。 其主要功能包括: 系统管理员用户功能介绍&#…...
Spark SQL 介绍
文章目录Spark SQL1、Hive on SparkSQL2、SparkSQL 优点3、SparkSQL 特点1) 容易整合2) 统一的数据访问3) 兼容 Hive4) 标准的数据连接4、DataFrame 是什么5、DataSet 是什么Spark SQL Spark SQL 是 Spark 用于结构化数据(structured data) 处理的Spark模块。 1、Hive on Spa…...
升级到 CDP 后Hive on Tez 性能调整和故障排除指南
优化Hive on Tez查询永远不能以一种万能的方法来完成。查询的性能取决于数据的大小、文件类型、查询设计和查询模式。在性能测试期间,要评估和验证配置参数和任何 SQL 修改。建议在工作负载的性能测试期间一次进行一项更改,并且最好在生产环境中使用它们…...
理解HDFS工作流程与机制,看这篇文章就够了
HDFS(The Hadoop Distributed File System) 是最初由Yahoo提出的分布式文件系统,它主要用来: 1)存储大数据 2)为应用提供大数据高速读取的能力 重点是掌握HDFS的文件读写流程,体会这种机制对整个分布式系统性能提升…...
Intel处理器分页机制
分页模式 Intel 64位处理器支持3种分页模式: 32-bit分页PAE分页IA-32e分页 32-bit分页 32-bit分页模式支持两种页面大小:4KB以及4MB。 4KB页面的线性地址转换 4MB页面的线性地址转换 PAE分页模式 PAE分页模式支持两种页面大小:4KB以及…...
Linux常用命令
linux常用命令创建一个目录mkdir 命令可以创建新目录。mkdir 是 make directory 的缩写。[rootiZ2ze66tzux2otcpbvie88Z ~]# ls [rootiZ2ze66tzux2otcpbvie88Z ~]# mkdir web [rootiZ2ze66tzux2otcpbvie88Z ~]# ls web [rootiZ2ze66tzux2otcpbvie88Z ~]# 创建一个文件2.1 在 Li…...
基于STM32设计的音乐播放器
一、项目背景与设计思路 1.1 项目背景 时代进步,科学技术的不断创新,促进电子产品的不断更迭换代,各种新功能和新技术的电子产品牵引着消费者的眼球。人们生活水平的逐渐提高,对娱乐消费市场需求日益扩大,而其消费电子产品在市场中的占有份额越来越举足轻重。目前消费电…...
微服务开发
目录 微服务配置管理 权限认证 批处理 定时任务 异步 微服务调用 (协议)...
【(C语言)数据结构奋斗100天】二叉树(上)
【(C语言)数据结构奋斗100天】二叉树(上) 🏠个人主页:泡泡牛奶 🌵系列专栏:数据结构奋斗100天 本期所介绍的是二叉树,那么什么是二叉树呢?在知道答案之前,请大家思考一下…...
Java 验证二叉搜索树
验证二叉搜索树中等给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下:节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。示例 1&…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
es6+和css3新增的特性有哪些
一:ECMAScript 新特性(ES6) ES6 (2015) - 革命性更新 1,记住的方法,从一个方法里面用到了哪些技术 1,let /const块级作用域声明2,**默认参数**:函数参数可以设置默认值。3&#x…...
