Re0: 从零实现一个解除文件占用的小工具

news2024/11/25 12:53:41

前言

相信大家或多或少都遇到过想删除一个文件,却提示被占用的情况:

image-20230915142102489

不知道各位都是如何处理的,反正我一直都是用的火绒😄。但是作为一名程序员,自己写一个小程序实现多有意思,是吧。况且为了一个小工具去安装一个杀毒软件,是一个合格的程序员,你们说对不对🤔。基于以上的原因,最终出现了这篇文章,效果如下,本文所对应的完整代码已上传到GitHub,可自行取用~~~

动画

一些可以使用的工具

在正式编码之前,这里先介绍一些已有的工具,如果想看编码实现,可以跳过本节。

火绒等杀毒软件

这里以火绒自带的工具为例,使用方式如下所示:

在这里插入图片描述

image-20230915150649563

通过火绒自带的工具,可以看到文件被什么程序占用了,然后进行解锁。

专用工具

UnlockerLockHunterIObit Unlocker,由于未实际使用过,这里不再展开介绍。

任务管理器

image-20230915152320763

image-20230915152310084

通过Windows 自带的任务管理器也可以查询文件的占用状态,缺点是无法只解锁文件,只能关闭占用的进程。

Sysinternals 下的 handle

Sysinternals 是 Windows 平台上使用的一个工具集合,可以监控系统的绝大部分文件,磁盘,网络,进程线程,模块,工具全集可以在微软官网进行下载,这里只讲解用于句柄操作的 Handle:

首先在官网进行下载,可以发现包含的文件很简单,exe 文件可以直接运行:

image-20230915153039272

在这里我们选择其中的 handle64 即可,首先以管理员身份运行终端,然后运行以下命令:

handle64 "C:\Users\xxx\Desktop\demo.gif"

image-20230915153740032

然后我们就可以看到上图所示的占用的程序进程号和对应的文件句柄,之后我们就可以运行以下命令去解除占用了,其中 1CE8 和 20392 分别是上述命令获取到的文件句柄和占用进程号:

handle64 -nobanner -c 1CE8 -y -p 20392

20230915154527

自己编码实现

以上讲解了一些解除文件占用的第三方功能,下面则开始步入正题,从零实现一个解除文件占用的小工具。

软硬件运行环境及工具

  • Windows11

  • Visual Studio 2022

  • Qt5.15.2/QML(用于展示简单结果文本,不了解 Qt 也没什么影响)

  • Inno Setup(用于创建程序的安装程序)

编码实现

首先说明以下程序的整体思路:程序初始判断是否有传参,如果无参说明程序是手动运行,执行添加注册表实现右键菜单包含解锁文件选项的逻辑。如果包含参数,说明程序是通过右键菜单运行的,根据传递的参数(即文件路径)执行相应的文件解锁操作。

以下不展示全部代码,完整代码可在前言中的GitHub查看,全部逻辑都在 main.cpp 中。

注册表功能实现

最终效果如下:

image-20230915160158253

image-20230915160218365

结合上图和以下代码即注释,相关代码不难理解,主要步骤如下:

  1. 添加名为unlockfile的注册键,包含两个键值,一个默认项解锁文件对应右键菜单显示的名称,一个Icon设置为应用程序的地址对应右键菜单显示的图标。
  2. unlockfile下添加名为command的子键,值是程序路径和 “%1”(对应传递的文件路径参数用于文件解锁操作)。

使用注册表时要特别注意文件编码,字符串类型转换的处理。

QVariant showInfo;
string appPath = QCoreApplication::applicationDirPath()
    .replace(QRegExp("/"), "\\").toStdString() + "\\unlockfile.exe";
if (setRightMenu("unlockfile", "解锁文件", appPath))
{
			showInfo = u8"注册表添加成功";
}
else
{
	showInfo = u8"注册表添加失败, 请确保以管理员身份运行";
}
QMetaObject::invokeMethod(root, "showInfo", Q_ARG(QVariant, showInfo));

/// <summary>
/// 设置右键菜单
/// </summary>
/// <param name="strRegKeyKey">注册键</param>
/// <param name="strRegKeyName">注册名</param>
/// <param name="strApplication">应用地址</param>
/// <returns>是否添加成功</returns>
bool setRightMenu(string strRegKeyKey, string strRegKeyName, string strApplication)
{
	HKEY hresult;
	string strRegKey = "*\\shell\\" + strRegKeyKey;
	string strRegSubkey = strRegKey + "\\command";
	string strApplicationValue = "\"" + strApplication +  "\"" + " \"%1\"";
	DWORD dwPos;
	// 创建注册表键, 对应右键菜单项
	if (RegCreateKeyEx(HKEY_CLASSES_ROOT, stringToWString(strRegKey.c_str()), 0,
		NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_ALL_ACCESS, NULL, &hresult, &dwPos) != ERROR_SUCCESS)
	{
		RegCloseKey(hresult);
		return false;
	}

	// 创建注册表值, 对应右键菜单项显示的内容
	if (RegSetValueEx(hresult, NULL, 0, REG_SZ, (BYTE*)stringToWString(strRegKeyName.c_str()), (wcslen(stringToWString(strApplicationValue.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
	{
		RegCloseKey(hresult);
		return false;
	}

	// 设置右键菜单图标
	if (RegSetValueEx(hresult, stringToWString("Icon"), 0, REG_SZ, (BYTE*)stringToWString(strApplication.c_str()), (wcslen(stringToWString(strApplication.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
	{
		RegCloseKey(hresult);
		return false;
	}

	// 创建注册表子项键, 对应点击右键菜单项后的命令项
	if (RegCreateKeyEx(HKEY_CLASSES_ROOT, stringToWString(strRegSubkey.c_str()), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_ALL_ACCESS, NULL, &hresult, &dwPos) != ERROR_SUCCESS)
	{
		RegCloseKey(hresult);
		return false;
	}

	// 创建注册表子项值, 对应点击右键菜单项后的具体执行命令
	if (RegSetValueEx(hresult, NULL, 0, REG_SZ, (BYTE*)stringToWString(strApplicationValue.c_str()), (wcslen(stringToWString(strApplicationValue.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
	{
		RegCloseKey(hresult);
		return false;
	}
	RegCloseKey(hresult);
	return true;
}

实现的效果如下,其中解锁文件就是我们创建的:

image-20230915160922756

解锁文件逻辑实现

这部分逻辑稍微复杂一些,具体步骤如下:

  1. 首先执行init()进行初始化的操作,包括加载 Native API 和遍历系统中所有句柄。
  2. 调用getFileObjectTypeNumber()获取文件句柄对应的编号(句柄有很多种,比如窗口、文件、图标和菜单),经测试,不同系统版本的编号也有所不同:win11: 40 win10: 37 win7: 28
  3. 遍历执行init()得到的系统所有句柄信息,只处理其中类型为文件且不属于系统进程的句柄。
  4. 对符合条件的文件句柄去获取其文件名,如果文件名和传递的文件名相同,则关闭相应的句柄即可实现解锁文件的效果,同时获取占用的进程路径展示给用户。

⚠️ 特别注意,在 ring3 级调用NtQueryObject会出现阻塞的情况,因此需要通过开一个线程增加超时处理,避免程序卡住。此外,由于是跨进程处理句柄,因此需要调用DuplicateHandle方法。

/// <summary>
/// 查询对象信息
/// </summary>
/// <param name="lpParam">参数</param>
/// <returns>返回值</returns>
DWORD queryObj(LPVOID lpParam)
{
    return NtQueryObject(hCopy, 1, pObject, MAX_PATH * 2, NULL);
}

/// <summary>
/// 获取文件名
/// </summary>
/// <param name="hCopy">文件句柄</param>
/// <param name="hCopy">文件名</param>
void getFileName(string& fileName)
{
    // 查找句柄对象信息并分配内存进行保存
    pObject = (POBJECT_NAME_INFORMATION)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * 2);
    if (pObject == 0)
    {
        HeapFree(GetProcessHeap(), 0, pObject);
        return;
    }

    // NtQueryObject 调用会出现阻塞, 启动线程增加超时处理
    HANDLE hThread = CreateThread(NULL, 0, queryObj, NULL, 0, NULL);
    if (hThread == 0)
    {
        HeapFree(GetProcessHeap(), 0, pObject);
        return;
    }
    DWORD dwSatus = WaitForSingleObject(hThread, 200);
    if (dwSatus == WAIT_TIMEOUT)
    {
        HeapFree(GetProcessHeap(), 0, pObject);
        return;
    }

    // 返回文件名
    if (pObject->NameBuffer != NULL)
    {
        DWORD n = WideCharToMultiByte(CP_OEMCP, NULL, pObject->NameBuffer, -1, NULL, 0, NULL, FALSE);
        char* name = new char[n + 1];
        memset(name, 0, n + 1);
        WideCharToMultiByte(CP_OEMCP, NULL, pObject->NameBuffer, -1, name, n, NULL, FALSE);
        fileName = name;
        delete[] name;
        HeapFree(GetProcessHeap(), 0, pObject);
        return;
    }
    HeapFree(GetProcessHeap(), 0, pObject);
    return;
}

/// <summary>
/// 初始化处理
/// </summary>
/// <returns>是否正常初始化</returns>
bool init()
{
    // 从 ntdll.dll 中加载 Native API: NtQuerySystemInformation 用于遍历获取系统信息
    HMODULE hNtDll = LoadLibrary(L"ntdll.dll");
    if (hNtDll == NULL)
    {
        return false;
    }
    NTQUERYSYSTEMINFOMATION NtQuerySystemInformation = (NTQUERYSYSTEMINFOMATION)GetProcAddress(hNtDll, "NtQuerySystemInformation");
    if (NtQuerySystemInformation == NULL)
    {
        return false;
    }

    // 用于获取操作系统中文件类型句柄对应的对象类型数字
    nulFileHandle = CreateFile(L"NUL", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
    if (nulFileHandle == NULL)
    {
        return false;
    }

    // 从 ntdll.dll 中加载 Native API: NtQueryObject 用于获取句柄对象信息
    NtQueryObject = (PNtQueryObject)GetProcAddress(hNtDll, "NtQueryObject");

    // 查找所有的句柄信息并分配内存进行保存
    DWORD nSize = 4096;
    pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), 0, nSize);
    while (NtQuerySystemInformation(SystemHandleInformation, pHandleInfo, nSize, NULL) == STATUS_INFO_LENGTH_MISMATCH)
    {
        HeapFree(GetProcessHeap(), 0, pHandleInfo);
        nSize += 4096;
        pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), 0, nSize);
    }
    if (pHandleInfo == NULL)
    {
        return false;
    }
    return true;
}

/// <summary>
/// 获取文件类型对应的对象编号, 经测试 win11: 40 win10: 37 win7: 28, 默认返回 win11 下的编码
/// </summary>
/// <returns>文件类型对应的对象编号</returns>
int getFileObjectTypeNumber()
{
    // 遍历所有的句柄
    for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
    {
        PSYSTEM_HANDLE pHandle = (PSYSTEM_HANDLE) & (pHandleInfo->HandleInfo[i]);

        if ((int)GetCurrentProcessId() == pHandle->ProcessId && pHandle->Handle == (USHORT)nulFileHandle)
        {
            return (int)pHandle->ObjectTypeNumber;
        }
    }
    return 40;
}

/// <summary>
/// 关闭文件
/// </summary>
/// <param name="closeFileName">关闭的文件名</param>
void closeFile(string& closeFileName)
{
    int fileObjectTypeNumber = getFileObjectTypeNumber();
    // 遍历所有的句柄
    for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
    {
        PSYSTEM_HANDLE pHandle = (PSYSTEM_HANDLE) & (pHandleInfo->HandleInfo[i]);
        // 只处理类型为文件且不属于系统进程(id 为 4)的句柄
        if (pHandle->ObjectTypeNumber != fileObjectTypeNumber || pHandle->ProcessId == 4 || pHandle->Handle == 0)
        {
            continue;
        }
        // 打开句柄对应的进行并进行复制用于后续操作
        HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pHandle->ProcessId);
        if (hProcess == NULL)
        {
            continue;
        }
        hCopy = 0;
        if (!DuplicateHandle(hProcess, (HANDLE)pHandle->Handle, GetCurrentProcess(), &hCopy, MAXIMUM_ALLOWED, FALSE, 0))
        {
            continue;
        }

        // 根据句柄获取文件名
        int pid = pHandle->ProcessId;
        string fileName;
        getFileName(fileName);
        if (fileName.find(closeFileName) != -1)
        {
            // 获取占用的进程名称
            WCHAR tmpName[MAX_PATH] = {};
            DWORD size = MAX_PATH;
            QueryFullProcessImageName(hProcess, 0, tmpName, &size);
            wStringToString(processName, tmpName);

            // 关闭占用的文件句柄
            HANDLE h_tar = NULL;
            if (DuplicateHandle(hProcess, (HANDLE)pHandle->Handle, GetCurrentProcess(), &h_tar, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
            {
                CloseHandle(h_tar);
            }
            CloseHandle(hCopy);
            CloseHandle(hProcess);
            return;
        }
        CloseHandle(hCopy);
        CloseHandle(hProcess);
    }
    HeapFree(GetProcessHeap(), 0, pHandleInfo);
    return;
}

界面展示实现

界面展示这里使用了 Qt 的 QML 进行实现,页面比较简单,包含以下两个界面。

主界面

主界面只是简单展示一下文本,其中文本会根据注册表添加成功或失败展示相应的信息(在注册表功能实现部分的代码开头可以看到)。

import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    id: w
    visible: true
    width: 320
    height: 120
    title: "unlockfile"

    function showInfo(infoText) {
        info.text = infoText
    }

    Text {
        id: info
        anchors.fill: parent
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        text: "Enjoy!"
    }
}
解锁界面

解锁界面稍微复杂一些,通过 Timer 定时器实现动态的查找中...展示,在解锁文件完成后会通过showFile函数展示占用的进程名。

import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    id: w
    visible: true
    width: 480
    height: 200
    title: "unlockfile"

    property bool run: true
    property int count: 0

    function showFile(fileText) {
        file.text = fileText
        run = false
    }

    Text {
        id: file
        anchors.fill: parent
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        text: "查找中"
    }

    Timer {
        interval: 1000
        running: run
        repeat: true
        onTriggered: {
            let str = ""
            for (let i = 0; i < count; i++) {
                str += "."
            }
            file.text = "查找中" + str
            count = (count + 1) % 4
        }
    }
}

其中设置进程名的代码操作在 main.cpp 文件中:

QThreadPool::globalInstance()->start([=]() {
	string fileName = gbkToUTF8(argv[1]).substr(3);
	if (init())
	{
		closeFile(fileName);
        string info = u8"解锁成功, 占用程序: " + processName;
        QMetaObject::invokeMethod(root, "showFile",
                                  Q_ARG(QVariant, QString::fromStdString(info)));
    }
});

制作安装程序

最后再介绍如何制作程序的安装程序,前提是需要先对 Qt 程序进行打包(此处省略 500 字),然后就可以使用Inno Setup工具进行制作了,步骤如下:

  1. 设置应用的名称版本:

    image-20230915164652480

  2. 设置应用的安装路径,同时允许用户进行自定义:

    image-20230915164914606

  3. 设置执行程序的路径和根文件夹路径:

  4. 之后全部点击下一步,然后在选择语言时按需选择:

    image-20230915165026664

  5. 然后可以设置程序的图标和安装程序输出路径,之后全部点击下一步即可:

    image-20230915165216300

  6. 然后就可以在输出路径看到生成的安装程序:

    image-20230915165529460

  7. 点击运行就是熟悉的程序安装界面了,按需进行选择后即可使用,同时需要以管理员身份运行:

    image-20230915165625703

安装程序也可以在GitHub中找到,目前只在 win10 和 win11 进行了测试。

总结

本文讲解了如何实现一个解除文件占用的小程序,不过还存在很多不完善的地方:

  • 注册表添加项无法自定义,同时未提供删除注册表的操作
  • 不是列出所有占用项让用户选择进行解锁
  • 只测试了 win10 和 win11 环境下的运行
  • 未实现批量解除文件占用的功能

不过相信各位参考本文的思路,实现以上的功能也是轻而易举,欢迎一起交流讨论~~~

所以,我还是选择使用火绒🤣。

参考文献

在实现这个小工具的过程中,踩了很多坑,非常感谢以下文章所提供的解决思路:

  • 用 Windows Native API 枚举所有句柄及查找文件句柄对应文件名的方法
  • C++解除文件占用
  • C++程序环境变量添加以及右键菜单
  • 跨进程关闭句柄的方式

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

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

相关文章

【微信小程序开发】宠物预约医疗项目实战-注册实现

【微信小程序开发】宠物预约医疗项目实战-注册实现 第二章 宠物预约医疗项目实战-注册实现 文章目录 【微信小程序开发】宠物预约医疗项目实战-注册实现前言一、打开项目文件二、编写wxss代码2.1 什么是wxss2.2 配置主程序全局样式 三. 在sign文件下的wxml文件中编写如下代码并…

使用qt完善对话框功能

1、 完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&#xff0c;给定两…

【深度学习实验】线性模型(五):使用Pytorch实现线性模型:基于鸢尾花数据集,对模型进行评估(使用随机梯度下降优化器)

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入库 1. 线性模型linear_model 2. 损失函数loss_function 3. 鸢尾花数据预处理 4. 初始化权重和偏置 5. 优化器 6. 迭代 7. 测试集预测 8. 实验结果评估 9. 完整代码 一、实验介…

大语言模型的机遇和挑战

自然语言处理包含自然语言理解和自然语言生成两个方面, 常见任务包括文本分类, 结构分析 (词法分析, 分词, 词性标注, 句法分析, 篇章分析), 语义分析, 知识图谱, 信息提取, 情感计算, 文本生成, 自动文摘, 机器翻译, 对话系统, 信息检索和自动问答等. 在神经网络方法出现之前,…

Vue3_vite

使用Vue-cli创建 使用vite创建 Composition API 组合API setup 1.Vue3中的一个新的配置项,值为一个函数 2.可以将组件中所用到的数据,方法等配置在setup中. 3.setup函数的两种返回值 3.1若返回一个对象,则对象中的属性,方法,在模板中均可以直接使用. 3.2若返回一个渲染函数…

Leetcode.337 打家劫舍 III

题目链接 Leetcode.337 打家劫舍 III mid 题目描述 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一个“父“房子与之相连。一番侦察之后&#xff0c;聪明的小偷意识到“这个地方的所有…

「聊设计模式」之建造者模式(Builder)

&#x1f3c6;本文收录于《聊设计模式》专栏&#xff0c;专门攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;带你早日登顶&#x1f680;&#xff0c;欢迎持续关注&&收藏&&订阅&#xff01; 前言 设计模式是众多优秀软件开发实践的总结和提炼&#xff0c;…

STM32 ADC介绍和应用

目录 1.ADC是什么&#xff1f; 2.ADC的性能指标 3.ADC特性 4.ADC通道 5.ADC转换顺序 6.ADC触发方式 7.ADC转化时间 8.ADC转化模式 扫描模式 单次转换/连续转换 9.ADC实验 使用ADC读取烟雾传感器的值 代码实现思路&#xff1a; 1.ADC是什么&#xff1f; 全称&#…

DMNet复现(一)之数据准备篇:Density map guided object detection in aerial image

一、生成密度图 密度图标签生成 采用以下代码&#xff0c;生成训练集密度图gt&#xff1a; import cv2 import glob import h5py import scipy import pickle import numpy as np from PIL import Image from itertools import islice from tqdm import tqdm from matplotli…

UG NX二次开发(C#)-计算直线到各个坐标系轴向的投影角度

文章目录 1、前言2、需求分析3、NXOpen方法实现3.1 创建基准坐标系3.2 然后计算直线到基准坐标系的轴向角度3.3 代码调用4、测试效果为:1、前言 最近有个粉丝问我如何计算直线到坐标系各个轴向的角度,这里用UG NX二次开发(C#)实现。当然,这里的内容是经验之谈,如果有更好的…

基于matlab实现的船舶横摇运动仿真程序

完整程序&#xff1a; clc clear syms w we; w0.4:0.05:1.6;mu90;v6;%kb1;kt1;%航速6m/s&#xff0c;航向90度&#xff0c;即横浪&#xff0c;cos(90)0 T3;B10;Sw0.785;%船宽10米&#xff0c;吃水3米,水线面系数假设为0.785 weww.^2.*v/9.8; for i1:24 delta_we(i)we(i1)-…

【计算机网络】——数据链路层(应用:局域网、广域网、设备 )

//仅做个人复习和技术交流&#xff0c;图片取自王道考研&#xff0c;侵删 一、大纲 1、介质访问控制 信道划分介质访问控制 随机访问介质访问控制 2、局域网 3、广域网 4、数据链路层设备 二、局域网 1、局域网基本概念和体系结构 局域网(LocalArea Network): 简称LAN&…

Stable Diffusion - 采样器 DPM++ 3M SDE Karras 与 SDXL Refiner 测试

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132978866 Paper: DPM-Solver: Fast Solver for Guided Sampling of Diffusion Probabilistic Models 扩散概率模型&#xff08;DPMs&#xff09;…

基于matlab实现的多普勒脉冲雷达回波仿真

完整程序&#xff1a; clear all;clc;close all; fc3e9; %载波频率 PRF2000; Br5e6; %带宽 fs10*Br; %采样频率 Tp5e-6; %脉宽 KrBr/Tp; %频率变化率 c3e8; %光速 lamda…

linux入门---共享内存

目录标题 共享内存的原理共享内存的理解shmget函数key和shmid的区别ipcs -m和shmctlshmatshmdt共享内存的通信共享内存的优点共享内存的缺点共享内存的特点 共享内存的原理 通过前面的内容我们知道不同的进程通过虚拟地址空间和页表能够将自己的数据映射到内存上的不同地方比如…

2023全新TwoNav开源网址导航系统源码 | 去授权版

2023全新TwoNav开源网址导航系统源码 已过授权 所有功能可用 测试环境&#xff1a;NginxPHP7.4MySQL5.6 一款开源的书签导航管理程序&#xff0c;界面简洁&#xff0c;安装简单&#xff0c;使用方便&#xff0c;基础功能免费。 TwoNav可帮助你将浏览器书签集中式管理&#…

Qt5开发及实例V2.0-第三章-Qt布局管理

Qt5开发及实例V2.0-第三章-Qt布局管理 第3章 Qt 5布局管理3.1 分割窗口QSplitter类3.2 停靠窗口QDockWidget类3.3 堆栈窗体QStackedWidget类3.4 基本布局&#xff08;QLayout&#xff09; 本章相关例程源码下载1.Qt5开发及实例_CH301.rar 下载2.Qt5开发及实例_CH302.rar 下载3.…

将json-bigint处理为数值分区数组的字段全部自动转为字符串

json-bigint虽然能帮我们处理好id 但 他的模式 显然不是直接可以用的 我们如果要到业务逻辑单独处理 那就太麻烦了 对系统也非常不友好 我们可以在vue项目中 src目录下创建一个utils 下面创建一个conversionLong.js 这个名字大家随便取 参考代码如下 var data {}; const Br…

黑马JVM总结(十四)

&#xff08;1&#xff09;分代回收_1 Java虚拟机都是结合前面几种算法&#xff0c;让他们协同工作&#xff0c;具体实现是虚拟机里面一个叫做分代的垃圾回收机制&#xff0c;把我们堆内存大的区域划分为两块新生代、老年代 新生代有划分为伊甸园、幸存区Form、幸存区To 为什…

Linux常用工具

文章目录 前言一、Linux编辑器-vim使用1.vim的基本概念2. vim的基本操作3. vim命令集1. 正常模式1. 模式切换和光标移动2. 删除文字及复制3. 其他操作 2. 底行模式 二、Linux编译器-gcc/g使用1. 命令和选项2. 预处理3. 编译4. 汇编(生成机器可识别代码)5. 连接(生成可执行文件或…