Chrome 首次启动引导页里触发 Pref 设置,为什么主进程收不到 IPC

在做浏览器首次启动引导功能时,我遇到一个非常典型、但又很容易误判的问题:

  • 浏览器第一次启动时,会先弹一个引导窗口;
  • 我在这个引导窗口的前端控制台里触发 pref 设置;
  • 预期应该进入 ExternalApisTabHelper::OnMessageReceived() 再走到 ExternalApisTabHelper::OnPrefApiCmd()
  • 实际却是:主进程完全没有收到对应 IPC

很多人第一反应会怀疑:

  • 是不是 base::BindOnce() 参数绑错了?
  • 是不是 proceed 参数位置不对?
  • 是不是首次启动回调没正确触发?

但真正的问题,通常不在回调绑定,而在页面所处的运行环境和消息通道根本不是一条链路

这篇文章就把这个问题从架构层、消息链路层、对象生命周期层彻底拆开。


一、问题现象

先看启动阶段的核心代码。首次启动时,代码会走到类似下面这段逻辑:

复制代码
if ((first_run::IsChromeFirstRun() ||
     command_line.HasSwitch("show-startup-devtools")) && first_run_) {
  base::OnceCallback<void(bool, std::vector<std::string>)> continue_startup =
      base::BindOnce(
          [](const base::CommandLine& command_line,
             Profile* profile,
             const base::FilePath& cur_dir,
             const std::vector<GURL>& urls,
             chrome::startup::IsProcessStartup process_startup,
             chrome::startup::IsFirstRun is_first_run,
             bool proceed_arg,
             std::vector<std::string> runtime_args) {
            StartupBrowserCreator creator;
            base::CommandLine local_cmd_line = command_line;
            for (const auto& arg : runtime_args) {
              local_cmd_line.AppendSwitch(arg);
            }
            OpenNewWindowForFirstRun(command_line, profile, cur_dir, urls,
                                     process_startup, is_first_run,
                                     proceed_arg);
          },
          command_line,
          profile,
          cur_dir,
          first_run_tabs_,
          process_startup,
          is_first_run);

  StartupGuideView::Show(profile, std::move(continue_startup));
  first_run_ = false;
  return;
}

这个逻辑的意图其实非常清晰:

  1. 如果是浏览器首次运行,先不要直接开主窗口;
  2. 先显示引导页 StartupGuideView::Show()
  3. 把真正"继续启动浏览器"的逻辑包装成一个 base::OnceCallback<void(bool, std::vector<std::string>)>
  4. 等引导页完成后,再执行这个 callback 去继续启动主浏览器窗口。

而我遇到的问题是:

  • 在引导页里触发 pref 设置;
  • 预期进入:
    • ExternalApisTabHelper::OnMessageReceived()
    • ExternalApisTabHelper::OnPrefApiCmd()
  • 结果完全没进去。

对应的消息接收代码长这样:

复制代码
bool ExternalApisTabHelper::OnMessageReceived(const IPC::Message& message,
    content::RenderFrameHost* render_frame_host) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(ExternalApisTabHelper, message)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_AppCmd, OnAppCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_AppCmdBuffer, OnAppCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_FileApiCmd, OnFileApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_FileApiCmdBuffer, OnFileApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_RegistryApiCmd, OnRegistryApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_RegistryApiCmdBuffer, OnRegistryApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_DesktopApiCmd, OnDesktopApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_DesktopApiCmdBuffer, OnDesktopApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_PrefApiCmd, OnPrefApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_PrefApiCmdBuffer, OnPrefApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_BrowserApiCmd, OnBrowserApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_BrowserApiCmdBuffer, OnBrowserApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_TabApiCmd, OnTabApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_TabApiCmdBuffer, OnTabApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_ExternalStat, OnExternalStat)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_WidgetApiCmd, OnWidgetCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_WidgetApiCmdBuffer, OnWidgetCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_EventApiCmd, OnEventCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_SettingPanelApiCmd, OnSettingPanelApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_SettingPanelApiCmdBuffer, OnSettingPanelApiCmdBuffer)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()

  return handled;
}

其中 pref 分支是:

复制代码
void ExternalApisTabHelper::OnPrefApiCmd(
    const ExtensionHostMsg_PrefApiCmd_Params& params) {
  int prefcmd_id = params.invoke_id;
  const std::string& module_name = params.s1;
  const std::string& function_name = params.s2;
  const std::string& p1 = params.s3;
  const std::string& p2 = params.s4;
  const std::string& p3 = params.s5;
  const std::string& p4 = params.s6;

  if (module_name == "prefOperation") {
    prefcmd_handler::OnPrefApiCmd(prefcmd_id, module_name, function_name, p1,
                                  web_contents(), nullptr);
  }
}

现象非常明确:这里根本没进来。


二、先把一个容易误判的点说清楚:BindOnce 没有问题

很多人看到上面的 lambda,会卡在这个地方:

复制代码
bool proceed_arg,
std::vector<std::string> runtime_args

然后怀疑:

proceed 不应该被 bind,应该是运行时参数吧?

这个判断本身是对的。

在 Chromium 的 callback 模型中,base::BindOnce() 的行为是:

  • 前面传进去的实参,会被提前固化
  • lambda 末尾剩下的形参,会变成 callback 在 Run() 时需要提供的运行时参数。

所以你现在这个写法里:

  • 已经 bind 的:
    • command_line
    • profile
    • cur_dir
    • urls
    • process_startup
    • is_first_run
  • 没有 bind 的:
    • proceed_arg
    • runtime_args

因此最后得到的 callback 类型正好就是:

复制代码
base::OnceCallback<void(bool, std::vector<std::string>)>

也就是说:

  • proceed_arg 是运行时参数;
  • runtime_args 也是运行时参数;
  • 这和你想实现的语义一致。

所以,这段回调绑定不是 IPC 收不到的主因

它最多只会影响"引导页结束后是否继续开浏览器窗口",而不会决定"引导页里的 JS 能否发到 ExternalApisTabHelper"。

这两个问题是两个层次:

  • BindOnce 解决的是控制流
  • ExternalApisTabHelper 解决的是消息接收链路

三、真正的关键:首次启动引导页不是普通浏览器标签页

这是整个问题最核心的地方。

1. 普通"外部 API 页面"的创建方式

我在代码里确认到,挂载 ExternalApisTabHelper 的地方并不是全局自动发生的,而是依赖特定的 WebContents 创建路径。

WebHostView::WebHostView() 里有这样一段:

复制代码
web_contents_ = WebContents::Create(
    content::WebContents::CreateParams(browser_context, std::move(inset)));

PrefsTabHelper::CreateForWebContents(web_contents_.get());
extensions::SetViewType(web_contents_.get(),
    extensions::mojom::ViewType::kExtensionPopup);
web_contents_->SetDelegate(this);
web_contents_->SetZoomLevelDisable(true);
content::WebContentsObserver::Observe(web_contents_.get());
PrefsTabHelper::CreateForWebContents(web_contents_.get());
ExternalApisTabHelper::CreateForWebContents(web_contents_.get());
prefs_notify::PrefsNotifyTabHelper::CreateForWebContents(web_contents_.get());

注意其中最关键的三件事:

  1. WebContents 挂了 PrefsTabHelper::CreateForWebContents()
  2. 挂了 ExternalApisTabHelper::CreateForWebContents()
  3. 挂了 prefs_notify::PrefsNotifyTabHelper::CreateForWebContents()

这说明:

这套 pref / browser / widget / external API 的消息接收能力,并不是 WebContents 天生自带,而是通过 helper 显式挂上去的

也就是说,如果某个页面对应的 WebContents 没有执行这三步初始化,那么:

  • 它就没有 ExternalApisTabHelper
  • 它也就不会接收 ExtensionHostMsg_PrefApiCmd
  • 你的 OnPrefApiCmd() 自然永远进不去。

2. 首次启动引导页的创建方式

再看你的引导页 StartupGuideView 是怎么创建页面的:

复制代码
web_view_ = AddChildView(std::make_unique<views::WebView>(profile));
web_view_->LoadInitialURL(GURL("chrome://startup-guide/"));

这是一个完全不同的创建路径。

它不是通过 WebHostView 来创建页面,而是通过 views::WebView 直接承载一个 chrome://startup-guide/ 页面。

这个差异非常关键:

  • WebHostView 是你们定制链路里的"宿主页面容器";
  • views::WebView 则是 Chromium 通用的嵌入式 WebContents 视图控件;
  • 后者不会自动附带你们在前者里显式创建的 helper。

因此,首次启动引导页所在的 WebContents,默认并没有接入 ExternalApisTabHelper 这套消息接收体系。

这就是为什么你在引导页控制台里调 pref,主进程却收不到 ExtensionHostMsg_PrefApiCmd

不是因为消息中途丢了,而是因为:

这条页面根本没有连接到那根线。


四、为什么"在控制台里触发 pref 设置"不等于"会走到 ExternalApisTabHelper"

这里还涉及第二个经常被忽略的技术点:页面类型不同,前后端通信机制也不同。

你的引导页加载的是:

复制代码
chrome://startup-guide/

这说明它是一个 WebUI 页面,而不是普通扩展页,也不是普通网页。

1. WebUI 的标准通信方式

WebUI 的标准 JS -> Browser 通信,不是 ExtensionHostMsg_* 这套旧 IPC,而是:

  • 前端用 chrome.send(...)
  • C++ 侧用 web_ui->RegisterMessageCallback(...)

你当前的 StartupGuideUI 里已经明确用了这套机制:

复制代码
web_ui->RegisterMessageCallback(
    "finishSetup",
    base::BindRepeating(&StartupGuideUI::HandleFinishSetup,
                        base::Unretained(this)));

web_ui->RegisterMessageCallback(
    "showWindow",
    base::BindRepeating(&StartupGuideUI::HandleShowWindow,
                        base::Unretained(this)));

也就是说,这个页面天然应该走的是 WebUI message pipeline

2. ExternalApisTabHelper 的通信方式

ExternalApisTabHelper::OnMessageReceived() 处理的则是另一套链路:

复制代码
IPC_MESSAGE_HANDLER(ExtensionHostMsg_PrefApiCmd, OnPrefApiCmd)

这意味着它依赖的是:

  • Renderer 侧发送 ExtensionHostMsg_PrefApiCmd
  • Browser 侧有对应的 observer / listener / helper 挂在这个 WebContents
  • 最终进入 OnMessageReceived()

所以从架构上讲,这其实是两套完全不同的前后端桥接方式

  1. chrome.send / RegisterMessageCallback:WebUI 体系
  2. ExtensionHostMsg_* / IPC_MESSAGE_HANDLER:扩展/旧 IPC 体系

你在一个 WebUI 页面里,期待它自动具备另一套体系的收发能力,本身就是不成立的,除非你显式给这个页面对应的 WebContents 补齐那套初始化。


五、从对象关系上看,消息到底卡在哪一层

我们把对象关系画清楚,问题会更容易理解。

场景 A:普通承载 External API 的页面

对象链路大概是:

复制代码
WebHostView
  └── WebContents
        ├── PrefsTabHelper
        ├── ExternalApisTabHelper
        └── PrefsNotifyTabHelper

Renderer 发出 ExtensionHostMsg_PrefApiCmd 后:

复制代码
Renderer
  -> Browser-side WebContents route
  -> ExternalApisTabHelper::OnMessageReceived()
  -> ExternalApisTabHelper::OnPrefApiCmd()
  -> prefcmd_handler::OnPrefApiCmd()

场景 B:首次启动引导页

对象链路大概是:

复制代码
StartupGuideView
  └── views::WebView
        └── WebContents
              └── WebUI (StartupGuideUI)

这条链路里默认只有:

  • views::WebView
  • WebContents
  • WebUI
  • StartupGuideUI

没有

  • ExternalApisTabHelper
  • PrefsTabHelper
  • prefs_notify::PrefsNotifyTabHelper

所以消息会卡在什么地方?

答案是:

不是卡在 OnPrefApiCmd(),而是在更前面------接收器根本不存在

即使你的前端代码"自认为"发出了某种 pref 指令,也不意味着 Browser 侧这条 WebContents 有对应的接收对象。


六、为什么这个问题在首次启动场景特别容易出现

因为首次启动往往有一种"错觉":

看起来都是浏览器里显示的一个页面,应该和普通 tab 差不多。

但实际上首次启动往往是"浏览器 UI 初始化前"的一个临时宿主环境,它和正常标签页存在几个关键差异。

1. 生命周期不同

首次启动引导页在 StartupGuideView::Show() 中创建,展示结束后才触发真正的浏览器窗口启动。

这意味着:

  • 引导页先活着;
  • 真正的浏览器主窗口还没起来,或者还没完成正常初始化;
  • Browser*、TabStrip、普通页面 helper 链路可能都还不是稳定状态。

而普通 tab 的很多 Browser-side helper,天然是围绕"已存在的浏览器窗口"和"常规 tab 生命周期"设计的。

2. 宿主环境不同

首次启动页当前使用的是 views::WebView,不是 WebHostView

这意味着:

  • 它少了 自定义宿主的初始化逻辑;
  • 少了 helper 挂载;
  • 少了某些 view type 标记;
  • 少了某些默认代理和委托。

3. 通信模型不同

首次启动页是 chrome://startup-guide/,天然属于 WebUI 页面。

WebUI 页面的推荐通信方式,本来就不应该优先走 ExtensionHostMsg_*

所以这类问题非常容易在"首启页、设置页、引导页、气泡页、悬浮窗页"等非标准 tab 宿主环境里暴露出来。


七、如何验证问题确实出在 helper 没挂上

如果你想把这个问题彻底坐实,最有效的方式不是猜,而是分层打日志。

验证 1:引导页 WebContents 是否存在

StartupGuideView 处加日志:

复制代码
auto* web_contents = web_view_->GetWebContents();
LOG(INFO) << "startup guide web_contents=" << web_contents;

验证 2:helper 是否被挂载

如果 ExternalApisTabHelper 提供了 FromWebContents(...),可以这样打:

复制代码
LOG(INFO) << "external api helper="
          << ExternalApisTabHelper::FromWebContents(web_contents);

如果输出为空,问题就已经坐实了。

验证 3:消息接收函数是否有任何触发

ExternalApisTabHelper::OnMessageReceived() 开头打印:

复制代码
LOG(INFO) << "ExternalApisTabHelper::OnMessageReceived type=" << message.type();

如果普通页面能看到日志、引导页看不到,那就说明两者根本不是同一接收链路。

验证 4:WebUI 自己的 callback 是否能收到

StartupGuideUI 注册一条测试消息:

复制代码
web_ui->RegisterMessageCallback(
    "pingNative",
    base::BindRepeating(&StartupGuideUI::HandlePingNative,
                        base::Unretained(this)));

如果前端 chrome.send('pingNative') 能到达,而 pref IPC 到不了,就能进一步证明:

页面本身没问题,问题出在你走错了通信通道。


八、修复思路一:按 WebUI 体系重做 pref 设置(推荐)

这是我更推荐的方案,因为它和当前页面的架构天然一致。

既然 chrome://startup-guide/ 是一个 WebUI 页面,那就让它走 WebUI 的标准消息机制。

1. 在 StartupGuideUI 里注册消息

比如增加:

复制代码
web_ui->RegisterMessageCallback(
    "setStartupPref",
    base::BindRepeating(&StartupGuideUI::HandleSetStartupPref,
                        base::Unretained(this)));

2. 在 C++ 里实现处理逻辑

复制代码
void StartupGuideUI::HandleSetStartupPref(const base::Value::List& args) {
  AllowJavascript();

  if (args.size() < 2)
    return;

  const std::string* pref_name = args[0].GetIfString();
  const std::string* pref_value = args[1].GetIfString();
  if (!pref_name || !pref_value)
    return;

  Profile* profile = Profile::FromWebUI(web_ui());
  if (!profile)
    return;

  profile->GetPrefs()->SetString(*pref_name, *pref_value);
}

3. JS 侧直接调用

复制代码
chrome.send('setStartupPref', ['my.pref.key', '1']);

这种方式的优点是:

  • StartupGuideUI 当前已有的消息机制一致;
  • 不依赖 ExternalApisTabHelper
  • 不依赖 ExtensionHostMsg_*
  • 更符合 Chromium 对 WebUI 页面的设计方式;
  • 维护成本更低。

如果你的引导页只需要做少量 pref 写入,这是最干净的方案。


九、修复思路二:在引导页的 WebContents 上补挂 External API helper

如果你的目标不是"简单改几个 pref",而是必须复用现有整套 prefOperation JS 封装,那可以考虑把首次启动页也接入同一条 helper 链路。

StartupGuideView::StartupGuideView() 中,拿到 WebContents 后,参考 WebHostView::WebHostView() 的初始化方式补挂 helper。

伪代码如下:

复制代码
auto* web_contents = web_view_->GetWebContents();
if (web_contents) {
  PrefsTabHelper::CreateForWebContents(web_contents);
  ExternalApisTabHelper::CreateForWebContents(web_contents);
  prefs_notify::PrefsNotifyTabHelper::CreateForWebContents(web_contents);
}

必要时还可能要补:

复制代码
extensions::SetViewType(web_contents,
    extensions::mojom::ViewType::kExtensionPopup);

但这里有几个风险要注意。

风险 1:你只是"挂了 helper",不代表 Renderer 一定会发同样的 IPC

ExternalApisTabHelper::OnMessageReceived() 是 Browser 侧接收器,但前提是 Renderer 侧也得有对应发送逻辑。

而 WebUI 页面和扩展页的 renderer 环境差异很大:

  • JS 注入方式不同;
  • 暴露给页面的 native bridge 不同;
  • 安全策略不同;
  • 是否允许某些 API 注入也不同。

所以你不能只看 Browser 侧挂没挂 helper,还要确认:

引导页前端实际调用的 prefOperation,到底是不是在发送 ExtensionHostMsg_PrefApiCmd

风险 2:helper 挂载顺序可能影响行为

某些 helper 依赖:

  • WebContentsDelegate
  • ViewType
  • profile / browser context
  • 甚至特定 navigation state

因此照抄 WebHostView::WebHostView() 也不一定 100% 等价。

风险 3:架构污染

如果一个页面本质上是 WebUI 页面,却为了复用旧 API 硬接 ExtensionHostMsg_*,长期看可能把架构越搞越乱:

  • WebUI 逻辑一部分走 chrome.send
  • 一部分走 ExtensionHostMsg_*
  • 后期排查问题会非常痛苦

所以除非你必须复用成熟的 external api 体系,否则我仍然建议优先走 WebUI 原生消息。


十、为什么 WebHostView::OnMessageReceived() 只处理 AppCmd 也值得关注

还有一个容易被忽略的细节:在 WebHostView 里,OnMessageReceived 其实只处理了:

复制代码
IPC_MESSAGE_HANDLER(ExtensionHostMsg_AppCmd, OnAppCmd)

而你贴出来的 pref 处理是在 ExternalApisTabHelper::OnMessageReceived() 里。

这进一步说明:

  • WebHostView 自己并不负责所有 External API 消息;
  • 更完整的处理分发逻辑是在 ExternalApisTabHelper 里;
  • 因此光有 WebContents 本身还不够,必须把 helper 挂上去 ,才能接到 PrefApiCmd 这类消息。

这个点从侧面再次证明了前面的判断:

引导页如果没挂 ExternalApisTabHelper,就不可能走到 OnPrefApiCmd()


十一、从 Chromium 架构角度看,这个问题本质是什么

如果把这件事抽象一下,它本质上是一个非常典型的 Chromium/Browser 架构问题:

1. WebContents 只是页面容器,不等于"功能完备页面"

很多人会误以为:

只要我有一个 WebContents,前端发过来的所有东西 Browser 都能收到。

其实不是。

WebContents 更像是一个"浏览内容载体"。真正的业务能力,往往来自挂在它身上的各种 helper / observer / delegate:

  • PrefsTabHelper
  • ExternalApisTabHelper
  • FaviconTabHelper
  • PasswordManagerClient
  • 等等

所以"同样都是页面",功能是否可用,取决于它是不是走了同一套组装路径。

2. Browser-side capability 是"组装出来的"

首次启动页与普通 tab 最大的区别,不在于 UI 长得不同,而在于:

  • 它们的 WebContents 创建者不同;
  • 初始化流程不同;
  • 挂载 helper 不同;
  • 所属消息通道不同。

也就是说:

Chromium 里很多能力不是"页面自带",而是"宿主装配出来的"。

3. 控制流与消息链路是两套正交系统

这次问题很有代表性的一点在于:

  • 你一开始看的是 BindOncecontinue_startup
  • 但真正的问题发生在 WebContents 的消息接收装配层。

这说明在浏览器开发中,必须把两个维度分开看:

  1. 控制流:谁先创建、谁后显示、谁回调谁
  2. 通信流:谁能给谁发消息、谁在接收、谁具备处理能力

很多"看起来像回调有问题"的 bug,最后本质都是"对象根本不在同一个消息平面上"。


十二、我最终给出的结论

把这次问题压缩成一句话就是:

首次启动引导页 StartupGuideView 里使用的是 views::WebView + WebUI 机制创建的 chrome://startup-guide/ 页面,而不是走 WebHostView 那条会自动挂载 ExternalApisTabHelper 的创建链路。因此,引导页中触发的 pref 操作并不会自然进入 ExternalApisTabHelper::OnPrefApiCmd()

换句话说:

  • base::BindOnce() 参数位置不是主要问题;
  • proceed 作为运行时参数保留在 callback 末尾是正确的;
  • 真正的问题是:首启引导页的 WebContents 没有接入你预期的 External API IPC 接收体系。

十三、实践建议

如果你未来还会做类似页面,我建议按下面原则处理:

建议 1:先定义页面类型,再决定通信方式

  • 如果页面是 WebUI,优先用 chrome.send + RegisterMessageCallback
  • 如果页面是扩展页 / 特殊宿主页,再考虑 ExtensionHostMsg_*

不要混用,除非非常清楚两条链路都已经被正确装配。

建议 2:不要假设所有 WebContents 默认等价

每创建一个新页面,都问自己三个问题:

  1. 它是谁创建的?
  2. 它挂了哪些 helper?
  3. 它的 JS -> Browser 通信走哪条桥?

建议 3:遇到"收不到消息",先检查接收器是否存在

排查顺序建议固定成:

  1. 页面有没有对应的 WebContents
  2. helper 有没有挂上
  3. renderer 是否真的发了这条消息
  4. Browser 侧是否有 handler
  5. handler 是否因为条件判断被短路

而不是一上来就怀疑 callback 参数。


十四、一个更直白的总结

如果把整个问题类比成现实世界:

  • BindOnce 决定的是"什么时候出发、谁来开门";
  • ExternalApisTabHelper 决定的是"这栋楼里有没有前台收信";
  • 你的引导页问题并不是"门
相关推荐
小鹿软件办公2 小时前
谷歌 Chrome 终于推出垂直标签页与更智能的阅读模式
前端·chrome
我头发多我先学2 小时前
C++ STL vector 原理到模拟实现
c++·算法
鲸渔2 小时前
【C++ 入门】第一个程序:Hello World 与基本语法规则
开发语言·c++·算法
EverestVIP2 小时前
C++ 仿函数(Functors)
c++
会编程的土豆2 小时前
【数据结构与算法】 时间复杂度计算
数据结构·c++·算法
John_ToDebug2 小时前
Chromium 页面类型与 IPC 通信机制深度解析
前端·c++·chrome
小年糕是糕手2 小时前
【35天从0开始备战蓝桥杯 -- Day9】
数据结构·数据库·c++·算法·蓝桥杯
山甫aa2 小时前
STL---常见数据结构总结
开发语言·数据结构·c++·学习
H Journey2 小时前
C++ 11 新特性 基于范围的for循环
c++·c++11·for循环