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 相关的处理需求了。

相关推荐
扶尔魔ocy10 小时前
【qt openGL入门笔记】基础光照
qt·opengl
weixin_4407305010 小时前
内置函数、json文本、pickle二进制
开发语言·python·json
code_whiter11 小时前
01-MySQL数据库基础
数据库·mysql
殷色玫瑰11 小时前
C++ string类详解:常用接口、字符串操作与模拟实现
java·linux·c语言·开发语言·数据结构·c++
李游Leo11 小时前
HarmonyOS 7 + Spatial Recon Kit-C++:3DGS 高斯参数的非有限值隔离与可渲染性门禁【鸿蒙心迹】
开发语言·c++·3d·harmonyos
weixin_3823952311 小时前
本地部署 ERP 选型记录:从 Excel 到轻量系统的这几年
数据库·人工智能·数据挖掘
时间的拾荒人11 小时前
Qt 文件操作详解:从基础类到读写实战
开发语言·qt·面试
2401_8914092611 小时前
期货量化用得上的几类行情数据:逐笔、Level2、分钟线和五档tick
数据库
Wang's Blog11 小时前
Java 项目实战: 外卖平台优化-YApi接口管理平台与文档导入导出
java·开发语言·yapi
jimy112 小时前
基类没有虚析构函数,基类指针删除派生类对象,行为是未定义的
开发语言·c++