目录

system V共享内存

共享内存示意图

共享内存函数

shmget函数

shmat函数

shmdt函数

shmctl函数

代码示例

shm头文件

构造函数

获取key值

创建者的构造方式

GetShmHelper 函数

GetShmUseCreate 函数

使用者的构造方式

GetShmForUse 函数

分离附加操作

DetachShm 函数

AttachShm 函数

RoleToString

析构函数

客户端

服务端

命名管道与共享内存的区别

一、定义与工作原理

二、性能与效率

三、使用场景与限制

四、实现与操作


system V共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存示意图

共享内存在我们的虚拟地址空间的共享区当中,这意味着它一旦通信建立,一有数据就能一下被拿到。

共享内存函数

shmget函数

功能:用来创建共享内存
原型int shmget(key_t key, size_t size, int shmflg);
参数key:这个共享内存段名字size:共享内存大小shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat函数

功能:将共享内存段连接到进程地址空间
原型void *shmat(int shmid, const void *shmaddr, int shmflg);
参数shmid: 共享内存标识shmaddr:指定连接的地址shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

说明:

shmaddr为NULL,核心自动选择一个地址 shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。 shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA) shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

功能:将共享内存段与当前进程脱离
原型int shmdt(const void *shmaddr);
参数shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数shmid:由shmget返回的共享内存标识码cmd:将要采取的动作(有三个可取值)buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

代码示例

shm头文件

#ifndef _SHM_HPP_
#define _SHM_HPP_#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>#define Creater 1
#define User 2
const int gShmSize = 4096;
const std::string gpathname = "/home/lsf/lesson24/shm";
const int gproj_id = 0x66;class Shm
{
private:key_t GetCommKey(){key_t k = ftok(_pathname.c_str(), _proj_id);if (k < 0){perror("ftok");}return k;}int GetShmHelper(key_t key, int size, int flag){int shmid = shmget(key, size, flag);if (shmid < 0){perror("shmget");}return shmid;}std::string RoleToString(int who){if (who == Creater)return "Creater";else if (who == User)return "User";elsereturn "None";}void *AttachShm(){if (_addrshm != nullptr)DetachShm(_addrshm);void *shmaddr = shmat(_shmid, nullptr, 0);if (shmaddr == nullptr){perror("shmat");}std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;return shmaddr;}void DetachShm(void *shmaddr){if (shmaddr == nullptr)return;shmdt(shmaddr);std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;}public:Shm(const std::string &pathname, int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr){_key = GetCommKey();if (_who == Creater)GetShmUseCreate();else if (_who == User)GetShmForUse();_addrshm = AttachShm();std::cout << "shmid: " << _shmid << std::endl;std::cout << "_key: " << ToHex(_key) << std::endl;}std::string ToHex(key_t key){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", key);return buffer;}bool GetShmForUse(){if (_who == User){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT);// sleep(10);if (_shmid >= 0){std::cout << "shm get done..." << std::endl;return true;}}return false;}bool GetShmUseCreate(){if (_who == Creater){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);// sleep(10);if (_shmid >= 0){std::cout << "shm create done..." << std::endl;return true;}}return false;}void *Addr(){return _addrshm;}void Zero(){if (_addrshm){memset(_addrshm, 0, gShmSize);}}~Shm(){if (_who == Creater){int res = shmctl(_shmid, IPC_RMID, nullptr);}std::cout << "shm remove done..." << std::endl;}private:key_t _key;int _shmid;std::string _pathname;int _proj_id;int _who;void *_addrshm;
};#endif

成员变量中除了_pathname和_who一个表示路径名一个表示身份外,其余的都是我们上文提到的函数的参数,我们遇到时一一讲解。

构造函数

Shm(const std::string &pathname, int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr){_key = GetCommKey();if (_who == Creater)GetShmUseCreate();else if (_who == User)GetShmForUse();_addrshm = AttachShm();std::cout << "shmid: " << _shmid << std::endl;std::cout << "_key: " << ToHex(_key) << std::endl;}

我们分别将我们的路径,_proj_id(后面说),身份,_addeshm这些能设的初始值先设置好。然后我们就可以先获得一个我们共享内存的key标识值,为什么不直接设置,而是要写个函数呢?这是为了达到我们唯一性的目的,我们程序员自己设置的话难免会设置到重复的,为了避免它我们就可以用一个ftok函数,它会根据我们的路径和_proj_id用一种算法设计出一个key值,我们采用它能达到我们心目中的要求。

它的返回值就是我们要的key值。

项目标识符(proj_id):除了文件路径外,ftok()还需要一个项目标识符(proj_id)。这个标识符的最低有效8位将被用于生成键值。proj_id必须是非零的,以确保生成的键值不是全零(全零的键值在System V IPC中有特殊含义,通常表示无效的键值)。

获取key值
key_t GetCommKey(){key_t k = ftok(_pathname.c_str(), _proj_id);if (k < 0){perror("ftok");}return k;}

这就是我们的获取key值的函数,我们只是对ftok简单的封装了一下。

我们回到构造函数,在我们完成了key值的设置之后我们就要对创建者和使用则进行不同的构造方式了。

创建者的构造方式
int GetShmHelper(key_t key, int size, int flag){int shmid = shmget(key, size, flag);if (shmid < 0){perror("shmget");}return shmid;}bool GetShmUseCreate(){if (_who == Creater){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);// sleep(10);if (_shmid >= 0){std::cout << "shm create done..." << std::endl;return true;}}return false;}

GetShmHelper函数就是对我们的shmget函数做个简单封装,

GetShmHelper 函数

这个函数根据给定的键值(key)、大小(size)和标志(flag)来获取或创建一个共享内存段。

  • 参数
    • key_t key:共享内存段的键值,由ftok()函数生成。
    • int size:共享内存段的大小(以字节为单位)。
    • int flag:控制shmget()行为的标志。可以是IPC_CREAT(如果键不存在,则创建新的共享内存段)、IPC_EXCL(与IPC_CREAT一起使用时,如果键已存在,则调用失败)等标志的组合。
  • 返回值
    • 成功时返回共享内存段的标识符(shmid)。
    • 失败时返回-1,并通过perror()函数打印错误信息。
GetShmUseCreate 函数

这个函数检查某个条件(这里是通过_who变量与Creater比较),如果条件满足,则尝试创建一个新的共享内存段。

  • 变量
    • _who:用于指示当前进程的角色。
    • _key:共享内存段的键值。
    • gShmSize:共享内存段的大小。
    • _shmid:用于存储共享内存段标识符的变量。
  • 逻辑
    • 如果_who等于Creater,则调用GetShmHelper函数尝试创建新的共享内存段。
    • GetShmHelper的调用使用了IPC_CREAT | IPC_EXCL | 0666作为标志,这意味着如果键值已存在,则调用将失败(因为IPC_EXCL标志的存在)。0666是权限设置,但由于IPC_CREATIPC_EXCL的存在,它实际上只在创建新段时有效。
    • 如果GetShmHelper返回非负值(即成功创建了共享内存段或获取了已存在的共享内存段),则函数返回true
    • 如果GetShmHelper返回-1(即失败),则函数返回false,并且不会打印"shm create done..."消息。
使用者的构造方式
bool GetShmForUse(){if (_who == User){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT);// sleep(10);if (_shmid >= 0){std::cout << "shm get done..." << std::endl;return true;}}return false;}
GetShmForUse 函数
  • 目的:尝试获取一个共享内存段以供使用。当前角色是用户(_who == User),尝试获取或创建共享内存段。

  • 参数

    • 隐式参数(全局变量):
      • _who:当前进程的角色。
      • _key:共享内存段的键值。
      • gShmSize:共享内存段的大小。
      • _shmid:用于存储共享内存段标识符的变量。
  • 逻辑

    • 如果_who等于User,则调用GetShmHelper函数尝试获取或创建共享内存段。
    • GetShmHelper的调用使用了IPC_CREAT作为标志。这意味着:
      • 如果键值对应的共享内存段已存在,则shmget将返回该段的标识符。
      • 如果键值对应的共享内存段不存在,则shmget将创建一个新的共享内存段,并返回其标识符。
    • 如果GetShmHelper返回非负值(即成功获取了已存在的段或创建了新段),则函数返回true,打印"shm get done..."。
    • 如果GetShmHelper返回-1(即失败,这通常不应该发生,因为IPC_CREAT允许创建新段)。
    • 如果_who不是User,则函数直接返回false
分离附加操作
void DetachShm(void *shmaddr){if (shmaddr == nullptr)return;shmdt(shmaddr);std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;}void *AttachShm(){if (_addrshm != nullptr)DetachShm(_addrshm);void *shmaddr = shmat(_shmid, nullptr, 0);if (shmaddr == nullptr){perror("shmat");}std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;return shmaddr;}

DetachShm 函数

  • 目的:分离(detach)之前附加(attach)的共享内存段。

  • 参数

    • shmaddr:指向之前附加的共享内存段的指针。
  • 逻辑

    • 如果传入的shmaddrnullptr,则函数直接返回,不执行任何操作。这是一种防御性编程实践,用于防止对空指针进行解引用。
    • 使用shmdt函数分离共享内存段。shmdt接受一个指向共享内存段的指针,并分离该进程与该内存段之间的关联。
    • 打印一条消息,指示当前角色(通过RoleToString(_who)获取)正在分离共享内存段。RoleToString是一个将角色转换为字符串的函数。

AttachShm 函数

  • 目的:附加(attach)到一个已存在的共享内存段。

  • 参数:无。

  • 逻辑

    • 如果全局变量(或类成员变量)_addrshm不是nullptr,则首先调用DetachShm函数分离当前附加的共享内存段(如果有的话)。这是一种清理操作,确保在附加新的共享内存段之前不会泄漏旧的内存段。
    • 使用shmat函数附加到共享内存段。shmat接受共享内存段的标识符、一个指向期望附加地址的指针(这里传入nullptr,让系统选择地址)、以及一组标志(这里传入0,表示使用默认行为)。
    • 如果shmat返回nullptr,则使用perror函数打印一条错误消息。这意味着附加操作失败,可能是由于资源不足、权限问题或其他原因。
    • 打印一条消息,指示当前角色(通过RoleToString(_who)获取)正在附加共享内存段。
    • 返回shmat返回的指针,即指向附加的共享内存段的指针。如果shmat失败,则返回nullptr
RoleToString
std::string RoleToString(int who){if (who == Creater)return "Creater";else if (who == User)return "User";elsereturn "None";}

将_who转换成字符串。

接下来我们可以打印一下共享内存标识符和我们的key值,我们将key值转换成十六进制,方便观察。

std::string ToHex(key_t key){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", key);return buffer;}
void *Addr(){return _addrshm;}

我们可以使用这个函数让上层获取共享内存的地址。

void Zero(){if (_addrshm){memset(_addrshm, 0, gShmSize);}}

我们可以用memset将这段共享内存清空。

析构函数

~Shm(){if (_who == Creater){int res = shmctl(_shmid, IPC_RMID, nullptr);}std::cout << "shm remove done..." << std::endl;}

这里我们也是让创建者把共享内存移除。

int res = shmctl(_shmid, IPC_RMID, nullptr);:这行代码调用 shmctl 函数尝试删除共享内存。_shmid 是共享内存的标识符,IPC_RMID 是删除共享内存的命令,nullptr 是指向 shmid_ds 结构的指针(在这个命令中不需要,因此传递 nullptr)。函数返回的结果存储在 res 变量中。

客户端

#include "Shm.hpp"
#include "namedPipe.hpp"int main()
{//1.先创建共享内存Shm shm(gpathname,gproj_id,User);shm.Zero();char *shmaddr = (char*)shm.Addr();sleep(3);//2.打开管道NamePiped fifo(comm_path,User);fifo.OpenForWrite();char ch = 'A';while(ch <= 'Z'){shmaddr[ch-'A'] = ch;ch++;std::string temp = "weakup";std::cout<< "add" <<ch<<" into shm, "<<"weakup reader"<<std::endl;fifo.WriteNamedPipe(temp);sleep(2);}return 0;
}

我们创建共享内存并清空原先的数据,获取一下共享内存地址,然后我们使用之前的管道,管道在这里的作用就是同步,因为我们的共享内存是直接内存访问,这意味着我们的客户端一发送服务端就能看到。所以我们需要借助管道的特性达到同步的效果,管道在这里发送什么信息并不重要。

服务端

#include "Shm.hpp"
#include "namedPipe.hpp"int main()
{//1.先创建共享内存Shm shm(gpathname,gproj_id,Creater);char *shmaddr = (char*)shm.Addr();//2.创建管道NamePiped fifo(comm_path,Creater);fifo.OpenForRead();while(true){std::string temp;fifo.ReadNamedPipe(&temp);std::cout<<"shm memory content: " << shmaddr << std::endl;sleep(1);}sleep(5);return 0;
}

服务端的逻辑也是一样的,不同的是服务端是只读。

代码效果:

命名管道与共享内存的区别

命名管道与共享内存是两种常见的进程间通信(IPC)机制,它们各自具有独特的特点和适用场景。以下是两者的主要区别:

一、定义与工作原理

  1. 命名管道(Named Pipe)

    • 也称为FIFO(First In First Out)管道。
    • 它允许无亲缘关系的进程间进行通信,通过一个在文件系统中存在的名字来标识,进程可以通过这个名字来访问和通信。
    • 数据流动是单向的,即数据只能从一个进程流向另一个进程。如果需要双向通信,通常需要创建两个管道。
    • 命名管道常用于跨进程通信,并且允许数据以流的方式从一个进程传输到另一个进程。
  2. 共享内存(SharedMemory)

    • 它是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
    • 它是针对其他进程间通信方式运行效率低而专门设计的,允许两个或多个进程共享一个给定的存储区。
    • 一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程通过一个简单的内存读取读出,从而实现了进程间的通信。

二、性能与效率

  1. 命名管道

    • 数据传输通过内核缓冲区进行,存在一定的开销。
    • 相比其他IPC机制(如信号、消息队列等),命名管道的性能处于中等水平。
    • 它支持阻塞和非阻塞操作,提供了灵活的通信方式。
  2. 共享内存

    • 是最快的IPC方式之一,因为它允许进程直接访问共享的内存区域,减少了数据的拷贝次数。
    • 相比命名管道等机制,共享内存具有更高的数据传输效率和更低的延迟。
    • 但是,共享内存需要额外的同步机制(如信号量)来避免数据竞争和不一致性问题。

三、使用场景与限制

  1. 命名管道

    • 适用于需要在不同进程间传递流式数据的场景。
    • 由于其半双工特性和需要创建两个管道以实现双向通信的限制,命名管道在某些复杂通信场景中可能不够灵活。
  2. 共享内存

    • 适用于需要高效数据传输和大量数据共享的场景。
    • 由于允许多个进程直接访问同一块内存区域,因此需要注意同步和互斥问题,以避免数据竞争和访问冲突。

四、实现与操作

  1. 命名管道

    • 在类Unix系统中,可以通过mkfifo函数创建命名管道,并使用open、read、write等系统调用进行读写操作。
    • 在Windows系统中,命名管道通常通过特定的API进行创建和管理。
  2. 共享内存

    • 在类Unix系统中,可以使用shmget、shmat、shmdt、shmctl等系统调用进行共享内存的创建、挂接、去关联和控制操作。
    • 在Windows系统中,也有相应的API用于共享内存的创建和管理。

综上所述,命名管道和共享内存是两种各具特色的进程间通信机制。命名管道适用于需要在不同进程间传递流式数据的场景,而共享内存则适用于需要高效数据传输和大量数据共享的场景。在选择使用哪种IPC机制时,需要根据具体的应用需求和场景进行权衡和选择。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

6月15日星期日早报简报微语报早读

6月15日星期日&#xff0c;农历五月二十&#xff0c;早报#微语早读。 1、证监会拟修订期货公司分类评价&#xff1a;明确扣分标准&#xff0c;优化加分标准&#xff1b; 2、国家考古遗址公园再添10家&#xff0c;全国已评定65家&#xff1b; 3、北京多所高校禁用罗马仕充电宝…

破解关键领域软件测试“三重难题”:安全、复杂性、保密性

在国家关键领域&#xff0c;软件系统正成为核心战斗力的一部分。相比通用软件&#xff0c;关键领域软件在 安全性、复杂性、实时性、保密性 等方面要求极高。如何保障安全合规前提下提升测试效率&#xff0c;确保系统稳定&#xff0c;已成为软件质量保障的核心挑战。 关键领域…

记录一次 Oracle DG 异常停库问题解决过程

记录一次 Oracle DG 异常停库问题解决过程 某医院有以下架构的双节点 Oracle 集群&#xff1a; 节点1:172.16.20.2 节点2:172.16.20.3 SCAN IP&#xff1a;172.16.20.1 DG&#xff1a;172.16.20.1206月12日&#xff0c;医院信息科用户反映无法连接 DG 服务器。 登录 DG 服务…

MySQL使用EXPLAIN命令查看SQL的执行计划

1‌、EXPLAIN 的语法 MySQL 中的 EXPLAIN 命令是用于分析 SQL 查询执行计划的关键工具,它能帮助开发者理解查询的执行方式并找出性能瓶颈‌‌。 语法格式: EXPLAIN <sql语句> 【示例】查询学生表关联班级表的执行计划。 (1)创建班级信息表和学生信息表,并创建索…

Go语言2个协程交替打印

WaitGroup 无缓冲channel waitgroup 用来控制2个协程 Add() 、Done()、Wait() channel用来实现信号的传递和信号的打印 ch1: 用来记录打印的信号 ch2:用来实现信号的传递&#xff0c;实现2个协程的顺序打印 package mainimport ("fmt""sync" )func ma…

微信小程序 路由跳转

路由方式 官方参考文档 wx.switchTab 实现底部导航栏 1.配置信息 app.json"tabBar": {"custom": true,"list": [{"pagePath": "pages/home/index","text": "首页"},{"pagePath": "p…

[Java 基础]正则表达式

正则表达式是一种强大的文本模式匹配工具&#xff0c;它使用一种特殊的语法来描述要搜索或操作的字符串模式。在 Java 中&#xff0c;我们可以使用 java.util.regex包提供的类来处理正则表达式。 :::color3 正则表达式不止 Java 语言提供了相应的功能&#xff0c;很多其他语言…

ArcGIS安装出现1606错误解决办法

问题背景&#xff1a; 由于最近Arcgis10.2打是有些功能不正常退出&#xff0c;比如arctoolbox中的&#xff0c;table to excel 功能&#xff0c;只要一点击&#xff0c;arcgis就报错退出&#xff0c;平常在使用过程中&#xff0c;也经常出现一些莫名其妙的崩溃现象&#xff0c…

wpf 解决DataGridTemplateColumn中width绑定失效问题

感谢酪酪烤奶 提供的Solution 文章目录 感谢酪酪烤奶 提供的Solution使用示例示例代码分析各类交互流程 WPF DataGrid 列宽绑定机制分析整体架构数据流分析1. ViewModel到Slider的绑定2. ViewModel到DataGrid列的绑定a. 绑定代理(BindingProxy)b. 列宽绑定c. 数据流 关键机制详…

语音转文本ASR、文本转语音TTS

ASR Automatic Speech Recognition&#xff0c;语音转文本。 技术难点&#xff1a; 声学多样性 口音、方言、语速、背景噪声会影响识别准确性&#xff1b;多人对话场景&#xff08;如会议录音&#xff09;需要区分说话人并分离语音。 语言模型适配 专业术语或网络新词需要动…

通用embedding模型和通用reranker模型,观测调研

调研Qwen3-Embedding和Qwen3-Reranker 现在有一个的问答库&#xff0c;包括150个QA-pair&#xff0c;用10个query去同时检索问答库的300个questionanswer Embedding模型&#xff0c;query-question的匹配分数 普遍高于 query-answer的匹配分数。比如对于10个query&#xff0c…

基于YOLOv8+Deepface的人脸检测与识别系统

摘要 人脸检测与识别系统是一个集成了先进计算机视觉技术的应用&#xff0c;通过深度学习模型实现人脸检测、识别和管理功能。系统采用双模式架构&#xff1a; ​​注册模式​​&#xff1a;检测新人脸并添加到数据库​​删除模式​​&#xff1a;识别数据库中的人脸并移除匹…

Grdle版本与Android Gradle Plugin版本, Android Studio对应关系

Grdle版本与Android Gradle Plugin版本&#xff0c; Android Studio对应关系 各个 Android Gradle 插件版本所需的 Gradle 版本&#xff1a; https://developer.android.com/build/releases/gradle-plugin?hlzh-cn Maven上发布的Android Gradle Plugin&#xff08;AGP&#x…

用c语言实现简易c语言扫雷游戏

void test(void) {int input 0;do{menu();printf("请选择&#xff1a; >");scanf("%d", &input);switch (input){menu();case 1:printf("扫雷\n");game();break;case 2:printf("退出游戏\n");break;default:printf("输入…

系统辨识的研究生水平读书报告期末作业参考

这是一份关于系统辨识的研究生水平读书报告&#xff0c;内容系统完整、逻辑性强&#xff0c;并深入探讨了理论、方法与实际应用。报告字数超过6000字 从理论到实践&#xff1a;系统辨识的核心思想、方法论与前沿挑战 摘要 系统辨识作为连接理论模型与客观世界的桥梁&#xff…

开源、免费、美观的 Vue 后台管理系统模板

随着前端技术的不断发展&#xff0c;Vue.js 凭借其轻量、高效、易上手的特性&#xff0c;成为国内外开发者最喜爱的前端框架之一。在构建后台管理系统时&#xff0c;Vue 提供了以下优势&#xff1a; 响应式数据绑定&#xff1a;让页面和数据保持同步&#xff0c;开发效率高。 …

适合 Acrobat DC 文件类型解析

文件类型 (File Type)ProgID (Continuous)ProgID (Classic)主要用途.pdfAcroExch.Document.DCAcroExch.Document.20XX (版本特定)Adobe PDF文档格式&#xff0c;用于存储文档内容和格式.pdfxmlAcroExch.pdfxmlAcroExch.pdfxmlPDF与XML结合的格式&#xff0c;可能用于结构化数据…

C/C++数据结构之漫谈

概述 在当今的数字化时代&#xff0c;无论是刷短视频、社交聊天&#xff0c;还是使用导航软件、网络购物&#xff0c;背后都离不开计算机技术的支持。但你是否想过&#xff1a;为什么同样的功能&#xff0c;有的软件运行得飞快&#xff0c;有的却严重卡顿&#xff0c;半天没有响…

4步使用 vue3 路由

路由的基本使用步骤分为以下4步 第一步&#xff1a;定义路由组件&#xff1a;略 第二步&#xff1a;定义路由链接和路由视图&#xff1a; <template><div class"app-container"><h1>App根组件</h1><router-link to"/home">…

VScode使用npm启动项目以及npm install ,npm start报错问题处理

安装启动步骤 打开cmd 输入指令 npm -v 查看npm是否安装&#xff0c;需要先安装node.js node.js安装&#xff1a;node.js安装 安装包下载后&#xff0c;一直点击next &#xff0c;安装完成&#xff0c;打开cmd 输入 node -v 查看安装是否成功 使用VScode 打开项目&#xf…