日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

详解C++17下的string_view

發布時間:2023/12/15 c/c++ 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解C++17下的string_view 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

看到一個介紹 C++17 的系列博文(原文),有十來篇的樣子,覺得挺好,看看有時間能不能都簡單翻譯一下,這是第五篇~

當字符串數據的所有權已經確定(譬如由某個string對象持有),并且你只想訪問(而不修改)他們時,使用 std::string_view 可以避免字符串數據的復制,從而提高程序效率,這(指程序效率)也是這篇文章的主要內容.

這次要介紹的 string_view 是 C++17 的一個主要特性.

我假設你已經了解了一些 std::string_view 的知識,如果沒有,可以看看我之前的這篇文章.C++ 中的 string 類型在堆上存放自己的字符串數據,所以當你處理 string 類型的時候,很容易就會產生(堆)內存分配.

Small string optimisation

我們先看下以下的示例代碼:

#include <cassert> #include <iostream> #include <string> #include <string_view>void* operator new(std::size_t count) {std::cout << " " << count << " bytes" << std::endl;return malloc(count); }void getString(const std::string& str) {}int main() {std::cout << std::endl;std::cout << "std::string" << std::endl;// 這里會調用上面的new函數,輸出16字節(根據編譯器的不同,值可能不同,下同)std::string small = "0123456789"; small = "12345678"; // 這里不會再調用上面的new函數,因為長度小于16字節// 因為字符串長度大于16了,這里會再調用上面的new函數,輸出32字節small = "1234567890123456789000066666";/* 這里會調用上面的new函數,第一次輸出16字節(默認為substr分配16字節內存)第二次是發現為small.substr(5)長度大于16,所以重新為substr分配內存以便能容納下它,輸出32*/std::string substr = small.substr(5);std::cout << " " << substr << std::endl;std::cout << std::endl;std::cout << "getString" << std::endl;getString(small);// 這里不會調用上面的new函數,因為small長度沒有變得更長getString("0123456789"); // 這里會調用上面的new函數,因為長度小于16,所以輸出16const char message[] = "0123456789";getString(message); // 這里會調用上面的new函數,因為長度小于16,所以輸出16std::cout << std::endl;system("pause()");return 0; }

代碼第6到第10行,我重載了全局的 new 操作符,這樣我就能跟蹤(堆)內存的分配了,而后,代碼分別在第23、31、39、41行創建了string對象,所以這幾處代碼都會產生(堆)內存分配.相關的程序輸出如下:

咦, 紅色方框中的32字節是怎么產生的?這是怎么回事?其實 string 類型只有在字符串超過指定大小(具體實現相關)時才會申請(堆)內存,對于 MSVC 來說,指定大小為 15, 對于 GCC 和 Clang,這個值則為 23.關于string的內存配置機制,當超過指定編譯器下string內存配置的長度會重新分配內存,所以這里重新分配內存了,從而輸出紅色方框的32字。string內存分配機制具體請參見《STL庫中string類內存布局的探究》。

這也就意味著,較短的字符串數據是直接存儲于 string 的對象內存中的,不需要分配(堆)內存.

從現在開始,示例代碼中的字符串將擁有至少30個字符,這樣我們就不需要關注短字符串優化了.好了,帶著這個前提(字符串長度>=30個字符),讓我們重新開始講解.

No memory allocation required

現在, std::string_view 無需復制字符串數據的優點就更加明顯了(std::string不進行短字符串優化的情況下),下面的代碼就是例證.

#include <cassert> #include <iostream> #include <string> #include <string_view>void* operator new(std::size_t count) {std::cout << " " << count << " bytes" << std::endl;return malloc(count); }void getString(const std::string& str) {}void getStringView(std::string_view strView) {}int main() {std::cout << std::endl;std::cout << "std::string" << std::endl;std::string large = "0123456789-123456789-123456789-123456789";std::string substr = large.substr(10);std::cout << std::endl;std::cout << "std::string_view" << std::endl;std::string_view largeStringView{ large.c_str(), large.size() };largeStringView.remove_prefix(10);assert(substr == largeStringView);std::cout << std::endl;std::cout << "getString" << std::endl;getString(large);getString("0123456789-123456789-123456789-123456789");const char message[] = "0123456789-123456789-123456789-123456789";getString(message);std::cout << std::endl;std::cout << "getStringView" << std::endl;getStringView(large);getStringView("0123456789-123456789-123456789-123456789");getStringView(message);std::cout << std::endl;return 0; }

代碼22行,23行,39行,41行因為創建了 string 對象 所以會分配(堆)內存,但是代碼29行,30行,47行,48行,49行也相應的創建了 string_view 對象,但是并沒有發生(堆)內存分配!輸出結果如下:

上圖輸出結果中紅色框都表示指定的字符串變量因為長度不夠(大于編譯器默認為該字符串分配的內存長度),重新發生了內存分配,上圖中的16是定義字符串時,編譯器默認為該字符串分配的內存空間。

這個結果令人印象深刻,(堆)內存分配是一個非常耗時的操作,盡量的避免(堆)內存分配會給程序帶來很大的性能提升,使用 string_view 能提升程序效率的原因也正是在此,當你需要創建很多 string 的子字符串時, string_view 帶來的效率提升將更加明顯.

O(n) versus O(1)

std::string 和 std::string_view 都有 substr 方法, std::string 的 substr 方法返回的是字符串的子串,而 std::string_view 的 substr 返回的則是字符串子串的"視圖".聽上去似乎兩個方法功能上比較相似,但他們之間有一個非常大的差別:?std::string::substr 是線性復雜度, std::string_view::substr 則是常數復雜度.這意味著 std::string::substr 方法的性能取決于字符串的長度,而std::string_view::substr 的性能并不受字符串長度的影響.

讓我們來做一個簡單的性能對比:

#include <chrono> #include <fstream> #include <iostream> #include <random> #include <sstream> #include <string> #include <vector>#include <string_view>static const int count = 30; static const int access = 10000000;int main() {std::cout << std::endl;std::ifstream inFile("grimm.txt");std::stringstream strStream;strStream << inFile.rdbuf();std::string grimmsTales = strStream.str();size_t size = grimmsTales.size();std::cout << "Grimms' Fairy Tales size: " << size << std::endl;std::cout << std::endl;// random valuesstd::random_device seed;std::mt19937 engine(seed());std::uniform_int_distribution<> uniformDist(0, size - count - 2);std::vector<int> randValues;for (auto i = 0; i < access; ++i) randValues.push_back(uniformDist(engine));auto start = std::chrono::steady_clock::now();for (auto i = 0; i < access; ++i) {grimmsTales.substr(randValues[i], count);}std::chrono::duration<double> durString = std::chrono::steady_clock::now() - start;std::cout << "std::string::substr: " << durString.count() << " seconds" << std::endl;std::string_view grimmsTalesView{ grimmsTales.c_str(), size };start = std::chrono::steady_clock::now();for (auto i = 0; i < access; ++i) {grimmsTalesView.substr(randValues[i], count);}std::chrono::duration<double> durStringView = std::chrono::steady_clock::now() - start;std::cout << "std::string_view::substr: " << durStringView.count() << " seconds" << std::endl;std::cout << std::endl;std::cout << "durString.count()/durStringView.count(): " << durString.count() / durStringView.count() << std::endl;std::cout << std::endl;return 0; }

展示程序結果之前,讓我先來簡單描述一下:測試代碼的主要思路就是讀取一個大文件的內容并保存為一個 string ,然后分別使用 std::string 和 std::string_view 的 substr 方法創建很多子字符串.我很好奇這些子字符串的創建過程需要花費多少時間.

我使用了<格林童話>作為程序的讀取文件.代碼中的 grimmTales(第22行) 存儲了文件的內容.代碼34行中我向 std::vector 填充了 10000000 個范圍為[0, size - count - 2]的隨機數字.接著就開始了正式的性能測試.代碼37行到40行我使用 std::string::substr 創建了很多長度為30的子字符串,之所以設置長度為30,是為了規避 std::string 的短字符串優化.代碼46行到49行使用 std::string_view::substr 做了相同的工作(創建子字符串).

程序的輸出如下,結果中包含了文件的長度, std::string::substr 所花費的時間, std::string_view::substr 所花費的時間以及他們之間的比例.我使用的編譯器是 GCC 6.3.0.

Size 30

沒有開啟編譯器優化的結果:

開啟編譯器優化的結果:

編譯器的優化對于 std::string::substr 的性能提升并沒有多大作用,但是對于 std::string_view::substr 的性能提升則效果明顯.而 std::string_view::substr 的效率幾乎是 std::string::substr 的 45 倍!

Different sizes

那么如果我們改變子字符串的長度,上面的測試代碼又會有怎樣的表現呢?當然,相關測試我都開啟了編譯器優化,并且相關的數字我都做了3位小數的四舍五入.

對于上面的結果我并不感到驚訝,這些數字正好反應了 std::string::substr 和 std::string_view::substr 的算法復雜度. std::string::substr 是線性復雜度(依賴于字符串長度), std::string_view::substr 則是常數復雜度(不依賴于字符串長度).最后的結論就是: std::string_view::substr 的性能要大幅優于 std::string::substr.

?

總結

以上是生活随笔為你收集整理的详解C++17下的string_view的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。