動的な二次元配列

表の形のデータをどう置くか

第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回で済み、領域が飛び飛びにならないぶん速く読めます。行の長さがそろっているならこちらが有利です。

では、人数を読み込んで点数表を動的に確保し、学生ごとと科目ごとの合計を出してみましょう。

要件

  1. 行へのポインタの配列と、各行の実体を malloc で確保する
  2. 学生ごとの合計を 学生1の合計は240点です の形で人数ぶん出す
  3. 続けて科目ごとの合計を 科目1の合計は140点です の形で3行出す
  4. 最後に各行と表本体を解放する

入出力例

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点です"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.c
学習モード

メモ

動的な二次元配列

⌘S で保存