CppCon 2017 學習:Effective Qt: 2017 Edition

這段內容講的是 Qt 容器(Qt Containers)和標準庫容器(STL Containers)之間的選擇和背景:

主要觀點:

  1. Qt 容器的歷史背景
    • Qt 自身帶有一套容器類(如 QList, QVector, QMap 等),主要是因為歷史原因:
      • 早期Qt需要支持沒有標準庫(STL)支持的平臺。
      • 避免將標準庫的符號暴露在Qt的ABI(應用二進制接口)中,保證二進制兼容性。
  2. 現在的情況(Qt 5及以后)
    • Qt 5以后,已經假定目標平臺有“可用的、可用的STL實現”。
    • 這意味著Qt本身內部開始依賴標準庫,標準庫的可用性已成為前提。
  3. Qt容器在API中的角色
    • Qt依然在API中使用Qt容器,且這些容器對應用程序開發者開放。
    • 但對于新項目和業務代碼,建議優先使用標準庫容器,只有在必要時才用Qt容器。

總結

  • Qt容器是為了歷史兼容和API穩定性存在。
  • 對于大多數現代C++項目,尤其是跨平臺和與第三方庫交互,推薦使用標準庫容器(如 std::vector, std::map 等)
  • 僅在和Qt框架接口交互時,或者特殊性能需求時考慮Qt容器。

這部分內容詳細對比了 Qt 容器和 C++ 標準庫容器的設計哲學及對應關系,并介紹了一個特殊的 Qt 容器——QVarLengthArray。以下是總結:

Qt 容器設計哲學 vs 標準庫設計哲學

特點Qt容器標準庫容器
目標足夠好,主要用于構建 GUI真正通用,適合各種場景
API 風格使用 camelCase(例如 isEmpty()使用 snake_case(例如 empty()
設計重點易用性和API易發現性高效和正確性
典型用法示例QVector<int> v; v << 1 << 2 << 3;std::vector<int> v; v.push_back(1);
拷貝行為拷貝可能較“便宜”(淺拷貝或引用計數機制)拷貝通常是深拷貝
算法實現作為成員函數(如 contains()通過標準算法(如 std::find

Qt與標準庫對應容器對照表

Qt 容器標準庫容器
QVectorstd::vector
QList
QLinkedListstd::list
QVarLengthArray
QMapstd::map
QMultiMapstd::multimap
QHashstd::unordered_map
QMultiHashstd::unordered_multimap
QSetstd::unordered_set

QVarLengthArray 介紹

  • QVarLengthArray 是 Qt 中一個特殊的容器,類似于 std::vector,但它預先在棧上分配一定的空間,避免頻繁的堆分配。
  • 類似于 Boost 的 small_vector,支持所謂的“短小優化”(SSO,small string optimization思想),提高小規模數據的性能。
  • 適用于大多數情況下容器元素數量不會超過某個固定值的場景。
  • 示例聲明:QVarLengthArray<Obj, 32> vector; 表示預分配32個對象的空間。

這段內容進一步強調了 QList 以及整體 Qt容器 的局限性,并建議更傾向使用標準庫容器。總結如下:

QList 的特性和問題

  • 不是鏈表,而是基于數組實現的列表。
  • 對于存儲大于指針大小的對象非常低效,因為它會為每個對象單獨分配堆內存。
  • 建議避免使用 QList,除非別無選擇。
  • 自己寫代碼時優先使用 QVector

不建議使用 Qt 容器的理由

  • Qt 容器維護和更新不活躍,缺乏新特性。
  • STL 容器更快,生成的代碼更小且經過更多測試。
  • Qt 容器功能遠不及 STL,例如:
    • 存放的類型必須可默認構造和可復制。
    • 沒有異常安全保證。
    • 缺少許多 C++98/C++11/C++17 新增的API,如范圍構造、插入、就地構造(emplace)、基于節點的操作等。
    • 不支持靈活的分配器、比較器、哈希函數等自定義操作。
  • Qt 容器的API不一致,比如 QVector<T> 支持 append(T&&),但 QList<T> 不支持。
  • 還有在 resize、capacity、shrink 等行為上的差異。
  • Qt 容器API 與 STL 容器存在微妙差異,可能帶來使用上的困擾。

建議

  • 優先使用 STL 容器(如 std::vectorstd::mapstd::unordered_map 等)。
  • 只有在必須與 Qt API 交互時,才考慮使用 Qt 容器。

這段主要給出了在實際項目中選擇容器的建議,核心點總結如下:

選擇哪個容器?

  • STL 容器大多數情況下性能和特性都優于 Qt 容器。
  • Qt 自身實現內部也開始采用 STL 容器,說明它們的優勢。
  • Qt 的 API 仍然暴露 Qt 容器,無法輕易替換,因為 Qt 有強 API 和 ABI 兼容性承諾

應用程序的推薦策略:

  1. 優先使用 STL 容器。
  2. 僅在以下情況考慮使用 Qt 容器:
    • 沒有對應的 STL 或 Boost 容器(這幾乎不存在)。
    • 與 Qt 或基于 Qt 的庫接口交互時。
  3. 如果用到了 Qt 容器,盡量避免來回轉換 STL 和 Qt 容器。
    保持使用 Qt 容器,減少性能開銷和復雜度。
    簡單來說,除非為了兼容 Qt 接口,推薦用 STL 容器,既現代又高效。

關于 resize、capacity、shrink 這幾個行為,Qt 容器和 STL 容器確實存在一些細節差異:

1. resize()

  • STL 容器
    • resize(n) 會調整容器大小到 n,如果變大,會用默認值或指定值填充新增元素。
    • 對于 std::vector,新增元素構造且初始化。
    • 可以保證元素連續且大小準確。
  • Qt 容器(比如 QVector
    • resize(n) 也會調整大小,但內部實現可能采用引用計數共享數據。
    • 新增元素初始化行為和 STL 類似,但某些情況下效率可能略差。
    • QList 的行為因內部結構不同,resize() 可能導致額外的內存分配,效率不佳。

2. capacity()

  • STL 容器
    • capacity() 返回當前已分配但未使用的內存空間大小。
    • std::vector 會預先分配一定空間,減少擴容次數。
    • 可以通過 reserve() 來預先分配容量,避免多次重新分配。
  • Qt 容器
    • capacity() 也返回預分配空間大小。
    • 但 Qt 容器(尤其是老版本)對容量管理不如 STL 靈活,擴容策略可能不同。
    • 不能像 STL 一樣明確調用 reserve() 保證容量。

3. shrink_to_fit()

  • STL 容器
    • C++11 引入的函數,shrink_to_fit() 用于請求減少容量以匹配當前大小。
    • 實現是非強制的,但多數現代庫會釋放多余內存。
    • 提高內存利用率,避免浪費。
  • Qt 容器
    • 大多數 Qt 容器沒有 shrink_to_fit() 接口。
    • 只能通過拷貝或交換技巧手動釋放多余容量,比如重新構造一個容器拷貝數據。
    • 缺乏直接控制容量的函數,不夠靈活。

額外說明

  • Qt 容器內部通常使用引用計數和共享數據的技術,這導致某些操作(比如 resize)會更復雜,可能出現延遲復制(copy-on-write)。
  • STL 容器行為更加透明直接,便于性能優化和行為預測。

總結

操作STL 容器Qt 容器
resize()直接調整大小,初始化新增元素類似,但可能因共享數據延遲復制
capacity()返回預分配容量,可用 reserve 控制返回預分配容量,容量管理不夠靈活
shrink_to_fit()標準接口,嘗試釋放多余內存無對應接口,需手動技巧釋放多余容量

總結來說,針對 Qt 6:

  • Qt 容器必須繼續保留,確保兼容性和穩定的 API/ABI,不會做大破壞性改動。
  • QList 在 Qt 中使用非常廣泛,但它其實并不是一個理想的線性容器。
  • 未來有可能讓 QList 直接成為 QVector 的別名(typedef),簡化內部實現。
  • 同時,Qt 可能會推出一個新的容器類型(比如 QArrayList)來替代 QList 的部分功能,提供更好的性能和設計。
  • Qt 容器通過類型特征(type traits)來優化性能,尤其是判斷一個類型是否支持relocatable(可重定位)
  • 如果類型是可重定位的,容器擴容時可以直接用 realloc 這樣高效的內存操作,而不需要一個個移動元素,性能大幅提升。
  • 使用 Qt 容器時,建議用 Q_DECLARE_TYPEINFO 宏來告訴 Qt該類型是否可重定位,從而啟用優化。
  • 一些典型例子:
    • 簡單結構體(如 IntVector)通常是可重定位的。
    • 有指針指向自己或有內部聯系的結構(如 TreeNode)通常不可重定位,因為移動內存會破壞指針。
    • 有短字符串優化(SSO)的字符串類型,如果內部指針指向內部緩沖區,也不可重定位。
      這個機制可以顯著提高 Qt 容器的性能,前提是正確聲明類型信息。

編譯器不能自動判斷類型是否可重定位(relocatable),需要開發者手動標注。

  • Qt 通過宏 Q_DECLARE_TYPEINFO(Type, Kind) 來告訴容器該類型的“性質”,Kind 可以是:
    • Q_PRIMITIVE_TYPE
      • 類型非常簡單,比如 int,任何位模式都是有效的
      • 構造和析構可以跳過,直接內存拷貝即可
    • Q_MOVABLE_TYPE
      • 類型可被內存移動(如用 memmoverealloc
      • 但仍然調用構造和析構函數
    • Q_COMPLEX_TYPE(默認)
      • 普通復雜類型,需要正常調用構造、復制、析構
  • EASTL 有類似機制(EASTL_DECLARE_TRIVIAL_RELOCATE),而 STL 標準庫本身沒有明確這個特性。
    這讓 Qt 容器能根據類型特性選擇最優內存操作,提高性能。

每次定義可能會被放入 Qt 容器的自定義類型時,都應該用 Q_DECLARE_TYPEINFO 顯式聲明其類型信息。

  • 例如:
    struct IntVector {int size, capacity;int *data;
    };
    Q_DECLARE_TYPEINFO(IntVector, Q_MOVABLE_TYPE);
    
  • 如果之后再加這個 trait,有可能會導致 ABI 兼容性問題,影響程序穩定性和升級安全。
    所以,建議一開始就定義好,避免后期修改帶來的麻煩。

Qt 的**隱式共享(Implicit Sharing)**核心思想是:

  • 對象內部包含一個指向實際數據(pimpl)的指針,這個數據塊有一個引用計數器(refcount)。
  • 創建對象時,refcount = 1。
  • 拷貝對象時,只拷貝指針,refcount +1。
  • 調用 const 方法不改數據,refcount 不變。
  • 調用非 const 方法時,如果 refcount > 1,說明數據被共享,必須先detach(深拷貝數據),保證修改不會影響其他對象(寫時拷貝,Copy-On-Write)。
    這樣設計的好處是:
  • 拷貝操作很輕量(只增引用計數),節省性能。
  • 保證數據修改時不會影響到其他對象,實現值語義。
  • 但需要注意調用非 const 方法會觸發隱式深拷貝,可能會有性能開銷。
    這個機制常見于 Qt 的字符串(QString)、容器等類。
    要小心“隱藏的 detach”,即你可能沒有意識到調用了非 const 方法,導致了拷貝開銷。

這里演示了隱式共享在 QVector 中的實際效果。

示意過程是這樣的:

QVector<int> v {10, 20, 30};
QVector<int> v2 = v;  // 復制v,不會馬上拷貝數據,而是共享內部數據
  • vv2 共享同一塊內存(payload),里面存著 {10, 20, 30}。
  • 引用計數(refcount)為 2,表示兩個 QVector 對象共享同一數據。
  • 這時,內存只保存了一份數據,拷貝成本很低。
    只有當你對 vv2 調用非 const 方法(修改操作)時,如果 refcount > 1,就會觸發 detach,深拷貝數據,分配獨立內存,避免數據沖突。
    總結:
  • 復制 QVector 很輕量(共享數據 + refcount++)
  • 修改共享數據前會觸發深拷貝(detach)

這個例子具體展示了隱式共享(copy-on-write)機制在 QVector 修改時的行為:

QVector<int> v {10, 20, 30};
QVector<int> v2 = v;  // v2 共享 v 的數據,refcount = 2,payload = {10, 20, 30}
v2[0] = 99;  // 修改 v2,第一個元素變成 99

過程分析:

  • 初始時,vv2 共享同一份數據(payload),內容是 {10, 20, 30},引用計數是 2。
  • 當執行 v2[0] = 99 這個寫操作時,v2 檢測到引用計數大于 1(表示數據被共享),觸發detach
  • detach 意味著 v2 會進行一次深拷貝,分配自己的內存來存儲數據。
  • 修改只會影響 v2v 依舊保持原數據 {10, 20, 30}
  • 結果是:
    • v 仍然是 {10, 20, 30}
    • v2 變成了 {99, 20, 30}
    • 兩者的數據不再共享,引用計數分別為 1。
      這個機制保證了:
  • 復制對象時開銷很小,都是共享數據。
  • 只有寫操作時才真正做深拷貝,保證數據安全。
    這是 Qt 容器里隱式共享的核心思想,也是性能優化的關鍵點。

Qt 的 Implicit Sharing(隱式共享) 的總結。以下是對這段內容的詳細理解解釋:

什么是 Implicit Sharing?

隱式共享是一種 “寫時拷貝”(Copy-On-Write, COW) 機制,結合了引用計數和延遲深拷貝的技術,核心目的是:

  • 節省內存
  • 提高性能
  • 簡化代碼書寫

為什么有用?

“This mechanism makes writing code a lot simpler”

這句話的意思是:你可以像寫普通值一樣寫 Qt 的類(例如 QString, QByteArray, QVector 等),而不用擔心性能問題。
比如:

QString a = "hello";
QString b = a; // 不會拷貝數據,只是增加引用計數
b[0] = 'H';    // 觸發深拷貝(detach),a 仍然是 "hello",b 變成 "Hello"

你可以放心用:

  • 值返回(return QString
  • 拷貝賦值(QString b = a
  • 不需要頻繁考慮性能陷阱

哪些 Qt 類支持 Implicit Sharing?

“The great majority of Qt value classes are implicitly shared”

常見支持隱式共享的類包括:

類型是否支持隱式共享
QString
QByteArray
QVariant
QImage / QPixmap
QVector
QList
QMap / QHash
QVarLengthArray

例外:QVarLengthArray 是值語義容器,不走引用計數,它在棧上直接分配數據,所以不隱式共享。

工作原理簡述:

  1. 拷貝對象時,不復制數據,只是引用計數 +1。
  2. 寫入對象時,如果引用計數 > 1,就自動復制一份(detach),以避免影響其他對象。
  3. 析構對象時,引用計數 -1,當為 0 時釋放資源。

總結:

隱式共享是 Qt 的一大特色。它允許你:

  • 寫出高效、簡潔的值語義代碼;
  • 不擔心性能陷阱;
  • 但也要注意:寫操作(即使看起來是 const)可能隱式觸發深拷貝。

Implicit sharing and containers: where’s the catch?”指出了 Qt 隱式共享容器的一些隱藏陷阱和誤區。以下是逐句解析與理解:

隱式共享和容器:問題出在哪?

? Handing out references to data inside a container does not make the container unshareable

意思:
即使你將容器中的元素通過引用的方式返回或傳遞出去,比如:

QVector<int> v1 = {1, 2, 3};
QVector<int> v2 = v1; // 現在引用計數是 2
int &ref = v1[0]; // 獲取引用

此時 v1 仍然是可共享的,Qt 不會自動觸發 detach。
也就是說:只是訪問引用,不會破壞共享關系。

? It's easy to accidentally detach a container

意思:
一旦你對容器做了寫操作(哪怕是間接的),就會觸發 detach(深拷貝)。比如:

v2[0] = 100; // 一寫就會 detach,變成獨立的副本

這種操作很容易發生在你沒意識到的地方,從而悄悄改變了對象的共享狀態。

? Accidental detaching can hide bugs

意思:
這種悄悄發生的 detach 行為可能導致 bug 被隱藏,因為:

  • 你以為兩個對象共享同一份數據(如 v1, v2),但其實不再共享;
  • 導致數據不同步、調試困難;
  • 在多線程或資源受限環境中尤其危險。
    例如:
if (v1 == v2) {doSomething(); // 你以為它們是同一份數據,但可能早就 detach 了
}

? IOW, it's not just about performance

IOW = In Other Words(換句話說)
不是只有性能問題,還是“正確性問題”!

  • 深拷貝帶來的性能開銷固然重要;
  • 代碼邏輯混亂共享狀態錯亂數據不一致更加危險;
  • 這些 bug 可能非常隱蔽,特別是當代碼中混入了隱藏的 detach 操作。

? Code polluted by (out-of-line) detach/destructor calls

意思:
編譯后的代碼里會因為 Qt 的隱式共享機制,出現許多:

  • 隱藏的拷貝構造函數調用
  • 深拷貝(detach)操作
  • 析構函數調用
    這會讓代碼生成“變重”、函數調用棧變復雜,甚至會破壞 inlining,從而降低性能或調試可讀性。

總結:使用 Qt 隱式共享容器的注意事項

項目建議
寫入操作明確知道何時觸發了 detach
多對象共享容器時小心副作用、不可預期的獨立副本
性能敏感代碼盡量使用 std::vector 等無隱式共享的 STL 容器
傳引用/指針訪問內部數據知道不會破壞共享狀態,但不要寫入!

這段內容解釋了 Qt 隱式共享容器(如 QVector)中一個容易被忽略的陷阱引用(包括迭代器)不會阻止容器被拷貝(detach),可能導致代碼行為與你預期的不同。

下面是逐句解釋和深入理解:

Returning references to data inside a container(從容器中返回引用)

“Handing out references to data inside a container does not make the container unshareable”

意思是:
即使你取出了容器中某個元素的引用,這個容器仍然是“共享的”,Qt 不會因為你持有引用而主動拷貝(detach)數據
也就是說,這不是 COW(Copy-On-Write)觸發的條件。

“E.g. of such references: iterators”

像下面這樣:

QVector<int> v = {10, 20, 30};
auto it = v.begin();     // 拿到迭代器
int &ref = v[0];         // 或直接取引用

這些引用/迭代器不會改變引用計數,也不會讓 Qt 自動 detach。

例子分析:

QVector<int> v {10, 20, 30};
auto &r = v[0];        // 取引用
QVector<int> v2 = v;   // 現在 v 和 v2 是共享的,refcount = 2
r = 99;                // 修改通過 v 的引用,會影響共享數據!

圖示如下(內存共享前):

v 和 v2 共用同一個 payload:
payload = [10, 20, 30]
refcount = 2

當執行 r = 99; 時,沒有觸發 detach,因為 r 是直接引用底層數據。
結果是:

v  = [99, 20, 30]
v2 = [99, 20, 30]    <-- 也被修改了!

你以為 v2[0] 還是 10,結果 斷言失敗:

assert(v2[0] == 10); //  fails!

關鍵陷阱總結:

行為是否觸發 detach?說明
賦值一個容器不觸發引用計數增加
調用非 const 成員函數可能觸發v[0] = 99,會自動復制(detach)
手動獲取元素引用然后修改不自動 detach數據是共享的,兩個容器都會變
使用迭代器修改內容不自動 detach同樣直接影響共享內存

正確做法建議:

  1. 避免持久使用引用或迭代器后再修改容器副本
  2. 如果你想“安全修改一個副本”,請手動 detach:
    QVector<int> v2 = v;
    v2.detach();           // 強制深拷貝
    v2[0] = 99;            // 不會影響原 v
    
  3. 使用 STL 容器(如 std::vector)時,這種問題不會出現,因為沒有隱式共享。

Qt 隱式共享容器中「意外的深拷貝(accidental detach)」問題。這是 Qt 使用者常常忽略的一大坑。以下是詳細解釋:

你需要理解的核心要點:

例子:

QVector<int> calculateSomething();
const int firstResult = calculateSomething().first(); 

問題分析:

QVector<T>::first(); // 是非 const 的,返回 T&

這就意味著:

  • calculateSomething() 生成了一個臨時的 QVector<int>
  • .first()非 const 成員函數,所以 Qt 會 觸發 detach(即 deep copy 臨時對象的數據)。
    但其實你并不需要修改這個容器!你只是想拿第一個值!但 Qt 沒法知道你的意圖,調用了非 const 版本,就要執行 Copy-On-Write。

結果:

你只是想:

const int x = QVector<int>{10, 20, 30}.first(); 

但 Qt 背后悄悄:

  • 創建 QVector 臨時對象
  • 進行一次深拷貝(detach)
  • 然后返回引用(其實沒用到)
    這就引發了不必要的內存分配和復制——而你完全沒有意識到!

正確的寫法:

const int firstResult = calculateSomething().constFirst();

這樣:

  • .constFirst()const 成員函數
  • 不會觸發 detach
  • 沒有不必要的深拷貝
  • 返回值仍然是你需要的第一個元素(但是只讀的)

總結:Qt 中意外 detach 的教訓

錯誤寫法原因正確寫法
v.first()非 const 成員函數,可能 deep copyv.constFirst()
v.last()同上v.constLast()
v[i]返回引用,非 const,可能 detachv.at(i)const auto val = v[i];

怎么發現這些問題?

  • 它們在編譯時不會報錯;
  • 但會在 heap profiler(如 massif, heaptrack)中看到內存突增;
  • 一旦你分析出代碼里這些細節,會發現許多“不該拷貝的地方在偷偷拷貝”。

Qt 容器的隱式共享(implicit sharing)機制以及 accidental detach(意外拷貝) 帶來的陷阱。現在我們來逐條解析你說的這個更嚴重的問題:

1. Accidental Detach 導致的 Bug(不是性能問題,而是邏輯錯誤)

場景代碼:

QMap<int, int> map;
// ...
if (map.find(key) == map.cend()) {std::cout << "not found" << std::endl;
} else {std::cout << "found" << std::endl;
}

問題來了:

  • map.find(key) 是一個 非常量成員函數(non-const)
  • 它可能導致容器 detach,即做一次深拷貝(復制 pimpl)。
  • 而你已經提前調用了 map.cend(),它指向 原始容器的末尾
  • 之后容器被 detach 成新副本,map.find() 返回的是 新副本的迭代器
  • 兩個 end 迭代器 來自不同的容器副本,它們 不相等!

結果:

哪怕 key 根本就不在原始 map 中,也可能打印:

found

這就不是性能問題了,而是一個 邏輯 Bug,非常隱蔽、危險!

正確做法:使用 const 方法

if (map.constFind(key) == map.cend()) {std::cout << "not found" << std::endl;
}

或者,等價更安全:

if (!map.contains(key)) {std::cout << "not found" << std::endl;
}

總結建議:Qt 容器 + 隱式共享 + 非 const 方法 = 潛在 Bug

場景錯誤方法原因正確做法
查找元素map.find(key)可能觸發 detach,破壞邏輯判斷map.constFind(key)
訪問第一個元素v.first()非 const 方法可能導致 deep copyv.constFirst()
遍歷時比較混用 iteratorconst_iterator來自不同容器副本,比較結果錯誤統一用 const_iterator

🗣 標準庫的立場 vs Qt 的立場

C++ STLQt
避免隱式共享機制(copy-on-write)依賴 implicit sharing
強調明確語義、值語義更偏重方便與 API 一致性
更安全、更一致更方便、更快捷,但埋雷多
如果你開發的是性能敏感或邏輯嚴謹的模塊(比如底層庫、工具鏈),建議:
  • 盡量使用 STL 容器;
  • 只在與 Qt API 交互時使用 Qt 容器;
  • 嚴格區分 const 和非 const 使用;
  • 使用靜態分析工具(如 Clazy)來檢測 Qt-specific misuse。

千萬不要再用 foreachQ_FOREACH

原因總結:

foreach (var, container)

等價于以下代碼:

{const auto _copy = container;  //  拷貝了整個容器!auto it = _copy.begin(), end = _copy.end();for (; it != end; ++it) {var = *it;body;}
}

嚴重問題:

1. 容器整體拷貝一次

即使你只想遍歷,但 Qt 容器采用隱式共享機制(copy-on-write),會把整個容器 復制一份!這完全是你意料之外的。

2. 邏輯錯誤隱患

容器變了,你拿到的是副本,里面元素可能不對,還以為遍歷的是原始容器。

3. 性能問題非常嚴重

在有大量數據或頻繁迭代的場景下,每次循環都在悄悄 deep copy。

正確做法:使用 C++11 的 range-based for

for (const auto& value : container) {// Safe, efficient, no copy
}
  • 不會觸發隱式深拷貝
  • 更現代,更清晰
  • 完美支持 STL 和 Qt 容器(如 QVector, QStringList

尤其小心 Qt 容器

由于 Qt 容器隱式共享(implicit sharing)+ Q_FOREACH 的復制行為,一起使用等于 踩雷必炸
例如:

QStringList list = {"a", "b", "c"};
foreach (QString s, list) {// 修改 s 沒問題,但 list 是被拷貝的副本
}

你以為你在修改 list,其實根本沒改到!

永久禁用建議(企業/團隊級別)

.proCMakeLists.txt 中添加:

DEFINES += QT_NO_FOREACH

這會在你代碼中使用 foreach / Q_FOREACH編譯失敗

總結

特性Q_FOREACHC++11 range-for
是否復制容器會復制不會
支持 STL 容器
是否安全高效
是否推薦使用完全不推薦強烈推薦

foreach / Q_FOREACH 使用建議總結

“優點”(其實是個誤導):

  • “可以安全修改原容器”
    其實是誤導:因為循環中你操作的是拷貝副本,不是原容器。真正修改容器行為反而不可控、不清晰。

缺點(致命):

缺點說明
總是復制容器Q_FOREACH 會拷貝一份容器(無論你愿不愿意),即使你只讀。對 STL 容器非常昂貴
拷貝的是 const 容器無法修改容器元素(因為元素是 const)
可讀性差隱式語義不清楚,容易出 bug
與現代 C++ 不兼容不支持 STL 容器,不支持迭代器,不支持 structured bindings 等
將在 Qt 6 中被移除官方明確計劃廢棄此功能

最佳實踐

  • 使用 C++11 的 range-based for
for (const auto& item : container) {// 安全、高效、清晰
}
  • 禁用 Q_FOREACH / foreach
    在項目中定義:
DEFINES += QT_NO_FOREACH  // for qmake
# 或
add_definitions(-DQT_NO_FOREACH)  // for CMake

這將使編譯器在你使用 foreach直接報錯,強制你使用現代 C++。

額外建議

替換建議替代語法
foreach (auto x, list)for (const auto& x : list)
foreach (QString s, strings)for (const QString& s : strings)
foreach (int i, QVector<int>)for (int i : QVector<int>)
如果你愿意,我可以:
  • 掃描一個項目中的所有 foreach 并一鍵轉換為 C++11 語法;
  • 或者寫一個 clang-tidy 規則 / Python 腳本幫助自動替換。

這一節講的是 range-based for 循環(基于范圍的 for 循環) 在 Qt 和 STL 容器上的行為細節,尤其是它可能引發的 隱式分離(implicit detach) 問題。

Range-based for 的真實展開形式:

for (var : container) body;

等價于

{auto &&c = (container);auto i = begin(c);auto e = end(c);for (; i != e; ++i) {var = *i;body;}
}

STL 容器(如 std::vector<T>)的行為

  • iestd::vector<T>::iterator
  • 如果你不在 body 中修改元素或容器,沒有副作用
  • 這也是現代 C++ 推薦的方式。

Qt 容器(如 QVector<T>)的行為

  • ieQVector<T>::iterator不是 const_iterator
  • 即使你不在 body 中修改容器,也可能導致 隱式 detach

    因為 Qt 容器在調用 non-const 成員函數(如 begin()end())時,如果 refcount > 1,會觸發深拷貝(detach)。

例如:

QVector<QString> v = ...;
for (const auto& s : v) {qDebug() << s;
}

乍一看沒問題,但 v.begin()v.end()非 const 成員函數,可能導致:

  • 性能開銷:觸發 deep copy
  • 行為變化:影響共享數據的其它副本

正確做法(避免 detach)

推薦方式:明確使用 const&const_iterator

// 使用 const 引用,避免 detach
for (const QString& s : v) {qDebug() << s;
}

或者,如果你寫模板代碼,優先使用 const QVector<T>& 參數,這樣 begin() 會是 const_iterator,避免 detach。

小結:如何安全使用 range-based for?

情況建議
使用 STL 容器直接使用 range-based for,安全高效
使用 Qt 容器容器變量加 const&,元素加 const&
不確定是否 detachconst_iterator 避免陷阱

這一部分是對 Qt 的 Q_FOREACH 和 C++11 的 range-based for loop 在使用 Qt 容器和 STL 容器時的行為差異總結。下面幫你歸納一下關鍵點:

range-based for vs Q_FOREACH 對比總結

容器類型Q_FOREACHrange-based for (auto & : c)range-based for (const auto & : c)
Qt 非 constOK(cheap)可能會 detach(非 const 迭代器)可能會 detach(begin() 不是 const)
Qt constOK(cheap)不會 detach(const 迭代器)不會 detach(const 迭代器)
STL 非 const會 deep copy(復制一份)OKOK
STL const會 deep copyOKOK

為什么有這些區別?

Q_FOREACH 的問題:

  • 始終復制容器(哪怕是 const 容器),對于 STL 容器來說是災難性的(深拷貝)。
  • 對 Qt 容器沒什么問題,因為 Qt 使用了 implicit sharing,所以復制是廉價的。
  • 缺點是代碼難以推理,性能不透明,因此 Qt 6 已廢棄 Q_FOREACH

range-based for 的細節:

for (auto &item : container) {// ...
}
  • 如果 containerQt 非 const 容器
    • 調用的是 begin()end(),它們是 非常量成員函數
    • 如果容器被共享(refcount > 1),會發生 detach(深拷貝)。
  • 如果 containerconst,就會使用 const_iterator,不會觸發 detach。

實踐建議(寫 Qt 代碼時):

  1. 避免使用 Q_FOREACH,在項目中定義:
    #define QT_NO_FOREACH
    
  2. 優先使用 range-based for,但要注意:
    const QVector<int> vec = ...;
    for (const auto &x : vec) {  //  安全,不會 detach...
    }
    QVector<int> vec = ...;
    for (auto &x : vec) {        //  可能 detach(如果被共享)...
    }
    
  3. 如果一定需要修改容器或元素,考慮:
    • 保證容器未共享;
    • 或使用 detach() 手動控制。
  • 非 const Qt 容器使用 range-based for 循環時,要小心可能觸發隱式 detach
  • 如果不修改容器,盡量用 const 容器或者通過 qAsConst()(Qt5.7起)或 std::as_const()(C++17起)將容器轉換為 const
  • 不能對臨時(rvalue)直接使用 qAsConst(),這種情況下先用 const 引用綁定,再循環。
    這樣可以避免不必要的深拷貝,提升性能,且代碼更安全。

Clazy,它是基于 Clang 的開源靜態分析工具,專門針對 Qt 代碼。總結一下:

  • Clazy 類似于 clang-tidy,但聚焦于 Qt 風格和 Qt 特有的坑。
  • 它自帶 50+ 規則檢查,比如:
    • detaching-temporary(檢測隱式 detach 相關問題)
    • strict-iterators(檢測迭代器使用)
    • missing-typeinfo(缺少類型信息)
    • foreach(檢測不建議用的 Qt foreach)
  • 它還能自動提供 fix-it,幫你自動改代碼。
  • 即使是 Qt 自己的代碼庫,也有不少問題被 Clazy 檢測出來。
  • 建議定期用 Clazy 掃描你的代碼,修復警告,提升代碼質量和性能。
    這工具對保持 Qt 代碼庫的健康和現代化很重要,尤其是避免隱式 detach 等細節導致的性能問題。

Qt 字符串類創建方式

常見的創建字符串的方式很多:

  1. 直接字符串字面量 "string"
  2. QByteArray("string") —— 字節數組,無編碼信息
  3. QByteArrayLiteral("string") —— 編譯時常量,不分配內存
  4. QString("string") —— UTF-16編碼字符串,分配內存
  5. QLatin1String("string") —— 輕量視圖,適合拉丁1編碼
  6. QStringLiteral("string") —— 編譯時UTF-16常量,不分配內存(Qt 5.9+)
  7. QString::fromLatin1("string") —— 從Latin1編碼構造
  8. QString::fromUtf8("string") —— 從UTF-8編碼構造
  9. tr("string") —— 用于國際化的字符串
  10. QStringView(u"string") —— 輕量視圖,不分配內存

QByteArray

  • 表示字節序列,類似 std::string,不包含編碼信息
  • 隱式共享(copy-on-write)
  • 構造函數會分配內存
  • QByteArray::fromRawData() 可以避免部分分配
  • QByteArrayLiteral() 不分配內存,存儲于只讀段,適合靜態數據

QString

  • 使用 UTF-16 編碼,支持 Unicode 操作(優于 std::u16string
  • 隱式共享
  • 構造函數會分配內存
  • QString::fromRawData() 可以避免分配(只讀視圖)
  • 推薦使用 QStringView 作為輕量字符串視圖
  • QStringLiteral() 自 Qt 5.9 起不分配內存,數據存儲在只讀段,適合字符串常量
    總結:
  • 用 Qt 來管理 Unicode 字符串,選 QString;如果只讀且想避免拷貝,選 QStringViewQStringLiteral
  • QByteArray 處理原始字節流或二進制數據。
    “Latin1” 是“ISO 8859-1”編碼的簡稱,全稱是 ISO/IEC 8859-1: Latin Alphabet No. 1
    簡單來說:
  • 它是一種單字節字符編碼,使用 1 個字節(8 位)表示一個字符;
  • 能表示西歐主要語言的字符集,比如英語、法語、德語、西班牙語等;
  • 范圍覆蓋了 0x00 到 0xFF 共 256 個字符,其中前 128 個字符和 ASCII 碼完全一樣;
  • 它不支持像中文、日文、韓文等復雜字符,只適合基本拉丁字母和西歐符號;
  • 在 Qt 里,QLatin1String 是對 Latin1 編碼字符串的輕量包裝,用來高效處理這類字符串,避免轉碼成本。
    總結:Latin1 是一種舊式的、西歐字符編碼,適合只包含拉丁字母的文本,不支持多語言 Unicode。

簡單總結一下 QLatin1String 的作用和特點:

  • 它是一個輕量的字符串包裝類,只包含一個 const char* 指針和字符串長度,不做內存管理;
  • 主要用來表示 Latin1(ISO 8859-1)編碼的字符串字面量,比如代碼里的 "foo"
  • 用于 QString 相關函數的重載,避免不必要的臨時 QString 分配和轉換,提高性能;
  • 例如:QString::startsWith() 同時有兩個版本,一個接受 QString,一個接受 QLatin1String,后者性能更好,因為不產生臨時字符串。
    你可以把它看成是 Qt 里對純 ASCII 或 Latin1 字符串字面量的一個“快捷通道”,用來減少字符串轉換和內存開銷。

總結一下這段內容的重點:

  • Qt 的主要字符串類是 QStringQByteArray
  • 這幾年對它們的改進不多,保持了比較穩定的設計。
  • 從 Qt 5.9 開始,QStringLiteralQByteArrayLiteral 這兩個宏的實現優化了——它們不會再動態分配內存,而是直接使用編譯時生成的靜態內存,這樣可以顯著提升性能。

簡單總結一下 QStringView

  • 從 Qt 5.10 引入的類型,是一個 非擁有(non-owning) 的字符串視圖,類似于 C++17 標準的 std::u16string_view
  • 它直接指向一段 UTF-16 編碼的字符序列(比如 QString 內部存儲格式),但不負責管理這段內存。
  • 這樣可以避免不必要的字符串拷貝,提升性能,特別適合只讀訪問場景。
  • 它提供了和 QString 大部分相似的只讀接口,方便使用。
  • Qt 5.11 之后還會有更多的 API 和對 QStringBuilder 的支持,使用體驗會更好。

這段講的是用 QStringView 作為函數參數類型的理由和好處,重點如下:

  • 主要用途QStringView 適合作為函數參數,尤其是函數需要讀取字符串但不需要保留它時。
  • 如果函數需要一個 Unicode 字符串參數,而且函數不會保存這個字符串,推薦用 QStringView,避免無謂的拷貝和分配。
  • 討論了一個例子:Document::find(StringType substring)StringType 用哪個類型好?
    • QString 會強制調用者提供一個完整的 QString,可能要動態分配內存,或者使用 QStringLiteral 編譯期字符串。
    • QByteArray 不是 Unicode 安全的,不適合處理 Unicode 文本。
    • QLatin1String 雖然性能好(因為是 Latin-1 編碼),但不 Unicode 安全,不過可以做為額外重載實現快速路徑。
      總結:QStringView 既支持 Unicode,也避免了字符串不必要的復制,非常適合作為 API 中接受字符串參數的接口類型。

這部分內容強調了 QStringView 作為接口類型的優勢:

  • QStringView 是 Unicode 安全的(支持 UTF-16 編碼)。
  • 它不會進行內存分配(alloc-free),性能好。
  • 它可以從多種字符串源構造:
    • 編譯時的字符串字面量(u"compile time"
    • 動態分配的 QString 對象
    • 甚至是一個大字符串的子串(通過 QStringView(bigString, 40)
      舉例:
class Document {iterator find(QStringView substring);
};
Document d;
d.find(u"compile time");          // 傳入編譯時字符串字面量
QString allocatedString = "...";
d.find(allocatedString);          // 傳入動態分配的QString
d.find(QStringView(bigString, 40)); // 傳入大字符串的子串視圖

另外,QStringView 還能作為“零分配”切割字符串的工具,比如:

QString str = "...";
QRegularExpression re("reg(.*)ex");
QRegularExpressionMatch match = re.match(str);
if (match.hasMatch()) {QStringView cap = match.capturedView(1); // 直接獲取子串視圖,無需分配內存// ...
}

總結:QStringView 讓字符串處理既高效又安全,非常適合作為函數參數和字符串子串的視圖類型。

這一部分講的是 QStringView 對 Qt API 的巨大影響:

  • 許多 Qt 函數現在接受 QString 參數,但實際上并不需要持有字符串數據,只是讀取它們。
  • 在 Qt 6 中,應該將這些函數改為接受 QStringView,這樣避免不必要的內存分配和拷貝,提高性能。
  • Qt 5 里還有一個類似的非擁有字符串視圖類型叫 QStringRef,但它設計有缺陷:
    • 它必須綁定到一個 QString 對象,而不能直接表示任何 UTF-16 字符序列。
    • 因此靈活性較差。
  • QStringRef 只是權宜之計,建議如果迫切需要用字符串視圖,可以暫時用它,但隨著 QStringView 在功能上達到 API 完整性,QStringRef 會被廢棄。
    總結:QStringView 是更現代、更高效的字符串視圖接口,未來 Qt 版本將以它為標準,替代舊的字符串引用方式。

POD(Plain Old Data) 平凡數據類型 類型是否等同于“可搬移”(relocatable)?

  • 答案是否定的:POD和可搬移是兩個獨立的概念。
  • 一個類型是否可搬移(relocatable)與它是否是POD類型無關。
  • 可搬移類型可能有非平凡的構造函數和析構函數,比如 Qt 里基于pimpl(指針實現)的值類。
  • 反過來,即使是平凡(trivial)的類型,也未必可搬移——比如某些類型的對象地址本身代表身份(identity),搬移會破壞語義。
  • 所有C數據類型都是trivial,但不一定是relocatable。
    另外,關于Qt中廢棄的API(deprecated APIs)
  • Qt會標記舊的API為廢棄,雖然它們仍然可用且通過測試,但Qt 6版本會移除大部分廢棄API。
  • Qt源碼中通過宏 QT_DEPRECATED_SINCE(major, minor) 來標記,并用 QT_DEPRECATED_X("建議替代方法") 生成編譯警告。
  • 你可以通過定義宏來控制廢棄API的使用:
    • 定義 QT_DEPRECATED_WARNINGS 來開啟廢棄API的警告。
    • 定義 QT_DISABLE_DEPRECATED_BEFORE=版本號 來將早于某版本的廢棄API使用視為錯誤。
      例如:
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050900

這樣就強制不允許使用 Qt 5.9.0 及更早版本中廢棄的API。
總結:

  • POD ≠ relocatable,二者語義不同。
  • Qt鼓勵逐步遷移,避免使用廢棄API,尤其是升級到Qt 6時。

QList 的核心問題是:

  • 它是一個“基于數組”的容器,但內部實現是一個 void* 指針數組。
  • 根據存儲的類型不同,QList 可能存放指向元素的指針(每個元素單獨堆分配),也可能直接存放元素本身。
  • 這種設計導致:
    • 每個元素單獨堆分配時性能和內存效率都很差。
    • 元素存儲方式依賴于平臺(32位 vs 64位)和元素類型,行為不穩定難以預測。
    • 對于小且可搬移的數據類型(如 int 在64位平臺)非常浪費空間。
  • QList 優化了前置插入操作(prepend),但代價較大。
  • 盡管有這些問題,QList仍然是Qt API中最常暴露的容器之一。
    總結:
    不要在自己的代碼中使用 QList,推薦使用 QVector 或 STL 容器,除非必須和 Qt API 交互。

總結下 QList 和 QVector 的區別和使用建議:

  • 推薦使用 QVector,除非你必須調用需要 QList 的 Qt API。
  • QVector 通常生成更少的代碼,性能也更好。
  • QVector 在大多數操作上比 QList 快,唯一例外是:
    • 經常在前面插入元素時,QList表現可能更好。
    • 對非常大的對象進行重新分配時,QList可能更合適。
  • QVector 重新分配時,不保證引用或指針的有效性(會失效)。
  • 如果需要引用或指針保持有效,建議用指針的容器,比如 QVector<T*>
    簡單說就是:

絕大多數情況下用 QVector,只有少數場景考慮 QList。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/910448.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/910448.shtml
英文地址,請注明出處:http://en.pswp.cn/news/910448.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Pandas 核心數據結構詳解:Series 和 DataFrame 完全指南

1. 前言&#xff1a;為什么需要 Pandas 數據結構&#xff1f; 在數據處理和分析中&#xff0c;我們需要高效的方式來存儲和操作結構化數據。Python 原生的列表&#xff08;List&#xff09;和字典&#xff08;Dict&#xff09;雖然靈活&#xff0c;但缺乏針對數據分析的優化。…

使用 Solscan API 的開發指南:快速獲取 Solana 鏈上數據

Solana 生態中有多個區塊瀏覽器&#xff0c;其中 Solscan 提供了功能全面的 API&#xff0c;適用于查詢地址資產、Solana 生態中有多個區塊瀏覽器&#xff0c;其中 Solscan 提供了功能全面的 API&#xff0c;適用于查詢地址資產、交易詳情、合約交互等多種開發場景。相比直接使…

高效工具-libretv

什么是libretv? LibreTV 是一個輕量級、免費的在線視頻搜索與觀看平臺&#xff0c;提供來自多個視頻源的內容搜索與播放服務。無需注冊&#xff0c;即開即用&#xff0c;支持多種設備訪問。項目結合了前端技術和后端代理功能&#xff0c;可部署在支持服務端功能的各類網站托管…

回溯----5.括號生成

題目鏈接 /** 合法括號生成規則: 第一個括號必須是左括號(第一個為右必定無法閉合) 選擇過程中左括號數量必須小于n才可選擇左括號(大于n則一定有括號無法閉合) 左括號數量必須大于右括號數量才可選擇右括號(相等代表所有前驅括號都已閉合) 所需參數: left 記錄已選擇左括號數…

【weaviate】分布式數據寫入之LSM樹深度解析:讀寫放大的權衡

文章目錄 一、LSM樹的設計哲學&#xff1a;寫優化的根本動機1、 傳統B樹存儲的性能瓶頸2、 LSM樹的根本性創新 二、寫入路徑的深度技術分析1、 WAL機制的精密設計2、 MemTable的數據結構3、 刷盤&#xff08;Flush&#xff09;過程的技術細節 三、Compaction策略&#xff1a;LS…

Pygame 大魚吃小魚

【Pygame 大魚吃小魚】是一款基于Python編程語言和Pygame庫開發的趣味游戲。Pygame是Python中一個廣泛用于開發2D游戲的開源模塊集合&#xff0c;它提供了豐富的功能&#xff0c;如窗口管理器、事件處理、圖形繪制等&#xff0c;使得初學者也能快速上手創建游戲。 這段 Python …

【為什么在觸發的事件中修改控件屬性需要使用`Invoke`】

在C#中&#xff0c;特別是在使用Windows Forms或WPF等GUI框架時&#xff0c;控件的屬性和狀態通常只能在創建它們的線程&#xff08;即UI線程&#xff0c;即主線程或用戶界面線程&#xff09;中直接修改。這是由于這些框架的設計基于單線程模型&#xff0c;其中所有與用戶界面&…

Android 當apk是系統應用時,無法使用webView的解決方案

最近在做項目時&#xff0c;遇到了一個無法使用webView的問題&#xff0c;apk是系統應用&#xff0c;點擊加載webView時應用就是崩潰&#xff0c;原因是系統應用時&#xff0c;Android會覺得webView不安全&#xff0c;不避讓加載。 解決的思路就是使用映射&#xff0c;把原生的…

ArcGIS Pro無插件加載(無偏移)天地圖!一次添加長久使用

以前我們介紹過&#xff1a;ArcGIS無插件加載&#xff08;無偏移&#xff09;天地圖。這次我們來介紹ArcGIS Pro中如何添加天地圖。 我們將通過從天地圖官網自己添加服務鏈接并添加至收藏的方式以及應急的方法來做本次的介紹。天地圖的數據主要包括影像、電子地圖、地形圖等。我…

Go堆內存管理

# Go堆內存管理 1. Go內存模型層級結構 Golang內存管理模型與TCMalloc的設計極其相似。基本輪廓和概念也幾乎相同&#xff0c;只是一些規則和流程存在差異。 2. Go內存管理的基本概念 Go內存管理的許多概念在TCMalloc中已經有了&#xff0c;含義是相同的&#xff0c;只是名字…

零售 EDI:Chewy EDI 項目注意事項

在此前的文章《供應商對接Chewy的EDI需求》中&#xff0c;介紹了Chewy的EDI需求&#xff0c;本文主要為大家分享Chewy對于各個業務單據的細節性需求&#xff0c;了解這些細節性注意事項將幫助企業快速基于知行軟件提供的EDI服務與Chewy建立EDI對接。 基于知行之橋EDI系統能夠通…

Android錄制視頻自帶鋪滿多行水印

文章目錄 引言環境要求代碼實現總結 引言 之前做過幾種水印需求&#xff0c;這篇文章是關于使用Android原生庫開發錄制視頻自帶滿幀文字水印。 環境要求 Android 7.0以上Android Studio &#xff0c;官方開發者官網視頻錄制功能參考開源庫PictureSelector的camerax庫 //用到的…

觀遠ChatBI:加速零售消費企業數據驅動的敏捷決策

近年來&#xff0c;隨著國產大模型&#xff08;如DeepSeek&#xff09;的快速發展&#xff0c;企業對智能化數據分析工具的需求日益增長。觀遠數據推出的ChatBI&#xff0c;基于大語言模型&#xff08;LLM&#xff09;打造&#xff0c;旨在通過自然語言交互降低數據分析門檻&am…

鴻蒙NEXT-鴻蒙三層架構搭建,嵌入HMRouter,實現便捷跳轉,新手攻略。(1/3)

接下來&#xff0c;我將手把手帶領大家去完善&#xff0c;搭建一個鴻蒙的三層架構&#xff0c;另實現HMRouter的嵌入。完成后&#xff0c;大家可任意跳轉頁面&#xff0c;在三層架構中&#xff0c;書寫屬于自己的篇章。 第0步&#xff0c;項目與AGC華為控制臺關聯起來 首先AG…

鴻蒙ArkTs仿網易云音樂項目:架構剖析與功能展示

鴻蒙ArkTs仿網易云音樂項目&#xff1a;架構剖析與功能展示 一、引言 在移動應用開發的浪潮中&#xff0c;音樂類應用始終占據著重要的一席之地。網易云音樂憑借其豐富的音樂資源、個性化的推薦算法和獨特的社交互動功能&#xff0c;深受廣大用戶的喜愛。本文將詳細介紹一個基…

【web 安全】從 HTTP 無狀態到現代身份驗證機制

文章目錄 Web 安全與系統設計Web存在的問題&#xff1a;Web 是無狀態的解決方案一、早期解決方案&#xff1a;Session Cookie 的誕生二、第二階段&#xff1a;Token 的出現&#xff08;前后端分離 移動端的解決方案&#xff09;三、分析總結&#xff1a;1.早期版本&#xff1…

FlutterUnit TolyUI | 布局游樂場

FlutterUnit 基于 TolyUI 大大簡化了界面構建的代碼復雜程度&#xff0c;因此之前想要實現的一些小功能&#xff0c;就可以輕松支持。布局游樂場是通過交互的方式來 直觀體驗 組件的布局特性&#xff0c;從而更易學和掌握。目前 FlutterUnit 已在 知識集錄模塊新增了 布局寶庫&…

【數據分析一:Data Collection】信息檢索

本節內容含有各典型數據集的推薦&#xff0c;以及其網址&#xff0c;大家根據需要自取 一、檢索 最簡單、最靈活的數據獲取方式就是依靠檢索&#xff1a; Google&#xff1a;更適合搜索英文信息 Google Dataset Search&#xff08;Google 數據集搜索&#xff09; 網址&…

23.ssr和csr的對比?如何依賴node.js實現

1.為什么說ssr 的node中間層請求速度快。相當于內網&#xff1f; 那vue.js加載怎么沒有ssr和csr的說法啊 第一問&#xff1a;為什么說 SSR 的 Node 中間層請求速度快&#xff1f;是不是相當于內網&#xff1f; ? 是的&#xff0c;本質上就是「內網請求」&#xff0c;所以更快…

力扣刷題(第六十四天)

靈感來源 - 保持更新&#xff0c;努力學習 - python腳本學習 第一個錯誤的版本 解題思路 初始化左右邊界&#xff1a;左邊界 left 1&#xff0c;右邊界 right n。二分查找循環&#xff1a; 計算中間版本號 mid。若 mid 是錯誤版本&#xff0c;說明第一個錯誤版本在 [le…