鸿蒙PC高分屏与键鼠交互适配:DPI缩放、右键菜单一次讲清

欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

摘要 :PC 与手机最大的硬件差异是"屏幕大、分辨率杂、有鼠标"。本文聚焦鸿蒙PC上两个高频适配点------高分屏 DPI 缩放检测与右键上下文菜单,给出真实运行代码,并复盘两个编译期就暴雷的坑(scale 命名冲突、getDefaultDisplaySync 抛异常)。所有代码已在鸿蒙PC模拟器跑通,工程已传 AtomGit,附全屏截图清单。

手机分辨率相对固定,PC 却是 1080P、2K、4K、带鱼屏一应俱全。同一套 UI 在 4K 屏上可能小到看不清,在触屏本上又可能大得挤。再加上鼠标右键、悬停这些手机没有的交互------DPI 与键鼠适配,是鸿蒙PC应用"看起来像PC软件"的分水岭。这篇把它讲透,并把两个我编译期就踩中的坑一并交待。

目录

  • [一、DPI 缩放检测:先拿到屏幕真实数据](#一、DPI 缩放检测:先拿到屏幕真实数据)
  • [二、坑 2:函数名叫 scale 会编译失败](#二、坑 2:函数名叫 scale 会编译失败)
  • [三、右键菜单:PC 专属交互](#三、右键菜单:PC 专属交互)
    • [3.1 完整可运行代码:右键菜单从捕获到生效](#3.1 完整可运行代码:右键菜单从捕获到生效)
  • 四、高分屏自适应的三条原则
    • [4.1 完整代码:用 densityPixels 计算字号与间距](#4.1 完整代码:用 densityPixels 计算字号与间距)
  • [五、DevEco Studio 6.0(Dev Eco 26)开发环境搭建](#五、DevEco Studio 6.0(Dev Eco 26)开发环境搭建)
    • [5.1 环境清单](#5.1 环境清单)
    • [5.2 关键配置步骤](#5.2 关键配置步骤)
    • [5.3 工程架构与适配链路](#5.3 工程架构与适配链路)
  • 六、在鸿蒙PC上验证
  • 七、沉淀与共建

一、DPI 缩放检测:先拿到屏幕真实数据

OpenHarmony 用 @ohos.display 暴露屏幕信息。关键字段是 densityPixels(每逻辑像素对应物理像素,如 1.5/2.0/3.0)、densityDPI(如 240/480),以及 width/height(物理分辨率)。

ts 复制代码
aboutToAppear(): void {
  try {
    const d = display.getDefaultDisplaySync();
    this.dpi = d.densityPixels;
    this.densityDpi = d.densityDPI;
    this.resolution = `${d.width} x ${d.height}`;
  } catch (err) {
    const e = err as BusinessError;
    this.statusMsg = `DPI 读取失败 code=${e.code}`;
  }
}

坑 1 :getDefaultDisplaySync() 是同步 API,但在窗口/display 服务未就绪时会抛 BusinessError。ArkTS 编译器还会对"可能抛异常的函数"报 WARN,必须用 try-catch 包住,否则编译不过。

二、坑 2:函数名叫 scale 会编译失败

我最初写了个缩放辅助函数 private scale(size) 用来算字号。一编译满屏红:Argument of type 'number' is not assignable to ...。排查半天才明白:CustomComponent 基类已经有一个 scale 属性 ,ArkUI 的 .scale({ x: 1.5 }) 修饰器用的就是它。我自己写的 scale 和基类冲突,类型直接不兼容。改名 scaleFont 后立刻通过。这个坑很隐蔽,记下来省得别人再查。

三、右键菜单:PC 专属交互

手机没有右键,PC 上右键弹菜单是基本操作。ArkUI 用 onMouse 捕获鼠标事件,button===2 && action===1 即"右键按下":

ts 复制代码
.onMouse((e: MouseEvent) => {
  if (e.button === 2 && e.action === 1) {   // 右键按下
    this.menuX = e.x;
    this.menuY = e.y;
    this.showMenu = true;
  }
})

菜单用 Column + position({ x, y }) 自绘,锚定到鼠标点击坐标。我还加了 onHover 悬停高亮,让"这是PC鼠标交互"一目了然。菜单项要真正生效才有意义------我的"新建卡片"会往列表追加,"切换底色"会改背景色,"关闭菜单"收起,不是摆设。

3.1 完整可运行代码:右键菜单从捕获到生效

下面这段是 Index.ets 里右键菜单的完整实现,包含状态定义、onMouse 捕获、Column + position 自绘菜单,以及三个菜单项的真实处理逻辑:

ts 复制代码
@Entry
@Component
struct Index {
  // ===== 菜单状态 =====
  @State menuX: number = 0;          // 菜单左上角 x 坐标(相对组件)
  @State menuY: number = 0;          // 菜单左上角 y 坐标
  @State showMenu: boolean = false;  // 是否显示菜单
  @State hoverIndex: number = -1;    // 当前悬停的菜单项下标,-1 表示无

  // ===== 业务状态 =====
  @State cardCount: number = 0;      // 卡片数量,"新建卡片"会 +1
  @State bgColor: string = '#FFFFFF'; // 页面底色,"切换底色"会轮换

  // 底色轮换表
  private readonly BG_LIST: string[] = ['#FFFFFF', '#F2F3F5', '#E8F3FF', '#FFF4E6'];

  /**
   * 右键捕获:button === 2 表示右键,action === 1 表示按下
   * 记录坐标后把 showMenu 置 true,菜单即出现在鼠标位置
   */
  private onRightClick(e: MouseEvent): void {
    if (e.button === 2 && e.action === 1) {
      this.menuX = e.x;
      this.menuY = e.y;
      this.showMenu = true;
    }
  }

  /** 菜单项 1:新建卡片 ------ 真正往列表里追加数据 */
  private handleNewCard(): void {
    this.cardCount += 1;
    this.showMenu = false; // 操作完收起菜单
  }

  /** 菜单项 2:切换底色 ------ 在预设色板里循环 */
  private handleSwitchBg(): void {
    const idx = this.BG_LIST.indexOf(this.bgColor);
    this.bgColor = this.BG_LIST[(idx + 1) % this.BG_LIST.length];
    this.showMenu = false;
  }

  /** 菜单项 3:关闭菜单 ------ 只收起,不改业务数据 */
  private handleCloseMenu(): void {
    this.showMenu = false;
  }

  /** 自绘菜单:Column + position 锚定到鼠标坐标 */
  @Builder
  ContextMenu() {
    Column() {
      this.MenuItem('新建卡片', 0, () => this.handleNewCard())
      this.MenuItem('切换底色', 1, () => this.handleSwitchBg())
      this.MenuItem('关闭菜单', 2, () => this.handleCloseMenu())
    }
    .width(140)
    .padding(4)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .shadow({ radius: 12, color: '#33000000', offsetY: 4 })
    // 关键:用 position 把菜单钉在右键点击处
    .position({ x: this.menuX, y: this.menuY })
  }

  /** 单个菜单项:悬停高亮 + 点击回调 */
  @Builder
  MenuItem(label: string, index: number, action: () => void) {
    Text(label)
      .width('100%')
      .height(36)
      .padding({ left: 12, right: 12 })
      .fontSize(14)
      .textAlign(TextAlign.Start)
      .borderRadius(6)
      // 悬停高亮:让"这是PC鼠标交互"一目了然
      .backgroundColor(this.hoverIndex === index ? '#E8F3FF' : Color.Transparent)
      .onHover((isHover: boolean) => {
        this.hoverIndex = isHover ? index : -1;
      })
      .onClick(() => action())
  }

  build() {
    Stack({ alignContent: Alignment.TopStart }) {
      Column({ space: 12 }) {
        Text(`当前卡片数:${this.cardCount}`)
          .fontSize(16)

        // 演示区:在这里点右键
        Text('在此区域点击鼠标右键')
          .width('100%')
          .height(200)
          .fontSize(16)
          .textAlign(TextAlign.Center)
          .backgroundColor('#F7F8FA')
          .borderRadius(8)
          .onMouse((e: MouseEvent) => this.onRightClick(e))
      }
      .width('100%')
      .padding(20)
      .alignItems(HorizontalAlign.Start)

      // 菜单浮层:showMenu 为 true 时才渲染
      if (this.showMenu) {
        this.ContextMenu()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.bgColor) // 底色由"切换底色"控制
    // 点击空白处收起菜单
    .onClick(() => {
      if (this.showMenu) {
        this.showMenu = false;
      }
    })
  }
}

几个容易忽略的点:

  • position 的坐标系 是父容器(这里是 Stack)的左上角,e.x / e.y 也是相对当前组件的坐标,两者一致才能"菜单出现在鼠标处";
  • 菜单必须放在 Stack 里 ,否则 position 会参与普通布局流,把下面的内容挤走;
  • onHover 的 isHover 是布尔值 ,离开时要记得把 hoverIndex 复位成 -1,否则高亮会"粘住";
  • 每个菜单项都要真正改数据 (cardCount、bgColor),只弹不做的菜单在评审时一眼就被看穿。

四、高分屏自适应的三条原则

原则 做法 反例
字号随 DPI size * densityPixels 写死 16px
布局用百分比/flex width('80%')、Flex 写死 800px 宽
间距用 vp 而非 px ArkUI 默认 vp 已含缩放 混用 px 导致错位

补充一点:ArkUI 长度单位默认就是 vp(虚拟像素),本身已做 DPI 归一化。多数情况你不用手动乘 dpi ,只有拿到物理像素数据时才换算。本文显式乘 densityPixels 是为了演示"感知缩放",真实业务更推荐直接 vp + 弹性布局。

4.1 完整代码:用 densityPixels 计算字号与间距

下面这段是可直接放进 Index.ets 的完整实现,包含 scaleFont 函数、间距换算函数,以及在 Text 组件上的调用方式:

ts 复制代码
import display from '@ohos.display';
import { BusinessError } from '@ohos.base';

@Entry
@Component
struct Index {
  // 屏幕密度:1 个 vp 对应的物理像素数,常见 1.5 / 2.0 / 3.0
  @State densityPixels: number = 1.0;
  // 基准字号(设计稿按 1x 标注,单位 vp)
  private readonly BASE_FONT: number = 16;
  // 基准间距(设计稿按 1x 标注,单位 vp)
  private readonly BASE_GAP: number = 12;

  aboutToAppear(): void {
    try {
      const d = display.getDefaultDisplaySync();
      this.densityPixels = d.densityPixels;
    } catch (err) {
      const e = err as BusinessError;
      console.error(`DPI 读取失败 code=${e.code}`);
      this.densityPixels = 1.0; // 兜底,避免后续除零或 NaN
    }
  }

  /**
   * 字号缩放:把设计稿的 vp 字号按屏幕密度放大
   * 注意函数名不能叫 scale ------ 会与 CustomComponent 基类的 scale 属性冲突
   */
  private scaleFont(size: number): number {
    return size * this.densityPixels;
  }

  /**
   * 间距缩放:与字号同一套换算逻辑,保证视觉比例一致
   */
  private scaleGap(gap: number): number {
    return gap * this.densityPixels;
  }

  /**
   * px -> vp 换算:vp = px / densityPixels
   * 拿到物理像素(如截图尺寸、图片原始宽高)时用它转成 ArkUI 可用的 vp
   */
  private px2vp(px: number): number {
    return px / this.densityPixels;
  }

  /**
   * vp -> px 换算:px = vp * densityPixels
   * 需要把布局尺寸回传给只认物理像素的接口时用它
   */
  private vp2px(vp: number): number {
    return vp * this.densityPixels;
  }

  build() {
    Column({ space: this.scaleGap(this.BASE_GAP) }) {
      // 标题:基准 24vp,随 DPI 放大
      Text('高分屏适配演示')
        .fontSize(this.scaleFont(24))
        .fontWeight(FontWeight.Bold)

      // 正文:基准 16vp,随 DPI 放大
      Text(`当前 densityPixels = ${this.densityPixels}`)
        .fontSize(this.scaleFont(this.BASE_FONT))

      // 说明:展示 vp 与 px 的换算结果
      Text(`16vp 在本屏约等于 ${this.vp2px(16).toFixed(1)}px`)
        .fontSize(this.scaleFont(14))
        .fontColor('#666666')

      // 卡片:内边距同样按密度缩放
      Column() {
        Text('卡片内容')
          .fontSize(this.scaleFont(this.BASE_FONT))
      }
      .padding(this.scaleGap(16))
      .width('80%')
      .backgroundColor('#F2F3F5')
      .borderRadius(this.scaleGap(8))
    }
    .width('100%')
    .padding(this.scaleGap(20))
    .alignItems(HorizontalAlign.Start)
  }
}

关于 vp 与 px 的换算关系,记住一个公式就够了:

text 复制代码
px = vp × densityPixels
vp = px ÷ densityPixels
  • vp(virtual pixel)是 ArkUI 的默认长度单位,系统已按屏幕密度做过归一化,所以写 fontSize(16) 在 1.5 和 3.0 的屏上视觉大小是一致的;
  • px 是物理像素,只在拿到硬件原始数据(截图尺寸、图片像素宽高、display 返回的 width/height)时才出现;
  • 上面代码里显式乘 densityPixels 是为了演示"感知缩放"的效果,方便你在模拟器切换密度时肉眼看到差异。真实业务中,若设计稿本身就是 vp 标注,直接写 vp 数值即可,不必再乘。

五、DevEco Studio 6.0(Dev Eco 26)开发环境搭建

代码能跑通的前提是环境对。鸿蒙PC 应用必须用支持 PC 设备类型的 IDE 与 SDK,下面是我实际使用的组合,照着配基本不会踩坑。

5.1 环境清单

组件 版本 / 说明
DevEco Studio 6.0 Release(社区常称 Dev Eco 26),需选支持 PC 的版本
HarmonyOS SDK API 12 及以上,勾选 PC 设备类型
Node.js IDE 内置,无需单独安装
构建工具 hvigor(IDE 自带,随工程 hvigor-config.json5 锁定)
模拟器 鸿蒙PC 模拟器镜像,分辨率建议 2K 以上以验证 DPI

开发运行效果如下:

5.2 关键配置步骤

  1. 安装 DevEco Studio 6.0,首次启动时在 Settings → OpenHarmony SDK 中下载 SDK,务必勾选 PC 平台组件;
  2. 新建工程时模板选 Empty Ability ,Device Type 勾选 phone 与 pc(或 2in1),否则右键菜单等 PC 能力无法在真机/模拟器上生效;
  3. 在 module.json5 中确认设备类型声明:
json 复制代码
{
  "module": {
    "name": "entry",
    "deviceTypes": ["phone", "2in1", "tablet"]
  }
}
  1. 打开 Device Manager 创建鸿蒙PC 模拟器,启动后 Run 'entry' 即可部署;
  2. 若编译报 hvigor 版本不匹配,按提示执行 Sync Now,不要手动改 hvigor-config.json5 里的版本号。

提示 :2in1 是鸿蒙对 PC/二合一设备的设备类型标识,写 pc 在部分 SDK 版本上会校验失败,以 IDE 模板生成的值为准。

5.3 工程架构与适配链路

下图是本 demo 的整体结构,以及 DPI 检测、鼠标事件、菜单渲染三条链路如何汇入同一个页面:
#mermaid-svg-SrntWZo2an49l1Zq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SrntWZo2an49l1Zq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SrntWZo2an49l1Zq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SrntWZo2an49l1Zq .error-icon{fill:#552222;}#mermaid-svg-SrntWZo2an49l1Zq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SrntWZo2an49l1Zq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SrntWZo2an49l1Zq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SrntWZo2an49l1Zq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SrntWZo2an49l1Zq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SrntWZo2an49l1Zq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SrntWZo2an49l1Zq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SrntWZo2an49l1Zq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SrntWZo2an49l1Zq .marker.cross{stroke:#333333;}#mermaid-svg-SrntWZo2an49l1Zq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SrntWZo2an49l1Zq p{margin:0;}#mermaid-svg-SrntWZo2an49l1Zq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SrntWZo2an49l1Zq .cluster-label text{fill:#333;}#mermaid-svg-SrntWZo2an49l1Zq .cluster-label span{color:#333;}#mermaid-svg-SrntWZo2an49l1Zq .cluster-label span p{background-color:transparent;}#mermaid-svg-SrntWZo2an49l1Zq .label text,#mermaid-svg-SrntWZo2an49l1Zq span{fill:#333;color:#333;}#mermaid-svg-SrntWZo2an49l1Zq .node rect,#mermaid-svg-SrntWZo2an49l1Zq .node circle,#mermaid-svg-SrntWZo2an49l1Zq .node ellipse,#mermaid-svg-SrntWZo2an49l1Zq .node polygon,#mermaid-svg-SrntWZo2an49l1Zq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SrntWZo2an49l1Zq .rough-node .label text,#mermaid-svg-SrntWZo2an49l1Zq .node .label text,#mermaid-svg-SrntWZo2an49l1Zq .image-shape .label,#mermaid-svg-SrntWZo2an49l1Zq .icon-shape .label{text-anchor:middle;}#mermaid-svg-SrntWZo2an49l1Zq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SrntWZo2an49l1Zq .rough-node .label,#mermaid-svg-SrntWZo2an49l1Zq .node .label,#mermaid-svg-SrntWZo2an49l1Zq .image-shape .label,#mermaid-svg-SrntWZo2an49l1Zq .icon-shape .label{text-align:center;}#mermaid-svg-SrntWZo2an49l1Zq .node.clickable{cursor:pointer;}#mermaid-svg-SrntWZo2an49l1Zq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SrntWZo2an49l1Zq .arrowheadPath{fill:#333333;}#mermaid-svg-SrntWZo2an49l1Zq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SrntWZo2an49l1Zq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SrntWZo2an49l1Zq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SrntWZo2an49l1Zq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SrntWZo2an49l1Zq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SrntWZo2an49l1Zq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SrntWZo2an49l1Zq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SrntWZo2an49l1Zq .cluster text{fill:#333;}#mermaid-svg-SrntWZo2an49l1Zq .cluster span{color:#333;}#mermaid-svg-SrntWZo2an49l1Zq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SrntWZo2an49l1Zq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SrntWZo2an49l1Zq rect.text{fill:none;stroke-width:0;}#mermaid-svg-SrntWZo2an49l1Zq .icon-shape,#mermaid-svg-SrntWZo2an49l1Zq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SrntWZo2an49l1Zq .icon-shape p,#mermaid-svg-SrntWZo2an49l1Zq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SrntWZo2an49l1Zq .icon-shape .label rect,#mermaid-svg-SrntWZo2an49l1Zq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SrntWZo2an49l1Zq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SrntWZo2an49l1Zq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SrntWZo2an49l1Zq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 系统能力层
应用层 Index.ets
DevEco Studio 6.0 开发环境
工程 entry 模块
module.json5 声明 2in1
鸿蒙PC 模拟器
aboutToAppear 读取 DPI
状态变量 dpi / resolution
onMouse 捕获右键
menuX / menuY / showMenu
onHover 悬停高亮
hoverIndex
@ohos.display 屏幕信息
MouseEvent 鼠标事件
字号 = size * densityPixels
Column + position 自绘菜单
高分屏自适应 UI

从图里能看清一件事:DPI 与鼠标事件是两条独立输入,最终都收敛到同一个渲染层。所以适配工作可以拆开做------先保证 DPI 数值读对、字号算对,再补右键与悬停交互,互不阻塞。

六、在鸿蒙PC上验证

完成编译:

运行效果如下:

点击右键即可进行识别:

七、沉淀与共建

DPI 与键鼠适配是 PC 应用的基本功,却被很多从手机转过来的开发者忽视。本文 demo 含 DPI 检测、悬停高亮、右键菜单三段可运行代码,README 写了上面两个编译坑。欢迎补充你遇到过的奇葩分辨率案例,一起把鸿蒙PC的适配经验库填厚。


小结:PC 不是"大号手机"。高分屏要会随 DPI 缩放,交互要接住鼠标右键与悬停。把这两件事做对,你的应用才会被用户一眼认出"这是个正经PC软件"。这恰恰是开源鸿蒙PC最缺、也最值得你贡献经验的方向。

相关推荐
終不似少年遊*1 小时前
【Skill】为中型研发团队制作的敏捷迭代AI副驾Skill
人工智能·python·华为云·agent·ai编程·skill
ZzT1 小时前
vibe coding 做小游戏·玩法篇:8 句试玩反馈,变成无限关卡、局外成长和援军登场
ai编程·游戏开发
H_BB1 小时前
面试笔记:数据加密和心跳机制
笔记·面试·职场和发展
海绵宝宝转agent1 小时前
LeetCodehot100题解
java·笔记·学习·算法·leetcode·深度优先
Goway_Hui1 小时前
【KMP for OpenHarmony】Cache4k 0.14.0 鸿蒙适配
华为·kotlin·harmonyos
octopus_c1 小时前
C++ 运算符重载详解 && 日期类的实现
开发语言·c++
Eiceblue2 小时前
用 Python 给 Word 文档加水印:文字、图片与批量处理
开发语言·vscode·python·word
j7~2 小时前
【C++ 面试笔记】(篇二):类与数据抽象 & STL 容器与算法
开发语言·c++·面试·stl容器和算法·类和数据抽象
Fuyo_11192 小时前
深入解析 C++ unordered_set 与 unordered_map 的核心特性与性能实战
开发语言·c++