tags: 大一程設-下 東華大學 東華大學資管系 基本程式概念 資管經驗分享

字串String

在這篇筆記會介紹 C-string(char array)、string 的應用,同時也會介紹常用到的 function,還有在使用 char array 跟 string 時須注意的細節。

cstring

C-string and null character

請務必記得引入函式庫

#include <cstring>

null character '\0'

和一般的陣列相同,C-string的index初始都是0,不同的是,C-string並非使用整數的變數來追蹤目前有多少陣列被使用,而是使用 '\0'值放在所有值的最尾端。當電腦在逐一讀取C-string value時,只要它讀到陣列裡的值是'\0' ,電腦便會停下來,也因此在使用C-string時,陣列的最後一定是null character。

假設程式碼如下,宣告一個名為c的Cstring,長度為5,然而這個程式碼在執行後電腦會報錯。

報錯資訊: 初始化給的值對陣列而言太長了。

c[0]='1' c[1]='2' c[2]='3' c[3]='4' c[4]='5'

#include <iostream> #include <cstring> using namespace std; int main(){ char c[5]="12345"; cout<<c; }

會發生這樣的問題,就要回到這一節所介紹的null character,由於我們cstring的長度一開始就設好為5,值剛好index是0~4,長度也同樣是5,而電腦在宣告cstring時會自動幫我們補上一個'\0',然而這個範例的陣列並沒有為null character預留長度1空間,因此系統才會報錯。

所以當我們將陣列長度改為6,就可以正常執行。

c[0]='1' c[1]='2' c[2]='3' c[3]='4' c[4]='5' c[5]='\0'

#include <iostream> #include <cstring> using namespace std; int main(){ char c[6]="12345"; cout<<c; if(c[5]=='\0') cout<<endl<<"null character 有在這!"; return 0; }

宣告cstring時,長度總是要比需要的長度多1,來存放'\0'

不只是宣告cstring時需要注意,在更改值時也要特別留意,若我們不小心將上題c[5]='a'原本存放null character的位置改成別的字符,雖然程式依舊能執行,但如果有使用到其他的function,而那些function在處理cstring仍需要讀到null character才停止,使用上就會有問題。

C-string如何使用

在宣告cstring時就和宣告其他型態的陣列差不多

  • 宣告陣列
char a[10];
  • 宣告陣列時直接給初始值

    • 第一種:有給長度

      ​​​​​​​​char a[10]="abcde";
    • 第二種:沒給初始長度

      ​​​​​​​​char a[]="abcde";

      程式跑起來後,電腦會自動幫a這個cstring配置空間,並給予長度6的cstring陣列,長度5給"abcde",而最後一個長度給'\0'

      c[0]='a' c[1]='b' c[2]='c' c[3]='d' c[4]='e' c[5]='\0'

  • 宣告陣列後才給值(需 #include<cstring> )

#include<cstring> int main(){ char a[10]; strcpy(a,"abcde"); //將後面的abcde指派給a[10] } return 0;

c[0]='a' c[1]='b' c[2]='c' c[3]='d' c[4]='e' c[5]='\0'

c[6]=? c[7]=? c[8]=? c[9]=?

cstring function

以下介紹的幾個function皆需使用cstring函式庫,為cstring提供不同的功能。

Function名稱 說明
strcpy(cs1, cs2) 把cs2的字串指派給cs1
strcat(cs1, cs2) 把cs2的字串接在cs1後方
strlen(cs1) (int型態) 回傳cs1長度
strcmp(cs1, cs2) 回傳int型態,等於0代表兩字串相同,小於0代表cs1小於cs2,大於0則代表cs1大於cs2

若你是使用 visual studio,請注意以下事項!!!礙於編輯器的關係,有些 function 的名字有些不同。

  • strcpy(cs1, cs2) 在 visual studio 為 strcpy_s(cs1, cs2)
  • strcat(cs1, cs2) 在 visual studio 為 strcat_s(cs1, cs2)

原因是因為微軟的編輯器對於字串處理比較嚴格,所以在設計上有額外的處理,但 function 的意義跟用法並沒有改變。


由於上面這幾個function都可能存在一些問題,像是無法確認變數是否足夠大可以儲存複製過來的變數,因此根據上面這些function又有了下面的這幾個function,功能差不多,只是下面的function更安全。

Function名稱 說明
strncpy(cs1, cs2 , Length_Limit ) 與strcpy(cs1, cs2)相同, cs2複製給cs1最多Length_Limit個
char c[]="abcdef",C[]="ABCDEF"; strncpy(c, C , 3); cout<<c;
ABCdef

Function名稱 說明
strncat(cs1, cs2 , Length_Limit ) 與strcat(cs1, cs2)相同, 取cs2字串Length_Limit個來串接在cs1後方
char c[]="abcdef",C[]="ABCDEF"; strncat(c, C , 3); cout<<c;
abcdefABC

Function名稱 說明
strncmp(cs1, cs2 , Length_Limit ) 與strncmp(cs1, cs2)相同, 取cs2字串Length_Limit個 跟 取cs1字串Length_Limit個來比較
char c[]="abcdef",C[]="abcDEF"; cout<<strncmp(c, C , 3);
0    //比較的結果為相同

cstring的輸入法 : cin、cin.getline()、get()

想輸入值到cstring變數時,有多種不同的輸入方法。

cin

在輸入cstring變數時可以直接寫cin輸入即可,不需要像輸入陣列那般,使用for迴圈來一一輸入值,當然,cin要注意的是,只要輸入的字串中間有空格(包括\n\t),就會停下


char b3[]=""; cin>>b3; cout<<b3;

Input:

abc

Output:

abc

cin輸入的字串,會停在空格之前。

char b[]=""; cin>>b; cout<<b;

Input:

Hello! everyone

Output:

Hello!

cin.getline

可以在輸入cstring時,輸入包含空格符號的字串,遇到換行符號會停下。

  • cin.getline(cstring_variable,限制輸入的長度)
    當然你也可以宣告長度後,在給陣列值,不過如果出現以下程式碼的情況,就需要留意一下。
char b[3]=""; cin.getline(b,3); //限制只能輸入三位 cout<<b;

Input:

123

雖然我們長度設為3,但別忘了電腦在處理cstring時,是去尋找'\0'的位置在哪,只要找到那個位置,即是知道這裡是陣列的尾端。因此在輸入cstring時需預留一個空位給電腦,它會自動幫你加在值的最尾端。這也是為什麼我們輸入了三個值123,但輸出的結果卻只有12
實際上為12+\0

Output:

12

cin.get()

會一個一個符號讀使用者輸入的值,可接受空格、\n\t。如果讀取成功會回傳非0的值,失敗則回傳0。

  • cin.get(cstring_variable)
char c; cin.get(c); cout<<c;

Input:

A

Output:

A

  • cin.get(cstring_variable,長度)
    限制讀幾個字符
char c[20]; cin.get(c,5); cout<<c;

Input:

1234567890

第五個讀入的為'\0'

Output:

1234    
  • cin.get(cstring_variable,長度,終止符號)
    讀20個字符,但一讀到字符5就停下,字符5並不會被讀入。
char c[20]; cin.get(c,20,'5'); cout<<c;

Input:

1234567890

Output:

1234    

將cstring轉換成數字

需使用cstdlib 函式庫,若我們想將cstring的變數轉換成數字,會用到以下三種作法,差別在資料型態的不同:

  • atoi
    cstring-to-int
char c[20]="123456"; int num = atoi(c); cout<<num;
  • atol
    cstring-to-long
    如果數字太大難以用int來儲存,那你可以考慮使用long。

  • atof
    cstring-to-double

double y = atof("12.37");

-

String

string可直接透過+來串接,+號左方的字串會在前面,+號右方的字串會在後面。

string s,hi="hi",everyone="everyone"; s=hi+" "+everyone; cout<<s;

Output:

hi everyone

但如果將+號左右變數的位置對調

string s,hi="hi",everyone="everyone"; s=everyone+" "+hi; cout<<s;

Output:

everyone hi

我們可以把string想成是由多個char array組成的,因此string可以像陣列那般用index來取值。

範例

string s="Hi"; cout<<s[0]<<endl<<s[1];

Output:

H
i

string輸入法

cin

在string用cin大家應該很熟悉,cin會在遇到空格、換行、tab時停下

getline

可以讀空格,遇到換行跟tab會停下。
要注意的是,如果是string就要使用getline,而上面所介紹的cin.getline就專門用在cstring。

  • getline(istream& ,string_variable)
string s; getline(cin,s); cout<<s;
  • getline(istream& ,string_variable,停止符號)
    終止符號不會被讀進去。
    以下範例為輸入一字串遇到字符1就停下,且字符1不會被讀進去。
string s; getline(cin,s,'1'); cout<<s;

Input:

97531

Output:

9753

string才可使用的函示庫

需要使用到string 函式庫。該函式庫的function不能給cstring使用喔!

請務必記得引入函式庫:

#include<string>

Function名稱 說明
str[i] 取str字串的第i個值,類似陣列取值
str.at(i) 如同陣列取值str[i],取str陣列的第i個位置,at()會先檢查index是否合理,較str[i]直接取值更安全。
str.substr(position,length) 擷取position後的length個str字串。position跟index一樣,從0開始

str[i]str.at(i) 的差異

str[i] 不會做 out_of_range 的確認,str.at(i) 會,可以試著把以下兩段程式碼分別執行,觀看結果。

str.at[i]

// 我們都知道 a 的索引值不可能到 8 string a = "hello"; cout << a.at(8);

str[i]

// 我們都知道 a 的索引值不可能到 8 string a = "hello"; cout << a[8];

str.substr()

string s="123456789"; cout<<s.substr(0,3);

Output:

123

Function名稱 說明
str.length() str字串的長度
str.empty() 回傳bool值。檢查str這個字串是否為空字串,是空字串回傳true,不是空字串則回傳false
str.insert(pos,str2) 插入str2字串在str的第pos個位置
string num="1234",s="abcd"; cout<<num.insert(0,s);

Output:

abcd1234

Function名稱 說明
str.erase(pos,length) 從str字串的第pos個位置移除length個
string num="1234"; cout<<num.erase(0,2);

Output:

34

Function名稱 說明
str.find(str1) 在str字串找str1這個字串的位置,如果有找到則會回傳位置,位置的初始為0。如果沒有找到,則會回傳最大整數string::npos
str.find(str1,pos) 和str.find(str1)的功能相同,差在是從第pos個位置開始尋找
  • 找到的情況
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
string s="hello"; cout<<s.find("h");

Output:

0
  • 沒找到的情況
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
string s="hello"; cout<<s.find("0");

Output:

18446744073709551615
  • 重複的字一律以前面的優先
string s="hello"; cout<<s.find("l");

Output:

2

索引值(index) 為 2 的位置是第一個 l 所在的位置,那如果想要找第二個 l 怎麼辦?,必須從第一個 l 的索引值加 1 開始往下搜尋 l 這個字。像下面這樣。

int main(){ string s="hello"; int pos = 0; pos = s.find("l"); cout << pos << endl; // the first `l` pos = s.find("l", pos+1); cout << pos; // the second 'l' return 0; }

Output:

2
3

Function名稱 說明
str.find_first_of(str1) str1最先出現在str字串的位置
str.find_first_of(str1,pos) 從第pos個位置開始找,str1最先出現在str字串的位置

雖然l有兩個,但只會回傳最先找到的位置。
s[0]=h s[1]=e s[2]=l s[3]=l

string s="hello"; cout << s.find_first_of("l"); // 印出 2 cout << s.find_first_of("l", 1); // 印出 2 cout << s.find_first_of("l", 2); // 印出 2 cout << s.find_first_of("l", 3); // 印出 3 cout << s.find_first_of("l", 4); // 印出 string::npos

Function名稱 說明
str.find_first_not_of(str1) 最先出現不是str1字串在str字串的位置
str.find_first_not_of(str1,pos) 從第pos個位置開始找,回傳最先出現不是str1字串在str字串的位置

最先出現不是a的位置是最左邊的b,位置是2。

s[0]=a s[1]=a s[2]=b

string s="aabbbcc"; cout<<s.find_first_not_of("a");

Output:

2

如何將string 轉換成cstring

  • string_variable.c_str();
s.c_str()

可將s這個string型態的變數轉成cstring

以下範例為: 先將s這個string字串轉換成cstring後再複製(等同於assign功能)給cs這個cstring,最後輸出cs。

#include <iostream> #include <string> #include <cstring> using namespace std; int main(){ string s="aabbbcc"; char cs[]="123456"; strcpy(cs,s.c_str()); cout<<cs; return 0; }

Output:

aabbbcc

[超級重要!]cstring跟string差異

作法 cstring String
assign strcpy() =
輸入法 cin、cin.getline()、cin.get() cin、getline()、gets()
串接 strcat() +
比較字串是否相等 strcmp() ==
回傳長度 strlen() string.length()
轉換型態 cstring-to-整數: atoi、atof、atol string-to-cstring: string.c_str()

[期中/期末考必考觀念!]字串產生的緩衝區問題

cin 與 getline 交互使用產生的緩衝區問題

學到字串這一章必須和大家討論的問題之一就是非常有名的「緩衝區」問題。在了解緩衝區之前我們先看一個例子。

int main(){ string a = "", b = ""; cin >> a; // 假設我們「想」輸入 hello 後按下 enter cout << "string a: " << a << endl; getline(cin, b); // 假設我們「想」輸入 world 後按下 enter cout << "string b: " << b << endl; return 0; }

閱讀程式碼,我們「應該」會印出如下內容。

string a: hello
string b: world

但很難過的,程式碼輸出如下。

相信非常令人納悶,這是為甚麼呢?
原因就是因為緩衝區內的內容讓我們沒有輸入字串進字串 b

解釋緩衝區

在講到問題的關鍵之前,請仔細閱讀程式碼,第 3 行使用 cin 來做字串輸入,第 5 行使用 getline() 來做字串輸入。

而透過上面的筆記我們都知道 cin 沒有辦法讀入空白、換行等符號,所以當我們第一次輸入 hello 後按下 enter,真的被存進字串 a 的內容是 hello,而 enter 我們都知道它代表 \n 換行字串,他其實也是字串,因為 cin 不吃,所以 \n 就被丟到了所謂的緩衝區去了。

意思就是說,對於沒有讀到的字串,他不會不見,而是會被丟去緩衝區佔存起來,直到有人來把它拿走。

接著第 5 行又要執行輸入了,而 getline 可以接收空白字串,這個時候按照一般邏輯,我們應該可以打字輸入字串 b,但其實在輸入前程式會先去看緩衝區內有沒有東西,此時的緩衝區有一個 \n 符號,所以這個換行符號會直接被 getline 先做讀入,因此我們就沒辦法輸入字串了,因為已經執行 \n。
照常理我們都知道,做字串輸入的時候一旦按下 enter 表示輸入結束,所以這個緩衝區的換行符號直接做了 enter,所以字串 b 我們根本沒有輸入。

根據上面的說明,此時的字串 b 裡面存了甚麼呢?
答案是甚麼都沒有存。

解決緩衝區問題

那針對這個問題怎麼解決呢?我們必須使用 cin.ignore() 這個 function 來清空緩衝區,對於上面的例子,我們只需在 cin 結束後下一行,先清空緩衝區即可。

int main(){ string a = "", b = ""; cin >> a; // 我們輸入 hello 後按下 enter cout << "string a: " << a << endl; cin.ignore(); // 清空緩衝區內的 \n 符號 getline(cin, b); // 我們輸入 world 後按下 enter cout << "string b: " << b << endl; return 0; }

所以針對緩衝區的問題,只要 cin 與 getline 交互使用(一定是 cin 先,再 getline),就一定要來清空緩衝區,否則會有輸入上的問題。

比較奇特處理緩衝區字串的方法

利用 cin.get()

int main(){ string a = "", b = ""; cin >> a; // 我們輸入 hello 後按下 enter cout << "string a: " << a << endl; cin.get(); // cin.get() 會取一個字元,此時緩衝區是一個 \n // 所以會把 \n 取走 getline(cin, b); // 我們輸入 world 後按下 enter cout << "string b: " << b << endl; return 0; }

多 getline 一次

int main(){ string a = "", b = ""; cin >> a; // 我們輸入 hello 後按下 enter cout << "string a: " << a << endl; getline(cin, b); // 因為 getline 可以取走緩衝區的 \n getline(cin, b); // 我們輸入 world 後按下 enter cout << "string b: " << b << endl; return 0; }

更加認識 getline()

想必 \n 造成的緩衝區問題還是困擾著你們,想必有人會問,那如果連續 getline 兩次,第二個 getline 不是仍舊會吃到第一個字串輸入時的 \n 嗎?

答案是不會,原因是因為 getline 預設是以 \n 作為終止字符,而 getline 的終止字符是不會被放進緩衝區的,他會直接被捨棄,但是 cin 會後按下 enter 的 \n 會被放進緩衝區。

所以 getline 其實有一個 overloading function
getline(輸入類型, 輸入字串變數, 終止字符)
預設的 getline 他的終止字符是 \n(字元型別),所以我們每次作如下的輸入時,
getline(cin, a) 其實他都是 getline(cin , a, '\n')

那至於你問為什麼 getline 的終止字符不會被放進緩衝區,但 cin 會呢? 這就跟 C++ 背後的運作邏輯有關,我們就不討論。


Reference