C言語編 第25章 配列と文字列

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

配列

配列とは、変数が複数連結されたものです。

標準入力から 5つの整数を受け取り、それを逆順に表示し直したいとします。 この章までの知識だけで、このようなプログラムを書くと、次のようになるでしょう。

#include <stdio.h>

int main(void)
{
	char str[40];
	int num1;
	int num2;
	int num3;
	int num4;
	int num5;

	puts( "整数を5回入力して下さい。" );

	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num1 );

	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num2 );

	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num3 );

	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num4 );

	fgets( str, sizeof(str), stdin );
	sscanf( str, "%d", &num5 );


	/* 見やすいように、少し間をあけておく */
	printf( "\n\n" );

	/* 逆順に出力 */
	printf( "%d\n", num5 );
	printf( "%d\n", num4 );
	printf( "%d\n", num3 );
	printf( "%d\n", num2 );
	printf( "%d\n", num1 );
		
	return 0;
}

実行結果:

整数を5回入力して下さい。
3
7
4
1
9


9
1
4
7
3

逆順に出力するという要求のためには、入力された整数をすべて保存しておく必要があります。 保存のために用意する変数は、それぞれ個別のものになるため、for文を使ってソースコードをまとめることができず、 上のような長々としたプログラムになってしまいます。

いつも書いているように、同じ意味合いのソースコードを複数書くべきではなく、まとめ上げるべきです。 先ほどのようなプログラムを書いて平気でいられるようではダメです。

ここで配列の出番です。 まずは、配列を使ったプログラムをお見せしましょう。

#include <stdio.h>

#define INPUT_NUM	5		/* 入力回数 */

int main(void)
{
	char str[40];
	int num[INPUT_NUM];
	int i;

	puts( "整数を5回入力して下さい。" );

	for( i = 0; i < INPUT_NUM; ++i ) {
		fgets( str, sizeof(str), stdin );
		sscanf( str, "%d", &num[i] );
	}


	/* 見やすいように、少し間をあけておく */
	printf( "\n\n" );

	/* 逆順に出力 */
	for( i = INPUT_NUM - 1; i >= 0; --i ) {
		printf( "%d\n", num[i] );
	}
	
	return 0;
}

実行結果:

整数を5回入力して下さい。
3
7
4
1
9


9
1
4
7
3

配列は変数の集まりに過ぎないので、単なる変数同様、宣言してから使います。 配列の宣言は次のように書きます。

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

要素数の指定が加わること以外は、単なる変数の宣言と同じです。 この形を見て分かるように、fgets関数を使うときに用意する「char str[40];」というのと同じです。 つまりこれも配列で、char型の配列と呼びます。

先ほどのプログラムでは、int型の配列を宣言しており、その要素数は 5 です( 5 は何度か登場するので #define で記号定数にしてあります)。 要素数が 5 ということは、int型の変数が 5つ連結された状態であるということです。 ここで、配列に含まれた1つ1つを要素と呼びます。

配列の要素には、添字(そえじ)という整数を使ってアクセスします。 添字は、0以上で要素数未満の整数です。 例えば、要素数 5 の配列であれば、添字の範囲は 0〜4 です。
0 から始まるというのは不思議な感じがするかも知れませんが、このおかげで、配列の先頭から末尾までを順番にアクセスする場合に、

for( i = 0; i < ARRAY_SIZE; ++i ){
}

のようなシンプルな for文の終了判定が使えます。 最後の要素の添字は、要素数 -1 であるため、「要素数未満の間、繰り返す」という条件になる訳です。

とは言いつつも、配列の要素数を指定する必要がある箇所では、 第28章で取り上げる関数形式マクロを使い、計算によって要素数を算出した方が良いです。 この方法を使えば、配列の要素数を後から変更したとき、コードが自動的に追従してくれる可能性が高くなります。 具体的には、次のようになります。

#define SIZE_OF_ARRAY(array)	(sizeof(array)/sizeof(array[0]))

for( i = 0; i < SIZE_OF_ARRAY(num); ++i ){
}


先ほどのサンプルプログラムでのポイントは、ばらばらの変数を 5つ使うのとは違い、1つの配列であれば、同じ名前(配列名)でアクセスできる点にあります。 添字を for文で変化させながらアクセスを繰り返せば、配列の各要素に順番にアクセスできるので、プログラムを大幅に簡略化できます。

なお、添字の部分が式になっていても構いません。

int index = 5;
array[index + 3];	/* array[8] */

範囲外アクセス

配列の添字は、「0〜要素数-1」までが有効な範囲です。 では、これ以外の値を添字に使ってしまうとどうなるのでしょうか。

有効でない範囲の添字を使って、配列の要素にアクセスする行為は不正です。しかし、どうなるかは未定義です。 C言語では、このような未定義の動作というものが各所で登場しますが、 要するに、「どんな悪いことでも起こり得るし、何も起こらないかも知れない」ということです。 これは恐ろしいことなので、未定義の動作は避けなくてはなりません

厳密には、添字が負数であることが許されない訳ではなく、あくまでも配列の範囲外をアクセスすることが不正なだけです。 例えば、array[5] のアドレスを指すポインタp があるとき、p[-3] のような指定は有効です。 (ポインタは第31章で登場します)。

配列の初期化

配列も単なる変数に過ぎないので、明示的に初期値を与えなければ、どんな値が入っているかは不定です。 1つの変数のように、宣言の際に、初期値を与えることも可能です。 配列の場合は次のように書きます。

データ型 配列名[要素数] = { 0番目の初期値, 1番目の初期値, … };

配列の場合、要素が複数あるので、各要素の初期値を { } の中に , で区切って記述します

{ } の中に記述した初期値の個数が、要素数に満たない場合、足りない部分には自動的に 0 が与えられます。 逆に、要素数をオーバーしてしまった場合は、コンパイルエラーになります

#include <stdio.h>

#define ARRAY_SIZE	10

int main(void)
{
	int array[ARRAY_SIZE] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int i;

	for( i = 0; i < ARRAY_SIZE; ++i ){
		printf( "%d ", array[i] );
	}
	printf( "\n" );
	
	return 0;
}

実行結果:

0 1 2 3 4 5 6 7 8 9

なお、配列の宣言時に初期値を与える場合は、要素数の指定を省略することができます

データ型 配列名[] = { 0番目の初期値, 1番目の初期値, … };

この場合、{ } の中に記述した初期値の個数が、要素数であるとみなされます

C99 (指示付きの初期化子)

C99 では、指示付きの初期化子という機能が追加されています。 これを使うと、配列の特定の要素を選んで初期値を与えることができます。

#include <stdio.h>

#define ARRAY_SIZE	10

int main(void)
{
	int array[ARRAY_SIZE] = {
		[0] = 10,
		[ARRAY_SIZE - 1] = 99
	};
	int i;
	
	for( i = 0; i < ARRAY_SIZE; ++i ){
		printf( "%d ", array[i] );
	}
	printf( "\n" );
	
	return 0;
}

実行結果:

10 0 0 0 0 0 0 0 0 99

配列array の初期化のところを見て下さい。 [0] = 10, のように書けば、array[0] の初期値が 10 になります。 同様に、[ARRAY_SIZE - 1] = 99 によって、array[ARRAY_SIZE - 1] の初期値が 99 になります。
なお、この例の場合、array[1]〜array[ARRAY_SIZE - 2] は 0 で初期化されます。

VisualC++ 2013/2015/2017、clang 3.7 のいずれも対応しています。

配列のメモリイメージ

変数を定義すると、メモリ上に、その変数の値を記憶しておくための場所が作られます。 以前、第3章では、変数を「値を入れておく箱」と表現しましたが、この箱はメモリ上に置かれている訳です。

配列も、複数の変数が連結した状態に過ぎないので、やはりメモリ上に配置されます。 重要な点は、配列の要素1つ1つが、必ずメモリ上で連続した場所に配置されていることです。 今のところ、そうであることの価値は実感できませんが、第31章で登場するポインタやアドレスの概念を学ぶと、 その意味が理解できるようになります。

文字列

文字列というのは、文字列定数(文字列リテラル)のことです。 要するに、"" で囲まれた 0文字以上の文字の並びですが、定数でない文字の並びであっても、文字列と呼ぶことが多いです。

文字列をどこかに記憶しておくには、文字の配列を使います。 文字の配列とは、すなわち char型の配列です。

配列の宣言時に初期値を与えられますが、このとき、char型の配列に限り、次のような構文が許可されます。

データ型 配列名[要素数] = "文字列";

{ } で囲む形式に代わって、"" で囲まれた文字の並びを記述します。 すると、1文字ずつが1要素として格納されます。

文字列の末尾には、'\0' という特殊な終端文字が存在することを忘れないで下さい。 先ほどのように "" で囲んだ初期値を与えた場合、自動的に末尾に '\0' が付加されています。 配列の要素数は、この自動的に付加される '\0' のことも考慮して指定しなければなりません
例えば、

char str[5] = "abcd";
char str[5] = "abcde";

上の方は、'\0' を付加するスぺースがあるので問題ありませんが、 下の方は、スペースがないのでエラーになるかも知れません(コンパイラ依存です。ただし C++ では必ずエラーです)。
いずれにせよ、末尾に '\0' がない文字列は、文字列としての要件を満たしていませんから、下の方のような初期化は避けるべきです。


また、char型の配列でも、{ } で囲み、1文字ずつ初期値を与えることは可能ですがお勧めしません。 1文字ずつ指定する場合、次のように書きます。

char str[5] = { 'a', 'b', 'c', 'd' };
char str[5] = { 'a', 'b', 'c', 'd', 'e' };

上の方は、要素数5 に対して、初期値の個数が 4つしかありません。 この場合、文字列の末尾の '\0' は自動的に付加されます
一方、下の方は、要素数5 に対して、初期値の個数も 5つなので、'\0' を付けるスペースがありません。 この場合、エラーではなく、'\0' が付かないまま初期化されてしまいます。 従って、これは文字列としての要件を満たしていないことになります。

なお、初期値を "" で与える方法でも、{ } で与える方法でも、要素数の指定を省略した場合には、必ず末尾の '\0' が付加されます


このように、文字の配列を宣言する際には、"" で囲んだ文字列で初期化できます。 しかし、後から代入する場合には、

str = "abcde";

のようなことはできません。 代わりに、strcpy関数(⇒リファレンス)を使い、

strcpy( str, "abcde" );

と書きます。 この辺りの事情について知るには、第31章でポインタについて学ぶ必要があります。

配列を引数や戻り値にするには

配列を関数に渡したり、逆に return文で配列を返したりしたいときもあるでしょう。 実は、C言語では配列を関数に受け渡しすることは出来ません

しかし、代わりの手段は存在します。 代わりの手段というのは、第31章で説明するポインタを使うことです。 具体的な例は、第33章で取り上げるので、それまでは、配列を関数に受け渡しする方法は説明しないことにします。


練習問題

問題@ 要素数10 の int型配列に、2 から始まる 2 のべき乗を順番に格納し、それを逆の順番で表示するプログラムを作成して下さい。

問題A 次のように文字の配列を定義します。

char str[] = "abcdef";

この文字列を 1文字ずつ改行しながら出力するプログラムを作成して下さい。

問題B 問題Aと同じ内容で、文字の配列の定義を次のように変えた場合、どうなるでしょう?

char str[] = "abc\0def";


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/12/27 SIZE_OF_ARRAYマクロの定義を修正。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/23 配列の要素数を関数形式マクロで求める方法について、コラムとして追記。

'2015/8/18 VisualC++ 2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/10/18 clang 3.2 に対応。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/14 VisualC++ 2008 の対応終了。

'2014/1/11 clang 3.0 に対応。

'2013/4/21 「C99 (指示付きの初期化子)」の項を追加。

'2009/8/21 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ