字串是由字元組成的。
指標的應用:字串
char
char 的使用:
如果要使用 character literal,需要用單引號 (single quotation marks)
int main()
{
char c = '0';
cout << static_cast<int>(c) << " ";
c = 'A'
cout << static_cast<int>(c) << " ";
c = '\n'
cout << static_cast<int>(c) << " ";
return 0;
}
就跟上面講得一樣,因為 char 其實就是用整數來儲存符號數字等等,所以你把它 cast 成整數的時候就會變成一個數字 (也就是 ASCII code)。
可以對 char 做加減:
int main()
{
char c = 48;
cout << c << " "; // 0
c += 10;
cout << c << " "; // :
if (c > 50)
cout << c << " "; // :
return 0;
}
第一個例子,如果當使用者輸入 'Y'或是'y' 就去執行,不是的話就不執行(重來)
int main()
{
int a = 0, b = 0;
char c = 0;
do
{
cout << "Enter 2 integers: ";
cin << a << b;
cout << "Add? ";
cin << c;
} while (c != 'Y' && c != 'y');
cout << a + b << "\n";
return 0;
}
可以讓英文字母的大小寫互換
// ASCII code:
// A : 65 -> B : 66 .... Z : 90
// a : 97 -> b : 98 ....
int main()
{
char c = 0;
while(cin >> c)
{
if (c >= 65 && c <= 90)
cout << static_cast<char>(c + 32);
else
cout << c;
cout << "\n";
}
return 0;
}
但你怎麼可能會隨時記得 ASCII code 是甚麼啦,所以比較好的做法是使用 standard library : ,裡面就有很多函數可以幫你轉換:
int islower(int c);
: 小寫的話: return 0;大寫: return nonzero。int isupper(int c);
: 大寫的話: return 0;小寫: return nonzero 。int tolower(int c);
: 轉換成小寫的數字(ASCII code)。int toupper(int c);
: 轉換成大寫的數字(ASCII code)。為什麼 return int : 因為在 C 語言的時候就是這樣,因此沿用。
int main()
{
cout << " 0123456789\n";
for (int i = 30; i <= 126; i++)
{
if (i % 10 == 0)
cout << setw(2) << i / 10 << " "; //setw(2) 控制欄位有兩格
if (isprint(i))
cout << static_cast<char>(i);
else
cout << " ";
if (i % 10 == 9)
cout << endl;
}
return 0;
}
C strings - basics
String literal
在C語言裡面,有兩種 string:
但是 C string 會使用到 pointer 的概念,可以加強我們對於 pointer 的使用經驗。
其實我們在很早很早以前,就已經用過 string 了
就是我們在用 cout << "Hello World!";
的時候,雙引號中間這段東西,其實就是 string。
character array 因為也是 array,所以也可以像 array 一樣 initialize
```cpp
char s[10] = {0};
char t[10] = {'a', 'b', 'c'};
```
e.g.,
這段程式可以會讓你輸入,等到輸入'#'後或是輸入10個字元後就停止
```cpp
const int LEN = 10;
int main()
{
char s[LEN] = {0};
int n = 0;
do
{
cin >> s[n];
n++;
}while (s[n - 1] != '#' && n < LEN);
for (int i = 0; i < n; i++)
cout << s[i];
return 0;
}
```
要怎麼 input string?
在C語言中,他們把存在 char array 裡面的 operation overloading(就是可以做出更多事,跟function 的 overloading 頗像)。
特別是 cin >>
& cout <<
你可以直接 cin 一個 char array,
char str[10];
cin >> str; // if we type "abcde"
cout << str[0]; //a
cout << str[3]; //d
或是可以直接 cout 一個 char array
int value[5];
cout << value; //an address
char str[10] = {0};
cin << str; // 如果我們打 "abcde"
cout << str; // 就會跑出 abcde
但是! 為什麼我們只打了 5 個 char,且 cout 出來也真的是 5 個? 可是我們原本宣告的陣列有 10 項欸,那我們怎麼知道這段陣列後面的是甚麼?
The null character: \0
當我們在 cin >> 的時候,當輸入到最後一項,電腦會自動在最後附加上一個 null character(\0 = 斜線零),來表示你的 cin 結束了。
null character 的 ASCII code 就是 0。
這表示 null character 本質上也會佔一個空間, 因此你如果宣告了長度是 n 的 array,你最多只能存入 (n- 1)個 char。
C string 的 initialization ,可以寫成這樣
char s[100]
= "abc";
這是因為 = 這個 operator 也被 overloaded 過了。(未來會講)
也同樣的,在你宣告成 "abc" 的同時,電腦也會自動在最後加上 \0,以表示你宣告的東西就是這麼多。
char a[100] = "abcde FGH";
cout << a << "\n"; //abcde FGH
char b[100] = "abcde\0FGH";
cout << b << "\n";
所以當我們做這件事的時候,會發現在 b[] 裡面,印完 abcde 就沒了,這就是因為電腦認為你印到 e就結束了。
那 C string 的initialization 也可以寫成這樣:
char s[100] = {'A', 'g', 'd'};
但是這個時候,null character 卻不會被加到最後一項的後面。
這就是這兩種 宣告方式的不同了。
那講了那麼多,到底甚麼時候會 append null character呢?
舉幾個例子好了:
String assignments:
Assignment with double quotations are allowed only for Initialization
" " 的宣告,只能在 initialization 的時候做。
char s[100];
s = "this is a string"; // compilation error
但是如果是個別的做的話,就可以!
char s[100];
s[0] = 'A';
s[10] = 'B';
char c[100] = {0};
cin >> c; // 123456789
cin >> c;// abcde
cout << c << "\n"; // "abcde"
c[5] = '*';
cout << c << "\n"; // abcde*789
這個的原因是甚麼呢?
我們可以用圖表示:
【Cin 表示圖】
第幾次 cin or cout | c[0] | c[1] | c[2] | c[3] | c[4] | c[5] | c[6] | c[7] | c[8] | c[9] |
---|---|---|---|---|---|---|---|---|---|---|
first | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0... |
second | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | \0 |
third | a | b | c | d | e | \0 | 7 | 8 | 9 | \0 |
fourth | a | b | c | d | e | * | 7 | 8 | 9 | \0 |
因為 在輸入 abcde 的時候,它最後面也輸入了 \0,因此我們第三個cout 就只有印出 abcde ,但是我們在 assign * 給 s[5] 之後,\0消失了,所以就可以印出後面的東西了。
那如果我們輸入超出一個 array 的東西的話,則可能會有 error
char a[5] = {0};
cin >> a; //"123456789"
cout << //"123456789" or an error
所以可以知道 cout << 不會理你這個 array 裡面有多少東西,只會一直印,直到碰到 \0。
有個奇怪的例子:
char a1[100];
cin >> a1; // "this is an apple"
cout << a1; // "this"
為什麼只會輸出 this 呢?
還記得我們以前 cin 的時候,電腦會根據空白來切開我們輸入的不同的數字。
所以同理,輸入"this is an apple"的時候,就會只輸入 this 了!
那在 C++ 裡面,正確的使用是這個樣子:
char a[100];
cin.getline(a, 100); // Hi, this is John Cena
cout << a << "\n"; // Hi, this is John Cena
可以先把 cin.getline() 想像成一個函數,且在切換字元方面,它是依照換行來區別不同的東西。
(a, 100) → a 就是要傳入的陣列、100就是你想輸入的數量。
e.g.,
今天我們的陣列裡面有 100 項,我們要輸入一串字,讓這個程式數我們的字中到底有幾個 space (空白)
char a[100] = "abcde FGH";
while (cin.getline(a, 100))
{
int i = 0;
int spaceCount = 0;
while (a[i] != '\0')
{
// 如果多個space只算一個的話
//if (a[i] == ' ' && a[i - 1] == ' ')
//{
// i++;
// continue;
//}
if (a[i] == ' ')
spaceCount ++;
i++;
}
cout << spaceCount << "\n";
}
String array
如果你今天需要儲存很多個 string 的話,這時候就必須用一個二位陣列來儲存這麼多個 char array。
char name[4][10] = {"John", "Mikasa", "Eren", "Armin"};
cout << name << "\n"; // an address
cout << name[1] << "\n"; // Mikasa
cin >> name[2]; // Obama
cout << name[2][0] << "\n"; // O
可以想像成:
第幾個 array | [][0] | [][1] | [][2] | [][3] | [][4] | [][5] | [][6] | [][7] | [][8] | [][9] |
---|---|---|---|---|---|---|---|---|---|---|
0 | J | o | h | n | \0 | ... | ||||
1 | M | i | k | a | s | a | \0 | ... | ||
2 | E | r | e | n | \0 | ... | ||||
3 | A | r | m | i | n | \0 | ... |
C strings as character pointers
pointer 是可以指向一個 array 的,所以可以利用 character pointer 來使用 C string:
char a[100] = "12345";
char* p = s;
cout << p << "\n";
cin >> p; // or s
cout << s; // or p
因為 pointer 只是指向一個地址,所以我們必須要先宣告一個 array(配置記憶體),才能使用 pointer。
但是如果你 initialize pointer,就可以使用了! (但是這段空間只能讀不能做更改或是其他的宣告)
char* p = "12345";
cout << p + 2 << "\n"; // 345
Passing a string to a function?
要怎麼把 string 傳入 function 裡面? 其實很簡單,把你的 parameter 改成 pointer 或是 char array 就好了!
#include<iostream>
#include<cstring>
using namespace std;
void reverse(char p[]);
void print(char* p);
int main()
{
char s[100] = "12345";
reverse(&s[1]);
print(s);
return 0;
}
void reverse(char p[])
{
int n = strlen(p); // string 的長度
char* temp = new char[n];
for (int i = 0; i < n; i++)
temp[i] = p[n - i - 1];
for (int i = 0; i < n; i++)
p[i] = temp[i];
delete [] temp; // 把空間釋放(避免 memory leak)
}
void print(char* p)
{
cout << p << "\n";
}
我們想要寫一個程式,可以讓我們傳入的 string 可以反轉過來。
而且我們傳入的時候不一定要傳入 哪個位置,所以當然我們也可以傳 &s[1] (也就是第二項 2),這樣的話,結果就會顯示 15432 ,那如果改成 &s[2], 就會變成 12543 這樣!
小提醒: 你也可以用指標來宣告一段 "string",例如:
char* ptr = "12345";
這個時候,記憶體的操作就是把一塊空間空出來給你 放 "12345"
,但是你其實不知道在哪裡,接著再把 ptr 指在 "1" 上面。如此這般你就可以用 ptr 來讀取這段 string 了。
Main function arguments
如果你在 sublime 或是 visual studio 上面打 int ,他會給你選擇,如果你選擇的畫,他就會跑出:
int main(int argc, char* argv[])
{
}
你可能會發現 main function 裡面竟然有 argument,真的太奇葩了吧。
我原本根本搞不懂這個在幹嘛,所以就一直不敢用他。
在C++ 的 main function 裡面,只能傳入這兩個 argument
那我們來寫一個跟這些有關的程式:
int main(int argc, char* argv[])
{
for (int i = 0; i < argc; i++)
{
cout << argv[i] << "\n";
}
return 0
}
但是寫完如果你按編譯,他其實不會有任何的反應,所以你必須要打開終端機(win + R → cmd),然後按 cd(change direction) 找到你在的那個檔案資料夾(我的話是 Desktop),最後選擇 Untitled1.exe(我的檔案名稱) ,並在後面加上你想加入的string: 結果會這樣顯示:
C:\Users\user\Desktop>Untitled1.exe 1 2 3 4 t y u hgh gg
Untitled1.exe
1
2
3
4
t
y
u
hgh
gg
C:\Users\user\Desktop>
就可以看到第一個印出來的是 執行檔的名稱,接著後面的就是我傳進去的東西
但實際上我們不太會用到(可能要寫作業程式的時候會用到)。
使用 c string 很方便的函數,這些函數都存在 <cstring>這個library 裡面,且有很多是 pointer-based 的 function。另外<cstdlib>裡面也有蠻多好用的函數。要查的話可以用 http://www.cplusplus.com/ 裡面可以查到很多東西。
String length query
strlen():
unsigned int strlen(const char* str);
// 這個函式不會影響你傳入的 str- | - | - | - | - | - | - | - | - | - | - |
---|
-|-|-|1|2|3|4|5|\0|-|-
-|-|-|↑str|-|-|-|-|-|-|-
就像是這樣
E.g.
char* p = new char[100];
cin >> p;
cout << strlen(p);
p[3] = '\0';
cout << strlen(p + 1) << "\n";
delete [] p;
結果會 cout 2
,原因是這樣的:
如果我們原本輸入12345,就會自動在後面加上\0
1 2 3 4 5 \0
但我們之後把 p[3] 改成 '\0' 之後,就會變成:
1 2 3 \0 5 \0
所以當 strlen()
在偵測的時候就會發現 從 p+1 跑到 \0 只有兩個,所以就會顯示 2 了!
簡單說,strlen()
就是把你覺得是 string 的東西丟進去就可以數他的長度。所以像上面,若你把一個指標丟給它,它也會幫你數。
sizeof() 跟 strlen() 的區別
char* p = "12345";
cout << strlen(p) << "\n"; //
char a[100] = "1234567890";
cout << strlen(a) << "\n"; //
cout << sizeof(a) << "\n"; //
cout << sizeof(a + 2) << "\n"; // 8 -> 因為sizeof 偵測的是大小 a+2 會被認為是一個指標
應用:
還記得上面寫的找空格的程式嗎?其實有了 strlen(),我們就可以把 while 改成 for loop:
char a[100] = "abcde FGH";
while (cin.getline(a, 100))
{
int i = 0;
int spaceCount = 0;
for(int i = 0; i < strlen(a); i++)
{
if (a[i] == ' '
spacecount++;
}
cout << spaceCount << "\n";
}
Likewise.
searching in a string
strchr()
char* strchr(char* str, int character)
(給我一個 character,找看看有沒有存在 str 裡面,有的話就回傳他的位置,再加上心號,就會變成一串字串)char a[100] = "1234567890";
char* p = strchr(a, '8');
if (strchr(a, 'a') == nullptr)
cout << "!!!\n";
cout << strchr(a, '4') << "\n";
cout << strchr(a, '4') - a;
結果會顯示:
!!!
4567890
: 這是因為函式回傳 4 的地址,再加上*就會變成它後面的一串字串了。
3
:因為 4 與 1 差了三格
如果想要驗證的話可以用下面這段:
char* ptr = "1234";
cout << ptr << "\n";//1234
cout << ptr + 1 ; //234
cout 一個指向string的指標的話,就會 cout 它後面剩下的 string。
char a[100] = "This is a book.";
char* p = strchr(a, ' '); //指向第一個space
while(p != nullptr)
{
*p = '_'; //把space 換成underline
p = strchr(p, ' '); // 由上一個space當作起點找下一個
}
cout << a;
searching for a substring:
strstr():
char* strshr(char* str1, const char* str2)
傳入兩個 stringchar a[100] = "this is a book";
char* p = strstr(a, "is");
while(p != nullptr)
{
*p = 'I'; // 並不是 p = "IS" 這樣會變成 IS\0 而且 p 會完全被改變
*(p + 1) = 'S';
p = strstr(p, "is");
}
cout << a;
String-number conversion
簡單說就是把 string 中的數字轉換成 真的數字。
在cstdlib裡面有兩個:
int atoi(const char* str);
array to integer
double atof(const char* str);
array to float
char a[100] = "1234";
cout << atoi(a) * 2 << endl;
char b[100] = "-12.34";
cout << atof(b) * 2 << endl;
結果會跑出:
2468
-24.68
char* itoa(int value, char*str, int base);
integer to array
e.g.
char a[100] = {0};
itoa(123, a, 2);
cout << a << endl;
itoa(123, a, 10)
cout << a[2] << " " << a << endl;
String comparisons
功能:可以把 character 拿來排序,就是比較每個字元的 ASCII code
呼叫:
int strcmp(const char* str1, const char* str2);
int strcmp(const char* str1, const char* str2, unsigned int num);
回傳:
使用:
char a[100] = "the";
char b[100] = "they";
char c[100] = "them";
cout << strcmp(a, b) << endl;
cout << strcmp(b, c) << " " << strcmp(b, c, 2); //後面的2就是比較前兩個 char
String copy
功能:可以直接取代一串 substring(也就是改了很多個 char)
呼叫:char* strcpy(char* dest, const char* source, unsigned int num);
(把 source 的東西 copy 到 destination); 最後的 num 則是選source前面 前 num 個 char 傳入 dest
回傳:char*
(dest 的 address)
使用:
//instance1
char a[100] = "watermelon";
char b[100] = "orange";
cout << a << "\n";
strcpy(a, b)
cout << a << "\n";
需要注意: copy 完之後,只是把原本存在上面的東西取代,但如果後來的字比較短 → 原本字串後面的東西就會留下。像是上面的 copy 之後就會變成
orange\0lon\0
接著我們想要借剛剛的程式,來換"is"
//instance2
char a[100] = "this is an apple";
char* p = strstr(a, "is");
while(p != nullptr)
{
strcpy(p, "IS");
p = strstr(p, "is")
}
cout << a;
這個時候就只會跑出 thIS,原因是因為我們做 copy 的時候,IS後面加了 \0,所以cout只會讀到 \0 就結束,如果你去 cout << a[5];
,就會發現它會跑出 剩下的 is an apple
String Concatenation
功能:把兩個字串串在一起(從\0開始把字串接上)
呼叫:char* strcat(char* dest, const char* source, unsigned int num);
(把 source 的東西 copy 到 destination);最後的 num 則是選source前面 前 num 個 char 傳入 dest
回傳:char*
(dest 的 address)
使用:
//instance1
char a[100] = "watermelon";
char b[100] = "orange";
cout << a << "\n";
strcat(a, b)
cout << a << "\n";
- |
---|
w|a|t|e|r|m|e|l|o|n|\0
o|r|a|n|g|e|\0||||
- | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
---|
w|a|t|e|r|m|e|l|o|n|\0|o|r|a|n|g|e|\0|
用圖解釋就非常的清楚了!
注意! :要準備足夠的空間,你 copy 或是 concatenation 的時候很可能會 踩到人家的地盤,也很可能會發生 run time error。因此要注意你的 destination要是一個陣列,不要只是一個 pointer 指向的空間。
sorting names alphabetically
e.g.
Before: John, Mikasa, Eren, Armin
After: Armin, Eren, John, Mikasa
strategy:
using bubble sort:
75469 → 57469 → 54769 → 54679
這樣可以確認最右邊是最大 再去排左邊 兩個兩個比較→換→比較→換
comparison: using strcmp()
swapping: using strcpy()
const int CNT = 4;
const int LEN = 10;
void swapName(char* n1, char* n2)
{
char temp[LEN] = {0};
strcpy(temp, n1);
strcpy(n1, n2);
strcpy(n2, temp);
}
int main()
{
char name[CNT][LEN] = {"John", "Mikasa", "Eren", "Armin"};
for (int i = 0; i < CNT; ++i)
for (int j = 0; j < CNT - i - 1; ++j)
if (strcmp (name[j], name[j + 1]) > 0) // 大於0代表 str1 在 str2 後面
swapName(name[j], name[j + 1]);
for (int i = 0; i < CNT; ++i)
{
cout << name[i] << " ";
}
return 0;
}
improvement :
可以 swap pointers
const int CNT = 4;
const int LEN = 10;
void swapPtr(char*& p1, char*& p2) //*&對指標參數做 call by reference
{
char* temp = p1;
p1 = p2;
p2 = temp;
}
int main()
{
char name[CNT][LEN] = {"John", "Mikasa", "Eren", "Armin"};
char* ptr[CNT] = {name[0], name[1], name[2], name[3]};
// 用指標陣列儲存 name 陣列的位置
for (int i = 0; i < CNT; ++i)
for (int j = 0; j < CNT - i - 1; ++j)
if (strcmp (ptr[j], ptr[j + 1]) > 0) // 大於0代表 str1 在 str2 後面
swapPtr(ptr[j], ptr[j + 1]);
for (int i = 0; i < CNT; ++i)
{
cout << ptr[i] << " ";
}
return 0;
}
Splitting a string into substrings
區隔東西的人: delimiters
被區隔開來的人:token
input: www.im.ntu.edu.tw/~lckung/courses/PD16
output: www im ntu edu tw ~lckung courses PD16
當然可以一個一個來切,但是比較好的方式是:
strtok():
const int CNT = 100;
const int WORD_LEN = 50;
const int SEN_LEN = 1000;
int main()
{
char url[SEN_LEN];
char delim[] = ".//";
char word[CNT][WORD_LEN] = {0};
int wordCnt = 0;
cin >> url;
char* start = strtok (url, delim);
while(start != nullptr)
{
strcpy(word[wordCnt], start);
wordCnt++;
start = strtok(nullptr, delim); // 傳nullptr ->電腦會找剛剛紀錄的那個起點,再繼續找
}
for (int i = 0; i < wordCnt; ++i)
cout << word[i] << " ";
return 0;
}
pointer 延伸出來的應用真的好多....
而且應用上往往要圖像化才清楚..
希望我可以把它用的更熟一點 ?
Better and better.