動的な二次元配列
表の形のデータをどう置くか
第4章で Student の動的配列を作りました。あれは1人につき1つの点数を持つ、細長い一列のデータでした。ここからは学生ごとに国語・数学・英語の3科目を持つ、表の形のデータを扱います。
人数が実行時に決まる以上、int table[10][3]; のような固定の二次元配列では足りません。行のほうを、動く形で用意する必要があります。
int は「行へのポインタを並べた配列」
C で動的な表を作るときの定番は、次の2段構えです。
int **table = malloc(sizeof(int *) * n); /* 行へのポインタを n 本 */
for (int i = 0; i < n; i++) {
table[i] = malloc(sizeof(int) * 3); /* 各行の実体を 3 個ずつ */
}1回目の malloc が確保しているのは点数ではなく ポインタ** です。だから要素1個の大きさは sizeof(int *) になります。ここを sizeof(int) と書き間違えると、64 ビット環境では必要な量の半分しか取れず、後ろのほうの行を書いた瞬間に他人の領域を壊します。しかも壊れるのは書いた場所ではなく、あとから読んだ別の場所なので、原因を追うのがとても難しくなります。
2回目以降の malloc が、実際に点数が入る場所です。table[i] は int * なので、table[i][j] と書けば i 番目の学生の j 科目目になります。これは *(*(table + i) + j) の短い書き方で、第1章でやった a[i] と *(a + i) の同値が、そのまま2段に重なっただけです。
確保と解放は逆の順にする
解放するときは、確保したのと逆の順に戻します。
for (int i = 0; i < n; i++) {
free(table[i]);
}
free(table);先に free(table) を書いてしまうと、そのあとの table[i] は解放済みの領域を読むことになります。第2章で扱ったダングリングポインタそのもので、行の置き場所を書いた紙を先に捨ててから、中身を捨てに行こうとしている状態です。
行ごとに長さを変えられる
この形は行を別々に確保しているので、行ごとに長さを変えられます。学生によって受けた科目数が違う表も素直に書けます。
もう1つ、malloc(sizeof(int) * n * m) で1枚の大きな領域を取り、data[i * m + j] と自分で添字を計算する方法もあります。こちらは確保も解放も1回で済み、領域が飛び飛びにならないぶん速く読めます。行の長さがそろっているならこちらが有利です。
では、人数を読み込んで点数表を動的に確保し、学生ごとと科目ごとの合計を出してみましょう。
要件
- 行へのポインタの配列と、各行の実体を malloc で確保する
- 学生ごとの合計を 学生1の合計は240点です の形で人数ぶん出す
- 続けて科目ごとの合計を 科目1の合計は140点です の形で3行出す
- 最後に各行と表本体を解放する
入出力例
main("2
80 90 70
60 70 80
") → "学生1の合計は240点です
学生2の合計は210点です
科目1の合計は140点です
科目2の合計は160点です
科目3の合計は150点です"
main("3
100 50 60
70 80 90
40 55 65
") → "学生1の合計は210点です
学生2の合計は240点です
学生3の合計は160点です
科目1の合計は210点です
科目2の合計は185点です
科目3の合計は215点です"