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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

string_view理解与用法(二)

發布時間:2023/12/15 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 string_view理解与用法(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

以前寫了《string_View理解與用法(一)》和《詳解C++17下的string_view》,請參考。

本篇文章從string_view引入的背景出發,依次介紹了其相關的知識點及使用方式,然后對常見的使用陷阱進行了說明,最后對該類型做總結。

一、背景

在日常C/C++編程中,我們常進行數據的傳遞操作,比如,將數據傳給函數。當數據占用的內存較大時,減少數據的拷貝可以有效提高程序的性能。在C中指針是完成這一目的的標準數據結構,而C++引入了安全性更高的引用類型。所以在C++中若傳遞的數據僅僅只讀,const string&成了C++的天然的方式。但這并非完美,從實踐來看,它至少有以下幾方面問題:

  • 字符串字面值、字符數組、字符串指針的傳遞仍要數據拷貝
    這三類低級數據類型與string類型不同,傳入時,編譯器需要做隱式轉換,即需要拷貝這些數據生成string臨時對象。const string&指向的實際上是這個臨時對象。通常字符串字面值較小,性能損耗可以忽略不計;但字符串指針和字符數組某些情況下可能會比較大(比如讀取文件的內容),此時會引起頻繁的內存分配和數據拷貝,會嚴重影響程序的性能。
  • substr O(n)復雜度
    這是一個特別常用的函數,好在std::string提供了這個函數,美中不足的是其每次都返回一個新生成的子串,很容易引起性能熱點。實際上我們本意并不是要改變原字符串,為什么不在原字符串基礎上返回呢?
  • C++17中引入了string_view,能很好的解決以上兩個問題。

    二、std::string_view

    從名字出發,我們可以類比數據庫視圖,view表示該類型不會為數據分配存儲空間,而且該數據類型只能用來讀。該數據類型可通過{數據的起始指針,數據的長度}兩個元素表示,實際上該數據類型的實例不會具體存儲原數據,僅僅存儲指向的數據的起始指針和長度,所以這個開銷是非常小的。

    要使用字符串視圖,需要引入<string_view>,下面介紹該數據類型主要的API。這些API基本上都有constexpr修飾,所以能在編譯時很好地處理字符串字面值,從而提高程序效率。

    2.1 構造函數

    constexpr string_view() noexcept; constexpr string_view(const string_view& other) noexcept = default; constexpr string_view(const CharT* s, size_type count); constexpr string_view(const CharT* s);

    基本上都是自解釋的,唯一需要說明的是:為什么我們代碼string_view foo(string("abc"))可以編譯通過,但為什么沒有對應的構造函數?

    實際上這是因為string類重載了string到string_view的轉換操作符:
    operator std::basic_string_view<CharT, Traits>() const noexcept;

    所以,string_view foo(string("abc"))實際執行了兩步操作:

  • string("abc")轉換為string_view對象a
  • string_view使用對象本篇文章從string_view引入的背景,
  • 2.2 自定義字面量

    自定義字面量也是C++17新增的特性,提高了常量的易讀。
    下面的代碼取值cppreference,能很好地說明自定義字面值和字符串語義的差異。

    #include <string_view> #include <iostream>int main() {using namespace std::literals;std::string_view s1 = "abc\0\0def";std::string_view s2 = "abc\0\0def"sv;std::cout << "s1: " << s1.size() << " \"" << s1 << "\"\n";std::cout << "s2: " << s2.size() << " \"" << s2 << "\"\n"; }

    輸出:

    s1: 3 "abc" s2: 8 "abc^@^@def"

    以上例子能很好看清二者的語義區別,\0對于字符串而言,有其特殊的意義,即表示字符串的結束,字符串視圖根本不care,它關心實際的字符個數。

    2.3 成員函數

    下面列舉其成員函數:忽略了函數的返回值,若函數有重載,括號內用...填充。這樣可以對其有個整體輪廓。

    // 迭代器 begin() end() cbegin() cend() rbegin() rend() crbegin() crend()// 容量 size() length() max_size() empty()// 元素訪問 operator[](size_type pos) at(size_type pos) front() back() data()// 修改器 remove_prefix(size_type n) remove_suffix(size_type n) swap(basic_string_view& s)copy(charT* s, size_type n, size_type pos = 0) string_view substr(size_type pos = 0, size_type n = npos) compare(...) starts_with(...) ends_with(...) find(...) rfind(...) find_first_of(...) find_last_of(...) find_first_not_of(...) find_last_not_of(...)

    從函數列表來看,幾乎跟string的只讀函數一致,使用string_view的方式跟string基本一致。有幾個地方需要特別說明:

  • string_view的substr函數的時間復雜度是O(1),解決了背景部分的第二個問題。
  • 修改器中的三個函數僅會修改string_view的數據指向,不會修改指向的數據。
  • 除此之外,函數名基本是自解釋的。

    2.4 示例

    Haskell中有一個常用函數lines,會將字符串切割成行存儲在容器里。下面我們用C++來實現

    string-版本

    #include <string> #include <iostream> #include <vector> #include <algorithm> #include <sstream>void lines(std::vector<std::string> &lines, const std::string &str) {auto sep{"\n"};size_t start{str.find_first_not_of(sep)};size_t end{};while (start != std::string::npos) {end = str.find_first_of(sep, start + 1);if (end == std::string::npos)end = str.length();lines.push_back(str.substr(start, end - start));start = str.find_first_not_of(sep, end + 1);} }

    ?

    上面我們用const std::string &類型接收待分割的字符串,若我們傳入指向較大內存的字符指針時,會影響程序效率。

    使用std::string_view可以避免這種情況:
    string_view-版本

    #include <string> #include <iostream> #include <vector> #include <algorithm> #include <sstream> #include <string_view>void lines(std::vector<std::string> &lines, std::string_view str) {auto sep{"\n"};size_t start{str.find_first_not_of(sep)};size_t end{};while (start != std::string_view::npos) {end = str.find_first_of(sep, start + 1);if (end == std::string_view::npos)end = str.length();lines.push_back(std::string{str.substr(start, end - start)});start = str.find_first_not_of(sep, end + 1);} }

    上面的例子僅僅是把string類型修改成了string_view就獲得了性能上的提升。一般情況下,將程序中的string換成string_view的過程是比較直觀的,這得益于兩者的成員函數的相似性。但并不是所有的“翻譯”過程都是這樣的,比如:

    void lines(std::vector<std::string> &lines, const std::string& str) {std::stringstream ss(str);std::string line;while (std::getline(ss, line, '\n')) {lines.push_back(line);} }

    這個版本使用stringstream實現lines函數。由于stringstream沒有相應的構造函數接收string_view類型參數,所以沒法采用直接替換的方式,所以翻譯過程要復雜點。

    三、使用陷阱

    世上沒有免費的午餐。不恰當的使用string_view也會帶來一系列的問題。

  • string_view范圍內的字符可能不包含\0

  • ?

    #include <iostream> #include <string_view>int main() {std::string_view str{"abc", 1};std::cout << str.data() << std::endl;return 0; }

    本來是要打印a,但輸出了abc。這是因為字符串相關的函數都有一條兼容C的約定:\0代表字符串的結尾。上面的程序打印從開始到字符串結束的所有字符,雖然str包含的有效字符是a,但cout認\0。好在這塊內存空間有合法的字符串結尾符,如果str指向的是一個沒有\0的字符數組,程序很有可能會出現內存問題,所以我們在將string_view類型的數據傳入接收字符串的函數時要非常小心。

    2.從[const] char*構造string_view對象時間復雜度O(n)
    這是因為獲取字符串的長度需要從頭開始遍歷。如果對[const] char*類型僅僅是一些O(1)的操作,相比直接使用[const] char*,轉為string_view是沒有性能優勢的。只不過是相比const string&,string_view少了拷貝的損耗。實際上我們完全可以用[const] char*接收所有的字符串,但這個類型太底層了,不便使用。在某些情況下,我們轉為string_view可能僅僅是想用其中的一些函數,比如substr。

    3.string_view指向的內容的生命周期可能比其本身短
    string_view并不擁有其指向內容的所有權,用Rust的術語來說,它僅僅是暫時borrow(借用)了它。如果擁有者提前釋放了,你還在使用這些內容,那會出現內存問題,這跟懸掛指針(dangling pointer)或懸掛引用(dangling references)很像。Rust專門有套機制在編譯時分析變量的生命期,保證borrow的資源在使用期間不會被釋放,但C++沒有這樣的檢查,需要人工保證。下面列出一些典型的問題情況:

    std::string_view sv = std::string{"hello world"}; string_view foo() {std::string s{"hello world"};return string_view{s}; } auto id(std::string_view sv) { return sv; }int main() {std::string s = "hello";auto sv = id(s + " world"); }

    四、總結

    string_view解決了一些痛點,但同時也引入了指針和引用的一些老問題。C++標準并沒有對這個類型做太多的約束,這引來的問題是我們可以像平常的變量一樣以多種方式使用它,如,可以傳參,可以作為函數返回值,可以做普遍變量,甚至我們可以放到容器里。隨著使用場景的復雜,人工是很難保證指向的內容的生命周期足夠長。所以,推薦的使用方式:僅僅作為函數參數,因為如果該參數僅僅在函數體內使用而不傳遞出去,這樣使用是安全的。

    本文轉自:https://segmentfault.com/a/1190000018387368

    總結

    以上是生活随笔為你收集整理的string_view理解与用法(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

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