<!-- /* Font Definitions */ @font-face {font-family:宋體; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@宋體"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋體; mso-font-kerning:1.0pt;} a:link, span.MsoHyperlink {color:blue; text-decoration:underline; text-underline:single;} a:visited, span.MsoHyperlinkFollowed {color:blue; text-decoration:underline; text-underline:single;} p {mso-margin-top-alt:auto; margin-right:0cm; mso-margin-bottom-alt:auto; margin-left:0cm; mso-pagination:widow-orphan; font-size:12.0pt; font-family:宋體; mso-bidi-font-family:宋體;} pre {margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt; font-size:12.0pt; font-family:宋體; mso-bidi-font-family:宋體;} span.right {mso-style-name:right;} span.left {mso-style-name:left;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.Section1 {page:Section1;} -->
C++ string 詳解
作者是 Nicolai M.Josuttis
///////////////////////////////////////////////////////////////////////////////////
C++ 語言是個十分優秀的語言,但優秀並不表示完美。還是有許多人不願意使用 C 或者 C++ ,爲什麼?原因衆多,其中之一就是 C/C++ 的文本處理功能太麻煩,用 起來很不方便。以前沒有接觸過其他語言時,每當別人這麼說,我總是不屑一顧,認爲他們根本就沒有領會 C++ 的精華,或者不太懂 C++ ,現在我接觸 perl, php, 和 Shell 腳本以後,開始理解了以前爲什麼有人說 C++ 文本處理不方便了。
舉例來說,如果文本格式是:用戶名
電話號碼,文件名
name.txt
Tom 23245332
Jenny 22231231
Heny 22183942
Tom 23245332
…
現在我們需要對用戶名排序,且只輸出不同的姓名。
那麼在 shell 編程中,可以這樣用:
? [Copy to clipboard] View Code CPP
awk '{print $1}' name.txt | sort | uniq |
簡單吧?
如果使用
C/C++
就麻煩了,他需要做以下工作:
先打開文件,檢測文件是否打開,如果失敗,則退出。
聲明一個足夠大得二維字符數組或者一個字符指針數組
讀入一行到字符空間
然後分析一行的結構,找到空格,存入字符數組中。
關閉文件
寫一個排序函數,或者使用寫一個比較函數,使用
sort()
排序
遍歷數組,比較是否有相同的,如果有,則要刪除,
copy…
輸出信息
你可以用 C++ 或者 C 語言去實現這個流程。如果一個人的主要工作就是處理這種類似的文本 ( 例如做 apache 的日誌統計和分析 ), 你說他會喜歡 C/C++ 麼?
當然,有了 STL ,這些處理會得到很大的簡化。我們可以使用 fstream 來代替麻煩的 fopen fread fclose, 用 vector 來代替數組。最重要的是用 string 來代替 char * 數組,使用 sort 排序算法來排序,用 unique 函數來去重。聽起來好像很不錯。看看下面代碼 ( 例程 1 ):
? [Copy to clipboard] View Code CPP
#include <string> #include <iostream> #include <algorithm> #include <vector> #include <fstream> using namespace std; int main() { ifstream in("name.txt"); string strtmp; vector<string> vect; while(getline(in, strtmp, '/n ')) vect.push_back(strtmp.substr(0, strtmp.find(' '))); sort(vect.begin(), vect.end()); vector<string>::iterator it=unique(vect.begin(), vect.end()); copy(vect.begin(), it, ostream_iterator<string>(cout, "/n ")); return 0; } |
也還不錯吧,至少會比想象得要簡單得多!(代碼裏面沒有對錯誤進行處理,只是爲了說明問題,不要效仿 ).
當然,在這個文本格式中,不用 vector 而使用 map 會更有擴充性,例如,還可通過人名找電話號碼等等,但是使用了 map 就不那麼好用 sort 了。你可以用 map 試一試。
這裏
string
的作用不只是可以存儲字符串,還可以提供字符串的比較,查找等。在
sort
和
unique
函數中就默認使用了
less
和
equal_to
函數
,
上面的一段代碼,其實使用了
string
的以下功能:
存儲功能,在
getline()
函數中
查找功能,在
find()
函數中
子串功能,在
substr()
函數中
string operator < ,
默認在
sort()
函數中調用
string operator == ,
默認在
unique()
函數中調用
總之,有了 string 後, C++ 的字符文本處理功能總算得到了一定補充,加上配合 STL 其他容器使用,其在文本處理上的功能已經與 perl, shell, php 的距離縮小很多了。 因此掌握 string 會讓你的工作事半功倍。
1 string 使用
其實, string 並不是一個單獨的容器,只是 basic_string 模板類的一個 typedef 而已,相對應的還有 wstring, 你在 string 頭文件中你會發現下面的代碼 :
? [Copy to clipboard] View Code CPP
extern "C++" { typedef basic_string <char> string; typedef basic_string <wchar_t> wstring; } // extern "C++" |
由於只是解釋 string 的用法,如果沒有特殊的說明,本文並不區分 string 和 basic_string 的區別。
string 其實相當於一個保存字符的序列容器,因此除了有字符串的一些常用操作以外,還有包含了所有的序列容器的操作。字符串的常用操作包括:增加、刪除、修改、查 找比較、鏈接、輸入、輸出等。詳細函數列表參看附錄。不要害怕這麼多函數,其實有許多是序列容器帶有的,平時不一定用的上。
如果你要想了解所有函數的詳細用法,你需要查看 basic_string ,或者下載 STL 編程手冊。這裏通過實例介紹一些常用函數。
1.1 充分使用 string 操作符
string 重載了許多操作符,包括 +, +=, <, =, , [], <<, >> 等,正式這些操作符,對字符串操作非常方便。先看看下面這個例子:
? [Copy to clipboard] View Code CPP
#include <string> #include <iostream> using namespace std; int main() { string strinfo="Please input your name:"; cout << strinfo ; cin >> strinfo; if( strinfo == "winter" ) cout << "you are winter!"<<endl; else if( strinfo != "wende" ) cout << "you are not wende!"<<endl; else if( strinfo < "winter") cout << "your name should be ahead of winter"<<endl; else cout << "your name should be after of winter"<<endl; strinfo += " , Welcome to China!"; cout << strinfo<<endl; cout <<"Your name is :"<<endl; string strtmp = "How are you? " + strinfo; for(int i = 0 ; i < strtmp.size(); i ++) cout<<strtmp[i]; return 0; } |
下面是程序的輸出
? [Copy to clipboard] View Code CPP
Please input your name:Hero you are not wende! Hero , Welcome to China! How are you? Hero , Welcome to China! |
有了這些操作符,在 STL 中仿函數都可以直接使用 string 作爲參數,例如 less, great, equal_to 等,因此在把 string 作爲參數傳遞的時候,它的使用和 int 或者 float 等已經沒有什麼區別了。例如,你可以使用:
? [Copy to clipboard] View Code CPP
map<string, int> mymap; // 以上默認使用了 less<string> |
有了 operator + 以後,你可以直接連加,例如:
? [Copy to clipboard] View Code CPP
string strinfo="Winter"; string strlast="Hello " + strinfo + "!"; string strtest="Hello " + strinfo + " Welcome" + " to China" + " !";// 你還可以這樣: |
看見其中的特點了嗎?只要你的等式裏面有一個 string 對象,你就可以一直連續 ”+” ,但有一點需要保證的是,在開始的兩項中,必須有一項是 string 對象。其原理很簡單:
系統遇到
”+”
號,發現有一項是
string
對象。
系統把另一項轉化爲一個臨時
string
對象。
執行
operator +
操作,返回新的臨時
string
對象。
如果又發現
”+”
號,繼續第一步操作。
由於這個等式是由左到右開始檢測執行,如果開始兩項都是 const char* ,程序自己並沒有定義兩個 const char* 的加法,編譯的時候肯定就有問題了。
有了操作符以後, assign(), append(), compare(), at() 等函數,除非有一些特殊的需求時,一般是用不上。當然 at() 函數還有一個功能,那就是檢查下標是否合法,如果是使用:
? [Copy to clipboard] View Code CPP
string str="winter";// 下面一行有可能會引起程序中斷錯誤 str[100]='!';// 下面會拋出異常 :throws: out_of_range cout<<str.at(100)<<endl; |
瞭解了嗎?如果你希望效率高,還是使用 [] 來訪問,如果你希望穩定性好,最好使用 at() 來訪問。
1.2 眼花繚亂的 string find 函數
由於查找是使用最爲頻繁的功能之一,
string
提供了非常豐富的查找函數。其列表如下:
函數名
描述
find
查找
rfind
反向查找
find_first_of
查找包含子串中的任何字符,返回第一個位置
find_first_not_of
查找不包含子串中的任何字符,返回第一個位置
find_last_of
查找包含子串中的任何字符,返回最後一個位置
find_last_not_of
查找不包含子串中的任何字符,返回最後一個位置以上函數都是被重載了
4
次,以下是以
find_first_of
函數爲例說明他們的參數,其他函數和其參數一樣,也就是說總共有
24
個函數
:
? [Copy to clipboard] View Code CPP
size_type find_first_of(const basic_string& s, size_type pos = 0) size_type find_first_of(const charT* s, size_type pos, size_type n) size_type find_first_of(const charT* s, size_type pos = 0) size_type find_first_of(charT c, size_type pos = 0) |
所有的查找函數都返回一個 size_type 類型,這個返回值一般都是所找到字符串的位置,如果沒有找到,則返回 string::npos 。有一點 需要特別注意,所有和 string::npos 的比較一定要用 string::size_type 來使用,不要直接使用 int 或者 unsigned int 等類型。其實 string::npos 表示的是 -1, 看看頭文件:
? [Copy to clipboard] View Code CPP
template <class _CharT, class _Traits, class _Alloc> const basic_string<_CharT,_Traits,_Alloc>::size_type basic_string<_CharT,_Traits,_Alloc>::npos = basic_string<_CharT,_Traits,_Alloc>::size_type) -1; |
find 和 rfind 都還比較容易理解,一個是正向匹配,一個是逆向匹配,後面的參數 pos 都是用來指定起始查找位置。對於 find_first_of 和 find_last_of 就不是那麼好理解。
find_first_of 是給定一個要查找的字符集,找到這個字符集中任何一個字符所在字符串中第一個位置。或許看一個例子更容易明白。
有這樣一個需求:過濾一行開頭和結尾的所有非英文字符。看看用 string 如何實現:
? [Copy to clipboard] View Code CPP
#include <string> #include <iostream> using namespace std; int main() { string strinfo=" //*---Hello Word!......------"; string strset="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; int first = strinfo.find_first_of(strset); if(first == string::npos) { cout<<"not find any characters"<<endl; return -1; } int last = strinfo.find_last_of(strset); if(last == string::npos) { cout<<"not find any characters"<<endl; return -1; } cout << strinfo.substr(first, last - first + 1)<<endl; return 0; } |
這裏把所有的英文字母大小寫作爲了需要查找的字符集,先查找第一個英文字母的位置,然後查找最後一個英文字母的位置,然後用 substr 來的到中間的一部分,用於輸出結果。下面就是其結果:
? [Copy to clipboard] View Code CPP
Hello Word |
前面的符號和後面的符號都沒有了。像這種用法可以用來查找分隔符,從而把一個連續的字符串分割成爲幾部分,達到 shell 命令中的 awk 的用法。特別是當分隔符有多個的時候,可以一次指定。例如有這樣的需求:
? [Copy to clipboard] View Code CPP
張三 |3456123, 湖南 李四 ,4564234| 湖北 王小二 , 4433253| 北京 ... |
我們需要以 “|” “,” 爲分隔符,同時又要過濾空格,把每行分成相應的字段。可以作爲你的一個作業來試試,要求代碼簡潔。
1.3 string insert, replace, erase
瞭解了
string
的操作符,查找函數和
substr
,其實就已經瞭解了
string
的
80%
的操作了。
insert
函數
, replace
函數和
erase
函數在使用起來相對簡單。下面以一個例子來說明其應用。
string
只是提供了按照位置和區間的
replace
函數,而不能用一個
string
字串來替換指定
string
中的另一個字串。這裏寫一個函數來實現這個功能:
? [Copy to clipboard] View Code CPP
void string_replace(string & strBig, const string & strsrc, const string &strdst) { string::size_type pos=0; string::size_type srclen=strsrc.size(); string::size_type dstlen=strdst.size(); while( (pos=strBig.find(strsrc, pos)) != string::npos) { strBig.replace(pos, srclen, strdst); pos += dstlen; } } |
看看如何調用:
? [Copy to clipboard] View Code CPP
#include <string> #include <iostream> using namespace std; int main() { string strinfo="This is Winter, Winter is a programmer. Do you know Winter?"; cout<<"Orign string is :/n "<<strinfo<<endl; string_replace(strinfo, "Winter", "wende"); cout<<"After replace Winter with wende, the string is :/n "<<strinfo<<endl; return 0; } |
其輸出結果:
? [Copy to clipboard] View Code CPP
Orign string is : This is Winter, Winter is a programmer. Do you know Winter? After replace Winter with wende, the string is : This is wende, wende is a programmer. Do you know wende? |
如果不用 replace 函數,則可以使用 erase 和 insert 來替換,也能實現 string_replace 函數的功能:
? [Copy to clipboard] View Code CPP
void string_replace(string & strBig, const string & strsrc, const string &strdst) { string::size_type pos=0; string::size_type srclen=strsrc.size(); string::size_type dstlen=strdst.size(); while( (pos=strBig.find(strsrc, pos)) != string::npos) { strBig.erase(pos, srclen); strBig.insert(pos, strdst); pos += dstlen; } } |
當然,這種方法沒有使用 replace 來得直接。
2 string 和 C-style 字符串
現在看了這麼多例子,發現 const char* 可以和 string 直接轉換,例如我們在上面的例子中,使用
? [Copy to clipboard] View Code CPP
string_replace(strinfo, "Winter", "wende"); 來代用 void string_replace(string & strBig, const string & strsrc, const string &strdst) |
在 C 語言中只有 char* 和 const char* ,爲了使用起來方便, string 提供了三個函數滿足其要求:
? [Copy to clipboard] View Code CPP
const charT* c_str() const const charT* data() const size_type copy(charT* buf, size_type n, size_type pos = 0) const |
其中:
c_str
直接返回一個以
/0
結尾的字符串。
data
直接以數組方式返回
string
的內容,其大小爲
size()
的返回值,結尾並沒有
/0
字符。
copy
把
string
的內容拷貝到
buf
空間中。
你或許會問,
c_str()
的功能包含
data()
,那還需要
data()
函數幹什麼?看看源碼:
? [Copy to clipboard] View Code CPP
const charT* c_str () const { if (length () == 0) return ""; terminate (); return data (); } |
原來 c_str() 的流程是:先調用 terminate() ,然後在返回 data() 。因此如果你對效率要求比較高,而且你的處理又不一定需要 以 /0 的方式結束,你最好選擇 data() 。但是對於一般的 C 函數中,需要以 const char* 爲輸入參數,你就要使用 c_str() 函數。
對於 c_str() data() 函數,返回的數組都是由 string 本身擁有,千萬不可修改其內容。其原因是許多 string 實現的時候採用了引用機制,也就是說,有可能幾 個 string 使用同一個字符存儲空間。而且你不能使用 sizeof(string) 來查看其大小。詳細的解釋和實現查看 Effective STL 的條款 15 :小心 string 實現的多樣性。
另外在你的程序中,只在需要時才使用 c_str() 或者 data() 得到字符串,每調用一次,下次再使用就會失效,如:
? [Copy to clipboard] View Code CPP
string strinfo("this is Winter"); ... // 最好的方式是 : foo(strinfo.c_str()); // 也可以這麼用 : const char* pstr=strinfo.c_str(); foo(pstr); // 不要再使用了 pstr 了 , 下面的操作已經使 pstr 無效了。 strinfo += " Hello!"; foo(pstr);// 錯誤! |
會遇到什麼錯誤?當你幸運的時候 pstr 可能只是指向 ”this is Winter Hello!” 的字符串,如果不幸運,就會導致程序出現其他問題,總會有一些不可遇見的錯誤。總之不會是你預期的那個結果。
3 string
和
Charactor Traits
瞭解了
string
的用法,該詳細看看
string
的真相了。前面提到
string
只是
basic_string
的一個
typedef
。看看
basic_string
的參數:
? [Copy to clipboard] View Code CPP
template <class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> > class basic_string { //... } |
char_traits
不僅是在
basic_string
中有用,在
basic_istream
和
basic_ostream
中也需要用到。
就像
Steve
Donovan
在過度使用
C++
模板中提到的,這些確實有些過頭了,要不是系統自己定義了相關的一些屬性,而且用了個
typedef
,否則還真不知道如何使用。
但複雜總有複雜道理。有了 char_traits ,你可以定義自己的字符串類型。當然,有了 char_traits < char > 和 char_traits < wchar_t > 你的需求使用已經足夠了,爲了更好的理解 string ,咱們來看看 char_traits 都有哪些要求。
如果你希望使用你自己定義的字符,你必須定義包含下列成員的結構: 表達式 描述
? [Copy to clipboard] View Code CPP
char_type 字符類型 int_type int 類型 pos_type 位置類型 off_type 表示位置之間距離的類型 state_type 表示狀態的類型 assign(c1,c2) 把字符 c2 賦值給 c1 eq(c1,c2) 判斷 c1,c2 是否相等 lt(c1,c2) 判斷 c1 是否小於 c2 length(str) 判斷 str 的長度 compare(s1,s2,n) 比較 s1 和 s2 的前 n 個字符 copy(s1,s2, n) 把 s2 的前 n 個字符拷貝到 s1 中 move(s1,s2, n) 把 s2 中的前 n 個字符移動到 s1 中 assign(s,n,c) 把 s 中的前 n 個字符賦值爲 c find(s,n,c) 在 s 的前 n 個字符內查找 c eof() 返回 end-of-file to_int_type(c) 將 c 轉換成 int_type to_char_type(i) 將 i 轉換成 char_type not_eof(i) 判斷 i 是否爲 EOF eq_int_type(i1,i2) 判斷 i1 和 i2 是否相等 |
想看看實際的例子,你可以看看 sgi STL 的 char_traits 結構源碼 .
現在默認的 string 版本中,並不支持忽略大小寫的比較函數和查找函數,如果你想練練手,你可以試試改寫一個 char_traits , 然後生成一個 case_string 類 , 也可以在 string 上做繼承,然後派生一個新的類,例如: ext_string ,提供一些常用的功能,例如:
定義分隔符。給定分隔符,把
string
分爲幾個字段。
提供替換功能。例如,用
winter,
替換字符串中的
wende
大小寫處理。例如,忽略大小寫比較,轉換等
整形轉換。例如把
”123″
字符串轉換爲
123
數字。
這些都是常用的功能,如果你有興趣可以試試。其實有人已經實現了,看看
Extended STL string
。如果你想偷懶,下載一個頭文件就可以用,有了它確實方便了很多。要是有人能提供一個支持正則表達式的
string
,我會非常樂意用。
4 string
建議
使用
string
的方便性就不用再說了,這裏要重點強調的是
string
的安全性。
string
並不是萬能的,如果你在一個大工程中需要頻繁處理字符串,而且有可能是多線程,那麼你一定要慎重
(
當然,在多線程下你使用任何
STL
容器都要慎重
)
。
string
的實現和效率並不一定是你想象的那樣,如果你對大量的字符串操作,而且特別關心其效率,那麼你有兩個選擇,首先,你可以看看你使用的
STL
版本中
string
實現的源碼;另一選擇是你自己寫一個只提供你需要的功能的類。
string
的
c_str()
函數是用來得到
C
語言風格的字符串,其返回的指針不能修改其空間。而且在下一次使用時重新調用獲得新的指針。
string
的
data()
函數返回的字符串指針不會以
’/0′
結束,千萬不可忽視。
儘量去使用操作符,這樣可以讓程序更加易懂
5
小結
難怪有人說:
string
使用方便功能強,我們一直用它!
6 附錄
? [Copy to clipboard] View Code CPP
string 函數列表 函數名 描述 begin 得到指向字符串開頭的 Iterator end 得到指向字符串結尾的 Iterator rbegin 得到指向反向字符串開頭的 Iterator rend 得到指向反向字符串結尾的 Iterator size 得到字符串的大小 length 和 size 函數功能相同 max_size 字符串可能的最大大小 capacity 在不重新分配內存的情況下,字符串可能的大小 empty 判斷是否爲空 operator[] 取第幾個元素,相當於數組 c_str 取得 C 風格的 const char* 字符串 data 取得字符串內容地址 operator= 賦值操作符 reserve 預留空間 swap 交換函數 insert 插入字符 append 追加字符 push_back 追加字符 operator+= += 操作符 erase 刪除字符串 clear 清空字符容器中所有內容 resize 重新分配空間 assign 和賦值操作符一樣 replace 替代 copy 字符串到空間 find 查找 rfind 反向查找 find_first_of 查找包含子串中的任何字符,返回第一個位置 find_first_not_of 查找不包含子串中的任何字符,返回第一個位置 find_last_of 查找包含子串中的任何字符,返回最後一個位置 find_last_not_of 查找不包含子串中的任何字符,返回最後一個位置 substr 得到字串 compare 比較字符串 operator+ 字符串鏈接 operator== 判斷是否相等 operator!= 判斷是否不等於 operator< 判斷是否小於 operator>> 從輸入流中讀入字符串 operator<< 字符串寫入輸出流 getline 從輸入流中讀入一行 |
///////////////////////////////////////////////////////////////////////////////////
之所以拋棄
char*
的字符串而選用
C++
標準程序庫中的
string
類,是因爲他和前者比較起來,不必擔心內存是否足夠、字符串長度等等,而且作
爲一個類出現,他集成的操作函數足以完成我們大多數情況下
(
甚至是
100%)
的需要。我們可以用
=
進行賦值操作,
==
進行比較,
+
做串聯(是不是很簡單
?
)。我們儘可以把它看成是
C++
的基本數據類型。
好了,進入正題
………
首先,爲了在我們的程序中使用
string
類型,我們必須包含頭文件
。如下:
? [Copy to clipboard] View Code CPP
#include <string> // 注意這裏不是 string.h string.h 是 C 字符串頭文件 |
1
.聲明一個
C++
字符串
聲明一個字符串變量很簡單:
? [Copy to clipboard] View Code CPP
string Str; |
這樣我們就聲明瞭一個字符串變量,但既然是一個類,就有構造函數和析構函數。上面的聲明沒有傳入參數,所以就直接使用了 string 的默認的構造函數,這個函數所作的就是把 Str 初始化爲一個空字符串。 String 類的構造函數和析構函數如下:
? [Copy to clipboard] View Code CPP
a) string s; // 生成一個空字符串 s b) string s(str) // 拷貝構造函數 生成 str 的複製品 c) string s(str,stridx) // 將字符串 str 內 “ 始於位置 stridx” 的部分當作字符串的初值 d) string s(str,stridx,strlen) // 將字符串 str 內 “ 始於 stridx 且長度頂多 strlen” 的部分作爲字符串的初值 e) string s(cstr) // 將 C 字符串作爲 s 的初值 f) string s(chars,chars_len) // 將 C 字符串前 chars_len 個字符作爲字符串 s 的初值。 g) string s(num,c) // 生成一個字符串,包含 num 個 c 字符 h) string s(beg,end) // 以區間 beg;end( 不包含 end) 內的字符作爲字符串 s 的初值 i) s.~string() // 銷燬所有字符,釋放內存 |
都很簡單,我就不解釋了。
2
.字符串操作函數
這裏是
C++
字符串的重點,我先把各種操作函數羅列出來,不喜歡把所有函數都看完的人可以在這裏找自己喜歡的函數,再到後面看他的詳細解釋。
? [Copy to clipboard] View Code CPP
a) =,assign() // 賦以新值 b) swap() // 交換兩個字符串的內容 c) +=,append(),push_back() // 在尾部添加字符 d) insert() // 插入字符 e) erase() // 刪除字符 f) clear() // 刪除全部字符 g) replace() // 替換字符 h) + // 串聯字符串 i) ==,!=,<,<=,>,>=,compare() // 比較字符串 j) size(),length() // 返回字符數量 k) max_size() // 返回字符的可能最大個數 l) empty() // 判斷字符串是否爲空 m) capacity() // 返回重新分配之前的字符容量 n) reserve() // 保留一定量內存以容納一定數量的字符 o) [ ], at() // 存取單一字符 p) >>,getline() // 從 stream 讀取某值 q) << // 將謀值寫入 stream r) copy() // 將某值賦值爲一個 C_string s) c_str() // 將內容以 C_string 返回 t) data() // 將內容以字符數組形式返回 u) substr() // 返回某個子字符串 v) 查找函數 w)begin() end() // 提供類似 STL 的迭代器支持 x) rbegin() rend() // 逆向迭代器 y) get_allocator() // 返回配置器 |
下面詳細介紹:
2 . 1 C ++ 字符串和 C 字符串的轉換
C++ 提供的由 C++ 字符串得到對應的 C_string 的方法是使用 data() 、 c_str() 和 copy() ,其中, data() 以字符數組的形式返 回字符串內容,但並不添加 ’/0’ 。 c_str() 返回一個以 ‘/0’ 結尾的字符數組,而 copy() 則把字符串的內容複製或寫入既有的 c_string 或字符數組內。 C++ 字符串並不以 ’/0’ 結尾。我的建議是在程序中能使用 C++ 字符串就使用,除非萬不得已不選用 c_string 。由於只是簡單介紹, 詳細介紹掠過,誰想進一步瞭解使用中的注意事項可以給我留言 ( 到我的收件箱 ) 。我詳細解釋。
2
.
2
大小和容量函數
一個
C++
字符串存在三種大小:
a)
現有的字符數,函數是
size()
和
length()
,他們等效。
Empty()
用來檢查字符串是否爲空。
b)max_size()
這個大小是指當前
C++
字符串最多能包含的字符數,很可能和機器本身的限制或者字符串所在位置連續內存的大小有關係。我們一般情況下不用關心他,應該大小
足夠我們用的。但是不夠用的話,會拋出
length_error
異常
c)capacity()
重新分配內存之前
string
所能包含的最大字符數。這裏另一個需要指出的是
reserve()
函數,這個函數爲
string
重新分配內存。重新分配的大小由其參數決定,
默認參數爲
0
,這時候會對
string
進行非強制性縮減。
還有必要再重複一下 C++ 字符串和 C 字符串轉換的問題,許多人會遇到這樣的問題,自己做的程序要調用別人的函數、類什麼的(比如數據庫連接函數 Connect(char*,char*) ),但別人的函數參數用的是 char* 形式的,而我們知道, c_str() 、 data() 返回的字符數組由該字 符串擁有,所以是一種 const char*, 要想作爲上面提及的函數的參數,還必須拷貝到一個 char*, 而我們的原則是能不使用 C 字符串就不使用。那麼,這時候我們的處理方式是:如果 此函數對參數 ( 也就是 char*) 的內容不修改的話,我們可以這樣 Connect((char*)UserID.c_str(), (char*)PassWD.c_str()), 但是這時候是存在危險的,因爲這樣轉換後的字符串其實是可以修改的(有興趣地可以自己試一試),所以我強 調除非函數調用的時候不對參數進行修改,否則必須拷貝到一個 char* 上去。當然,更穩妥的辦法是無論什麼情況都拷貝到一個 char* 上去。同時我們也祈 禱現在仍然使用 C 字符串進行編程的高手們(說他們是高手一點兒也不爲過,也許在我們還穿開襠褲的時候他們就開始編程了,哈哈 … )寫的函數都比較規範,那樣 我們就不必進行強制轉換了。
2
.
3
元素存取
我們可以使用下標操作符
[]
和函數
at()
對元素包含的字符進行訪問。但是應該注意的是操作符
[]
並不檢查索引是否有效(有效索引
0~str.length()
),如果索引失效,會引起未定義的行爲。而
at()
會檢查,如果使用
at()
的時候索引無效,會拋出
out_of_range
異常。
有一個例外不得不說,
const
string a;
的操作符
[]
對索引值是
a.length()
仍然有效,其返回值是
’/0’
。其他的各種情況,
a.length()
索引都是無效的。舉例如下:
? [Copy to clipboard] View Code CPP
const string Cstr(“const string”); string Str(“string”);
Str[3]; //ok Str.at(3); //ok
Str[100]; // 未定義的行爲 Str.at(100); //throw out_of_range
Str[Str.length()] // 未定義行爲 Cstr[Cstr.length()] // 返回 ‘/0’ Str.at(Str.length());//throw out_of_range Cstr.at(Cstr.length()) ////throw out_of_range |
我不贊成類似於下面的引用或指針賦值:
char& r=s[2];
char* p= &s[3];
因爲一旦發生重新分配, r,p 立即失效。避免的方法就是不使用。
2
.
4
比較函數
C++
字符串支持常見的比較操作符(
>,>=,<,<=,==,!=
),甚至支持
string
與
C-string
的比較
(
如
str<”hello”)
。在使用
>,>=,<,<=
這些操作符的時候是根據
“
當前字符特性
”
將字符按字典順序進行逐一得
比較。字典排序靠前的字符小,比較的順序是從前向後比較,遇到不相等的字符就按這個位置上的兩個字符的比較結果確定兩個字符串的大小。同
時,
string(“aaaa”)
另一個功能強大的比較函數是成員函數
compare()
。他支持多參數處理,支持用索引值和長度定位子串來進行比較。他返回一個整數來表示比較結果,返回值意義如下:
0-
相等
〉
0-
大於
<0-
小於。舉例如下:
? [Copy to clipboard] View Code CPP
string s(“abcd”); s.compare(“abcd”); // 返回 0 s.compare(“dcba”); // 返回一個小於 0 的值 s.compare(“ab”); // 返回大於 0 的值 s.compare(s); // 相等 s.compare(0,2,s,2,2); // 用 ”ab” 和 ”cd” 進行比較 小於零 s.compare(1,2,”bcx”,2); // 用 ”bc” 和 ”bc” 比較。 |
怎麼樣?功能夠全的吧!什麼?還不能滿足你的胃口?好吧,那等着,後面有更個性化的比較算法。先給個提示,使用的是 STL 的比較算法。什麼?對 STL 一竅不通?你重修吧!
2
.
5
更改內容
這在字符串的操作中佔了很大一部分。
首先講賦值,第一個賦值方法當然是使用操作符
=
,新值可以是
string(
如:
s=ns)
、
c_string(
如:
s=”gaint”)
甚至單一字符(如:
s=’j’
)。還可以使用成員函數
assign()
,這個成員函數可以使你更靈活的對字符串賦值。還是舉例說明吧:
? [Copy to clipboard] View Code CPP
s.assign(str); // 直接 s.assign(str,1,3);// 如果 str 是 ”iamangel” 就是把 ”ama” 賦給字符串 s.assign(str,2,string::npos);// 把字符串 str 從索引值 2 開始到結尾賦給 s s.assign(“gaint”); // 不說 s.assign(“nico”,5);// 把 ’n’ ‘I’ ‘c’ ‘o’ ‘/0’ 賦給字符串 s.assign(5,’x’);// 把五個 x 賦給字符串 |
把字符串清空的方法有三個:
s=””;s.clear();s.erase();(
我越來越覺得舉例比說話讓別人容易懂!
)
。
string
提供了很多函數用於插入(
insert
)、刪除(
erase
)、替換(
replace
)、增加字符。
先說增加字符(這裏說的增加是在尾巴上),函數有
+=
、
append()
、
push_back()
。舉例如下:
? [Copy to clipboard] View Code CPP
s+=str;// 加個字符串 s+=”my name is jiayp”;// 加個 C 字符串 s+=’a’;// 加個字符
s.append(str); s.append(str,1,3);// 不解釋了 同前面的函數參數 assign 的解釋 s.append(str,2,string::npos)// 不解釋了
s.append(“my name is jiayp”); s.append(“nico”,5); s.append(5,’x’);
s.push_back(‘a’);// 這個函數只能增加單個字符 對 STL 熟悉的理解起來很簡單 |
也許你需要在 string 中間的某個位置插入字符串,這時候你可以用 insert() 函數,這個函數需要你指定一個安插位置的索引,被插入的字符串將放在這個索引的後面。
? [Copy to clipboard] View Code CPP
s.insert(0,”my name”); s.insert(1,str); |
這種形式的
insert()
函數不支持傳入單個字符,這時的單個字符必須寫成字符串形式
(
讓人噁心
)
。既然你覺得噁心,那就不得不繼續讀下面一段
話:爲了插入單個字符,
insert()
函數提供了兩個對插入單個字符操作的重載函數:
insert(size_type
index,size_type num,chart c)
和
insert(iterator pos,size_type num,chart c)
。其中
size_type
是無符號整數,
iterator
是
char*,
所以,你這麼調用
insert
函數是不行
的:
insert(0,1,’j’);
這時候第一個參數將轉換成哪一個呢?所以你必須這麼
寫:
insert((string::size_type)0,1,’j’)
!第二種形式指出了使用迭代器安插字符的形式,在後面會提及。順便提一
下,
string
有很多操作是使用
STL
的迭代器的,他也儘量做得和
STL
靠近。
刪除函數
erase()
的形式也有好幾種(真煩!),替換函數
replace()
也有好幾個。舉例吧:
? [Copy to clipboard] View Code CPP
string s=”il8n”; s.replace(1,2,”nternationalizatio”);// 從索引 1 開始的 2 個替換成後面的 C_string s.erase(13);// 從索引 13 開始往後全刪除 s.erase(7,5);// 從索引 7 開始往後刪 5 個 |
2 . 6 提取子串和字符串連接
題取子串的函數是: substr(), 形式如下:
? [Copy to clipboard] View Code CPP
s.substr();// 返回 s 的全部內容 s.substr(11);// 從索引 11 往後的子串 s.substr(5,6);// 從索引 5 開始 6 個字符 |
把兩個字符串結合起來的函數是 + 。(誰不明白請致電 120 )
2
.
7
輸入輸出操作
1
.
>>
從輸入流讀取一個
string
。
2
.
<<
把一個
string
寫入輸出流。
另一個函數就是
getline(),
他從輸入流讀取一行內容,直到遇到分行符或到了文件尾。
2
.
8
搜索與查找
查找函數很多,功能也很強大,包括了:
? [Copy to clipboard] View Code CPP
find() rfind() find_first_of() find_last_of() find_first_not_of() find_last_not_of() |
這些函數返回符合搜索條件的字符區間內的第一個字符的索引,沒找到目標就返回
npos
。所有的函數的參數說明如下:
第一個參數是被搜尋的對象。第二個參數(可有可無)指出
string
內的搜尋起點索引,第三個參數(可有可無)指出搜尋的字符個數。比較簡單,不多說
不理解的可以向我提出,我再仔細的解答。當然,更加強大的
STL
搜尋在後面會有提及。
最後再說說
npos
的含義,
string::npos
的類型是
string::size_type,
所以,一旦需要把一個索引與
npos
相比,這個索引值
必須是
string::size)type
類型的,更多的情況下,我們可以直接把函數和
npos
進行比較
(如:
if(s.find(“jia”)==string::npos)
)。
任何人對本文進行引用都要標明作者是 Nicolai M.Josuttis 譯者是侯捷 / 孟巖