語法書 / AA 競程語法書 下冊 / 第八單元 / 多個字串與二維字元陣列(延伸知識)

8.10 多個字串與二維字元陣列(延伸知識)

有些題目一次給很多個字串,最典型的是「字元網格」:HW 欄的地圖,每列是一個長度 W 的字串。存法:二維字元陣列(上冊 6.9),一列住一個字串:

char g[105][106];    // 最多 105 列;每列是一個可存 105 字元 + '\0' 的字元陣列

第二維要多開一格——g[i] 就是「第 i 列那個一維字元陣列」,它要放整列字串加上 '\0',所以第二維至少是「最大寬度 +1」(比照慣例再加點)。

一次讀一整列

妙的地方來了:既然 g[i] 是一個字元陣列,那 cin >> g[i] 就是 8.7 學過的「讀一個單詞」——一行指令讀進一整列

int H, W;
cin >> H >> W;
for (int i = 0; i < H; i++) {
    cin >> g[i];                  // 第 i 列整串讀入,結尾自動補 '\0'
}

讀完之後,g[i][j] 就是第 i 列第 j 欄的字元(0-base),照 6.9 的巢狀迴圈遍歷即可。

範例程式碼

讀入 H \times W 的網格,數出寶藏 '#' 有幾個:

#include <iostream>
using namespace std;

const int MAX_H = 105;
char g[MAX_H][MAX_H + 1];          // 第二維多開一格給 '\0'

int main() {
    int H, W;
    cin >> H >> W;
    for (int i = 0; i < H; i++) {
        cin >> g[i];               // 一次讀一整列
    }

    int cnt = 0;
    for (int i = 0; i < H; i++) {
        for (int j = 0; j < W; j++) {
            if (g[i][j] == '#') cnt++;
        }
    }
    cout << cnt << '\n';
    return 0;
}

執行結果(輸入 2 3#.#..#):

3

想用 1-base 存?

如果想讓左上角住在 g[1][1](跟題目的「第 1 列第 1 欄」對齊,上冊 6.3 的存法 A),列的方向讓 g[0] 空著就好;的方向有個小技巧:

for (int i = 1; i <= H; i++) {
    cin >> (g[i] + 1);            // 從 g[i][1] 開始放:整列往右挪一格
}

g[i] + 1 的意思是「第 i 列開頭再往後一格的位置」——把讀入的起點從 g[i][0] 挪到 g[i][1]。當然,用 6.6 的逐字讀也完全可以,還不用煩惱 '\0' 的事:

for (int i = 1; i <= H; i++) {
    for (int j = 1; j <= W; j++) {
        cin >> g[i][j];           // 一格一格讀,想放哪就放哪
    }
}

動手試試看:把範例改成:數完 '#' 之後,再輸出每一列各有幾個 '#'(一行一個數字)。想想看你遍歷的方向跟 6.9 的「逐列展開」有什麼關係。