一、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。
六、常见坑与注意事项
-
toString()≠toEncoded()toString()默认做"漂亮解码",适合展示;如果要回传给网络层 或做持久化,请用toEncoded()或toString(QUrl::FullyEncoded)。 -
不要用
QUrl(url.toString())往返转换 因为toString()可能已经解码,重新解析会丢信息。正确做法是QUrl::fromEncoded(url.toEncoded())。 -
port()在未指定端口时返回-1使用url.port(443)这样的重载可以获取带默认值的端口。 -
查询参数别手写字符串拼接 手动拼
?a=1&b=中文很容易忘记编码。统一用QUrlQuery,它会自动处理%20、UTF-8 编码和特殊字符。 -
相对路径用
resolved(),不要用字符串拼接base.resolved(QUrl(".."))会正确处理..、/、//、?、#等语义。 -
本地文件路径用
fromLocalFile()/toLocalFile()直接QUrl("C:\\a b.txt")会被当成主机名解析,导致结果错误。 -
QUrl::FullyDecoded在toString()中有风险 完全解码可能产生歧义(比如%2F解码成/后改变了路径含义),只在纯展示场景使用。 -
用户名密码不要外泄 日志、错误提示里打印 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 相关的处理需求了。