1. 项目概述为什么我们需要一个C快速启动工具在Windows上工作久了你肯定遇到过这种场景桌面上堆满了各种软件的快捷方式任务栏也挤得满满当当。想打开一个不常用的工具要么得在开始菜单里翻找半天要么得先打开资源管理器再一层层点进安装目录。更别提那些命令行工具、脚本或者自己写的小程序了每次用都得开个终端敲一串长长的路径。这种效率上的“摩擦感”对于追求极致的开发者来说简直是一种折磨。于是像Launchy、Wox、Listary这类快速启动工具应运而生它们通过一个全局热键比如AltSpace呼出一个搜索框输入几个字母就能精准定位并启动程序、文件或执行命令极大地提升了操作效率。这些工具的核心价值在于“模糊匹配”和“极速响应”它们将用户从繁琐的路径记忆和鼠标点击中解放出来。那么我们为什么要用C来“重新造轮子”呢市面上不是已经有成熟的开源方案了吗这正是这个实战项目的价值所在。首先这是一个绝佳的C综合练手项目它几乎涵盖了桌面应用开发的方方面面从底层的系统API调用文件遍历、进程创建、注册表读取到核心的数据结构与算法字符串模糊匹配、索引构建再到上层的用户界面GUI框架如Qt或Win32 API和事件处理。其次通过亲手打造我们可以深度定制功能比如集成特定的开发命令一键编译、运行测试、绑定自定义脚本或者优化索引策略以应对海量文件。最后理解一个快速启动工具的内部机理能让你对操作系统的文件系统、进程管理和用户交互有更深刻的认识。接下来我将带你从零开始用C构建一个属于你自己的、高性能的快速启动工具。我们会从设计思路开始一步步实现核心的索引、搜索、启动功能并解决其中遇到的各种技术挑战和“坑”。2. 核心功能设计与技术选型一个快速启动工具看似简单但拆解开来核心模块不少。我们的目标是实现一个基础但完整可用的版本主要包含以下功能文件索引自动扫描指定目录如开始菜单、用户自定义目录收集可执行文件.exe, .lnk, .bat等的路径、名称等信息并建立索引。快速搜索用户输入关键词时能实时进行模糊匹配并按照匹配度排序展示结果。快速启动选中结果后能正确启动对应的应用程序或打开文件/目录。全局热键通过一个全局快捷键如CtrlShiftSpace随时呼出和隐藏搜索窗口。配置管理允许用户添加/移除索引目录、设置热键等。2.1 技术栈选型与考量1. 图形界面 (GUI):Qt: 这是我最推荐的选择尤其是对于C新手或希望快速构建跨平台应用的同学。Qt信号槽机制优雅地解决了界面与逻辑的耦合问题其丰富的控件和良好的文档能极大降低开发难度。我们的项目将主要基于Qt进行演示。它的跨平台特性Windows, macOS, Linux也是巨大优势。Win32 API / MFC: 如果你希望深入学习Windows原生开发追求极致的轻量化和性能或者想深入了解消息循环、窗口过程等底层机制可以选择Win32 API。但代码量会大很多开发效率较低。MFC相对古老不推荐新项目使用。其他: ImGui (Dear ImGui) 适合需要复杂渲染或工具类应用但风格偏“工具感”wxWidgets 是另一个跨平台选择但社区和生态略逊于Qt。选择Qt的深层原因除了易用和跨平台Qt的QFileSystemWatcher可以方便地监控目录变化以实现索引更新QProcess用于启动程序非常方便QSettings能轻松管理配置。这些“轮子”能让我们更专注于核心业务逻辑。2. 核心数据结构与算法:索引结构: 我们需要一个能支持前缀匹配和模糊匹配的数据结构。简单的std::vectorstd::pair名称, 路径配合线性搜索在数据量小时可以但为了效率我们需要更优的方案。前缀树 (Trie): 非常适合前缀匹配输入“chr”立刻匹配“chrome”。但对于“模糊匹配”如“cmoe”匹配“chrome”支持不好。倒排索引 (Inverted Index): 像搜索引擎一样对每个关键词记录包含它的文件。适合多关键词AND/OR查询但实现相对复杂。简化方案——排序向量二分查找: 将所有项目按名称排序用户输入时遍历所有项目计算一个匹配分数如判断输入字符串是否为项目名称的子序列并考虑连续匹配的奖励。这是实现模糊匹配最简单有效的方法在几千个条目内性能完全可以接受。我们将采用这种方案。匹配算法: 核心是计算用户输入query与候选项目名称candidate的匹配度分数。子序列匹配: 判断query是否是candidate的一个子序列不要求连续但顺序一致。例如“cmoe” 是 “chrome” 的子序列 (c-h-r-o-m-e)。评分策略: 匹配的字符越连续、越靠前分数应该越高。可以设计一个简单的评分函数基础分 连续匹配奖励 前缀匹配奖励。3. 系统相关操作:文件遍历: 使用std::filesystem(C17) 是现代化且跨平台的选择。在Windows上也可以用FindFirstFile/FindNextFileAPI但std::filesystem更简洁。进程启动: Qt 用QProcess::startDetached()原生Windows API 用CreateProcess或ShellExecute。ShellExecute更通用能处理文件关联比如用默认程序打开.txt文件。全局热键: Windows 平台需要使用RegisterHotKeyAPI。Qt本身不提供此功能需要调用原生API。这是本项目需要处理的一个平台相关点。配置存储: 简单的INI格式即可。Qt的QSettings类可以非常方便地读写INI文件或者使用像nlohmann/json这样的JSON库存储更结构化的配置。4. 项目结构规划:QuickLauncher/ ├── src/ │ ├── main.cpp # 程序入口初始化应用 │ ├── MainWindow.[h/cpp] # 主窗口类负责UI │ ├── SearchEngine.[h/cpp] # 核心搜索引擎负责索引和匹配 │ ├── SystemTray.[h/cpp] # 系统托盘图标管理 │ ├── HotkeyManager.[h/cpp] # 全局热键管理 (Windows API) │ └── ConfigManager.[h/cpp] # 配置管理 ├── resources/ # 图标等资源文件 ├── CMakeLists.txt # CMake构建脚本 └── README.md3. 核心模块实现详解3.1 搜索引擎 (SearchEngine) 的实现这是工具的大脑。我们需要一个类来管理所有可启动的项目并提供搜索功能。SearchEngine.h头文件设计:#ifndef SEARCHENGINE_H #define SEARCHENGINE_H #include vector #include string #include filesystem #include atomic #include mutex namespace fs std::filesystem; struct LauncherItem { std::string name; // 显示名称如 Visual Studio Code std::string path; // 完整路径如 C:\Program Files\Microsoft VS Code\Code.exe std::string arguments; // 启动参数 int score 0; // 本次搜索的匹配分数 }; class SearchEngine { public: SearchEngine(); ~SearchEngine(); // 初始化加载配置并开始索引 bool initialize(const std::vectorstd::string indexedPaths); // 执行搜索返回排序后的结果 std::vectorLauncherItem search(const std::string query); // 手动触发重新索引 void rebuildIndex(); // 获取索引状态 bool isIndexing() const { return m_isIndexing.load(); } int getItemCount() const { return m_items.size(); } private: // 索引一个目录 void indexDirectory(const fs::path dir); // 索引一个文件判断是否是可执行文件或快捷方式 void indexFile(const fs::path filePath); // 解析.lnk快捷方式文件获取其目标路径 (Windows特有) bool resolveShortcut(const fs::path lnkPath, std::string targetPath); // 计算匹配分数 int calculateScore(const std::string candidate, const std::string query); private: std::vectorLauncherItem m_items; // 所有索引项 std::atomicbool m_isIndexing{false}; mutable std::mutex m_mutex; // 保护 m_items 的并发访问 std::vectorstd::string m_indexedPaths; }; #endif // SEARCHENGINE_HSearchEngine.cpp关键实现:1. 索引构建 (indexDirectory):void SearchEngine::indexDirectory(const fs::path dir) { try { for (const auto entry : fs::recursive_directory_iterator(dir, fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink)) { if (m_isIndexing.load() false) break; // 允许取消 if (entry.is_regular_file()) { indexFile(entry.path()); } // 可以忽略目录或者将来把常用目录也作为可启动项 } } catch (const fs::filesystem_error e) { // 记录日志目录无法访问 std::cerr Failed to index directory: dir - e.what() std::endl; } }这里使用了recursive_directory_iterator进行递归遍历。skip_permission_denied选项很重要避免访问系统保护目录时程序崩溃。2. 文件识别与索引 (indexFile):void SearchEngine::indexFile(const fs::path filePath) { std::string extension filePath.extension().string(); std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); LauncherItem item; item.path filePath.string(); // 处理快捷方式 (.lnk) if (extension .lnk) { std::string target; if (resolveShortcut(filePath, target)) { item.path target; // 实际目标路径 item.name filePath.stem().string(); // 快捷方式名称作为显示名 } else { return; // 解析失败跳过 } } // 处理可执行文件 else if (extension .exe || extension .bat || extension .cmd || extension .ps1) { item.name filePath.stem().string(); } // 可以扩展其他类型如 .url, .appref-ms (ClickOnce) 等 else { return; // 非目标文件类型跳过 } // 去重检查简单的基于路径的去重 { std::lock_guardstd::mutex lock(m_mutex); auto it std::find_if(m_items.begin(), m_items.end(), [item](const LauncherItem i) { return i.path item.path; }); if (it m_items.end()) { m_items.push_back(std::move(item)); } } }3. 快捷方式解析 (resolveShortcut):这是Windows平台特有的部分需要用到COM组件。代码稍复杂但模式固定。#include windows.h #include shlobj.h #include shlwapi.h #include objbase.h #include propkey.h #pragma comment(lib, shell32.lib) #pragma comment(lib, shlwapi.lib) bool SearchEngine::resolveShortcut(const fs::path lnkPath, std::string targetPath) { HRESULT hr CoInitialize(NULL); // 初始化COM如果主线程已初始化这里可能会失败需处理 if (FAILED(hr)) return false; IShellLink* pShellLink nullptr; IPersistFile* pPersistFile nullptr; wchar_t target[MAX_PATH] {0}; // 创建IShellLink接口实例 hr CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)pShellLink); if (SUCCEEDED(hr)) { // 获取IPersistFile接口来加载.lnk文件 hr pShellLink-QueryInterface(IID_IPersistFile, (LPVOID*)pPersistFile); if (SUCCEEDED(hr)) { // 加载快捷方式文件 hr pPersistFile-Load(lnkPath.c_str(), STGM_READ); if (SUCCEEDED(hr)) { // 解析快捷方式获取目标路径 hr pShellLink-GetPath(target, MAX_PATH, NULL, SLGP_SHORTPATH); if (SUCCEEDED(hr)) { targetPath std::filesystem::path(target).string(); } } pPersistFile-Release(); } pShellLink-Release(); } CoUninitialize(); return SUCCEEDED(hr) !targetPath.empty(); }注意事项COM的初始化和释放需要小心处理。如果主程序如Qt应用已经调用了CoInitialize这里再调用可能会失败。更稳健的做法是在应用启动时统一初始化COM或者检查是否已初始化 (CoInitializeExwithCOINIT_APARTMENTTHREADED和COINIT_MULTITHREADED的区别也是坑点)。为了简化示例中每次解析都初始化/反初始化这在频繁调用时有效率问题但功能正确。4. 匹配评分算法 (calculateScore):这是决定搜索体验好坏的核心。我们实现一个简单的子序列匹配评分。int SearchEngine::calculateScore(const std::string candidate, const std::string query) { if (query.empty()) return 0; std::string candLower candidate; std::string qLower query; std::transform(candLower.begin(), candLower.end(), candLower.begin(), ::tolower); std::transform(qLower.begin(), qLower.end(), qLower.begin(), ::tolower); // 检查query是否是candidate的子序列 size_t i 0, j 0; int score 0; int consecutiveBonus 0; bool firstCharMatch false; while (i candLower.size() j qLower.size()) { if (candLower[i] qLower[j]) { if (j 0) { firstCharMatch (i 0); // 首字符匹配且位置为0给予高分奖励 } // 基础分匹配一个字符得10分 score 10; // 连续匹配奖励 if (j 0 candLower[i-1] qLower[j-1]) { consecutiveBonus 5; // 连续匹配额外加分 } j; // 只有匹配成功才移动query指针 } i; } // 如果query的所有字符都匹配上了 if (j qLower.size()) { score consecutiveBonus; if (firstCharMatch) score 20; // 前缀匹配奖励 // 额外奖励匹配的字符串越短分数越高鼓励精确匹配 score std::max(0, 30 - static_castint(candidate.size())/2); } else { score 0; // 不是完整子序列得0分 } return score; }这个算法虽然简单但效果不错。它保证了“chrome”输入“chr”比输入“ome”分数高输入“chrome”比输入“chorme”分数高后者可能不是子序列。更复杂的算法可以考虑使用编辑距离Levenshtein distance或像fzf那样的模糊匹配算法。5. 搜索函数 (search):std::vectorLauncherItem SearchEngine::search(const std::string query) { std::vectorLauncherItem results; std::lock_guardstd::mutex lock(m_mutex); // 搜索时也要加锁因为索引可能正在更新 for (auto item : m_items) { item.score calculateScore(item.name, query); if (item.score 0) { results.push_back(item); } } // 按分数降序排序 std::sort(results.begin(), results.end(), [](const LauncherItem a, const LauncherItem b) { return a.score b.score; }); // 限制返回数量提升响应速度 const size_t maxResults 20; if (results.size() maxResults) { results.resize(maxResults); } return results; }3.2 主窗口与用户交互 (MainWindow)使用Qt实现一个简洁的主窗口。核心是一个单行输入框 (QLineEdit) 和一个结果列表 (QListWidget)。关键点窗口样式: 设置为无边框 (Qt::FramelessWindowHint)、置顶 (Qt::WindowStaysOnTopHint)并添加半透明背景和阴影效果使其看起来像是一个“浮层”。全局热键响应: 在MainWindow中监听热键消息需要与HotkeyManager配合通过Windows消息机制转发。输入事件: 在QLineEdit的textChanged信号中调用SearchEngine::search并更新结果列表。键盘导航: 重写keyPressEvent处理上下箭头选择结果、Enter键启动、Esc键隐藏窗口。结果展示: 使用QListWidget每个项目可以自定义Widget显示图标、名称和路径片段。简化版MainWindow关键代码:// 在构造函数中设置UI和信号槽 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool); setAttribute(Qt::WA_TranslucentBackground); QWidget *centralWidget new QWidget(this); QVBoxLayout *layout new QVBoxLayout(centralWidget); m_lineEdit new QLineEdit(this); m_listWidget new QListWidget(this); layout-addWidget(m_lineEdit); layout-addWidget(m_listWidget); setCentralWidget(centralWidget); connect(m_lineEdit, QLineEdit::textChanged, this, MainWindow::onTextChanged); connect(m_listWidget, QListWidget::itemDoubleClicked, this, MainWindow::onItemActivated); // 初始化搜索引擎 m_searchEngine new SearchEngine(this); QStringList defaultPaths { C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs, QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) }; m_searchEngine-initialize(defaultPaths); } void MainWindow::onTextChanged(const QString text) { auto results m_searchEngine-search(text.toStdString()); m_listWidget-clear(); for (const auto item : results) { QListWidgetItem *listItem new QListWidgetItem(QString::fromStdString(item.name)); listItem-setData(Qt::UserRole, QString::fromStdString(item.path)); // 存储路径 m_listWidget-addItem(listItem); } if (!results.empty()) { m_listWidget-setCurrentRow(0); } } void MainWindow::onItemActivated(QListWidgetItem *item) { QString path item-data(Qt::UserRole).toString(); launchApplication(path); hide(); // 启动后隐藏窗口 } void MainWindow::launchApplication(const QString path) { QProcess process; // startDetached 会分离进程启动后不阻塞当前程序 bool success process.startDetached(path); if (!success) { // 尝试使用ShellExecuteWindows或xdg-openLinux作为后备 QDesktopServices::openUrl(QUrl::fromLocalFile(path)); } }3.3 全局热键管理 (HotkeyManager)这是让工具变得“全局可用”的关键。我们需要在系统层面注册一个热键即使我们的窗口没有焦点也能收到热键消息。HotkeyManager实现 (Windows):// HotkeyManager.h #include windows.h class HotkeyManager : public QObject { Q_OBJECT public: explicit HotkeyManager(QObject *parent nullptr); bool registerHotKey(int id, UINT modifiers, UINT key); // modifiers: MOD_ALT, MOD_CONTROL, MOD_SHIFT bool unregisterHotKey(int id); signals: void hotkeyTriggered(int id); protected: // 用于接收Windows消息需要配合Qt的nativeEventFilter bool nativeEventFilter(const QByteArray eventType, void *message, long *result) override; private: int m_hotkeyId 1; // 热键ID }; // HotkeyManager.cpp #include HotkeyManager.h #include QAbstractNativeEventFilter #include QCoreApplication HotkeyManager::HotkeyManager(QObject *parent) : QObject(parent) { qApp-installNativeEventFilter(this); // 安装原生事件过滤器 } bool HotkeyManager::registerHotKey(int id, UINT modifiers, UINT key) { return ::RegisterHotKey(NULL, id, modifiers, key); } bool HotkeyManager::unregisterHotKey(int id) { return ::UnregisterHotKey(NULL, id); } bool HotkeyManager::nativeEventFilter(const QByteArray eventType, void *message, long *result) { Q_UNUSED(eventType); Q_UNUSED(result); MSG *msg static_castMSG*(message); if (msg-message WM_HOTKEY) { // 判断热键ID是否是我们注册的 if (msg-wParam m_hotkeyId) { emit hotkeyTriggered(m_hotkeyId); return true; // 事件已处理 } } return false; // 事件未处理继续传递 }在MainWindow中连接hotkeyTriggered信号用于显示/隐藏窗口。重要提示全局热键可能会与其他应用程序冲突。好的实践是允许用户在配置中自定义热键并在注册前检查是否被占用可以通过尝试注册并捕获错误或者提供备选方案。3.4 配置管理使用QSettings存储用户配置非常简单。// ConfigManager.h #include QString #include QStringList class ConfigManager { public: static ConfigManager instance(); QStringList getIndexPaths() const; void setIndexPaths(const QStringList paths); QKeySequence getHotkey() const; void setHotkey(const QKeySequence seq); // ... 其他配置项 private: ConfigManager() default; // 单例 QSettings m_settings{MyCompany, QuickLauncher}; }; // 使用 ConfigManager::instance().setIndexPaths({ D:\\MyTools, C:\\Users\\Me\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu });4. 性能优化与进阶功能探讨一个基础版本完成后我们可以从以下几个方面进行优化和增强4.1 索引性能优化多线程索引: 初始索引可能很慢尤其是扫描整个开始菜单和多个自定义目录时。可以将indexDirectory任务放入QThreadPool或std::async中异步执行并更新进度条。增量更新: 使用QFileSystemWatcher监控索引目录当有文件增删改时只更新受影响的部分而不是全量重建索引。延迟加载图标: 获取应用程序图标SHGetFileInfo是一个相对耗时的IO操作。不要在索引时加载所有图标而是在搜索结果展示时对可见项进行异步加载。4.2 搜索算法优化预处理与缓存: 对m_items中的名称进行小写转换预处理避免每次搜索都转换。可以为每个项目名称预先计算一个“字符存在位图”加速子序列判断的初始筛选。使用更高效的算法: 当索引项超过数千时线性扫描可能成为瓶颈。可以考虑使用std::unordered_map建立首字母索引或者实现一个有限状态机FSM来进行更快速的模糊匹配。开源库fuzzywuzzy(C端口) 或re2(正则表达式库) 也可以集成。拼音搜索支持: 对于中文用户支持拼音搜索是刚需。可以集成像libpinyin这样的库将中文名称转换为拼音并建立额外的拼音索引。4.3 功能扩展插件系统: 设计一个插件接口允许用户或开发者编写插件来扩展搜索源。例如计算器插件: 输入 15*23直接显示结果。网页搜索插件: 输入g C快速启动直接调用浏览器搜索。系统命令插件: 输入shutdown /s /t 3600执行关机命令。历史记录与学习: 记录用户的启动选择并基于此对搜索结果进行加权使常用项排名更靠前。文件内容搜索: 不仅搜索文件名还能搜索文件内容需要集成EverythingSDK 或自己实现文件内容索引复杂度陡增。自定义动作: 对搜索结果不仅可以“打开”还可以定义“以管理员身份运行”、“打开所在目录”、“复制路径”等右键菜单动作。4.4 跨平台考量如果使用Qt跨平台已经解决了一大半。但部分模块需要平台特定实现全局热键: Windows用RegisterHotKeymacOS用RegisterEventHotKey(Carbon) 或NSEventaddGlobalMonitorForEventsMatchingMask(Cocoa)Linux用XGrabKey(X11) 或libxdo等。快捷方式解析: Windows解析.lnkmacOS解析.app和.weblocLinux解析.desktop文件。默认程序目录: 使用QStandardPaths类可以获取各平台的标准目录如应用程序目录、桌面目录等。5. 常见问题与调试技巧在开发过程中你几乎一定会遇到下面这些问题1. 热键注册失败 (RegisterHotKey返回false)原因: 热键已被其他程序占用如输入法、其他快速启动工具、游戏。排查: 使用GetLastError()获取错误码。常见错误ERROR_HOTKEY_ALREADY_REGISTERED(1409)。解决:提示用户更换热键组合。尝试注册一些不常用的组合如CtrlAltShift[Key]。实现一个“热键冲突检测”功能在用户设置时尝试注册并给出友好提示。2. 索引速度慢界面卡顿原因: 在主线程进行同步文件遍历和图标加载。解决:异步索引: 如前所述将索引任务放到工作线程。进度反馈: 使用信号槽在工作线程中发出progressUpdated和indexingFinished信号在主线程更新UI。分批次加载: 首次索引只加载基本信息图标在UI空闲时或显示时延迟加载。// 在工作线程中索引 void IndexWorker::run() { for (const auto path : m_paths) { if (m_stopRequested) break; indexDirectory(path); emit progress(/*...*/); // 发送进度信号 } emit finished(); }3. 搜索时CPU占用率高原因: 用户每输入一个字符就触发一次全量搜索和排序。解决:防抖 (Debounce): 使用QTimer用户停止输入约200ms后再触发搜索。void MainWindow::onTextChanged(const QString text) { m_searchTimer-stop(); // 停止之前的计时器 m_searchTimer-start(200); // 重启200ms计时器 m_lastQuery text; } // 连接 m_searchTimer 的 timeout 信号到实际的搜索槽函数最小搜索长度: 忽略过短的查询如1个字符直到输入2-3个字符后再开始搜索。4. 某些程序无法正确启动原因:路径包含空格或特殊字符没有用引号包裹。需要管理员权限。是商店应用 (UWP) 或特殊协议链接 (如steam://)。解决:使用QProcess::startDetached()或ShellExecuteEx它们能更好地处理命令行。对于需要管理员权限的可以尝试使用ShellExecuteEx并设置lpVerb为runas(这会弹出UAC确认框)。对于UWP应用需要解析其AppUserModelId并使用IApplicationActivationManager接口激活。这是一个深坑通常快速启动工具对UWP支持都不完美。5. 内存占用过高原因: 缓存了所有项目的图标QIcon每个图标可能包含多个尺寸的QPixmap。解决:使用QIcon::fromTheme或只缓存小尺寸图标如16x16。实现一个LRU最近最少使用缓存当图标数量超过阈值时淘汰最久未使用的。对于非常用项不缓存图标每次从文件实时加载会有性能损耗但节省内存。6. 在部分电脑上界面显示异常字体、DPI原因: 高DPI屏幕缩放导致布局错乱。解决:在main.cpp中在创建QApplication之前设置高DPI属性。int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication app(argc, argv); // ... }在UI设计时使用布局管理器 (QHBoxLayout,QVBoxLayout,QGridLayout) 而不是固定坐标并尽量使用相对大小和字体。开发这样一个工具从零到一的过程是对C综合能力的一次绝佳锻炼。你会接触到系统编程、GUI开发、数据结构算法、多线程、跨平台等诸多方面。当你最终用自己写的工具按下热键快速打开需要的软件时那种成就感是无可比拟的。更重要的是你拥有了一个完全受自己控制、可以无限扩展的效率利器。