Qt QUrl 详解与代码示例

一、QUrl 是什么

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

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

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

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

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

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

它广泛用于 QNetworkAccessManager、QWebEngineView、QMediaPlayer、QDesktopServices::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

常用标志还包括:RemoveUserInfo、RemovePort、RemovePath。


六、常见坑与注意事项

  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::FullyDecoded 在 toString() 中有风险 完全解码可能产生歧义(比如 %2F 解码成 / 后改变了路径含义),只在纯展示场景使用。

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


七、小结

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

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

相关推荐
wyhwust4 分钟前
程序设计基础--C语言输入输出单元测试
c语言·开发语言
秦时明月之君临天下16 分钟前
MySQL purge 参数详解:innodb_purge_threads 与 innodb_purge_batch_size
数据库·mysql·1024程序员节
云和数据.ChenGuang20 分钟前
python sorted函数详细剖析
开发语言·人工智能·windows·python·1024程序员节
三垣网安22 分钟前
某某大学心理健康教育平台弱口令及SQL注入漏洞
数据库·sql
打工仔折腾 AI24 分钟前
AI Agent 工程化实战(二):5 分钟跑通 Deep Agent 并逐层拆解底层链路
java·数据库·人工智能·后端·python·langchain·ai agent 实战
外收内放26 分钟前
FastAPI猜字谜实战(4):让AI主持人开口
开发语言·python·学习·fastapi
云和数据.ChenGuang26 分钟前
Python map() 函数详解:从语法、原理到实战用法
开发语言·人工智能·python·langchain·1024程序员节
秋名RG29 分钟前
(二)存储、CPU 与指令执行——程序为什么快,又为什么慢
java·开发语言
Highcharts.js32 分钟前
Highcharts甘特图开发带导航功能的项目管理图示例
开发语言·前端·javascript·甘特图·highcharts·项目管理图
kyle~33 分钟前
C++ 原子操作、内存模型与无锁并发编程
linux·开发语言·c++·操作系统