配列(array)は,変数のリスト(並び)のことを言う.プログラムでは,単一の変数を一つ一つ宣言して利用するよりも,
複数の変数の並び(=配列)を用いたほうがずっと使いやすいことが多々ある.その代表が文字列である.知ってのように,
文字列は,一つ一つの文字の並びからできている.これを,char型を一つ一つ宣言していてはとても使いやすとは言い難いが,
文字の一連の並びをまとめて宣言できれば,とても使いやすくなろうことは想像に難くない.C言語では,これを配列を用いて実現している.
ここではそのような配列の使い方について学ぶ.
1次元配列
1次元配列は,以下の様に宣言する.
型 変数名 [個数];
「型」はCの変数型名,「変数名」は配列の名前,「個数」は配列の要素の数を示す.例えば,1クラス最大80名の学生のテストの点数を示す配列を宣言するなら以下の様にするだろう.
int result_of_a_test [80];
または,正の整数だけ扱えればよく,最小のメモリ消費に抑えたければ,以下の様にする.
unsigned char result_of_a_test[80]; // 0-255までの値を80個扱える配列
配列の要素にアクセスするには,要素の番号を使って,配列に添字(index)をつける.
配列の一番最初の要素にアクセスするには,
result_of_a_test[0] = 100; // 配列の先頭の要素に100を代入
とする.
配列の要素番号は0から始まる.従って,最初に80個宣言した配列の最後の要素は,
result_of_a_test[79] = 100; // 配列の最後の要素に100を代入
のように,indexが79となる.
配列のメモリ配置
C言語では,配列は連続したメモリに配置される.その時,低いアドレスから順に配置される.つまり最初の要素が最も低い(小さい)アドレスにある.
例えば,
int a[5];
for(int i=0; i<5; i++)
a[i] = 'A'+i;
が実行されると,メモリ配置は,
となる.ここで,要素の中身を示す a[i] に対して,& をつけるとその要素のアドレスになる.
配列の要素番号(index)が0から始まる理由
配列の要素にアクセスするための書式,
[要素番号]
は,実は「演算子」であり,その意味するところは,
「
先頭アドレスからのオフセット」すなわち
「先頭アドレスから『要素番号』分,離れたアドレスにある要素」
を示す.最初の要素は先頭アドレスにあるため,オフセットは0になる.この考え方は,C言語というよりも,デジタルコンピュータに共通のアドレス変換に関する考え方によるもので,次回に学習する
「ポインタ」の考え方に共通する.
次回のテーマである,ポインタを先取りして簡単に紹介すると,例えば,今回の配列の要素へのアクセス,
a[3]
は,ポインタを用いて,
*(a+3)
と書くことができる.ここで,aは配列名であり,配列の先頭アドレスを示し,+3はオフセット,すなわち (a+3)は先頭から3要素分離れた場所にある要素へのアドレス(参照)を示す.その前についている *(asterisk)
は,参照の中身を示す演算子(ポインタ)であり,上のテーブルの場合,
*(a+3) == 'D'
である.
このポインタを用いて上記の配列のメモリ配置を示すと以下のようになる.
このように配列とポインタは互換性がある.
【ヒント】配列表記との違いに気をつけよう
配列の添字範囲のチェックはされない
Cでは,配列の添字範囲が
正しいかどうかはプログラマに委ねられている.
配列の添字を超えるアクセスは,多くの場合,ソフトウェアの実行そのものに甚大な悪影響を及ぼす.
以下のプログラムを実行して,動作を確認せよ.
#include <stdio.h>
int main(void)
{
int x[10];
for(int i=0; i<100; i++){
x[i] = i*i;
printf("x[%d]=%d\n", i, x[i]);
}
}
※上記のプログラムのどこがいけなかったのだろうか?
※どこをどのように直せばよいだろうか?
※同じような誤りを犯さない様にするにはどうすればよいだろうか?
【ヒント】ソースコード中に直接「数字」を書いてはいけない!
配列をひとまとめにコピーすることはできない
配列は,一つ一つの変数のリストにすぎず,配列の名称は,配列の先頭アドレスを示すラベルにすぎない.
従って,要素一つ一つをコピーすることなしに,(代入文などによって)全体のコピーを作ることはできない
*.
int a[10], b[10];
b = a; // エラー.a, b共にラベルであって,変数ではない.
b[9] = a[9]; // 最後の要素一つがコピーされるだけ.
要素一つ一つをコピーする関数を作る.
void copy_array(int b[], int a[], int n){
for(int i=0; i<n; i++)
b[i] = a[i];
}
int main(void){
int a[100], b[100];
copy_array(b,a,100); // copy a to b
}
あるいは,バイナリコピー関数を利用してコピーする.
#include <string.h>
int main(void){
int a[100], b[100];
bcopy(a,b,sizeof(int)*100); // copy a to b
}
* C++では,定義次第では,代入によって多くのオブジェクトを一気にコピーする機能を作ることもできる.
文字列の扱い
文字列は配列の一種で,その要素は文字のキャラクタコードである
*.
Cは,他の言語と異なり,組込みの文字型や文字列型というものが存在しない.
Cでよく文字を格納するのに用いられる char型は,その名前から「文字型」であると思われがちであるが,それは誤りである.
char型は,単なる「8ビット整数型」であるに過ぎない.
このため,C言語では,文字として単純に asciiコードを用い,文字列の終端には,ヌル文字(null character)
を置くことで文字列の終端を認識させている.
* 半角文字の場合はASCIIコード.全角文字の場合は多くのコード体系が混在している.これが文字化けの起こる理由である.ここでは半角のみ取り扱う.
ヌル文字
数値の0のこと.文字として '\0' と書くこともある.
文字定数を用いて,文字列を作成する場合,
char* str = "Meiji University";
のようにダブルコーテーションで括って書く.ダブルコーテーションで括られた
文字列が持つ値は,その
文字列が格納される場所のアドレスである.従って,str には(文字コードなどではなく)アドレスが代入される.
また,Meiji University の文字数は 16文字である(ホワイトスペースも文字である)が,実際には
最後の y の後に
ヌル文字が自動的に追加されて 17文字になる.
このため,この文字列を格納する配列を宣言するときには,最低でも17文字以上の領域が必要になる.
char s[20]; // 最低でも17文字以上必要
strcpy(s,"Meiji University"); // char型の配列sに文字列17文字分をコピー
代表的な文字列操作関数
以下はすべて,string.h で定義されている文字列操作関数である.
strcpy
既に上の例で使用したが,文字をコピーする関数である.destinationに既に文字が入っている場合は,sourceの文字数分塗りつぶされる.返り値は作成された文字列への参照.
char* strcpy(char* destination, const char* source);
strcat
文字列を追加する関数.はじめにdestinationに入っている文字の後に文字列を追加する.返り値は作成された文字列への参照.
char* strcat(char* destination, const char* source);
strcmp
2つの文字列を比較する.返り値は,s1>s2で正の値,s1==s2で0,s1<s2で負の値.
int strcmp(const char* s1, const char* s2);
strlen
文字列の長さを返す.このとき,最後の
ヌル文字は長さに含まないので注意.
int strlen(const char* s);
多次元配列
Cでは,一次元の配列だけではなく,二次元以上の配列を作ることもできる.例えば,3行4列の表(テーブル)状の配列を作るには,
int a[3][4];
と宣言する.
この時の配列の概念図は以下のようになる.

数学でのそれに近く,最初の[]が行を,後の[]が列を表している.
多次元配列のメモリ配置
プログラム中で多次元配列を作ることができても,実際にはコンピュータのメモリのアドレスは1次元に並んでいる.
このため,コンパイラは多次元配列が宣言されると,1次元のメモリ上に以下のように配置する.

従って,この2次元配列 a[i][j] は,以下のように1次元配列としてアクセスすることもできる.
この様な,多次元配列を1次元配列としてアクセスすることは非常によく行われる.
const int M = 3;
const int N = 4;
int a2[M][N];
int* a1 = a2[0]; // 先頭アドレスをあわせる.ポインタを使用
x = a1[N*i+j]; // == a2[i][j] とおなじ
高次元配列の宣言
3次元以上の配列も,同様に作ることができる.
int a3[10][3][4];
これは,3x4の2次元配列が10セット,3次元的にレイヤになっていると考えるとわかりやすい.
配列の初期化
配列の初期化は以下のように行う.
int array1[] = { 1, 2, 3, 4, 5 };
この時,[]の中身を書かなくても良い.サイズはコンパイラが自動的に決定してくれる.これは次の文字列の初期化に利用するとメリットが実感できるだろう.
char str[] = "mojisuu wo kazoenakutemo yoi!";
後で文字列を変更した時にも,文字数をわざわざ数える必要がない.
多次元配列の場合は,最初の[]のみ,書かなくても許される.
int array2[][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{10, 11, 12}
};
Quiz 7(第7回の提出課題ではありません)
以下の問それぞれに対応するプログラムを作成しなさい.
- 文字をキーボードから入力して,配列 str[ ] に格納する関数,
入力された文字列 str[ ] を画面に表示する関数,および,str[ ] 中の文字数を
カウントする関数をそれぞれ作成し,以下のプログラムを完成せよ.
なお,main 関数は以下の通りとし,変更しないこと.
int main(void)
{
char str[100];
input(str); // キーボードから入力された文字列をstrに移します.
disp(str); // strに格納された文字列を画面に表示します.
printf("文字数は %d です.\n", count(str));
return 0;
}
解答例 Q7-1

【ヒント】引数の (char* str) は,(char str[ ]) とほぼ同じ意味で,どちらでも使えます.
-
キーボードから文字を入力した時(英数字,大文字小文字,記号を問わない),
何の文字が何個ずつ使われているかを表示するプログラムを作成せよ.
//実行例
> 07-2
Type a string: ababccc
a: 2
b: 2
c: 3
解答例 Q7-2

【ヒント】上の解答例では,ループのスタート文字を'A'にしてしまっていますが,
これだと一部の文字や記号しかスキャンされません.たとえばスペース' '(==0x20)から始めて,
ティルダ'~' (==0x7E)までスキャンすると,ほぼすべての文字をカウントできます.
アスキーコード表をよく見て考えてみましょう.
-
以下の表に従い,各科目の平均点と,各個人の平均点を計算して表示するプログラムを作成せよ.
表は,二次元配列の初期化を用いてプログラム中に記入せよ.

解答例 Q7-3

- キーボードから文字(半角英数字記号)を入力し,
それをアスキーコード順に並び替えて画面に出力するプログラムを作れ.
但し,並び替えを行う関数 void sort(char s[ ]) を作成すること.
解答例 Q7-4

- 【チャレンジ問題】
文字列をキーボードより入力させ,リターンを押すたびに
それまで入力した文字列すべてが出力されるようにせよ.
"end"の文字列を入力すると,入力を終了し,
それまで入力してきたすべての文字列と,
その文字列を反転させた文字列が出力されるようにせよ.
実行例
> 07-5
>> abc
abc
>> 123
abc 123
>> pqr
abc 123 pqr
>> end
abc 123 pqr
rqp 321 cba
解答例 Q7-5

上の解答例にバグが発見されました
14行目.wordが一つも無いときにendが入力されると暴走する
ここは以下のように修正すべきでしょう.
14 if(!strcmp("end",word)){
15 int len = strlen(str)-1;
16 str[(len<0 ? 0 : len)] = 0; // 0以下にならないようにする
17 break;
18 }
Assingment(課題)7
【課題提出における注意】
- すべてのプログラムは,必ずコンパイル&実行し,正しく機能することをチェックしてから提出すること.
- プログラムは正しくインデントさせること.
Assignment 7のページ
課題提出
作成したプログラムを一つのファイル(*.zip)にしてOh-o! Meijiシステムレポート第7回に提出する.
提出するすべてのファイルには,年組番号 氏名を記入すること.
締め切りは日曜日よる0時までとする.
【注意】すべてのプログラムは,必ずコンパイル&実行し,正しく機能することをチェックしてから提出すること.
【注意】プログラムは正しくインデントさせること.
【注意】すべてのファイル名は半角のみで構成すること.
【注意】提出時のファイル名命名規則などを守っていない人がいる.これらのファイルは採点されないので注意すること.
【お願い】提出するファイルは,
フォルダに入れないでそのまま圧縮してください.