Qt QUrl 详解与代码示例

一、QUrl 是什么

QUrl 是 Qt 中用来表示和操作 URL(统一资源定位符) 的类。它的核心能力包括:

  • 解析:把一段 URL 字符串拆解成 scheme、host、port、path、query、fragment 等部分;

  • 构造:按部件拼装出一个合法 URL,自动处理百分号编码;

  • 编码/解码:处理中文、空格等特殊字符的百分号编码(Percent-Encoding);

  • 本地文件互转 :在本地文件路径和 file:// URL 之间转换;

  • 相对路径解析:基于基准 URL 解析相对地址。

它广泛用于 QNetworkAccessManagerQWebEngineViewQMediaPlayerQDesktopServices::openUrl() 等几乎所有需要 URL 的场景。


二、URL 的组成

以这个 URL 为例:

复制代码
https://alice:secret@www.example.com:8443/download/file.html?id=1024&name=Qt#chapter2
└─┬─┘ └────┬────┘ └──────┬──────┘└─┬─┘└──────┬──────┘└────┬────┘└──┬──┘
scheme  userInfo       host      port    path          query   fragment
部件 获取方法 说明
协议 scheme() http、https、ftp、file 等
用户名 userName() @ 前的用户信息
密码 password() 敏感信息,打印时建议 RemovePassword
主机 host() 域名或 IP
端口 port() 未指定时返回 -1,可用 port(defaultPort)
路径 path() 资源路径
查询 query() ? 之后的参数串
片段 fragment() # 之后的锚点
授权信息 authority() userInfo@host:port 整体

三、创建 QUrl 的几种方式

cpp 复制代码
// 1. 直接从字符串构造(TolerantMode,宽松解析,最常用)
QUrl url1("https://www.qt.io/download");

// 2. 从已编码的字节数组构造,可指定严格模式
QUrl url2 = QUrl::fromEncoded("https://www.qt.io/a%20b", QUrl::StrictMode);

// 3. 智能处理用户输入(地址栏场景),会自动补全 http:// 或识别本地路径
QUrl url3 = QUrl::fromUserInput("www.qt.io");
// 结果: http://www.qt.io

// 4. 从本地文件路径构造
QUrl url4 = QUrl::fromLocalFile("/home/user/我的文档/测试 文件.txt");

// 5. 空对象 + 逐项设置
QUrl url5;
url5.setScheme("https");
url5.setHost("www.example.com");
url5.setPort(8443);
url5.setPath("/api/v1/items");

四、完整示例代码

1. 工程文件

CMakeLists.txt(Qt6)

bash 复制代码
cmake_minimum_required(VERSION 3.16)
project(QUrlDemo LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 REQUIRED COMPONENTS Core)
add_executable(QUrlDemo main.cpp)
target_link_libraries(QUrlDemo PRIVATE Qt6::Core)

QUrlDemo.pro(qmake,Qt5/Qt6 通用)

cpp 复制代码
QT       += core
QT       -= gui

CONFIG   += console c++17
CONFIG   -= app_bundle

TARGET    = QUrlDemo
SOURCES  += main.cpp

2. main.cpp

cpp 复制代码
#include <QCoreApplication>
#include <QUrl>
#include <QUrlQuery>
#include <QDebug>
#include <QStringList>

// ---------- 1. 解析 URL 各部件 ----------
void demoParse()
{
    qDebug() << "===== 1. 解析 URL =====";

    QUrl url(QStringLiteral(
        "https://alice:secret@www.example.com:8443/download/file.html"
        "?id=1024&name=Qt#chapter2"));

    qDebug() << "isValid()   :" << url.isValid();
    qDebug() << "scheme()    :" << url.scheme();        // https
    qDebug() << "userName()  :" << url.userName();      // alice
    qDebug() << "password()  :" << url.password();      // secret
    qDebug() << "host()      :" << url.host();          // www.example.com
    qDebug() << "port()      :" << url.port();          // 8443
    qDebug() << "port(80)    :" << url.port(80);        // 8443,未指定时返回默认值
    qDebug() << "path()      :" << url.path();          // /download/file.html
    qDebug() << "query()     :" << url.query();         // id=1024&name=Qt
    qDebug() << "fragment()  :" << url.fragment();      // chapter2
    qDebug() << "authority() :" << url.authority();     // alice:secret@www.example.com:8443

    qDebug() << "isRelative():" << url.isRelative();    // false
    qDebug() << "isLocalFile():" << url.isLocalFile();  // false
    qDebug() << "hasQuery()  :" << url.hasQuery();      // true
    qDebug() << "hasFragment():" << url.hasFragment();  // true

    // 打印时隐藏密码
    qDebug() << "safe print  :" << url.toString(QUrl::RemovePassword);
}

// ---------- 2. 用 QUrlQuery 构造与读取查询参数 ----------
void demoQuery()
{
    qDebug() << "\n===== 2. QUrlQuery 查询参数 =====";

    QUrl url(QStringLiteral("https://www.example.com/search"));

    // --- 构造 ---
    QUrlQuery query;
    query.addQueryItem(QStringLiteral("keyword"), QStringLiteral("Qt 教程"));
    query.addQueryItem(QStringLiteral("page"),    QStringLiteral("2"));
    query.addQueryItem(QStringLiteral("sort"),    QStringLiteral("desc"));
    url.setQuery(query);

    qDebug() << "URL:" << url.toString(QUrl::FullyEncoded);
    // https://www.example.com/search?keyword=Qt%20%E6%95%99%E7%A8%8B&page=2&sort=desc

    // --- 读取 ---
    QUrlQuery q(url);
    qDebug() << "hasQueryItem(keyword):" << q.hasQueryItem(QStringLiteral("keyword"));
    qDebug() << "keyword =" << q.queryItemValue(QStringLiteral("keyword")); // 自动解码
    qDebug() << "page    =" << q.queryItemValue(QStringLiteral("page"));

    // --- 遍历 ---
    const QList<QPair<QString, QString>> items = q.queryItems();
    for (const auto &item : items) {
        qDebug() << "  item:" << item.first << "=" << item.second;
    }

    // --- 修改与删除 ---
    q.removeQueryItem(QStringLiteral("sort"));
    q.addQueryItem(QStringLiteral("sort"), QStringLiteral("asc"));
    url.setQuery(q);
    qDebug() << "modified:" << url.toString(QUrl::FullyEncoded);
}

// ---------- 3. 本地文件路径互转 ----------
void demoLocalFile()
{
    qDebug() << "\n===== 3. 本地文件路径 <-> QUrl =====";

    const QString path = QStringLiteral("/home/user/我的文档/测试 文件.txt");
    QUrl url = QUrl::fromLocalFile(path);

    qDebug() << "fromLocalFile:" << url.toString(QUrl::FullyEncoded);
    // file:///home/user/%E6%88%91%E7%9A%84%E6%96%87%E6%A1%A3/...
    qDebug() << "toLocalFile  :" << url.toLocalFile();
    qDebug() << "isLocalFile  :" << url.isLocalFile();   // true

    // Windows 路径(跨平台代码里同样可以这样写)
    QUrl winUrl = QUrl::fromLocalFile(QStringLiteral("C:/Users/Test/文件.txt"));
    qDebug() << "win url  :" << winUrl.toString(QUrl::FullyEncoded);
    qDebug() << "win path :" << winUrl.toLocalFile();
}

// ---------- 4. 相对 URL 解析 ----------
void demoResolved()
{
    qDebug() << "\n===== 4. resolved() 相对路径解析 =====";

    QUrl base(QStringLiteral("https://www.example.com/docs/guide/index.html"));
    qDebug() << "base:" << base.toString();

    qDebug() << base.resolved(QUrl(QStringLiteral("page2.html"))).toString();
    // https://www.example.com/docs/guide/page2.html

    qDebug() << base.resolved(QUrl(QStringLiteral("../api/index.html"))).toString();
    // https://www.example.com/docs/api/index.html

    qDebug() << base.resolved(QUrl(QStringLiteral("/login"))).toString();
    // https://www.example.com/login

    qDebug() << base.resolved(QUrl(QStringLiteral("//cdn.example.com/lib.js"))).toString();
    // https://cdn.example.com/lib.js

    qDebug() << base.resolved(QUrl(QStringLiteral("?page=3"))).toString();
    // https://www.example.com/docs/guide/index.html?page=3

    qDebug() << base.resolved(QUrl(QStringLiteral("#top"))).toString();
    // https://www.example.com/docs/guide/index.html#top
}

// ---------- 5. 百分号编码 / 解码 ----------
void demoEncoding()
{
    qDebug() << "\n===== 5. 百分号编码 =====";

    const QString raw = QStringLiteral("a b&c=d/中文?#");
    const QByteArray encoded = QUrl::toPercentEncoding(raw);
    qDebug() << "toPercentEncoding  :" << encoded;

    const QString decoded = QUrl::fromPercentEncoding(encoded);
    qDebug() << "fromPercentEncoding:" << decoded;

    // 只保留某些字符不编码(exclude 参数)
    qDebug() << "keep '/'           :"
             << QUrl::toPercentEncoding(QStringLiteral("a/b c"), QByteArray("/"));
    // a/b%20c
}

// ---------- 6. 处理用户输入 ----------
void demoFromUserInput()
{
    qDebug() << "\n===== 6. fromUserInput 处理用户输入 =====";

    const QStringList inputs = {
        QStringLiteral("www.qt.io"),
        QStringLiteral("qt.io/download"),
        QStringLiteral("https://doc.qt.io/qt-6/qurl.html"),
        QStringLiteral("/home/user/test.txt"),
    };

    for (const QString &in : inputs) {
        const QUrl u = QUrl::fromUserInput(in);
        qDebug().noquote()
            << QStringLiteral("%1  ->  %2   [localFile=%3]")
                   .arg(in, u.toString(QUrl::FullyEncoded))
                   .arg(u.isLocalFile() ? "true" : "false");
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    demoParse();
    demoQuery();
    demoLocalFile();
    demoResolved();
    demoEncoding();
    demoFromUserInput();

    return 0;
}

典型输出(节选):

cpp 复制代码
===== 1. 解析 URL =====
isValid()   : true
scheme()    : "https"
userName()  : "alice"
password()  : "secret"
host()      : "www.example.com"
port()      : 8443
path()      : "/download/file.html"
query()     : "id=1024&name=Qt"
fragment()  : "chapter2"
safe print  : "https://alice@www.example.com:8443/download/file.html?id=1024&name=Qt#chapter2"

===== 2. QUrlQuery 查询参数 =====
URL: "https://www.example.com/search?keyword=Qt%20%E6%95%99%E7%A8%8B&page=2&sort=desc"
keyword = "Qt 教程"
...

五、格式化输出选项

QUrl::toString() / toEncoded() 支持一组位标志,用于控制输出内容:

cpp 复制代码
url.toString(QUrl::FullyEncoded);                    // 全部百分号编码,适合发网络请求
url.toString(QUrl::PrettyDecoded);                   // 人类可读(默认)
url.toString(QUrl::RemovePassword);                  // 去掉密码,适合打日志
url.toString(QUrl::RemoveQuery | QUrl::RemoveFragment); // 去掉查询和锚点
url.toString(QUrl::StripTrailingSlash);              // 去掉结尾的 '/'
url.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority); // 只留 path

常用标志还包括:RemoveUserInfoRemovePortRemovePath


六、常见坑与注意事项

  1. toString()toEncoded() toString() 默认做"漂亮解码",适合展示;如果要回传给网络层 或做持久化,请用 toEncoded()toString(QUrl::FullyEncoded)

  2. 不要用 QUrl(url.toString()) 往返转换 因为 toString() 可能已经解码,重新解析会丢信息。正确做法是 QUrl::fromEncoded(url.toEncoded())

  3. port() 在未指定端口时返回 -1 使用 url.port(443) 这样的重载可以获取带默认值的端口。

  4. 查询参数别手写字符串拼接 手动拼 ?a=1&b=中文 很容易忘记编码。统一用 QUrlQuery,它会自动处理 %20、UTF-8 编码和特殊字符。

  5. 相对路径用 resolved(),不要用字符串拼接 base.resolved(QUrl("..")) 会正确处理 ..///?# 等语义。

  6. 本地文件路径用 fromLocalFile() / toLocalFile() 直接 QUrl("C:\\a b.txt") 会被当成主机名解析,导致结果错误。

  7. QUrl::FullyDecodedtoString() 中有风险 完全解码可能产生歧义(比如 %2F 解码成 / 后改变了路径含义),只在纯展示场景使用。

  8. 用户名密码不要外泄 日志、错误提示里打印 URL 时记得加 QUrl::RemovePassword


七、小结

需求 推荐 API
解析 URL QUrl(str) + scheme()/host()/path()...
处理查询参数 QUrlQueryaddQueryItem / queryItemValue / queryItems
用户输入转 URL QUrl::fromUserInput()
本地文件互转 QUrl::fromLocalFile() / toLocalFile()
相对地址解析 base.resolved(relative)
百分号编码 QUrl::toPercentEncoding() / fromPercentEncoding()
输出给网络层 toEncoded() / toString(QUrl::FullyEncoded)
输出给用户看 toString() ,敏感信息加 RemovePassword

掌握 QUrlQUrlQuery 这两个类,基本就能覆盖 Qt 里所有和 URL 相关的处理需求了。

相关推荐
小灰灰搞电子1 小时前
Rust Attribute(属性标记)完整整理
开发语言·后端·rust
程序员阿鹏2 小时前
如何实现MySQL分库分表?
数据结构·数据库·sql·mysql·缓存
tedcloud1232 小时前
God‘s Eye View 怎么搭建?在云服务器上部署一个实时 3D 地球可视化平台
linux·服务器·开发语言·后端·rust
风哥2号2 小时前
数据库教程FGMT33‑MySQL主从复制项目实施与维护06(MySQL8.4/9.7 MGR组复制)
数据库·mysql
Tairitsu_H2 小时前
[C++] C++11 Lambda与包装器深度解析
开发语言·c++·c++11·lambda·包装器
liangsheng_g2 小时前
Spring事务传播行为分派与挂起恢复源码实战
数据库·sql·spring
傲世仙尊2 小时前
从磁盘硬件到Ext文件系统-Linux磁盘级文件系统学习笔记
linux·运维·服务器·开发语言·c++
蓝速科技2 小时前
信创终端 POC 测试实战与选型避坑指南丨蓝速科技
运维·数据库·人工智能·科技·自然语言处理
小羊没烦恼!2 小时前
Memory 记忆设计讨论:为什么 Agent Memory 不能只靠向量数据库?
java·开发语言·windows·算法·c#