1. 程式人生 > 其它 >ACM/ICPC 語法學習筆記—字串

ACM/ICPC 語法學習筆記—字串

定義

字串常量是由一對雙括號引起的字元序列。例如"C language"、"student"、"123"等都是合法的字串常量。

字串常量和字元常量的區別:

  1. 字元常量由單引號括起來,字串常量由雙括號括起來。 'C' "China"

  2. 字元常量只能是單個字元,字串常量則可以含零個或多個字元。

  3. 可以把一個字元常量賦予一個字元變數,但不能把一個字串常量賦予一個字元變數。在C語言中沒有相應的字串變數,但是可以用一個字元陣列來存放一個字串常量。Str[6] = "China";

  4. 字元常量佔一個位元組的記憶體空間。字串不像其他資料型別具有固定的長度,不用字串是不等長的,因此,字串的儲存不光需要儲存其起始位置,還應該記載其結束位置。字串常量佔的記憶體位元組數等於字串中字元數加1,增加的一個位元組中存放字元'\0'

    (ASCII碼為0),這是字串結束的標誌。如字串常量"China"中實際上有六個字元,分別是'C''h''i''n''a''\0'

    注:字元常量'A'和字串常量"A"雖然都只有一個字元,但是在記憶體中的情況不同。

    如以下程式碼程式:

    #include <bits/stdc++.h>
    using namespace std;
    int main() {
        char a = 'C';
        string b = "C";
        printf("字元'c'的size:%d\n", sizeof(a));
        printf("字串%\"C\"的size:%d\n", sizeof(b));
    }
    

    程式碼執行的結果是:

    字元'c'的size:1
    字串%"C"的size:32
    

字元陣列與字串

在C語言中沒有專門的字串變數,通常用一個字元陣列來存放一個字串。(注:在C++中對於字串有類string可以通過引用#include <string>來直接使用string新建字串變數。具體內容在本文最後進行講解)字串是以\0作為串的結束符。因此把一個字串存入一個數組時結束符'\0'也需要存入陣列,並以此作為該字串結束的標誌。有了\0標誌之後,就不必再用字元陣列的長度來判斷字串的長度了。

C語言允許用字串的方式對字元陣列做初始化賦值,但是與用字元初始化是有區別的。

例如

char ch1[] = {'C','h','i','n','a'};
//陣列ch1的大小為5,包含1個大寫字母,4個小寫字母
char ch2[] = "China";
char ch3[] = {"China"};
//以上兩句話是等效的,陣列ch2、ch3的大小均為6,包含1個大寫字母,4個小寫字母,和一個\0

對於字元陣列可以有兩種使用方式:

  • 當做單個字元看待。(ch[0]ch[1]等)

  • 當做一個字串看待,使用陣列名訪問整個陣列,直接操作字串中的個所有字元。可以使用printfscanf函式一次性輸入輸出一個字元陣列中的字串,而不必使用迴圈語句逐個地輸入輸出每個字元。

    #include <bits/stdc++.h>
    using namespace std;
    int main() {
        char ch[6] = "China";
        //按照字元逐個輸出
        for (int i = 0; i < 6; i ++ ) {
            printf("%c", ch[i]);
        }
        printf("\n");
        //按照字串整體輸出
        printf("%s", ch);
        return 0;
    }
    

    特別的,在定義字串的時候,可以省略掉字元陣列的大小,如以下兩種情況在編譯過程中是完全等價的:

    ch[] = "China";
    ch[6] = "China";
    

兩種方式輸入字串:

同字串的輸出一樣,輸入一個字串有兩種方式。

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch1[6], ch2[6];
    //按照字元陣列逐個讀入
    for (int i = 0; i <= 5; i ++ ) {
        scanf("%c", &ch1[i]);
    }
    //按照字串整體讀入
    scanf("%s", ch2);

    printf("Ans1:%s\n", ch1);
    printf("Ans2:%s\n", ch2);
}

當輸入為China China時,輸出為:

Ans1:China 
Ans2:China

例題1:[洛谷 P5733 【深基6.例1】自動修正](P5733 【深基6.例1】自動修正 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn))

例題2:洛谷 P1914 小書童——凱撒密碼

例題3:洛谷 P1125 笨小猴

C語言中字串函式

C語言提供了豐富的字串處理函式,大致可分為字串的輸入輸出、合併、修改、比較、轉換、複製及搜尋幾類。使用這些函式可大大減輕變成的負擔。用於輸入輸出的字串函式,在食用前應包含標頭檔案stdio.h,使用其他字串函式則應包含標頭檔案string.h

字串輸出函式puts

  • 規格說明:int put(const char * pc);

  • 功能描述:把字元指標指定的字串輸出到標準輸出流中,空字元不輸出,但是輸出換行符及空格等分隔符。

  • 函式引數:pc可以是指標變數,也可以是字元陣列名。

  • 函式返回值:若出現些錯誤,則返回EOF(-1);否則返回非負數。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[] = {"China"};
    puts(ch);
    return 0;
}

執行結果為:China

注:puts()函式完全可以由printf()函式取代。當需要按一定格式輸出時,通常使用printf()函式。

字串讀入函式gets

  • 規格說明:char * gets(char * pc);

  • 功能描述:從標準輸入流讀字元到字元指標指向的字元陣列中,直至遇到換行符或輸入文件結束符,並且丟棄換行符或結束符,然後在字元陣列中新增一個空字元。

  • 函式引數:pc必須是字元陣列名或指向字元陣列的指標變數。不可以使用指標變數指向字串常量,因為常量空間不允許改變。

  • 函式返回值:若成功,返回pc1;若直接遇見換行符或結束符,pc所指陣列不變,返回空指標,若讀入長度超出陣列長度,陣列改變,會出現記憶體寫問題(超出陣列長度)。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[11];
    printf("input string:\n");
    gets(ch);
    printf("output string:\n");
    puts(ch);
    return 0;
}

執行結果為:

input string:
Love China
output string:
Love China

將字串讀入字元陣列還可以使用gets()函式,但是由於存在字元陣列越界的風險,已經不再建議使用,新的C++11標準更是刪除了這個函式,而輸出一個字串還可以使用puts(),同時會自動輸出換行,這倒是還能使用。

測字串長度函式strlen

  • 規格說明:int strlen(const char * pc);

  • 功能描述:計算pc所指向的字串的長度。

  • 函式引數:pc可以是指標變數,也可以是字元陣列名。

  • 函式返回值:返回pc指向陣列中儲存字串的長度,即第一個\0之前的有效字元個數。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch1[] = {'I',' ','l','o','v','e','\0','y','o','u','\0'};
    char ch2[] = {'I',' ','l','o','v','e',' ','y','o','u','\0'};
    int len1 = strlen(ch1);
    int len2 = strlen(ch2);
    printf("%d\n", len1);
    printf("%d", len2);
    return 0;
}

執行結果為:

6
10

字串連線函式strcat

  • 規格說明:char * strcat(char * pc1, const char * pc2);

  • 功能描述:把字元指標pc1所指向的字元陣列的末尾新增pc2所指向的字串(包括結尾的空字元在內)的副本,即用pc2所指向的字串的第一個字元覆蓋pc1所指向的字串末尾的空字元

  • 函式引數:pc1必須是字元陣列名或只想字元陣列的指標變數,不可以使用指標變數指向字串常量。pc2可以是指標變數,也可以是字元陣列名。

  • 函式返回值:返回pc1,若連線後的pc1長度超出pc1指向陣列長度,會出現記憶體寫問題(超出陣列長度)。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[20] = {"I love"};
    char ch2[7] = " China";
    strcat(ch, ch2);
    printf("%s", ch);
    return 0;
}

執行結果為:I love China

字串複製函式strcpy

  • 規格說明:char * strcpy(char * pc1, const char *pc2);

  • 功能描述:把字元指標pc2所指向的字串(包括結尾的空字元在內)複製到pc1所指向的字元陣列中。

  • 函式引數:pc1必須是字元陣列名或只想字元陣列的指標變數,不可以使用指標變數指向字串常量。pc2可以是指標變數,也可以是字元陣列名。

  • 函式返回值:返回pc1,若複製後的pc1長度超出pc1指向陣列的長度,會出現記憶體寫問題(超出陣列長度)。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch1[20] = "I love ";
    char ch2[20] = "China";
    strcpy(ch1, ch2);
    printf("%s", ch1);
    return 0;
}

執行結果為:China

字串比較函式strcmp

  • 規格說明:int strcmp(cons char *pc1, const char *pc2);
  • 功能描述:按照ASCII碼順序比較字元指標pc1pc2所指向的串的大小。
  • 函式引數:pc1pc2可以是指標變數,也可以是字元陣列名
  • 函式返回值:
    • pc1所指向的字串 == pc2指向的字串,返回值0;
    • pc1所指向的字串 > pc2`指向的字串,返回值大於0;
    • pc1所指向的字串 < pc2指向的字串,返回值小於0;

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char * pc1 = "abc";
    char * pc2 = "ABC";
    char * pc3 = "abc";
    char * pc4 = "abd";
    char * pc5 = "ab";
    printf("cmp pc1 ans pc2:%d\n", strcmp(pc1, pc2));
    printf("cmp pc1 ans pc3:%d\n", strcmp(pc1, pc3));
    printf("cmp pc1 ans pc4:%d\n", strcmp(pc1, pc4));
    printf("cmp pc1 ans pc5:%d\n", strcmp(pc1, pc5));
    return 0;
}

執行結果為:

cmp pc1 ans pc2:1
cmp pc1 ans pc3:0
cmp pc1 ans pc4:-1
cmp pc1 ans pc5:1

字串查詢函式strstr

  • 規格說明:char * strstr(const char * pc1, const char * pc2);
  • 功能描述:查詢字元指標pc1所指向的字串中第一次出現pc2所指向字串(不包括空字元)的地址。
  • 函式引數:pc1pc2可以是指標變數,也可以是字元陣列名。
  • 函式返回值:返回字元指標pc1所指向字串中第一次出現pc2所指向字串(包括空字元)的地址。若不出現返回空指標。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[20] = "I love China.";
    char *pc = "China";
    char *tmp = strstr(ch, pc);
    int pos = strlen(ch) + 1 - strlen(tmp);
    printf("%d", pos);
    return 0;
}

執行結果為:8

C++ string型別字串

使用C語言風格的字元陣列有諸多不便,比如不能彈性變化長度,不能直接賦值或者複製,也有陣列越界的風險。在C++中提供了一些更好的工具——標準模板庫(Standard Template Library,STL),將很多有用的功能進行了封裝,開箱即用,而不需要另外重新開發這些功能。STL包括各類容器(如佇列、棧等)、演算法(如排序)和其他的一些功能。現在,將要使用string資料型別來處理字串問題。

像前面所說的,C語言中沒有字串這種資料型別,而在C++中我們可以直接使用string來定義一個字串。

例題4:洛谷 P5015 標題統計

像上面這道例題,可以不使用字元陣列,而是使用一種新的“資料型別”——string。一個string型別的變數可以用來儲存一個字串,並且可以激昂這個字串當做一個整體進行處理——可以對string進行賦值、拼接、裁切等,而字元陣列畢竟是個陣列,做到這些相對麻煩。

輸入時使用cin語句,不斷讀入字串。當發現讀入檔案讀完後,會直接中斷while迴圈。

這裡的s可以理解為一個“加強版”的字元陣列,可以使用s.length()來直接查詢字串s的長度,也可以和字元陣列一樣使用s[0]來查詢這個字串最開頭的字元是什麼。

而且string型別的字串可以直接拿來賦值、拼接操作,比如s = s + s就是將兩個字串s拼接在一起,其結果賦值回s的意思。

例題5:洛谷 P5734 【深基6.例6】文書處理軟體

這道題用到了string型別的常用處理方法:

  1. string s定義一個名字為s的字串變數。
  2. s+= str或者s.append(str)在字串s後面拼接字串str。
  3. s<str比較字串s的字典序是否在str的字典序之前。
  4. s.size()s.length()得到字串s的長度。
  5. s.substr(pos, len)擷取字串s,從第pos個位置開始len個字元,並返回這個字串。
  6. s.insert(pos, len)在字串s的第pos個字元之前,插入字串str,並返回這個字串。
  7. s.find(str, [pos])在字串s中從第pos個字元開始尋找str,並返回位置,如果找不到返回-1。pos可以省略,預設為0。

例題6:洛谷 P1308 統計單詞數

程式碼中使用了getline()函式,可以講完整的一行的輸入資料讀入到字串中,無論這一行當中是否有空格。使用方法是getline(cin, 字串名稱)

其他類別函式

fgets()函式

fgets()函式用來讀入一行字串,並存入字元陣列中,空格也一起存下了。由於gets()函式因為存在可能溢位的風險而不是用。fgets(s, sizeof(s), stdin)這條語句中制定了字元陣列的最大讀入數量,因此是安全的。

sscanf()函式和sprintf()函式

sscanf()函式,可以從已經儲存下來的字串中讀入資訊。sprintf()可以將資訊輸出到字串當中。比如sscanf(s, "%d", a);就可以從s字串中讀入一個整數a。同理sprintf(s, %d, a);可以將一個int型別的數a輸出到字串s中。

例題7:洛谷 P1957 口算練習題

習題

注:例題和加粗習題為必做題

洛谷 P1765 手機

洛谷 P3741 honoka的鍵盤

洛谷 P1321 單詞覆蓋還原

洛谷 P1553 數字反轉(升級版)

洛谷 P1603 斯諾登的密碼

洛谷 P1200 你的飛碟在這兒Your Ride Is Here

洛谷 P1597 語句解析

洛谷 P1598 垂直柱狀圖 【儘量做】

例題答案

例題1:[洛谷 P5733 【深基6.例1】自動修正](P5733 【深基6.例1】自動修正 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn))

讀入字串,對每一個字元進行分析,如果當前字元ch[i]>='a'&&ch[i]<='z'證明這是一個小寫字母,把他變成大寫字母即可。

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[110];
    scanf("%s", ch);
    for (int i = 1; ch[i] != '\0'; i ++ ) {
        if (ch[i] >= 'a' && ch[i] <= 'z') {
            ch[i] += 'A' - 'a';
        }
    }
    printf("%s", ch);
    return 0;
}

例題2:洛谷 P1914 小書童——凱撒密碼

讀入字串,把其中每一個字元加上\(n\),特別注意的,在當前字元加上\(n\)之後,可能會超過'a'~'z'的範圍,需要特殊處理。

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    char ch[60];
    scanf("%d", &n);
    scanf("%s", ch);
    for (int i = 0 ; ch[i] != '\0'; i ++ ) {
        ch[i] = (ch[i] - 'a' + n) % 26 + 'a';
    }
    for (int i = 0; ch[i] != '\0'; i ++ ) {
        printf("%c", ch[i]);
    }
    return 0;
}

例題3:洛谷 P1125 笨小猴

讀入字串,統計該字串中出現最多的字母的次數和出現最少的字母的次數,進行相減,判斷結果是否為質數,如果是質數則輸出Lucky Word與這個質數,不是則輸出No Answer0

#include <bits/stdc++.h>
using namespace std;
#define inf 2147483647
int main() {
    char ch[110];
    int maxx = 0, minn = inf, cnt[26] = {0};
    scanf("%s", ch);
    for (int i = 0; ch[i] != '\0'; i ++ ) {
        int now = ch[i] - 'a';//獲取當前字母是26箇中的第幾個(0~25)
        cnt[now] ++ ;
    }
    for (int i = 0; i < 26; i ++ ) {
        if (cnt[i] == 0) {
            continue;
            //判斷26個字母中第i(0~25)個是否出現,不出現則不統計
        }
        if (cnt[i] >= maxx) {
            maxx = cnt[i];
        }
        if (cnt[i] <= minn) {
            minn = cnt[i];
        }
        //找到這個單詞中出現最多字母出現的次數和出現最少字母出現的次數
    }
    //下面為常規判斷質數的方法
    if (maxx - minn == 0 || maxx - minn == 1) {
        printf("No Answer\n0");
        return 0;
    }
    for (int i = 2; i * i <= maxx - minn; i ++ ) {
        if ((maxx - minn) % i == 0) {
            printf("No Answer\n0");
            return 0;
        }
    }
    printf("Lucky Word\n%d", maxx - minn);
    return 0;
}

例題4:洛谷 P5015 標題統計

這裡使用了string型別的變數,因為讀入字串會忽略前面的空格,並且讀到分隔符(空格或者換行)時停止,所以可以一直讀入字串,每次讀入一個字串,把其長度加入答案中即可。

#include <bits/stdc++.h>
using namespace std;
int main() {
    string s;
    int cnt = 0;
    while (cin >> s) {
        cnt += s.length();
    }
    printf("%d", cnt);
    return 0;
}

例題5:洛谷 P5734 【深基6.例6】文書處理軟體

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    scanf("%d", &n);
    string s;
    cin >> s;
    for (int i = 1; i <= n; i ++ ) {
        int opt, a, b;
        string tmp;
        scanf("%d", &opt);
        if (opt == 1) {
            cin >> tmp;
            s += tmp;
            cout << s << endl;
        }
        if (opt == 2) {
            scanf("%d%d", &a, &b);
            s = s.substr(a, b);
            cout << s << endl;
        }
        if (opt == 3) {
            scanf("%d", &a);
            cin >> tmp;
            s.insert(a, tmp);
            cout << s << endl;
        }
        if (opt == 4) {
            cin >> tmp;
            int ans = s.find(tmp);
            cout << ans << endl;
        }
    }
    return 0;
}

例題6:洛谷 P1308 統計單詞數

首先將讀入的單詞和句子中的每個字母都換成小寫字母(大寫字母也可)。然後我們發現,如果要查詢的單詞是to時,若句子為I like to walk to Toshiba Company.的時候,將單詞Toshiba中的to也參與了統計。那麼我們這裡利用一個小處理,把要查詢的單詞前後各加一個空格,輸入的句子最前邊和最後邊也加一個空格,這樣 ‘ ’+"to"+' '去查詢,即可統計單個的單詞數量。

#include <bits/stdc++.h>
using namespace std;
int main() {
    string s, SS;
    getline(cin, s);
    getline(cin,SS);
    for (int i = 0; i < SS.length(); i ++ ) {
        if (SS[i] >= 'A' && SS[i] <= 'Z') {
            SS[i] += 32;
        }
    }
    for (int i = 0; i < s.length(); i ++ ) {
        if (s[i] >= 'A' && s[i] <= 'Z') {
            s[i] += 32;
        }
    }
    s = ' ' + s + ' ';
    SS = ' ' + SS + ' ';
    if (SS.find(s) == -1) {
        cout << -1 << endl;
        return 0;
    }
    int nxt = SS.find(s);
    int pos = nxt;
    int cnt = 0;
    while (nxt != -1) {
        cnt ++ ;
        nxt = SS.find(s, nxt + 1);
    }
    cout << cnt << ' ' << pos << endl;
    return 0;
}

例題7:洛谷 P1957 口算練習題

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n, a, b, c;
    char opt, s[20], ans[20];
    scanf("%d\n", &n);
    while(n -- ) {
        fgets(s, sizeof(s), stdin);
        if (s[0] == 'a' || s[0] == 'b' || s[0] == 'c') {
            opt = s[0];
            s[0] = ' ';
        }
        sscanf(s, "%d %d", &a, &b);
        switch(opt) {
            case 'a' : 
                c = a + b;
                sprintf(ans, "%d+%d=%d", a, b, c);
                break;
            case 'b' :
                c = a - b;
                sprintf(ans, "%d-%d=%d", a, b, c);
                break;
            case 'c' :
                c = a * b;
                sprintf(ans, "%d*%d=%d", a, b, c);
        }
        printf("%s\n", ans);
        printf("%d\n", strlen(ans));
    }
    return 0;
}