设计模式在FileBrowser中的几个应用
设计模式是代码重构的最终目标,在程序设计中有效的运用这项技术,可以大大提高代码的可读性和可维护性。使整个程序设计结构趋向精致完美。在我维护的FileBrowser模块中可以针对以下方面
应用相应的模式。
1. 使用策略模式来处理文件夹扫描操作
作为网络文件浏览器,FileBrowser中自然有很多对文件夹的操作。包括计算文件夹信息,删除文件夹等,这些操作采用的都是标准的文件夹遍历的代码,只是对于各文件/文件夹进行不同的处理而已,基本代码如下:
HANDLE fp = FindFirstFile(szLocalFName, &fd);
while(TRUE)
{
if(fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
{
Handle the folder…
}
Else
{
Handle the file…
}
if(!FindNextFile(fp, &Fd))
break;
}
显而易见,这里很适合使用策略模式:定义一个通用的扫描函数,定义策略类架构,使每个策略对象对应一个文件处理方法。这样可以避免模块中大量的相似的文件夹遍历的代码,使之简洁明了,而且更加容易扩充。
我们看一下策略模式的定义:
结构图:
基于这个思想,我设计了以下文件夹扫描结构:
class folder_handler
{
public:
folder_handler(const tstring& root) : m_root(root){}
virtual ~folder_handler(){}
virtual bool handle_folder
(const tstring&, const WIN32_FIND_DATA&){ return true; }
virtual bool handle_file(const tstring&, const WIN32_FIND_DATA&)
{ return true; }
virtual bool folder_first(){ return true; }
protected:
tstring m_root;
};
void scan_folder(const tstring& folder_path, folder_handler& h)
{
tstring child_path(_T(""));
string find_name = folder_path + _T(“//*.*”);
WIN32_FIND_DATA fd = {0};
HANDLE hFindFile = FindFirstFile(find_name.c_str(), &fd);
if (INVALID_HANDLE_VALUE == hFindFile)
{
cout << "Cann't access folder " << folder_path << endl;
return;
}
// 先处理父文件夹
if (h.folder_first())
{
h.handle_folder(folder_path);
}
do
{
if (_T('.') == fd.cFileName[0])
{
continue;
}
child_path = folder_path + _T("//") + fd.cFileName;
if (FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes)
{
scan_folder(child_path, h);
}
else
{
m_numfiles++;
m_foldersize+=
MAKELONGLONG(fd.nFileSizeLow, fd.nFileSizeHigh);
h.handle_file(child_path, fd);
}
} while(FindNextFile(hFindFile, &fd));
FindClose(hFindFile);
// 后处理父文件夹
if (!h.folder_first())
{
h.handle_folder(folder_path);
}
}
这样我们想对文件夹做某种特定操作的时候,只需要定义一个新类,指定对子文件/子文件夹的特定操作即可,而不需要关注其遍历过程的细节,耦合度非常的低。例如我们想删除文件夹和扫描文件夹同步信息的话那么就可以定义以下两个类。
class folder_deleter : public folder_handler
{
public:
folder_deleter(const tstring& parent_folder)
: folder_handler(parent_folder)
{
}
virtual bool handle_folder
(const tstring& folder_path, const WIN32_FIND_DATA&)
{
if (!::RemoveDirectory(folder_path.c_str()))
{
_tcout << _T("Fail to remove the folder ") << folder_path << endl;
_tcout << _T("The error code ") << ::GetLastError() << endl;
return false;
}
return true;
}
virtual bool handle_file
(const tstring& file_path, const WIN32_FIND_DATA&)
{
if (!::DeleteFile(file_path.c_str()))
{
_tcout << _T("Fail to delete the file ") << file_path << endl;
_tcout << _T("The error code ") << ::GetLastError() << endl;
return false;
}
return true;
}
virtual bool folder_first(){ return false; }
};
class folder_inforecorder : public folder_handler
{
typedef struct
{
DWORD attrib;
LONGLONG size;
LONGLONG modified_time;
tstring path;
}file_info;
typedef vector<file_info> file_infos;
typedef file_infos::iterator fis_iter;
typedef file_infos::const_iterator fis_citer;
public:
folder_inforecorder(const tstring& parent_folder)
: folder_handler(parent_folder) {}
~folder_inforecorder(){}
virtual bool handle_file
(const tstring& folder_name, const WIN32_FIND_DATA& fd)
{
file_info fi;
fi.attrib = fd.dwFileAttributes;
fi.size = MAKELONGLONG(fd.nFileSizeLow, fd.nFileSizeHigh);
fi.modified_time =
MAKELONGLONG(fd.ftLastWriteTime.dwLowDateTime,
fd.ftLastWriteTime.dwHighDateTime);
tstring file_path = folder_name + _T("//") + fd.cFileName;
const tstring parentfolder = m_root + _T("//");
tstring::size_type pos = file_path.find(parentfolder);
if (tstring::npos == pos)
fi.path = file_path.c_str();
else
fi.path = file_path.substr(pos + parentfolder.size(),
tstring::npos).c_str();
m_FileInfos.push_back(fi);
return true;
}
public:
file_infos m_FileInfos;
};
使用的时候调用如下代码即可:
scanfolder scan
// 删除路径
scan.run_scan(folder_path, folder_deleter());
folder_inforecorder fir;
// 扫描路径文件信息
scan.run_scan(folder_path, fir);
2. 使用Command命令模式实现文件操作的Undo/Redo
FileBrowser对于文件操作的目前不支持Undo/Redo功能,用户所有的操作都是不可逆的,所以对于用户来说存在一定的操作风险以及不便。我们可以利用Command命令模式来实现这个功能:
将用户的命令封装成不同的对象,支持统一的接口Execute /UnExecute;定义一个命令管理类,支持Undo/Redo接口,维护一个执行命令对象链;每当用户完成一个操作之后,便生成相应的命令对象,将其加入命令对象链尾部,并将当前命令对象迭代器指向它。执行Undo/Redo功能时,根据当前命令对象迭代器调用当前命令对象Execute /UnExecute接口,并将当前命令对象迭代器在命令对象链中做相应的移动。
命令对象链操作示意图
| 当前命令对象迭代器 |
| 命令对象链 |
Figrure 3-1
命令对象类体系图
大致代码如下:
class CCommand
{
public:
CCommand(){}
virtual ~CCommand(){}
virtual void Execute() = 0;
virtual void UnExecute() = 0;
};
class CCommandManager
{
public:
CCommandManager() : m_CurPos(m_cCommandList.end()){}
virtual ~CCommandManager(){ Clear(); }
void AddCommand(CCommand* pcCommand)
{
ASSERT(pcCommand);
m_cCommandList.erase(m_cCommandList.begin(), m_CurPos);
m_cCommandList.insert(m_CurPos, pcCommand);
m_CurPos--;
}
BOOL CanUndo()
{
return (m_cCommandList.size() > 0 &&
m_CurPos != m_cCommandList.end());
}
BOOL CanRedo();
{
return (m_cCommandList.size() > 0
&& m_CurPos != m_cCommandList.begin());
}
CCommand* Undo()
{
CCommand* pcCommand = NULL;
if (CanUndo())
{
pcCommand = *m_CurPos;
ASSERT(pcCommand);
pcCommand->UnExecute();
m_CurPos++;
}
return pcCommand;
}
CCommand* Redo()
{
CCommand* pcCommand = NULL;
if (CanRedo())
{
m_CurPos--;
pcCommand = *m_CurPos;
ASSERT(pcCommand);
pcCommand->Execute();
}
return pcCommand;
}
void Clear();
private:
typedef list<CCommand*> commands;
typedef commands::iterator commands_iter;
commands m_cCommandList;// 命令对象指针容器
commands_iter m_CurPos;// 当前命令对象迭代器
};
比如我们想支持文件名命名操作Undo/Redo的话,定义以下类即可
class CCommand_Rename
{
public:
CCommand_Rename (){}
virtual ~CCommand_Rename (){}
virtual void Execute()
{
_trename(m_newfilename, m_oldfilename)
}
virtual void UnExecute()
{
_trename(m_oldfilename, m_newfilename);
}
private:
tstring m_oldfilename, m_newfilename;
};
3. 使用智能指针来管理内存(代理模式).
在程序中维护大量的指针,是非常头疼的问题。我们经常会忘记释放某些已分配内存的指针,特别是在一些复杂逻辑和很多出口的函数中,从而造成一些内存漏洞。而且有的地方我们为了保证释放内存,不得不写很多冗余的代码或者借助于一些不良的语法。
我们来看如下逻辑:
1) 先对指针A分配内存,A分配内存如果成功,那么对指针B分配内存,如果失败返回.
2) 如果B分配失败那么释放指针A返回,如果成功。那么对指针C分配内存.
3) 如果C分配失败那么释放指针A,B返回。如果成功对指针D分配内存.
4) 如果D分配失败那么释放指针A,B,C返回
…
一般的代码可能是这样
CA* A = new CA;
If (!A) return;
A->f1()
CB* B = new CB;
If (!B)
{
delete A;
return 0;
}
B-> f2();
CC* C = new CC;
If (!C)
{
delete A;
delete B;
return 0;
}
C-> f3();
CD* D = new CD;
If (!D)
{
delete A;
delete B;
delete C;
return 0;
}
…
这样的代码看上去非常的罗嗦,增加了代码阅读的困难,而且很容易遗漏一些内存释放的语句。
而有的程序员为了避免啰嗦,不得不借助于“goto”语句。
CA* A = NULL;
CB* B = NULL;
CC* C = NULL;
CD* D = NULL;
A = new CA;
if (!A) return 0;
A-> f1();
B = new CB;
if (!B)
{
goto CLEANUP;
}
B-> f2();
C = new CC;
if (!C)
{
goto CLEANUP;
}
C-> f3();
D = new CD;
if (!D)
{
goto CLEANUP;
}
…
CLEANUP:
if(A) delete A;
if(B) delete B;
if(C) delete C;
return 0;
但是goto语句早就已经是臭名昭著,其危害性很多文章里都有详细的描述,因此应该尽量避免,我们这里可以利用智能指针来避免这种尴尬。
auto_ptr<CA> A(new CA);
if (!&A) return 0;
A->f1();
auto_ptr<CB> B(new CB);
if (!&B) return 0;
B->f2();
auto_ptr<CC> C(new CC);
if (!&C) return 0;
C->f3();
auto_ptr<CD> D(new CD);
if (!&D) return 0;
…
这样代码简洁得多,而且由于它是在函数退出时,弹栈调用析构函数对所代理的指针进行删除,所以不会存在任何内存漏洞。
单件模式结构图:
4. 使用单件模式来管理资源
作为一个GUI程序,FileBrowser自然会和很多资源打交道,
目前这部分逻辑实现的不是很理想。有的地方通过全局变量有的是通过全局函数来访问资源.感觉上程序风格不是很统一,我们应该尽量避免使用全局变量。另外调用全局函数则每次都要加载释放资源动态库,比较的冗余低效。因为资源这块对于模块来讲只有唯一一个实例,而且可以被整个系统调用。所以可以使用单件模式来管理它。
定义一个资源管理管理类,保证其只有一个实例,模块中所有资源调用都是通过调用它的接口,结构图如下:
大致代码如下:
class CResManager
{
protected:
CResManager (){ LoadResouce(); }
~ CResManager (){FreeResouce (); }
public:
static CResManager& Instance()
{
static CResManager instance;
return instance;
}
void FsLoadString(UINT idString, LPTSTR lptsBuffer, DWORD dwLen);
void FsLoadBitMap(…);
void FsLoadMenu(HMENU* phMenu, UINT idMenu);
…
private:
void LoadResouce(){…}
void FreeResouce(){…}
private:
HINSTANCE m_hRes;
}
这样用户想读取一个字符串的话,调用以下语句即可
CResManager::Instance().FsLoadString(…);
整个模块的资源调用就显得统一简洁高效。
5. 使用备忘录模式来实现本地/远程目录的“前进/后退”功能
Windows自带的浏览器支持前进/后退的功能,用户可以来回浏览刚才访问过的路径,而不需要记住并敲入路径名,很是方便。其实利用备忘录模式,可以很容易的在FileBrowser中加入这一功能。
备忘录模式的机制在于定义一个对象状态的结构,当对对象进行操作之前,保存对象状态,撤销操作时根据保存的对象状态恢复此对象。这个描述看上去很像命令模式,不过命令模式保存的是对对象的操作而不是对象状态。
由于我们只要保存的文件夹路径这一单一信息,所以直接可以设置一个字符串链表即可,每次用户浏览新的路径,将当前路径名字符串保存到路径链表,并设置当前路径迭代器指向此字符串。用户前进/后退时,设置浏览器当前路径为路径迭代器所指向路径,并对路径迭代器做相应的调整。
路径链表操作示意图:
| 当前路径迭代器 |
| 路径字符串链表 |
大致代码如下:
class CPathControl
{
public:
CPathControl(void) : m_bInitialize(false) {}
~CPathControl(void){}
void CPathControl::Initialize(const tstring& path)
{
ASSERT(m_cPathList.empty());
m_cPathList.push_back(path);
m_CurPos = m_ PathList.begin();
m_bInitialize = true;
}
void AddNewPath(const tstring& path)
{
ASSERT(m_bInitialize);
m_CurPos = m_ cPathList.insert(++m_CurPos, path);
}
void GetCurPath(tstring& path)
{
ASSERT(m_bInitialize);
if (m_cPathList.size() > 0 && m_CurPos != m_cPathList.end())
(*m_CurPos).swap(path);.
}
BOOL CanMoveLast(void)
{
return (m_cPathList.size() > 0 && m_CurPos != m_cPathList.begin());
}
BOOL CanMoveNext(void)
{
list<st_viewinfo>::iterator iter = m_CurPos;
return (m_cPathList.size() > 0 && ++iter != m_cPathList.end());
}
void LastView(void)
{
if (!CanMoveLast())
m_CurPos--;
}
void NextView(void)
{
if (CanMoveNext())
m_CurPos++;
}
public:
list<tstring> m_cPathList;
list<tstring>::iterator m_CurPos;
private:
bool m_bInitialize;
};
class CFsSpDoc
{
…
Public:
void SetNewPath(const tstring& path)
{
m_cPathControl. AddNewPath(path);
BrowsePath(path);//浏览该路径
}
void Forward()
{
m_cPathControl.NextView();
tstring path(_T(“”));
m_cPathControl.GetPath(path);
BrowsePath(path); //浏览该路径
}
void Backward()
{
m_cPathControl.LastView();
tstring path(_T(“”));
m_cPathControl.GetPath(path);
BrowsePath(path);
}
…
private:
CPathControl m_cPathControl;
}
相关文章:
设计模式在FileBrowser中的几个应用
设计模式是代码重构的最终目标,在程序设计中有效的运用这项技术,可以大大提高代码的可读性和可维护性。使整个程序设计结构趋向精致完美。在我维护的FileBrowser模块中可以针对以下方面 应用相应的模式。 1. 使用策略模式来处理文件夹扫描操作 作为网…...
【JavaEE进阶】——Spring AOP
目录 🚩Spring AOP概述 🚩Spring AOP快速⼊⻔ 🎓引入AOP依赖 🎓编写AOP程序 🚩Spring AOP 详解 🎓Spring AOP核⼼概念 🎓通知类型 🎓PointCut 🎓切⾯优先级 Ord…...
Python - conda使用大全
如何使用Conda? 环境 创建环境 conda create -n spider_env python3.10.11查看环境 conda env listconda info -e激活环境 conda activate spider_env退出环境 conda deactivate删除环境 conda env remove -n spider_env包 导出包 说明:导出当前虚拟…...
ASPICE在汽车软件开发中的作用
ASPICE是一个专门为汽车软件开发过程而设计的评估和改进框架。它基于ISO/IEC 15504标准,为汽车供应商提供了一个评估和改进其软件开发流程的方法。ASPICE的目标是确保软件开发过程的一致性和可预测性,从而提高软件的质量和可靠性。 ASPICE的实施对汽车软…...
亚马逊云科技 re:Inforce 2024中国站大会
亚马逊云科技 re:Inforce 2024中国站大会 - 生成式AI时代的全面安全,将于7月25日本周四在北京富力万丽酒店揭幕...
Lottie:动态动画的魔法棒
文章目录 引言官网链接Lottie 的原理基础使用1. 导出动画2. 引入 Lottie 库3. 加载和播放动画 高级使用1. 动画控制2. 交互性3. 自定义动画例子:交互式按钮动画 优缺点优点缺点 结语 引言 Lottie 是 Airbnb 开源的一个动画库,它允许设计师在 Adobe Afte…...
IPython使用技巧整理
IPython 是一个增强的 Python 交互式 shell,它提供了许多便利的功能,比如自动补全、魔术命令、对象内省等。以下是 IPython 的一些使用技巧和示例,结合您提供的列表数据,我将给出一些相关的使用示例。 1. 自动补全(Tab…...
C#数组复习
一、一维数组 using System.Collections; using System.Collections.Generic; using UnityEngine;public class ShuZu : MonoBehaviour {#region 知识点一 基本概念//数组是存储一组相同类型数据的集合//数组分为 一维、二维、交错数组//一般情况 一维数组 就简称为数组#en…...
无人机之在农业上的用途
随着无人机技术的发展,农业现代化也迎来了崭新局面,田间随处可见无人机矫健的身影。当农业遇上科技,变革正悄然进行。农业无人机主要应用于农业、种植业、林业等行业。在使用过程中,其功能和作用并不单一,一般用于种植…...
opengaussdb在oepnEuler上安装
安装前提: 软件环境:openEuler 20.03LTS 个人开发者最低配置2核4G,推荐配置4核8G 数据库版本:openGauss-5.0.2-openEuler-64bit-all.tar.gz 数据库下载地址: https://docs-opengauss.osinfra.cn/zh/docs/5.0.0/docs/In…...
一些和颜色相关网站
1.中国传统色 2.网页颜色选择器 3.渐变色网站 4.多风格色卡生成 5.波浪生成 6.半透明磨砂框 7.色卡组合...
Linux系统编程-文件系统
目录 什么是Linux文件系统 文件系统的职责 存储介质抽象 inode:文件系统的核心 文件分配策略 目录结构 文件系统布局 日志和恢复机制 目录权限 粘滞位(t位): 硬链接和符号链接 硬链接的特点: 创建硬链接: 符号链接的…...
【解决】ubuntu20.04 root用户无法SSH登陆问题
Ubuntu root用户无法登录的问题通常可以通过修改SSH配置文件和系统登录配置来解决。 修改SSH配置文件 sudo vim /etc/ssh/sshd_config 找到 PermitRootLogin 设置,并将其值更改为 yes 以允许root用户通过SSH登录 保存并关闭文件之后,需要重启SSH服务…...
(前缀和) LeetCode 238. 除自身以外数组的乘积
一. 题目描述 原题链接 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&…...
【JVM基础05】——组成-能不能解释一下方法区?
目录 1- 引言:方法区概述1-1 方法区是什么?(What)1-2 为什么用方法区?方法区的作用 (Why) 2- ⭐核心:详解方法区(How)2-1 能不能解释一下方法区?2-2 元空间内存溢出问题2-3 什么是常量池?2-4 运行时常量池 …...
前端:Vue学习-3
前端:Vue学习-3 1. 自定义指令2. 插槽2.1 插槽 - 后备内容(默认值)2.2 插槽 - 具名插槽2.3 插槽 - 作用域插槽 3. Vue - 路由3.1 路由模块封装3.2 声明式导航 router-link 高亮3.3 自定义匹配的类名3.4 声明式导肮 - 跳转传参3.5 Vue路由 - 重…...
npm 安装报错(已解决)+ 运行 “wue-cli-service”不是内部或外部命令,也不是可运行的程序(已解决)
首先先说一下我这个项目是3年前的一个项目了,中间也是经过了多个人的修改惨咋了布置多少个人的思想,这这道我手里直接npm都安装不上,在网上也查询了多种方法,终于是找到问题所在了 问题1: 先是npm i 报错在下面图片&…...
江苏科技大学24计算机考研数据速览,有专硕复试线大幅下降67分!
江苏科技大学(Jiangsu University of Science and Technology),坐落在江苏省镇江市,是江苏省重点建设高校,江苏省人民政府与中国船舶集团有限公司共建高校,国家国防科技工业局与江苏省人民政府共建高校 &am…...
20分钟上手新版Skywalking 9.x APM监控系统
Skywalking https://skywalking.apache.org/ Skywalking是专为微服务、云原生和基于容器的(Kubernetes)架构设计的分布式系统性能监控工具。 Skywalking关键特性 ● 分布式跟踪 ○ 端到端分布式跟踪。服务拓扑分析、以服务为中心的可观察性和API仪表板。…...
【07】LLaMA-Factory微调大模型——微调模型导出与微调参数分析
上文介绍了如何对微调后的模型进行使用与简单评估。本文将介绍对微调后的模型进行导出的过程。 一、llama-3微调后的模型导出 首先进入虚拟环境,打开LLaMA-Factory的webui页面 conda activate GLM cd LLaMA-Factory llamafactory-cli webui 之后,选择…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...
Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...
