【C言語入門】2次元配列の使い方まとめ

2次元配列って使ってますか?2次元配列はポインタを使うと1次元配列と同じように扱うことができます。

文字列と同じように扱うことができるということですので簡単に扱うことができて便利です。3次元や4次元と多次元になればなるほど一見扱うのが難しいと感じるかもしれませんが、結局は1次元として扱うことができます。

多次元を扱う必要がある場合、まずは2次元配列を使って1次元配列と同じように扱うことができるようになる必要があります。

この記事では、2次元配列について

  • 2次元配列の使い方について

という基本的な内容から、

  • ポインタを使って2次元配列を扱う方法について
  • その他の方法について

など応用的な使い方の内容についても解説していきます。

今回は2次元配列について、使い方をわかりやすく解説します!

2次元配列の使い方について

2次元配列の宣言と初期化および代入などの使い方について説明します。

その前に1次元配列の宣言および初期化についておさらいしておきましょう。1次元配列の宣言および初期化は下記のように記述しました。

データ型名 配列名[要素数] = {要素1, 要素2, …...};

int nums[5] = {0, 1, 2, 3, 4};
char city1[16] = {‘T’, ‘o’, ‘k’, ‘y’, ‘o’};
char city2[16] = “Osaka”;

これに対して、2次元配列では加わる行数とその要素を加えていきます。行数は1次元の要素数の前に記述します。下記のとおりになります。

データ型名 配列名[行数][要素数];

また初期値のリストは「{ }」内にさらに「{ }」で囲われた要素を「,」(カンマ)で区切って並べていきます。下記のようになります。

データ型名 配列名[行数][要素数] = {{要素[0][0], 要素[0][1], …...}, {要素[1][0], 要素[1][1], …...}, …...};

それではサンプルコードで確認していきましょう。

#include <stdio.h>

int main(void) {
	// リストで初期化
	int nums1[3][5] = {{0, 1, 2, 3, 4},
						{1, 2, 3, 4, 5},
						{2, 3, 4, 5, 6}};
	for(int i = 0; i < 3; i++) {
		for(int j = 0; j < 5; j++) {
			if(j < 4) {
				printf("%d,", nums1[i][j]);
			} else {
				printf("%d\n", nums1[i][j]);
			}
		}
	}

	// 要素に1個ずつ代入して初期化
	int nums2[4][5];
	int i = 0;
	for(int j = 0; j < 4; j++) {
		for(int k = 0; k < 5; k++) {
			nums2[j][k] = i;
			i++;
			if(k < 4) {
				printf("%d,", nums2[j][k]);
			} else {
				printf("%d\n", nums2[j][k]);
			}
		}
	}

	// 文字列の配列(char型の2次元配列)
	char cities[3][16] = {"Tokyo",
							"Osaka",
							"Nagoya"};
	for(int i = 0; i < 3; i++) {
		for(int j = 0; j < 16; j++) {
			if(j < 15) {
				printf("%c,", cities[i][j]);
			} else {
				printf("%c\n", cities[i][j]);
			}
		}
	}

	return 0;
}

実行結果:

0,1,2,3,4
1,2,3,4,5
2,3,4,5,6
0,1,2,3,4
5,6,7,8,9
10,11,12,13,14
15,16,17,18,19
T,o,k,y,o,,,,,,,,,,,
O,s,a,k,a,,,,,,,,,,,
N,a,g,o,y,a,,,,,,,,,,

このサンプルコードでは、int型の2次元配列をリストを使って初期化した例とfor文で要素を1個ずつ代入して初期化した例を記載しています。

また、char型の2次元配列については文字列のリストを使って初期化した例を掲載しています。

ちなみに、リストを使って初期化する場合は行数の記述を省略して下記のように宣言することもできます。

データ型名 配列名[][要素数] = {{要素[0][0], 要素[0][1], …...}, {要素[1][0], 要素[1][1], …...}, …...};

int nums1[][5] = {{0, 1, 2, 3, 4}, {1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}};
char cities[][16] = {"Tokyo", "Osaka", "Nagoya"};

ポインタを使って2次元配列を扱う方法について

これまでは1行あたりの要素数や行数が固定の場合を扱ってきました。

しかし、常に1行あたりの要素数や行数が固定とは限りません。1行あたりの要素数や行数も変数を使って場合によっては配列のサイズを変更したいですよね。

変数を使って配列のサイズを変更し要素を割り当てることを動的に要素を割り当てるといいます。動的に要素を割り当てる際にはmalloc関数を使用します。

malloc関数の引数には配列全体で使用するバイト数を入力します。配列全体で使用するバイト数を入力したmalloc関数を配列のポインタに代入して使用します。

また2次元の配列になると1つの配列でメモリ領域を大きく使う場合もでてきます。使わなくなったメモリを放っておかないように、メモリの解放をこまめに行うことをおススメします。

メモリの解放にはfree関数を使用します。free関数を使用するには、ヘッダーファイル「stdlib.h」をインクルードする必要があります。

2次元配列として扱う方法

ポインタを使って2次元配列を扱う場合、2次元配列として扱う方法と1次元配列として扱う方法があります。

まずは2次元配列として扱う方法についてみていきましょう。2次元配列として扱う場合、それぞれの行のデータにアクセスするためのアドレスとそのアドレスを保持するためのポインタが必要になってきます。

またそれぞれの行のデータにアクセスするためのアドレスを指定する方法が2つあります。1つはそれぞれの行のデータごとに個別にアドレスを設定する方法です。

もう1つは1つのアドレス先に上の行から順にそれぞれの行のデータを並べていく方法です。

それでは順にサンプルコードを確認していきましょう。

#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
	int m, n;
	m = 5;
	n = 3;
 
	int **nums1;
	nums1 = malloc(sizeof(int *) * n); // n行個分のアドレスを保持する領域を確保
 
	for(int i = 0; i < n; i++) {
		nums1[i] = malloc(sizeof(int) * m); // 1行分のデータを保持する領域を確保
	}
 
	for(int i = 0; i < n; i++) {
		for(int j = 0; j < m; j++) {
			nums1[i][j] = i * m + j; // 初期化
			if(j < m -1) {
				printf("%d,", nums1[i][j]);
			} else {
				printf("%d\n", nums1[i][j]);
			}
		}
	}
 
	// メモリ解放
	for(int i = 0; i < n; i++) {
		free(nums1[i]);
	}
	free(nums1);
 
	int **nums2, *arr;
	nums2 = malloc(sizeof(int *) * n); // n行個分のアドレスを保持する領域を確保
	arr = malloc(sizeof(int) * m * n); // m×n個のデータを保持する一連の領域を確保
 
	for(int i = 0; i < n; i++) {
		nums2[i] = arr + i * m;
	}
 
	for(int i = 0; i < n; i++) {
		for(int j = 0; j < m; j++) {
			nums2[i][j] = i * m + j; // 初期化
			if(j < m -1) {
				printf("%d,", nums2[i][j]);
			} else {
				printf("%d\n", nums2[i][j]);
			}
		}
	}
 
	// メモリ解放
	free(arr);
	free(nums2);
 
	return 0;
}

実行結果:

0,1,2,3,4
5,6,7,8,9
10,11,12,13,14
0,1,2,3,4
5,6,7,8,9
10,11,12,13,14

このサンプルコードではまずそれぞれの行のデータごとに個別にアドレスを設定する方法について例を記述しています。

その後もう一方の1つのアドレス先に上の行から順にそれぞれの行のデータを並べていく方法について例を記述しています。

「nums1」はアスタリスクが2つ付いていますので、ポインタのポインタです。

「nums1」ポインタではそれぞれの行のデータにアクセスするためのアドレスを行数個分malloc関数を使って保持しています。「nums1」ポインタの各要素が指すアドレス先で各行のデータにアクセスしています。

「nums2」もアスタリスクが2つ付いていますので、ポインタのポインタです。「nums2」ポインタではそれぞれの行のデータにアクセスするためのアドレスを行数個分malloc関数を使って保持しています。

「arr」ポインタは上の行から順にそれぞれの行のデータを並べた一連のデータのアドレス先を指しています。「nums2」ポインタの各要素が指すアドレス先で各行のデータにアクセスしています。

1次元配列として扱う方法

ここからは2次元配列を1次元配列として扱う方法を説明します。1次元配列として扱うために前の行の最後尾のデータの次に行の先頭のデータを並べて、それを順につなげていきます。

これで2次元配列を1次元配列として扱うことができます。アドレスも1つだけで足ります。

それではサンプルコードを確認していきましょう。

#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
	int m, n;
	m = 5;
	n = 3;
 
	int *nums3;
	nums3 = malloc(sizeof(int) * m * n); // m×n個のデータを保持する一連の領域を確保
 
	for(int i = 0; i < n; i++) {
		for(int j = 0; j < m; j++) {
			nums3[i * m + j] = i * m + j; // 初期化
			if(j < m -1) {
				printf("%d,", nums3[i * m + j]);
			} else {
				printf("%d\n", nums3[i * m + j]);
			}
		}
	}
 
	// メモリ解放
	free(nums3);
 
	return 0;
}

実行結果:

0,1,2,3,4
5,6,7,8,9
10,11,12,13,14

このサンプルコードでは「nums3」ポインタが指すアドレス先で2次元のデータの個数分の領域をmalloc関数を使って保持しています。

そこに1行目の先頭のデータから順に代入しています。

その他の方法について

2次元の配列データをポインタを使って1次元配列として扱う方法を説明しました。

これにより2次元の配列を文字列と同じように扱うことができるので、関数の引数として、また構造体のメンバとして扱いやすくなります。

2次元配列を関数の引数として、また構造体のメンバとして使う方法について、サンプルコードで確認しながらみていきましょう。

関数の引数で使う方法について

それでは2次元配列を関数の引数で使う方法について説明します。

サンプルコードで確認していきます。

#include <stdio.h>
#include <stdlib.h>
 
void init(int *arr, int m, int n) {
	for(int i = 0; i < n; i++) {
		for(int j = 0; j < m; j++) {
			arr[i * m + j] = i * m + j; // 初期化
		}
	}
}
 
int main(void) {
	int m, n;
	m = 5;
	n = 3;
 
	int *nums;
	nums = malloc(sizeof(int) * m * n); // m×n個のデータを保持する一連の領域を確保
 
	init(nums, 5, 3);
 
	for(int i = 0; i < n; i++) {
		for(int j = 0; j < m; j++) {
			if(j < m - 1) {
				printf("%d,", nums[i * m + j]);
			} else {
				printf("%d\n", nums[i * m + j]);
			}
		}
	}
 
	// メモリ解放
	free(nums);
 
	return 0;
}

実行結果:

0,1,2,3,4
5,6,7,8,9
10,11,12,13,14

このサンプルコードでは2次元配列の各要素を初期化するための「init」関数を記述しています。「init」関数は2次元配列のアドレス先を引数とし、初期化した配列データを参照渡ししています。

また「nums」ポインタには2次元配列の各要素のデータを格納するアドレス先と領域をmalloc関数を使って代入しています。

構造体のメンバで使う方法について

構造体を使うことで2次元配列データおよび配列の基本情報となる1行あたりのデータ数および行数を、メンバとしてひとつにまとめることができます。

それではサンプルコードで確認していきましょう。

#include <stdio.h>
#include <stdlib.h>
 
typedef struct str{
	int m;
	int n;
	int *arr;
} number;
 
void init(number *num) {
	for(int i = 0; i < num->n; i++) {
		for(int j = 0; j < num->m; j++) {
			num->arr[i * num->m + j] = i * num->m + j; // 初期化
		}
	}
}
 
int main(void) {
	// 構造体の実体の宣言と初期化
	number num;
	num.m = 5;
	num.n = 3;
	num.arr = malloc(sizeof(int) * num.m * num.n); // m×n個のデータを保持する一連の領域を確保
 
	init(&num);
 
	for(int i = 0; i < num.n; i++) {
		for(int j = 0; j < num.m; j++) {
			if(j < num.m - 1) {
				printf("%d,", num.arr[i * num.m + j]);
			} else {
				printf("%d\n", num.arr[i * num.m + j]);
			}
		}
	}
 
	// メンバのメモリ解放
	free(num.arr);
 
	return 0;
}

実行結果:

0,1,2,3,4
5,6,7,8,9
10,11,12,13,14

このサンプルコードでは「number」型の構造体を宣言しています。変数「m」が1行あたりのデータの個数、変数「n」がデータの行数になります。

「arr」ポインタは2次元配列のデータが格納されるアドレス先です。

「init」関数は構造体の実体のアドレス先を引数とし、初期化した実体を参照渡ししています。

main関数内では「number」型の構造体の実体「num」を生成しています。構造体の実体「num」のメンバ「m」、「n」を初期化しています。

またメンバである「arr」ポインタには2次元配列の各要素のデータを格納するアドレス先と領域をmalloc関数を使って代入しています。

配列の使い方総まとめ

この記事では紹介しきれなかった配列のいろいろな使い方を次の記事にまとめているので、ぜひ確認してください!

まとめ

ここでは、2次元配列の使い方について説明しました。2次元配列はポインタを使って1次元配列と同じように扱うことができます。

3次元以上の多次元の配列であっても考え方は同じで、1次元配列と同じように簡単に扱うことができるので便利です。

この便利な考え方を使いこなすことができるように、この記事を何度も参考にして下さいね!

LINEで送る
Pocket

「プログラミング、右も左もわからない…」という方にオススメ

当プログラミングスクール「侍エンジニア塾」では、これまで6000人以上のエンジニアを輩出してきました。

その経験を通してプログラミング学習に成功する人は、「目的目標が明確でそれに合わせた学習プランがあること」「常に相談できる人がそばにいること」「自己解決能力が身につくこと」この3つが根付いている傾向を発見しました。

侍エンジニア塾は上記3つの成功ポイントを満たすようなサービス設計に磨きをかけております。

cta_under_bnr

「自分のスタイルや目的に合わせて学習を進めたいな」とお考えの方は、ぜひチェックしてみてください。

書いた人

長野 透

長野 透

熊本在住のフリープログラマ兼ライターです。C/C++/C#、Java、Python、HTML/CSS、PHPを使ってプログラミングをしています。専門は画像処理で最近は機械学習、ディープラーニングにはまっています。幅広くやってきた経験を活かしてポイントをわかりやすくお伝えしようと思います。
お問合せはこちらでも受け付けています。
[email protected]