標籤:style color 使用 strong 檔案 資料 問題 ar
儘管C++支援C風格字串,但在C++程式中最好還是不要使用它們。這是因為C風格字串不僅使用起來不太方便,而且極易引發程式漏洞,是諸多安全問題的根本原因。
字串字面值是一種通用結構的執行個體,這種結構即是C++由C繼承而來的C風格字串。C風格字串不是一種類型,而是為了表達和使用字串而形成的一種約定俗成的寫法。按此習慣書寫的字串存放在字元數組中並以Null 字元串結束。以Null 字元結束的意思是在字串最後一個字元後面跟著一個Null 字元(‘\0‘)。一般利用指標來操作這些字串。
C標準庫String函數
下表列出了C語言標準庫提供的一組函數,這些函數可用於操作C風格字串,它們定義在出string標頭檔中,出string是C語言標頭檔string.h的C++版本。
C風格字串的函數 |
strlen(p) 返回p的長度,Null 字元不計算在內 strcmp(p1,p2) 比較p1和p2的相等性。如果p1==p2,返回0;如果p1>p2,返回一個正值;如果p1<p2,則返回一個負值 strcat(p1,p2) 將p2附加到p1之後,返回p1 strcpy(p1,p2) 將p2拷貝給p1,返回p1 |
傳入此類函數的指標必須指向以Null 字元作為結束的數組:
char ca[]={‘c‘,‘+‘,‘+‘}; //不以Null 字元結束
cout<<strlen(ca)<<endl; //嚴重錯誤:ca沒有以Null 字元結束
此例中,ca雖然也是一個字元數組但他不是以Null 字元作為結束的,因此上述程式將產生未定義的結果。strlen函數將有可能沿著ca在記憶體中的位置不斷向前尋找,直到遇到Null 字元才停下來。
比較字串
比較兩個C風格字串的方法和之前學習過的比較標準庫string對象的方法大相徑庭。比較標準庫string對象的時候,用的是普通的關係運算子和相等性運算子:
string s1="A string example";
string s2="A different string";
if(s1<S2) //false:s2小於s1
如果把這些運算子用著兩個C風格字串上,實際比較的將是指標而非字串本身:
const char ca1[]="A string example";
const char ca2[]="A different string";
if(ca1<ca2) //未定義的:試圖比較兩個無關地址
謹記之前介紹過的,當使用數組的時候其實真正用的是指向數組首元素的指標。因此,上面的if條件實際上比較的是兩個const char*的值。這兩個指標指向的並非同一個對象,所以將得到未定義的結果。
要想比較兩個C風格字串需要調用strcmp函數,此時比較的就不再是指標了。如果兩個字串相等,strcmp返回0,如果前面的字串較大,返回正值;如果後面的字串較大,返回負值:
if(strcmp(ca1,ca2)<0) //和兩個string對象的比較s1<s2效果一樣
目標字串的大小由調用者指定
串連或拷貝C風格字串也與標準庫string對象的同類操作差別很大。例如,要想把剛剛定義的那兩個string對象s1和s2串連起來,可以直接攜程下面的形式:
//將largeStr初始化成s1,一個空格和s2的串連
string largeStr=s1+“ ”+s2;
同樣的操作如果放到ca1和ca2這兩個數組身上就會產生錯誤了。運算式ca1+ca2試圖將兩個指標相加,顯然這樣的操作沒什麼意義,也肯定是非法的。
正確的方法是使用strcat函數和strcpy函數。不過要想使用這兩個函數,還必須提供一個用於存放結果字串的數組,該數組必須足夠大以便容納下結果字串及末尾的Null 字元。下面的代碼雖然很常見,但是充滿了安全風險,極易引發嚴重錯誤:
//如果我們計算錯了largeStr的大小將引發嚴重錯誤
strcpy(largeStr,ca1); //把ca1拷貝給largeStr
strcat(largeStr," "); //在largeStr的末尾加上一個空格
strcat(largeStr,ca2); //把ca2串連到largeStr後面
混用string對象和C風格字串
允許使用字串字面值來初始化string對象:
string s("Hello World!"); //s的內容是Hello World
更一般的情況是,任何出現字串字面值的地方都可以用以Null 字元結束的字元數組來替代:
- 允許使用以Null 字元結束的字元數組來初始化string對象或為string對象賦值。
- 在string對象的加法運算中允許使用以Null 字元結束的字元數組作為其中一個運算對象(不是兩個運算對象都是):在string對象的複合賦值運算中允許使用以Null 字元數組作為右側的運算對象。
上述性質反過來就不成立了:如果程式的某處需要一個C風格字串,無法直接用string對象來代替它。例如,不能用string對象直接初始化指向字元的指標。為了完成該功能,string專門提供了一個名為c_str的成員函數:
char *str=s;//錯誤:不能用string對象初始化char*
const char *str=s.c_str(); //正確
顧名思義,c_str函數的傳回值是一個C 風格的字串。也就是說,函數的返回結果是一個指標,該指標指向一個以Null 字元結束的字元數組,而這個數組所存的資料恰好與那個string對象的一樣。結果指標的類型是const char*,從而確保我們不會改變字元數組的內容。
我們無法保證c_str函數返回的數組一直有效,事實上,如果後續的操作改變了s的值就可能讓之前返回的數組失去效用。
使用數組初始化vector對象
前面介紹過不允許使用一個數組為另一個內建類型的數組賦初始值,也不允許使用vector對象初始化數組。相反的,允許使用數組類初始化vector對象。要實現這一目的,只需指明要拷貝地區的首元素地址和尾後元素地址就可以了:
int int_arr[]={0,1,2,3,4,5};
//ivec有6個元素,分別是int_arr中對應元素的副本
vector<int> ivec(begin(int_arr),end(int_arr));
在上述代碼中,用於建立ivec的兩個指標實際上指明了用來初始化的值在數組int_arr中的位置,其中第二個指標應指向待拷貝地區尾元素的下一個位置。此例中,使用標準庫函數begin和end來分別計算int_arr的首指標和尾後指標,在最後的結果中,ivec將包含6個元素,它們的次序和值都與數組int_arr完全一樣。