20230318

盧冠綸 @ Sprout 2023

字元&C-style字串

在這堂課,你會學到...

        字元、字元陣列的概念與用法

        C-style 字串的相關函式

在以後,你會學到...

        C++ 的 string 的用法(敬請期待二階課程!)

在上堂課,你已經學到...

        一維陣列的概念與用法

字元

78 69 86 69 82 71 79 78 78 65 71 73 86 69 89 79 85 85 80

字元的種類

數字:'0'、'1'、'2'、'3'...

字母:'a'、'B'、'c'、'D'...

簡單符號:','、'.'、'@'、'!'、';'...

其他:'\n'、'\t'、'\''、'\\'...

(複習:跳脫字元)

char a;
a = 'x';
char b = 'A';

字元的內容,要用' '括起來。

宣告與賦值

來猜猜以下這些程式的輸出?

#include <iostream>

int main(){
    char a = 'A';
    std::cout << a;
}
#include <iostream>

int main(){
    char a = 'A';
    std::cout << 'a';
}
#include <iostream>

int main(){
    char a = 'A';
    std::cout << "a";
}
#include <iostream>

int main(){
    char a = 65;
    std::cout << a;
}

公佈答案:

#include <iostream>

int main(){
    char a = 'A';
    std::cout << a;
}
#include <iostream>

int main(){
    char a = 'A';
    std::cout << 'a';
}
#include <iostream>

int main(){
    char a = 'A';
    std::cout << "a";
}
#include <iostream>

int main(){
    char a = 65;
    std::cout << a;
}
a
A
a
A

右下角那個是發生什麼事?

其實字元同時就是整數(雖然字元只有 1 個 byte)。

每個字元都可以用整數來寫。我們稱之為 ASCII Code。

#include <iostream>

int main(){
    if ('A' == 65){
        std::cout << "\'A\' == 65" << std::endl;
    }else{
        std::cout << "\'A\' != 65" << std::endl;
    }
}
'A' == 65

ASCII Code

這裡附上一個小程式給大家玩玩。

#include <iostream>

int main(){
    std::cout << 'A' << " is " << (int)'A' << std::endl;
    std::cout << 'a' << " is " << (int)'a' << std::endl;
    std::cout << '0' << " is " << (int)'0' << std::endl;
    std::cout << '.' << " is " << (int)'.' << std::endl;
}
A is 65
a is 97
0 is 48
. is 46

大家應該有注意到:'0' != 0。

(等等會再細講關於 0 的部分)

既然剛剛說字元也是整數...那當然也可以用來做運算!

#include <iostream>

int main(){
    char b = 'b';
    std::cout << (char)(b  ) << " is " << (int)(b  ) << std::endl;
    std::cout << (char)(b-1) << " is " << (int)(b-1) << std::endl;
    std::cout << (char)(b+1) << " is " << (int)(b+1) << std::endl;
}
b=98
a=97
c=99

字元的運算

ASCII Table

關於 ASCII Table

其實就是字元與整數的對應表(不用特別記)。

幾個可以知道的:

'A' == 65, 'B' == 66, ... 依此類推

'a' == 97, 'b' == 98, ... 依此類推

'0' == 48, '1' == 49, ... 依此類推

'\0' == 0。等等會介紹 '\0' 的功能。

C-style 字串

字元陣列的用法

複習:陣列的初始化

字元陣列的初始化

基本做法:

char s[7] = {'s', 'p', 'r', 'o', 'u', 't'};

比較快的做法:

char s[7] = "sprout";

大家可以發現:字串是用 " " 來包。

                           字元是用 ' '  來包。

關於 '\0'

char s[7] = {'s', 'p', 'r', 'o', 'u', 't'};
char s[7] = "sprout";

'\0'(也就是正整數的 0),代表的是字串的結束!

宣告字元陣列時,要預留 '\0' 的空間!

字串的輸出

#include <iostream>

int main(){
    char s[7] = {'s', 'p', 'r', 'o', 'u', 't', '\0'};
    std::cout << s << std::endl;
}
sprout

'\0' 很重要!!在 cout 字串的時候,

程式會一直印,直到遇到 '\0' 才停止。

給大家猜猜看,以下幾個程式會輸出什麼?

#include <iostream>

int main(){
    char s[7] = {'s', 'p', 'r', '\0', 'u', 't', '\0'};
    std::cout << s << std::endl;
}
#include <iostream>

int main(){
    char s[7] = {'s', 'p', 'r', 'o', 'u', 't'};
    std::cout << s << std::endl;
}
#include <iostream>

int main(){
    char s[6] = {'s', 'p', 'r', 'o', 'u', 't'};
    std::cout << s << std::endl;
}

答案:

#include <iostream>

int main(){
    char s[7] = {'s', 'p', 'r', '\0', 'u', 't', '\0'};
    std::cout << s << std::endl;
}
#include <iostream>

int main(){
    char s[7] = {'s', 'p', 'r', 'o', 'u', 't'};
    std::cout << s << std::endl;
}
#include <iostream>

int main(){
    char s[6] = {'s', 'p', 'r', 'o', 'u', 't'};
    std::cout << s << std::endl;
}
spr
sprout

我也不知道

Tips

經過剛剛的實驗可以知道,'\0' 非常重要!

 

所以,開字元陣列的時候,一定要預留 '\0' 的位置,以避免各種奇怪的問題。

#include <iostream>

int main(){
    char s[7] = {'s', 'p', 'r', 'o', 'u', 't'};
    std::cout << s << std::endl;
}

字串的輸入

字串的輸入是以 ' ' 或 '\t' 或 '\n' 為分界。

#include <iostream>

int main(){
    char s[20];
    std::cin >> s;
    std::cout << s << std::endl;
}

以上圖為例。如果輸入是 Hello World。

那輸出就是 Hello。

字串的輸入

#include <iostream>

int main(){
    char s[20], t[20];
    std::cin >> s >> t;
    std::cout << s << std::endl;
    std::cout << t << std::endl;
}

再一例。如果輸入是 Hello World。

那輸出就是 Hello

                    World

字串的輸入

補充:如果我就是希望把 "Hello World" 整個輸進去:

#include <iostream>

int main(){
    char s[20];
    std::cin.getline(s, 20);
    std::cout << s << std::endl;
}

以上圖為例。如果輸入是 Hello World。

那輸出就是 Hello World。

小練習

參考架構

#include <iostream>

int main(){
    // 宣告字串
    // 輸入字串
    // for (int i=0 ; 字串還沒結束 ; i++){
        // 調整字串的第 i 個字母
    // }
    // std::cout << 字串 << std::endl;
}

參考提示

問:我要怎麼知道我的迴圈要執行到哪?

答:看到 '\0' 就表示字串結束了。

問:要怎麼把字母往後平移 3 個字母?

答:利用 ASCII Code。

這題最後要換行。

參考答案

#include <iostream>

int main(){
    char s[505];
    std::cin >> s;
    for (int i=0 ; s[i] != '\0' ; i++){
        if ((s[i] >= 'a') && (s[i] <= 'w')){
            s[i] = s[i] + 3;
        }else{
            s[i] = s[i] + 3 - 26;
        }
    }
    std::cout << s << std::endl;
}

字元陣列相關函式

#include <cstring>

接下來要介紹幾個字元陣列的小工具(相關函式)

學會的話可以幫你省很多時間。

重要:記得 #include <cstring>。

            否則在某些地方(如neoj)會 compile error。

strlen(str)

求出字串長度

strlen(str)。

       str 是你想求長度的字串。

#include <iostream>
#include <cstring>

int main(){
    char s[20] = "Sprout";
    std::cout << strlen(s) << std::endl;
}
6

用法

Q:如果不用 strlen 的話,我們都怎麼算字串長度?

A:從字串開頭開始數,直到遇到 '\0'。

#include <iostream>

int main(){
    char s[20] = "Sprout";
    int count = 0;
    for (int i=0 ; s[i]!='\0' ; i++){
        count ++;
    }
    std::cout << count << std::endl;
}
6

實際上,strlen 本身就是這樣運算的。所以...

小提示

不要把 strlen 放進 for 迴圈裡面!

// This is bad
for (int i=0 ; i<strlen(s) ; i++){
    // do something
}

用下面這種寫法比較省時間。(想想看為什麼?)

// This is good
int length = strlen(s);
for (int i=0 ; i<length ; i++){
    // do something
}

strcmp(str1, str2)

比較字串的字典序

用法

strcmp(str1 ,str2)。

       str1 和 str2 是你想比較的字串。

回傳值的意思:

       0:表示 str1 和 str2 內容一模一樣。

       >0:表示 str1 的字典序 ASCII 比 str2 大。

       <0:表示 str1 的字典序 ASCII 比 str2 小。

舉例

#include <iostream>
#include <cstring>

int main(){
    char a[20] = "abcde";
    char b[20] = "bcdef";
    char B[20] = "bcdef";
    std::cout << strcmp(a,b) << std::endl;
    std::cout << strcmp(b,a) << std::endl;
    std::cout << strcmp(b,B) << std::endl;
}
-1
1
0

小提示

當你想比較 str1 和 str2 內容是否一樣,

你可以:

if (strcmp(str1, str2) == 0){
    // do something
}

你不可以:

if (str1 == str2){
    // do something
}

記得別用 == 來比較兩個字元陣列==

(程式可能出錯)

strcpy(dest, src)

把 src 的內容複製到 dest

用法

strcpy(dest ,src)。

       把 src 的內容複製到 dest。

#include <iostream>
#include <cstring>

int main(){
    char a[20] = "oatmeal", b[20] = "super big bento";
    std::cout << "Before strcpy, a is " << a << std::endl;
    strcpy(a, b);
    std::cout << "After  strcpy, a is " << a << std::endl;
}
Before strcpy, a is oatmeal
After  strcpy, a is super big bento

小提示

你可以:

strcpy(dest, src);

你不可以:

dest = src;

(這應該會不能編譯。)

在使用 strcpy 時,記得也為 dest 預留 '\0' 的空間哦!

strcat(dest, src)

把 src 的內容接到 dest 之後

用法

strcat(dest ,src)。

       把 src 的內容接到 dest 之後。

#include <iostream>
#include <cstring>

int main(){
    char a[20] = "yo! ", b[20] = "battle";
    std::cout << "Before strcat, a is " << a << std::endl;
    strcat(a, b);
    std::cout << "After  strcat, a is " << a << std::endl;
}
Before strcat, a is yo! 
After  strcat, a is yo! battle

小提示

你可以:

strcat(dest, src);

你不可以:

dest += src;

(這應該會不能編譯。)

在使用 strcat 時,記得也為 dest 預留 '\0' 的空間哦!

其他字串相關函式

strncmp、strncpy、strncat

strncmp(str1, str2, n)

strncmp(str1, str2, n):

    比較 str1 和 str2 這兩個字串的前 n 個字元。

    0:表示兩者前 n 個字元內容一模一樣。

    >0:表示 str1 的前 n 個字元的字典序比較大。

    <0:表示 str1 的前 n 個字元的字典序比較小

#include <iostream>
#include <cstring>

int main(){
    char str1[20] = "Sprout 2022";
    char str2[20] = "Sprout 2023";
    std::cout << strcmp(str1, str2) << std::endl;
    std::cout << strncmp(str1, str2, 6) << std::endl;
    std::cout << strncmp(str1, str2, 11) << std::endl;
}
-1
0
-1

strncmp(str1, str2, n)

strncpy(dest, src, n)

strncpy(dest, src, n):

    把 src 的前 n 個字元複製給 dest。

 

    對,沒錯,只有前 n 個字元,他不會幫你複製 '\0'。

strncpy(dest, src, n)

#include <iostream>
#include <cstring>

int main(){
    char a[20] = "Sprout ", b[20] = "2023";
    std::cout << "a is " << a << std::endl;
    strncpy(a, b, 2);
    std::cout << "a is " << a << std::endl;
}
a is Sprout 
a is 20rout

strncpy(dest, src, count)

所以使用 strncpy 時,如果需要,記得自己手動補 '\0'。 

#include <iostream>

int main(){
    char a[20] = "Sprout ", b[20] = "2023";
    std::cout << "a is " << a << std::endl;
    strncpy(a, b, 2);
    a[2] = '\0';
    std::cout << "a is " << a << std::endl;
}
a is Sprout 
a is 20

strncat(dest, src, n)

strncat(dest, src, n):

    把 src 的前 n 個字元接在 dest 之後。

strncat(dest, src, n)

#include <iostream>
#include <cstring>

int main(){
    char a[20] = "Sprout ", b[20] = "2022";
    std::cout << "a is " << a << std::endl;
    strncat(a, b, 2);
    std::cout << "a is " << a << std::endl;
}
a is Sprout 
a is Sprout 20

小練習

可行的思路

思路一:直接用 strncpy 和 strncat

思路二:用迴圈,一個一個字元來處理。

參考答案

思路一:

#include <iostream>
#include <cstring>

int main(){
    char a[105], b[105], ans[205];
    std::cin >> a >> b;
    int m, n;
    std::cin >> m >> n;
    
    strncpy(ans, a, m);
    ans[m] = '\0';
    strncat(ans, b, n);
    
    std::cout << ans << std::endl;
    std::cout << ans << std::endl;
    std::cout << ans << std::endl;
}

小練習

思路二:

#include <iostream>

int main(){
    char a[105], b[105], ans[205];
    std::cin >> a >> b;
    int m, n;
    std::cin >> m >> n;
    
    for (int i=0 ; i<m ; i++){
        ans[i] = a[i];
    }
    for (int i=0 ; i<n ; i++){
        ans[m+i] = b[i];
    }
    ans[m+n] = '\0';
    
    std::cout << ans << std::endl;
    std::cout << ans << std::endl;
    std::cout << ans << std::endl;
}

謝謝大家

20230318 字元&C-style字串

By allen522019

20230318 字元&C-style字串

  • 621