前面的內(nèi)容我們使用過字符串字面值,并了解字符串字面值的類型是字符常量的數(shù)組,現(xiàn)在可以更加明確的認識到:字符串字面值的類型就是 const char 類型的數(shù)組。C++從C繼承下來的一種通用的結(jié)構(gòu)是C風格字符串(C-Style character string),字符串字面值就是該類型的實例。實際上,C風格字符串既不能確切的歸結(jié)為C語言類型,也不能歸結(jié)為C++的類型,而是以空字符 null 結(jié)束的字符數(shù)組:
char c1[] = {'C', '+', '+'}; //c1 維數(shù)為3,not c-style
char c2[] = {'C', '+', '+', '\0'}; //c2 維數(shù)為4
char c3[] = "C++"; //c3 維數(shù)為4,自動添加空字符
const char *cp = "C++"; //自動添加空字符
char *cp1 = c1; //指向c1數(shù)組的第一個元素,not c-style
char *cp2 = c2; //指向c1數(shù)組的第一個元素,以空字符 `null` 結(jié)束的字符數(shù)組
c1 和 cp1 都不是C風格字符串:c1 是不帶結(jié)束符 null 的字符數(shù)組。其他都是C風格字符串。
1、C風格字符串的使用
C++通過 (const) char* 類型的指針來操作C風格的字符串。
const char *cp = "C++";
while (*cp) {
cout << *cp << endl;
++cp;
}
輸出
C
+
+
如果cp指向的字符數(shù)組沒有 null 結(jié)束符,作為循環(huán)會失敗。這時循環(huán)繼續(xù)進行,知道遇到內(nèi)存中某處的 null 結(jié)束符。
2、C風格字符串的標準庫函數(shù)
下表類除了C語言標準庫函數(shù)提供的一系列處理C風格字符串的庫函數(shù),這些庫函數(shù)包含在 string.h 中。
| 庫函數(shù) | 含義 |
|---|---|
| strlen(s) | 返回s的長度,不包括null結(jié)束符 |
| strcmp(s1, s2) | 比較s1和s2是否相同。s1等于s2,返回0;s1大于s2,返回正數(shù);s1小于s2,返回負數(shù) |
| strcat(s1, s2) | 將字符串s2連接到s1后面,返回s1 |
| strcpy(s1, s2) | 將s2復(fù)制給s2,返回s1 |
| strncat(s1, s2, n) | 將s2的前n個字符連接到s1后面,并返回s1 |
| strncpy(s1, s2, n) | 將s2的前n個字符賦給s1,并返回s1 |
傳遞給這些函數(shù)的指針必須具有非零值,并且指向以null結(jié)束的字符數(shù)組中的第一個元素。并且要確保目標字符串必須有足夠大的空間存放結(jié)果串。
字符串的比較和比較結(jié)果都必須使用標準庫函數(shù) strcmp 進行
const char *cp1 = "C++";
const char *cp2 = "C";
int i = strcmp(cp1, cp2);
cout << i << endl;
i = strcmp(cp2, cp1);
cout << i << endl;
i = strcmp(cp1, cp1);
cout << i << endl;
輸出
1
-1
0
不要忘記字符串結(jié)束符null
char c[] = { 'c' };
cout << strlen(c) << endl;
輸出結(jié)果是不可預(yù)料的,因為會一直尋找null結(jié)束符,然后再輸出。比如在我的電腦是輸出是 324,這明顯是錯誤的,在不同的電腦會輸出不同的值。
必須確保目標字符串有足夠的大小
庫函數(shù) strcat 和 strcpy 的第一個參數(shù)必須有足夠大的空間
const char *cp1 = "C++";
const char *cp2 = "C";
char str[3 + 1 + 2];
strcpy(str, cp1);
strcat(str, " ");
strcat(str, cp2);
cout << str << endl;
輸出
C++ C
上述程序就目前來看是完全沒問題的,但如果 cp1 和 cp2 指向的字符串大小發(fā)生了改變,str 所需的大小就不滿足要求了。會導(dǎo)致嚴重的安全漏洞。
strn函數(shù)處理C風格的字符串
如果必須處理C風格字符串,strncat,strncpy會比strcat,strcpy更安全
char str[3 + 1 + 2];
strncpy(str, cp1, 4); //1. 包含null
strncat(str, " ", 2); //2. 包含null,看起來冗余,但是個好習(xí)慣
strncat(str, cp2, 2); //3. 要包含null
cout << str << endl;
分步驟解釋:
- strncpy cp1 時需要復(fù)制 4 個字符:cp1 中所有字符,加上 null 結(jié)束符;此時 strlen(str) = 3.
- strncat " " 時需要復(fù)制 2 個字符:一個空格字符,加上 null 結(jié)束符;此時空格符會把步驟1復(fù)制的 null 結(jié)束符覆蓋。此時 strlen(str) = 4.
- strncat cp2 時需要復(fù)制 2 個字符:cp2 中所有字符,加上 null 結(jié)束符;此時 strlen(str) = 6.
最后 str 的內(nèi)容:cp1 和 cp2 中所有字符,一個空格,和一個null結(jié)束符。
盡可能使用string類型
string str = cp1;
str += " ";
str += cp2;
可以達到上面一樣的效果。
3、創(chuàng)建動態(tài)數(shù)組
數(shù)組類型的限制:長度不變;編譯時須知道長度;數(shù)組只在定義它的語句內(nèi)存在。可以使用動態(tài)分配解決這一問題,跟C中的malloc和free類似,C++中使用 new 和 delete 實現(xiàn)。
3.1、定義
數(shù)組變量需要指定類型、數(shù)組名和維數(shù)定義,而動態(tài)分配的數(shù)組只需指定類型和長度,不必為數(shù)組對象命名。
int *ip = new int[10];
3.2、初始化
動態(tài)分配的數(shù)組,如果有類型,則使用類型的默認構(gòu)造函數(shù)初始化;如果是內(nèi)置類型,則無初始化:
string *sp = new string[10]; //初始化為10個空字符串
int *ip = new int[10]; //不初始化
int *ip2 = new int [10](); //初始化為10個0
圓括號要求編譯器對數(shù)組進行初始化;
動態(tài)分配的數(shù)組,其元素只能初始化為默認值,而不能像變量一樣提供初始化列表進行初始化
3.3、const對象的動態(tài)數(shù)組
必須為該數(shù)組提供初始化值,因為數(shù)組內(nèi)都是const對象,無法賦值
const int *caip_bad = new const int[10]; //error
const int *caip_ok = new const int[10](); //ok
也可以定義類類型的const數(shù)組,但該類型必須提供默認構(gòu)造函數(shù)
const string *csp = new const string[10]; //ok
3.4、動態(tài)分配空數(shù)組
有時候,編譯時并不知道數(shù)組的長度,這時可以動態(tài)分配空數(shù)組??慈缦鲁绦?/p>
size_t n = get_size();
int *p = new int[n];
for (int *q = p; q != p + n; ++q)
//process the array
即使 get_size() 返回 0 也是可以的。例如
char arr[0]; //error
char *cp = new char[0]; //ok,但不能被解引用
上述 cp 指針 允許 的操作有:比較;本身加(減)0。
3.5、動態(tài)空間釋放
動態(tài)分配的空間必須釋放,不然內(nèi)存會被逐漸耗盡
delete [] cp;
該語句回收了 cp 所指向的數(shù)組。把相應(yīng)的內(nèi)存返還給自由存儲區(qū)。
理論上,如果少了
[]應(yīng)該會導(dǎo)致少釋放空間,從而產(chǎn)生內(nèi)存泄露,因此,釋放動態(tài)數(shù)組時,不能忘記[]
3.6、動態(tài)數(shù)組的使用
長度不一樣的兩個字符串,賦給同一個新的字符數(shù)組
const char *txt1 = "HAHAHA";
const char *txt2 = "HEHEHEHEHEHE";
const char *txt;
if(condition) //condition表示某個條件
txt = txt1;
else
txt = txt2;
size_t demension = strlen(txt) + 1;
char *msg = new char[demension];
strncpy(msg, txt, demension);
cout << msg << endl;
4、新舊代碼的兼容
4.1、混合使用string庫和C風格字符串
可用字符串字面值初始化string對象
string str("Hello World");
通常,C風格字符串與字符串字面值有相同的數(shù)據(jù)類型,而且都是以空字符null結(jié)束,因此可以把C風格字符串用在任何可以使用字符串字面值的地方:
- C風格字符串對string對象進行賦值或初始化;
- C風格字符串可以作為string類型的加法操作兩個參數(shù)中的一個;
但反之不成立,但可以通過名為 c_str() 的函數(shù)轉(zhuǎn)化為C風格字符串
char *str1 = str; //error
const char *str2 = str.c_str(); //ok
4.2、使用數(shù)組初始化vector對象
const size_t arr_size = 6;
int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(int_arr, int_arr + arr_size);
//相當于用 int_arr[1], int_arr[2], int_arr[3] 初始化 ivec1
vector<int> ivec1(int_arr + 1, int_arr + 4);
舉例1
編寫程序: 從標準輸入設(shè)備讀入字符串,并把字符串存放在字符數(shù)組中(輸入的字符串長度不定)。
int main()
{
size_t arry_size = 10;
char *p_str = new char[arry_size];
int count = 0;//統(tǒng)計輸入的字符數(shù)
char c;//存放字符
cout << "Enter some chars:" << endl;
while (cin.get(c))//每次讀取一個字符賦給c
{
if (count + 1 >= arry_size)//超出預(yù)設(shè)長度
{
arry_size += 10;//長度增加10
char *p_temp = new char[arry_size];//申請新空間
strncpy(p_temp, p_str, count);//將就空間的內(nèi)容拷貝到新空間
delete[] p_str;//銷毀以前的p_str(p_temp)
p_str = p_temp;//將新空間p_temp賦給p_str
}
p_str[count] = c;
count++;
}
p_str[count] = '\0';//添加null結(jié)束符
cout << "outputs:" <<endl;
for (size_t i = 0;i != strlen(p_str); ++i)
{
cout << p_str[i];
}
cout << endl;
cout << p_str << endl;
delete[] p_str;//最后銷毀p_str
cout << "end" <<endl;
return 0;
}
運行測試
Enter some chars:
asdfghjklqwertyuiop↙
^Z↙
outputs:
asdfghjklqwertyuiop(輸出換行符)
asdfghjklqwertyuiop(輸出換行符)
end
舉例2
編寫程序:讀入一組string類型數(shù)據(jù),將它們存儲在vector中,然后將vector中的對象復(fù)制給一個字符指針數(shù)組。即為vector中的每個元素創(chuàng)建一個新的字符數(shù)組,然后把vector元素的數(shù)據(jù)復(fù)制到相應(yīng)的字符數(shù)組中,最后將指針插入到指針數(shù)組中。
int main()
{
vector<string> svec;
string str;
while (cin >> str) {
svec.push_back(str);
}
char **cp_arr = new char*[svec.size()];//指針數(shù)組
int cnt = 0;
for (vector<string>::iterator iter = svec.begin(); iter != svec.end(); ++iter) {
string s = *iter;
char *cp = new char[s.size()+1];//+1表示為null結(jié)束符預(yù)留空間
strncpy(cp, s.c_str(), s.size()+1);
cp_arr[cnt] = cp;
++cnt;
}
for (int i = 0; i != cnt; ++i) {
cout << cp_arr[i] << endl;
delete[] cp_arr[i];
}
delete[] cp_arr;
return 0;
}
運行結(jié)果
hello↙
world↙
^Z↙
hello
world
5、多維數(shù)組
嚴格的講,C++中沒有多維數(shù)組,只有數(shù)組的數(shù)組
初始化
int ia[3][2] = {
{0, 1},
{2, 3},
{4, 5}
};
且等價于
int ia[3][2] = {0, 1, 2, 3, 4, 5};
而下面情況只初始化第一個元素,則其余初始化為0
int ia[3][2] = {{1}, {2}, {3}};
等價于
int ia[3][2] = {{1, 0}, {2, 0}, {3, 0}};
但不會等價于
int ia[3][2] = {1, 2, 3};//等價于int ia[3][2] = {1, 2, 3, 0, 0, 0}
下標引用
const size_t rsz = 3;
const size_t csz = 2;
int ia[rsz][csz];
for (int i = 0; i != rsz; ++i) {
for (int j = 0;j != csz; ++j) {
ia[i][j] = i + j;
}
}
訪問特定元素時,必須提供行下標與列下標
指針與多維數(shù)組
與普通數(shù)組一樣,多為數(shù)組名也是指向第一個元素的指針。
int main()
{
int ia[3][2] = {
{ 0, 1 },
{ 2, 3 },
{ 4, 5 }
};
int (*ip)[2]; //ok: 一個指向包含2個int值的數(shù)組的指針
ip = &ia[1]; //ok: ia[1] 包含2個int值
cout << (*ip)[0] << "," << (*ip)[1] << endl;
return 0;
}
運行結(jié)果
2,3
int (*ip)[2];//指向數(shù)組的指針;即ip是指向包含兩個int值的數(shù)組的指針
int *ip[2];//指針的數(shù)組;即ip是包含兩個指針的數(shù)組
typedef簡化多維數(shù)組指針
指向 ia 的指針
typedef int int_arr[2];
int_arr *ip = ia;
用 int_arr 輸出 ia 的元素
int ia[3][2] = {
{ 0, 1 },
{ 2, 3 },
{ 4, 5 }
};
typedef int int_arr[2];
for (int_arr *p = ia; p != ia + 3; ++p) {
for (int *q = *p; q != *p + 2; ++q){
cout << *q << endl;
}
}
結(jié)果
0
1
2
3
4
5
END.
Github Pages同步更新: Humooo's Blog