C++學習(c++17)——2.X.使用string和string_view



原帖地址LeoRanbom的博客

博主:LeoRanbom

只在原帖地址的博客上發布,其他地方看到均為爬取。

如果覺得不錯希望來點個贊。

前言

結束了前2天水了一個基礎的小程序,現在開始深入學習。本節我將從字符串入手開始復習,將涉及到C語言風格的字符串、C++的字符串、還有C++17在字符串方面提出的新標准新特性。

1.動態字符串

在接觸了java、Python等面向對象的語言,它們的字符串有很多方便的特性:可拓展至任意大小,提取或替換子字符串。但轉過頭來看c語言就會感覺特別地蛋疼。它連邊界檢測都沒有。

1.1.C風格的字符串

眾所周知,C語言的字符串是char類型的數組。通常我們會以char*的形式來給它動態分配內存。

缺點:

  1. C字符串不會記錄自己的長度信息,獲取字符串長度時,會遍歷字節數組,直到遇到null字符,復雜度O(n)。(官方將null字符('\0')定義為NUL,只有一個L)
  2. C字符串不記錄長度,修改時也不會判斷內存空間是否足夠。若不夠,會造成緩沖區溢出。

雖然用的是C++,但是經常會調用一些用C編寫的接口。因此我們也要去了解C風格的字符串

1.1.1.易錯點

C字符串經常會被遺漏掉'\0'。舉個例子,"hello"是5個字母,但是卻由'h','e','l','l','o','\0'這6個字符組成,所以在內存空間中其也是6個字節。而C語言對字符串的操作strcpy()和strcat()函數是極其不安全的,他們不會對字符串的長度進行檢測。

1.1.2.strcpy()

這是對字符串進行復制的函數。它有2個字符串參數,將第二個字符串復制到第一個字符串,但是卻不會考慮空間是否足夠。

這里用函數包裝改良一下來解決原有的問題:

char* copyString(const char* str){
    char* newStr = new char[strlen(str) + 1];//strlen()函數可以獲得字符串的長度,但是不算\0
    strcpy(newStr,str);
    return newStr;
}//記得使用完后釋放copyString()分配的堆內存

1.1.3.strcat()

這里是對字符串進行拼接的函數。它也是2個字符串參數,將第二個字符串賦值到第一個后面,並儲存在第一個。同樣不會判斷內存空間。

這里也用函數來包裝一下,讓它能拼接3個字符串,並且自動擴充空間。

char* addStrings(const char* str1,const char* str2,const char* str3){
    char* newStr = new char[strlen(str1) + strlen(str2) + strlen(str3) + 1];
    strcpy(newStr,str1);
    strcat(newStr,str2);
    strcat(newStr,str3);
    return newStr;
}

1.1.4.對字符串用sizeof()關鍵字或者strlen()函數的區別

在上面的1.1.2與1.1.3中能看到,我是用strlen()來獲取字符串的長度。sizeof是獲取字節數,按道理也可以表示長度啊。

這里我來排一下坑(實驗室組織的某次考核中,我因為概念不清,稀里糊塗地用混了,但王學長私下告訴我那次是第一,並且讓我繼承了他的電路書,這電路書是我疫情期間接觸過的唯一一本課本,感激涕零)

在C風格的字符串里,sizeof()會根據字符串的存儲方式來返回不同的大小。如果是char[],則返回實際存儲的類型,包括'\0';而如果是char*,則sizeof()會根據平台的不同,返回你的系統下指針的內存大小(32位是4,64位是8)

1.1.5.安全C庫

VS里用C風格的字符串函數時會出現警告,說是已經廢棄了。使用strcpy_s()和strcat_s()這些“安全C庫”即可避免。不過還是最好切換到C++的std::string類。

1.2.字符串字面量

1.2.1.字面量

cout<<"hello"<<endl;

這樣包含字符串本身,而不是包含字符串變量。它本身是一個字符串字面量(string literal),以值的形式寫出,而不是以變量的形式。而與字面量相關聯的內存位於內存只讀區。通過這種機制,編譯器可以對相同的字符串字面量進行復用,從而優化內存使用。e.g.一個程序用了幾百次"hello"字符串字面量,但其實只創建了一個hello實例,這就是字面量池(literal pooling)

C++標准指出字符串字面量是const char的數組。它可以被賦值給變量,但在用指針指向的時候會產生風險。如:

char* ptr = "hello";//聲明一個字符串變量(指針指向字符串字面量)
ptr[1] = 'a';//Undefined Behaviour

本身字面量是const不允許修改,但這里硬是修改的話很明顯就是一個ub。它根據編譯器的不同,可能會導致程序崩潰,可能會繼續運行;可能會產生改變,可能會忽視改變。

更安全一點的寫法習慣則是:

const char* ptr = "hello";
ptr[1] = 'a';//ERROR,attempt to write to read-only memory

這樣子寫的話,編譯器會因為“嘗試向只讀區域寫入”而報錯。

注:如果用的是字符數組char[]的形式,開辟了對應大小的內存。那么這個字面量不會放在只讀的內存區,也不會使用字面量池。當然也就可以自由改變。

1.2.2.原始字符串字面量

原始字符串字面量是不對轉義字符進行轉換的處理方式。它以R"(開頭,以)"結尾。

cout<<R"(hello "world"!\n)";

控制台會輸出

hello "world"!\n

而如果想要換行的話,在原始字符串字面量里直接打回車就可以了。

但因為結尾是)",所以其中不能有)"存在,否則會報錯。解決方法是使用拓展的原始字符串字面量語法——可選的分隔符序列。

R"d-char-sequence(lalala)d-char-sequence"

分隔符序列最多能有16個字符。

1.3.C++ std::string類

C++中,std::string是一個類(實際上是basic_string模板類的一個實例),這個類支持 中提供的很多功能,還可以自動管理內存分配。在 頭文件中被定義。

1.3.1.有C的字符串,為什么還有C++的字符串?

C風格字符串的優勢和劣勢

優勢:

  • 簡單,只用到基本數據類型(char)和數組結構
  • 輕量,由於結構簡單,可以只占用所需的內存。
  • 低級,可以按照操作原始內存的方式來對字符串進行操作。
  • 能夠被C程序員理解

劣勢:

  • 為了模擬字符串,要花很多努力。
  • 使用難度大,容易產生bug,不安全
  • 沒有用到C++的oop思想
  • 要求程序員理解底層

1.3.2.使用string類

盡管string是一個類,但我更喜歡把它看做一個簡單的數據類型。通過運算符重載,讓對string的操作更為直觀和簡單。

string A("12");
string B("34");
string C;
C = A + B;//C is "1234"

+=,==,!=,<,[]等都被重載了。

而C語言中字符串的不安全(如果一個是char[],一個是char*,則會返回false,因為它比較的是指針的值,而非字符串內容。需要用strcmp(a,b)0來比較)

為了達到兼容的目的,可以用string類的c_str()方法來獲得一個C風格的const字符指針。不過一旦string進行內存重分配,或者對象被銷毀,這個指針也會跟着失效。

注意,不要從函數中返回在基於堆棧的string上調用c_str()方法得來的結果。

在C++17中,data()方法在非const字符調用時,會返回char(而14或更早的版本始終返回const char)

1.3.3.std::string字面量

源代碼中的字符串仍是const char*,但如果"xxx"s,那么可以把字面量變成std::string。

但需要std::string_literals或者std命名空間。

1.3.4.高級數值轉換

std名稱空間中有很多輔助函數,以便完成數值和字符串的轉換

  • string to_string(int val);
  • string to_string(unsigned val);
  • string to_string(long val);
  • string to_string(unsigned long val);
  • string to_string(long long val);
  • string to_string(unsigned long long val);
  • string to_string(float val);
  • string to_string(double val);
  • string to_string(long double val);

還有將字符串轉換為數值,其中str是要轉換的字符串,idx是一個指針,接收第一個未轉換字符的索引,base表示轉換過程中使用的進制數。指針可以是空指針,如果是空指針,則被忽略。如果不能執行任何轉換,則會跑出invalid_argument異常,如果超出返回類型的范圍,則拋出out_of_range異常

  • int stoi(const string& str, size_t *idx=0,int base=0);
  • long stol(const string& str, size_t *idx=0,int base=0);
  • unsigned long stoul(const string& str, size_t *idx=0,int base=0);
  • longlong stoll(const string& str, size_t *idx=0,int base=0);
  • unsigned long long stoull(const string& str, size_t *idx=0,int base=0);
  • float stof(const string& str, size_t *idx=0);
  • double stod(const string& str, size_t *idx=0);
  • long double stold(const string& str, size_t *idx=0);

1.3.5.低級轉換(C++17)

C++17中提供了許多低級數值轉換函數,在 頭文件中。這些函數不執行內存分配,而使用調用者分配的內存。對他們進行優化,可以實現高性能,並獨立於本地化。與高級轉換相比,性能更高,速度也要快幾個數量級。(如果要求更高,需要進行獨立於本地化的轉換,則應使用這些函數;如,在數值數據與可讀格式【json、xml等】之間序列化/反序列化)

若要將整數轉換為字符,可以使用下面這組函數:to_char_result to_chars(char* first, char* last, IntegerT value, int base = 10);。這里IntegerT可以是任何整數類型或字符類型。返回結果是to_char_result類型,看看定義:

struct to_char_result{
    char* ptr;
    errc ec;
};

如果成功轉換,ptr成員將等於所寫入字符的下一個位置(one-past-the-end)的指針,如果轉換失敗(即ec == errc.value_to_large),則它等於last。

下面舉個示例:

低級轉換.png

類似地,浮點數也可以被轉換:

to_chars_result to_chars(char* first,char* last, FloatT value);
to_chars_result to_chars(char* first,char* last, FloatT value, chars_format format);
to_chars_result to_chars(char* first,char* last, FloatT value, chars_format format, int precision);

可以通過修改chars_format來修改浮點數的數據類型

enum class chars_float{
    scientific, 	//Style:(-)d.ddde±dd
    fixed,			//Style:(-)ddd.ddd
    hex,			//Style:(-)h.hhhp±d (NoteL no 0x)
    general = fixed|scientific
}

默認格式是chars_format::general,將導致to_chars()將浮點值轉換為fixed的十進制表示形式或者scientific的十進制表示形式,得到最短的表示形式,小數點前至少有以為存在。如果指定了格式,但是沒有指定精度,那么會自動確定最簡短的表示形式。最大精度是6個數字。

而對於相反的轉換(即數->字符串),則有一下一組函數:

from_chars_result from_chars(const char* first, const char* last, IntegerT& value, int base = 10);
from_chars_result from_chars(const char* first, const char* last, FloatT& value, chars_format format = chars_format::general);

Here ,from_chars_result is a type defined as follows:

struct from_chars_result{
	const char* ptr;
    errc ec;
};

from_chars不會忽略任何前導空白。ptr指向未轉換的第一個字符。如果全部轉換,那么指向last,如果全沒轉換,指向first,ec將為errc::invalid_argument。如果值過大,則為errc::result_out_of_range。

1.4.std::string_view類(C++17)

​ 在之前,為了接受只讀字符串的函數選擇形參類型一直讓人很猶豫。不知道是用const string&還是const char*。C++17中引入了std::string_view類來解決這類問題。

​ 它是std::basic_string_view類模板的實例化,在<string_view>頭文件中定義。是const string&的簡易替代品。不會額外復制字符串,所以不會額外產生內存開銷,它支持和string類似的接口,但是缺少c_str()。並且添加了remove_prefix(size_t)和remove_suffix(size_t)方法。前者將起始指針 前移一定偏移量來收縮字符串,后者則將結尾指針倒退一定的偏移量來收縮。

注意:無法連接一個string和string_view,無法編譯(解決方法:使用string_view.data()這一方法)

當形參是string_view時,你就可以傳入string、 char*、字符串字面量(常量).

而如果以const string&為參數,則不能傳入字符串字面量常量和 char*。只能用string。(string_view轉換為string類方法:

1.xxx.data();

2.string(xxx)//explicit ctor。

1.4.1.string_view字面量

可使用"xxxxx"sv來讓字面量解釋為std::string_view。需要命名空間std::string_view_literals或者直接std。

1.5.非標准字符串

很多人不喜歡用C++風格的字符串,有些是因為不知道,有些則是不合口味。但無論是用MFC、QT內置的字符串,還是用自己開發的字符串,都應為項目或團隊確立一個標准。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM