4.配列と文字・文字列

Copyright(C) 16Jan2003 coskx

このページでは「配列」と「文字・文字列」について解説する。
ここで修得して欲しい内容は以下の通りである。
1.配列の宣言の仕方を修得する。
2.配列を使用することのメリットを理解する。
3.forなどの繰返しで配列要素を順に操作する方法を理解する。
4.配列ぼ大きさ(要素数)と要素番号の関係を理解する。
5.ソートのアルゴリズムを追跡できる。(バブルソート,単純ソート)
6.文字が文字コードで内部表現されていることを理解する。
7.文字列の構造(終端に'\0')を理解する。
8.二次元配列のイメージと表現方法を理解する。


  4.1 配列

4.1.1  配列を利用しない場合の例

複数の身長の平均値を求めることを考えてみよう。これまでの知識では次のようになる。
(身長データはプログラム中で定められていて変更できないが,scanfを使えば,身長データを実行時に設定することが可能である。)
ところで,この例では5名の身長データを扱ったが,1000名のデータを扱おうとしたら,1000個の変数が必要になってします。

List 4.1.1 平均値の計算プログラムと実行結果

#include <stdio.h>

int main()
{
    double heightA=173.2; /*cm*/
    double heightB=168.5; /*cm*/
    double heightC=178.1; /*cm*/
    double heightD=183.7; /*cm*/
    double heightE=164.2; /*cm*/
    double sum; /*合計*/
    double average; /*平均身長 cm*/
    sum=0.0;
    sum+=heightA;  /*sum=sum+heightA と同じ*/
    sum+=heightB;
    sum+=heightC;
    sum+=heightD;
    sum+=heightE;
    average=sum/5.0;
    printf("average=%f\n",average);
    return 0;
}
/*****実行結果*****
average=173.540000
*******************/


4.1.2 配列を利用した例

もし次のようにひとつの変数名で,内部にたくさんの部屋を持つ変数が使えたら都合がよい。
各部屋は部屋番号がついていて,部屋番号を指定してデータを保存できるようにしたい。

あったらいいなプログラムの一部

    height[0]=173.2; /*cm*/
    height[1]=168.5; /*cm*/
    height[2]=178.1; /*cm*/
    height[3]=183.7; /*cm*/
    height[4]=164.2; /*cm*/
    sum=0.0;
    sum+=height[0];
    sum+=height[1];
    sum+=height[2];
    sum+=height[3];
    sum+=height[4];
    average=sum/5.0;

この例では変数名「height」は,1つの名前しか持たないが,内部に値を格納するための複数の部屋を持っている変数として使われている。そして0から4までの部屋番号を持っている。
このように内部に値を格納する複数の部屋を持っている変数は「配列」と呼ばれている。また個々の部屋のことは「要素」または「配列要素」と呼ばれ,0から4までの部屋番号のことは,「配列要素番号」または単に「要素番号」と呼ばれる。

変数宣言部で

double height[5];

のように書けば,内部に5つの部屋を持つdouble型配列変数 height が宣言される。
配列要素数5のdouble型配列変数 height と表現される。)

5つの部屋の部屋番号は0号室から4号室である。5号室は存在しないことに注意しよう。
(このことは配列要素番号は0から4であると表現される。)

この配列を利用してList 4.1.1を書き直してみよう。

List 4.1.2 平均値の計算プログラムと実行結果

#include <stdio.h>

int main()
{
    double height[5]; /* 配列宣言 */
    double sum; /*合計*/
    double average; /*平均身長 cm*/
    height[0]=173.2; /*cm*/
    height[1]=168.5; /*cm*/
    height[2]=178.1; /*cm*/
    height[3]=183.7; /*cm*/
    height[4]=164.2; /*cm*/
    sum=0.0;
    sum+=height[0];
    sum+=height[1];
    sum+=height[2];
    sum+=height[3];
    sum+=height[4];
    average=sum/5.0;
    printf("average=%f\n",average);
    return 0;
}
/*****実行結果*****
average=173.540000
*******************/

配列 double height[5]; が宣言されたときの配列のイメージ(要素番号が0から始まっている)
(未使用と言っても0が入っているとは限らず,何らかの意味不明の値が入っている。)

要素番号
(部屋番号)
0
1
2
3
4
格納されている値
未使用
未使用 未使用 未使用 未使用

このままではあまり「ごりやく」を感じない。さらに要素番号を変数iで与えるように書き直して,
変数の初期化(最初の値の代入)を宣言の時に行なうと次のようになる。

List 4.1.3 平均値の計算プログラムと実行結果

#include <stdio.h>

int main()
{
    double height[5]={
        173.2, 168.5, 178.1, 183.7, 164.2
    };/*cm*/ /*double型配列height(部屋数5)を用意して,第0要素から第4要素まで(0号室から4号室まで)
               順に173.2,168.5,178.1,183.7,164.2 の5つの値
を代入しておくの意味*/
    double sum; /*合計*/
    double average; /*平均身長 cm*/
    int i;
    sum=0.0;
    for (i=0;i<5;i++) {
        sum+=height[i];
    }
    average=sum/5.0;
    printf("average=%f\n",average);
    return 0;
}
/*****実行結果*****
average=173.540000
*******************/

説明

    for (i=0;i<5;i++) {
        sum+=height[i];
    }


    sum+=height[0];
    sum+=height[1];
    sum+=height[2];
    sum+=height[3];
    sum+=height[4];
と同じ作業内容となる。

追加説明

    double height[5]={
        173.2, 168.5, 178.1, 183.7, 164.2
    };/*cm*/
のところは
    double height[]={
        173.2, 168.5, 178.1, 183.7, 164.2
    };/*cm*/
と書いても良い。配列の初期化(最初の値の代入)がある場合は,配列の大きさを省略することが出来る。
コンパイラが自動的に配列の大きさを決めてくれる。この場合は5に設定される。
多数のデータを書くと,人間は数え間違いをするので,配列の大きさの決定はコンパイラに任せるのが普通である。
もし大きさをプログラム中で知りたい場合はsizeof(height)/sizeof(double)で取得できる。
sizeof(sizeof演算子)を使うと,ある変数あるいは型が何バイトで出来ているのか求めることができる。
例 sizeof(char)は1,sizeof(short int)は2,sizeof(long int)は4になる。

 


また後からデータが追加される可能性がある場合などで,配列の大きさに余裕を持たせたい場合は
    double height[10]={
        173.2, 168.5, 178.1, 183.7, 164.2
    };/*cm*/
と書いても良い。大きさ10(部屋数10)の配列が出来,前半の5つだけ値が入っていて,後の5つは未使用となる。

配列
double height[5]
={
        173.2, 168.5, 178.1, 183.7, 164.2
    }
;
が宣言されたときの配列のイメージは次のようになる。

要素番号
(部屋番号)
0
1
2
3
4
格納されている値
173.2 168.5 178.1 183.7 164.2

配列
double height[]
={
        173.2, 168.5, 178.1, 183.7, 164.2
    }
;
が宣言されたときの配列のイメージは,上と同じになる。

要素番号
(部屋番号)
0
1
2
3
4
格納されている値
173.2 168.5 178.1 183.7 164.2

配列

double height[10]={
        173.2, 168.5, 178.1, 183.7, 164.2
    }
;
が宣言されたときの配列のイメージ (未使用の部屋には何らかの意味不明の値が入っていると考えていたほうが安全。)

要素番号
(部屋番号)
0
1
2
3
4
5
6
7
8
9
格納されている値
173.2 168.5 178.1 183.7 164.2未使用 未使用 未使用 未使用 未使用

追加説明
例えば変数宣言部で
double height[1000];
のように宣言すると,heightという変数は1000個の要素(部屋)を持つ変数ということになる。
配列要素には番号が付いていて,「第0要素」「第1要素」「第2要素」......「第998要素」「第999要素」という
1000個の要素となっていて,それぞれの要素がdouble型の変数になっている。
配列要素番号は,「第1要素」から「第1000要素」ではなく,「第0要素」から「第999要素」になっていることに
注意すること。

行ないたいこと

C言語での記述

「第54要素(54号室)」に値160.0を格納したい  height[54]=160.0;
「第200要素(200号室)」に格納されている値を「第300要素 (300号室)」に格納したい  height[300]=height[200];
「第159要素(159号室)」の値を画面に表示したい  printf("%f\n",height[159]);
キーボードから値を入力し,「第799要素(799号室)」に格納したい  scanf("%lf",&height[799]);

height[0],height[1],height[2],... の1つ1つのことを配列heightの要素といい,0,1,2...を要素番号という。
1000は配列heightの要素数という。

注意

height[1000]=160.0;
は「1000号室」に160.0を代入しようとしているが,
この例では「1000号室」が存在しないので誤りである。


4.1.3 配列の活用

配列にデータが入っていると,最大値,最小値も簡単に求められる。
先頭データを最大値,最小値の候補にしておき,ループ中でよりふさわしい候補が見つかったら候補を入れ替える作業を行ない,すべてのデータについてこの作業が終了すると,最大値,最小値が見つかる。

List 4.1.4 平均値の計算と最大最小を求めるプログラムと実行結果

#include <stdio.h>

int main()
{
    double height[5]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double sum; /*合計*/
    double average; /*平均身長 cm*/
    double tallest; /*最大身長が入るべき変数*/
    double shortest; /*最小身長が入るべき変数*/
    int i;
    sum=0.0;
    tallest=height[0];  /*仮の最大値*/
    shortest=height[0]; /*仮の最小値*/
    /*tallest=shortest=height[0]; は上の2行と同じ意味になる*/
    for (i=0;i<5;i++) {
        sum+=height[i];
        if (tallest<height[i]) {
            tallest=height[i];
        }
        if (height[i]<shortest) {
            shortest=height[i];
        }
    }
    average=sum/5.0;
    printf("average =%f\n",average);
    printf("tallest =%f\n",tallest);
    printf("shortest=%f\n",shortest);
    return 0;
}

/*****実行結果*****
average =173.540000
tallest =183.700000
shortest=164.200000
*******************/


プログラミング時に正確なデータ数がわかっていない場合のプログラミング

複数の身長データをキーボードから入力する。プログラミング時にはデータ数は100未満であることしかわかっていないとする。
負の数が入力されたら,その値は身長データとしては扱わず,入力の終わりを意味する約束にしておく。
実行例ではデータ数は5である。

List 4.1.5 キーボード入力値の平均値の計算プログラムと実行結果

#include <stdio.h>
#include <stdlib.h> /*exit(1)のために必要*/

int main()
{
    double height[100];/*cm*/
    double sum; /*合計*/
    double average; /*平均身長 cm*/
    double tallest; /*最大身長*/
    double shortest; /*最小身長*/
    int number; /*データ総数*/
    int i;
    i=0;
    printf("身長データを入力(負なら終了) >>");
    scanf("%lf",&height[i]);
    while (0.0<=height[i]) {
        i++;
        printf("身長データを入力(負なら終了) >>");
        scanf("%lf",&height[i]);
    }
    if (i==0) exit(1); /*強制終了*/
    number=i;
    sum=0.0;
    tallest=height[0];
    shortest=height[0];
    /*tallest=shortest=height[0]; と書いたら上の2行と同じ意味になる*/
    for (i=0;i<number;i++) {
        sum+=height[i];
        if (tallest<height[i]) {
            tallest=height[i];
        }
        if (height[i]<shortest) {
            shortest=height[i];
        }
    }
    average=sum/number; /*double型÷int型なので,numberはdouble型に自動的に変換され,正しい計算が行なわれる*/
    printf("average =%f\n",average);
    printf("tallest =%f\n",tallest);
    printf("shortest=%f\n",shortest);
    return 0;
}

/*****実行結果*****
身長データを入力(負なら終了) >>173.2
身長データを入力(負なら終了) >>168.5
身長データを入力(負なら終了) >>178.1
身長データを入力(負なら終了) >>183.7
身長データを入力(負なら終了) >>164.2
身長データを入力(負なら終了) >>-1
average =173.540000
tallest =183.700000
shortest=164.200000
*******************/


4.1.4 配列利用上の注意(不幸なエラーに落ち込む前に)

配列の要素数を無視したプログラムではどうなるか考えてみよう。

List 4.1.6 配列の要素数を無視した配列の読み出し

このプログラムは,文法に反していないので,コンパイル時にはエラーにならない。
しかし,array[5],array[6]は存在していないが,変な値が表示される。
この変な値は,その変数が使用中のメモリーの使用状況に依存するため,いつも同じ値が表示されるとは限らない。

#include <stdio.h>

int main()
{
    int arry[5]={101,102,103,104,105};
    int i;
    for (i=0;i<7;i++) {
        printf("i=%d arry[%d]=%d\n",i,i,arry[i]);
    }
    return 0;
}

/******実行結果*****
i=0 arry[0]=101
i=1 arry[1]=102
i=2 arry[2]=103
i=3 arry[3]=104
i=4 arry[4]=105
i=5 arry[5]=1245120
i=6 arry[6]=4198731
*******************/

List 4.1.7 配列の要素数を無視した配列の書き込み(その2)

このプログラムは,文法に反していないので,コンパイル時にはエラーにならない。
array[5],array[6]は存在していない。array[5],array[6]の位置にある
メモリは別の用途に用いられているので,実行時に異常な現象が起こる。
なにが起こるかは状況に依存するため,確定していない。
この例の場合では,このメモリはプログラム実行に重要な用途に使われて
いたようで,プログラムが異常終了した。

#include <stdio.h>

int main()
{
    int arry[5];
    int i;
    for (i=0;i<7;i++) {
        arry[i]=100+i;
        printf("i=%d arry[%d]=%d\n",i,i,arry[i]);
    }
    return 0;
}

/******実行結果*****
i=0 arry[0]=100
i=1 arry[1]=101
i=2 arry[2]=102
i=3 arry[3]=103
i=4 arry[4]=104
i=5 arry[5]=105
i=6 arry[6]=106
ここでエラーが発生し,異常終了
*******************/

List 4.1.8 配列の要素数を無視した配列の書き込み (その2)

このプログラムは,文法に反していないので,コンパイル時にはエラーにならない。
何事も無かったかのように動作する場合があるが,
得られた結果は誤りである。(arry1が壊されている)
これは,arry2が使用しているメモリの直後にarry1が存在したためこのような現象になっている。
もし,arry2が使用しているメモリの後ろに変数iがあったら繰返し動作に異常が生ずると考えられる。
この実行上のエラーは気づきにくいので注意

#include <stdio.h>

int main()
{
    int arry1[5];
    int arry2[5];
    int arry3[5];
    int i;
    for (i=0;i<5;i++) {  /*arry1,2,3に同じ値を代入*/
        arry1[i]=arry2[i]=arry3[i]=i;
    }
    for (i=0;i<5;i++) {   /*arry1,3を表示*/
        printf("arry1[%d],arry3[%d]=%d %d\n",i,i,arry1[i],arry3[i]);
    }
    for (i=0;i<7;i++) {  /*array2の値を変更 要素数を無視している*/
        arry2[i]=100+i;
        printf("i=%d arry2[%d]=%d\n",i,i,arry2[i]);
    }
    for (i=0;i<5;i++) {   /*arry1,3を表示 arry1,3は値を変更していないので変化するはずはないのだが*/
        printf("arry1[%d],arry3[%d]=%d %d\n",i,i,arry1[i],arry3[i]);
    }
    return 0;
}

/******実行結果*****
arry1[0],arry3[0]=0 0
arry1[1],arry3[1]=1 1
arry1[2],arry3[2]=2 2
arry1[3],arry3[3]=3 3
arry1[4],arry3[4]=4 4
i=0 arry2[0]=100
i=1 arry2[1]=101
i=2 arry2[2]=102
i=3 arry2[3]=103
i=4 arry2[4]=104
i=5 arry2[5]=105
i=6 arry2[6]=106
arry1[0],arry3[0]=105 0
arry1[1],arry3[1]=106 1
arry1[2],arry3[2]=2 2
arry1[3],arry3[3]=3 3
arry1[4],arry3[4]=4 4
*******************/


配列に関する大事な注意事項

配列のサイズを超える番号の配列要素に対する操作は,コンパイル時にはエラーにならないが,実行時に誤った実行結果を得たり,予期せぬ重大な事態が起きたりする。



 課題4 その1

(1)20点満点の英単語テストの得点データが10人分あり,これがint型配列score[10]に次のように格納されているものとする。
    int score[10]={15,8,12,18,20,20,9,16,20,17};
これを用いて,次のように棒グラフに表すプログラムを作りなさい。 (p04ex01.c)
次の実行結果をコピー&ペーストでエディタに貼り付けて,スペースの数をよく見て,同じ表示になるようにプログラミングします。
ヒント

<<実行結果>>
 1 15 :****|****|****|
 2  8 :****|***
 3 12 :****|****|**
 4 18 :****|****|****|***
 5 20 :****|****|****|****|
 6 20 :****|****|****|****|
 7  9 :****|****
 8 16 :****|****|****|*
 9 20 :****|****|****|****|
10 17 :****|****|****|**

(2)平年の1月から12月までの各月に含まれる日数がint型配列numberofdays[12]に次のように格納されているものとする。 (p04ex02.c)
    int numberofdays[12]={31,28,31,30,31,30,31,31,30,31,30,31};
キーボードから月日を次のように入力させ,1月1日から数えて何日目か答えるプログラムを作りなさい。
次の例で(1月25日と3月2日,4月10日,12月31日)で検証しなさい。
実行結果を次の例と同じ様に表示しなさい。
ヒント

<<実行例1>>
month day = 1 25
25

<<実行例2>>
month day = 3 2
61

<<実行例3>>
month day = 4 10
100

<<実行例4>>
month day = 12 31
365



  4.2 ソート(並び替え)

ソートを考える前に,変数の中身の交換について考えよう。 → 変数の中身の交換

配列を使うと,ソート(「大きい順に並び替え」または「小さい順に並び替え」)が出来る。
配列内のデータの交換だけで,第0要素には最大値を求め,第1要素には第1要素以降の最大値を,第2要素には第2要素以降の最大値を...
という作業を繰り返して行なえば,ソート(並び替え)が出来る。

List 4.2.1 ソートのプログラム

#include <stdio.h>

int main()
{
    double height[5]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double tmp;
    int i,j;
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    /*ソート(大きい順に並び替え)部 第0作業
    (第0要素が最大値になるようにする)*/
    for (j=1;j<5;j++) {
        if (height[0]<height[j]) {
            tmp=height[0];
            height[0]=height[j];
            height[j]=tmp;
        }
    }
    /*ソート(大きい順に並び替え)部 第1作業
    (第1要素が第1要素以降で最大値になるようにする)*/
    for (j=2;j<5;j++) {
        if (height[1]<height[j]) {
            tmp=height[1];
            height[1]=height[j];
            height[j]=tmp;
        }
    }
    /*ソート(大きい順に並び替え)部 第2作業
    (第2要素が第2要素以降で最大値になるようにする)*/
    for (j=3;j<5;j++) {
        if (height[2]<height[j]) {
            tmp=height[2];
            height[2]=height[j];
            height[j]=tmp;
        }
    }
    /*ソート(大きい順に並び替え)部 第3作業
    (第3要素が第3要素以降で最大値になるようにする)*/
    for (j=4;j<5;j++) {
        if (height[3]<height[j]) {
            tmp=height[3];
            height[3]=height[j];
            height[j]=tmp;
        }
    }
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}

/*****実行結果****************
173.2 168.5 178.1 183.7 164.2
183.7 178.1 173.2 168.5 164.2
******************************/

補足説明

if (height[0]<height[j]) {
    tmp=height[0];
    height[0]=height[j];
    height[j]=tmp;
}

「もし(height[0]<height[j])なら
height[0]の格納値とheight[j]の格納値を交換する」
の意味となる。

もし
if (height[0]<height[j]) {
    height[0]=height[j];
    height[j]=height[0];
}
と書いたら交換できるだろうか?


List4.2.1は二重ループにするとList4.2.2のようにプログラムが短くなる。
よく理解できない時は次のプログラムリストList4.2.3とその次のプログラムリストList4.2.4を見てみよう。
このソート方法は「単純選択ソート」あるいは「選択ソート」と呼ばれる。
ソートアルゴリズムの可視化アプリで鑑賞しよう

List 4.2.2 ソートのプログラム

#include <stdio.h>

int main()
{
    double height[5]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double tmp;
    int i,j;
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    /*ソート(大きい順に並び替え)部*/
    for (i=0;i<4;i++) {
        for (j=i+1;j<5;j++) {
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
        }
    }
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}

/*****実行結果****************
173.2 168.5 178.1 183.7 164.2
183.7 178.1 173.2 168.5 164.2
******************************/

動作理解や動作が思い通りでない時,プログラム中にprintfを使って検査したい変数値を表示させることがある。
これをスナップショットと呼ぶが,この方法で二重ループ中のiとjを表示してみよう。

List 4.2.3 ソートのプログラム中の二重ループでスナップショット

#include <stdio.h>

int main()
{
    double height[5]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double tmp;
    int i,j;
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    /*ソート(大きい順に並び替え)部*/
    for (i=0;i<4;i++) {
        for (j=i+1;j<5;j++) {
            printf("(%d,%d) ",i,j);
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
        }
        printf("\n");
    }
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}

/*****実行結果****************
173.2 168.5 178.1 183.7 164.2
(0,1) (0,2) (0,3) (0,4)
(1,2) (1,3) (1,4)
(2,3) (2,4)
(3,4)
183.7 178.1 173.2 168.5 164.2
******************************/

もうすこし,詳しくスナップショットを入れてみよう。実行結果の流れを見て,自分の理解が正しいかどうかチェックしなさい。

List 4.2.4 ソートのプログラム中の二重ループでスナップショット

#include <stdio.h>

int main()
{
    double height[5]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double tmp;
    int i,j;
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    /*ソート(大きい順に並び替え)部*/
    for (i=0;i<4;i++) {
        for (j=i+1;j<5;j++) {
           
printf("(%d,%d) ",i,j);
           
printf("[%.1f,%.1f]-> ",height[i],height[j]);
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
            printf("[%.1f,%.1f]\n",height[i],height[j]);
        }
    }
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}

/**********実行結果****************
173.2 168.5 178.1 183.7 164.2
(0,1) [173.2,168.5]-> [173.2,168.5]
(0,2) [173.2,178.1]-> [178.1,173.2]
(0,3) [178.1,183.7]->
[183.7,178.1]
(0,4) [183.7,164.2]-> [183.7,164.2]
(1,2) [168.5,173.2]->
[173.2,168.5]
(1,3) [173.2,178.1]->
[178.1,173.2]
(1,4) [178.1,164.2]-> [178.1,164.2]
(2,3)
[168.5,173.2]-> [173.2,168.5]
(2,4) [173.2,164.2]-> [173.2,164.2]
(3,4) [168.5,164.2]-> [168.5,164.2]
183.7 178.1 173.2 168.5 164.2
***********************************/

練習4.1

次のプログラムの実行結果を予想しなさい。
List 4.2.5 自力追いかけプログラム  

#include <stdio.h>

int main()
{
    int exam[10]={
        70,64,80,95,84
    };
    int tmp;
    int i,j,k;
    for (i=0;i<5;i++) {
        printf("%d ",exam[i]);
    }
    printf("\n");
    for (i=0;i<4;i++) {
        for (j=0;j<4-i;j++) {
            k=j+1;
            printf("[exam[%d]=%d,exam[%d]=%d]-> ",j,exam[j],k,exam[k]);
            if (exam[j]<exam[k]) {
                tmp=exam[j];
                exam[j]=exam[k];
                exam[k]=tmp;
            }
            printf("[exam[%d]=%d,exam[%d]=%d]\n",j,exam[j],k,exam[k]);
        }
    }
    for (i=0;i<5;i++) {
        printf("%d ",exam[i]);
    }
    printf("\n");
    return 0;
}

 

このソートは
「バブルソート」
と呼ばれる

ソートアルゴリズムの可視化アプリ
で鑑賞しよう


補足説明
 
配列を他の配列にコピーする場合は要素ごとにすべての要素をループを使ってコピーしなければならない。

List 4.2.6 配列を他の配列にコピーする場合

#include <stdio.h>

int main()
{
    int a[4]={3,5,7,9};
    int b[4];
    int i;
    for (i=0;i<4;i++)  b[i]=a[i];
    for (i=0;i<4;i++)  printf("%d %d %d\n",i,a[i],b[i]);
    return 0;
}

/********************* 実行結果 ********************
0 3 3
1 5 5
2 7 7
3 9 9
****************************************************/

注意 次のような記述は出来ない
    b=a; のように記述しても配列全体の代入はできない。
  この点がC言語の弱点である。
    b[4]=a[4]; のように記述しても配列全体の代入はできない。
  aの部屋番号4の格納値をbの部屋番号4へ代入するという意味になってしまう。
  そもそも,配列要素は0~3までしかない。

 

 課題4 その2

(3)次の配列を小さい順に並べて表示しなさい。(p04ex03.c)

int myarray[100]={
    467, 41,334,500,169,724,478,358,962,464,
    705,145,281,827,961,491,995,942,827,436,
    391,604,902,153,292,382,421,716,718,895,
    447,726,771,538,869,912,667,299, 35,894,
    703,811,322,333,673,664,141,711,253,868,
    547,644,662,757, 37,859,723,741,529,778,
    316, 35,190,842,288,106, 40,942,264,648,
    446,805,890,729,370,350,  6,101,393,548,
    629,623, 84,954,756,840,966,376,931,308,
    944,439,626,323,537,538,118, 82,929,541
}

並べ替え前の様子と並べ替えた後の様子をよくわかるように下に示すように表示しなさい。
ただし,得られた実行結果は,値10個ごとに改行しなさい。

before
467  41 334 500 169 724 478 358 962 464
705 145 281 827 961 491 995 942 827 436
  :

after
  6  35  35  37  40  41  82  84 101 106
118 141 145 153 169 190 253 264 281 288
  :


  4.3 文字の内部表現と,char型変数

コンピュータは数値なら変数に格納することが出来る。文字も数値に変換して変数に格納すればよい。
文字を数値に変換したものを文字コードと呼ぶ。この文字コード表を見ると,どの文字がどのような文字コードになっているかわかる。
例えば「Hello!」は「72,101,108,108,111,33」(十進法表示)のようになる。

コンピュータプログラムでは十六進法表示がよく用いられ,「48,65,6C,6C,6F,21」とも表現される。
プログラム中で16進法表示を書くときは,「0x」を前につける。(例えば、0x48,0x65,0x6C...)

まずint型変数にこれらの文字コードを代入し,表示するプログラムを作ってみよう。

List 4.3.1では,int型の変数a,b,cを書式を%d,%x,%cのように変えながら表示している。
%xは十六進法表示で,%cは変数値を文字コードと考え,その文字コードが表す文字を表示しているのがわかる。

List 4.3.1 文字の扱い
#include <stdio.h>

int main()
{
    int a=67;
    int b=65;
    int c=84;
    printf("%d %d %d\n",a,b,c);
    printf("%x %x %x\n",a,b,c);
    printf("%c %c %c\n",a,b,c);
    return 0;
}
実行結果
67 65 84
43 41 54
C A T

List4.3.2ではint型の変数a,b,cに値を十六進法表示で設定している。
printfの表示は,List 4.3.1とまったく同じになることがわかる。

List 4.3.2 文字の扱い
#include <stdio.h>

int main()
{
    int a=0x43;
    int b=0x41;
    int c=0x54;
    printf("%d %d %d\n",a,b,c);
    printf("%x %x %x\n",a,b,c);
    printf("%c %c %c\n",a,b,c);
    return 0;
}
実行結果
67 65 84
43 41 54
C A T

List4.3.3ではint型の変数a,b,cに値を文字定数で設定している。
printfの表示は,List 4.3.1とまったく同じになることがわかる。

List 4.3.3 文字の扱い
#include <stdio.h>

int main()
{
    int a='C';
    int b='A';
    int c='T';
    printf("%d %d %d\n",a,b,c);
    printf("%x %x %x\n",a,b,c);
    printf("%c %c %c\n",a,b,c);
    return 0;
}
実行結果
67 65 84
43 41 54
C A T

List4.3.4はint型配列に格納されている値を%cの書式で表示したものである。

List 4.3.4 文字の扱い

#include <stdio.h>

int main()
{
    int xx[]={
        72,101,108,108,111,33
    }; /*十進数の表現*/
    int yy[]={
        0x48,0x65,0x6C,0x6C,0x6F,0x21
    }; /*十六進数の表現*/
    int i;
    for (i=0;i<6;i++) {
        printf("%c",xx[i]);
    }
    printf("\n");
    for (i=0;i<6;i++) {
        printf("%c",yy[i]);
    }
    printf("\n");
    return 0;
}

/****実行結果****
Hello!
Hello!
*****************/

補足説明
printf("%c",yy[i]);
で「%c」は変数yy[i]の値を文字コードに見立てて,
その文字コードに対応する文字を表示するという意味になる。

文字コードをint型配列に格納するというのは通常は行なわれない。
これはテストプログラムである。まねをしないこと。
文字コードはchar型配列に格納するのが普通である。

文字コード表の文字は32から126の数値で出来ているので,1バイト(=8ビット)あれば格納できる。
そのため,文字コードを格納するにはint型変数ではなく
char型変数が用いられる。
どちらも整数を保存できるが,char型変数を使う方がメモリを節約できるからである。

今後は文字コードを保存するためにはchar型変数を使うこととする。(文字コードは整数である)
char型変数でもint型変数と同じようにforループのカウンタ変数になれる。すなわち
char ch;
for(ch=0x30; ch<0x39; ch++) のように使うことができる。

chをchar型変数として;
ch=65;
ch=0x41;
ch='A';
はすべて同じ意味となる。(文字コード表参照)
'A'のような表現は文字定数と呼ばれる。文字定数はint型の値で内部では文字コードになっている。

文字は「'(シングルクォーテーション)」で囲む。

0x41は16進法の41のことであり,'A'と書いたら65あるいは0x41と書いてあるのと同じである。
同様に
if (ch==65) {
if (ch==0x41) {
if (ch=='A') {
は同じ意味を表す。(文字コード表参照)

また
ch=0x48;
printf("%d %x %c\n",ch,ch,ch); (同じ値を10進法,16進法,文字として表している。)
では「72 48 H」が表示される。(文字コード表参照)

「%x」は整数を16進法で表示する書式である。

コンピュータは,変数に保存されている整数値に関して,整数なのか文字コードなのかは理解していない。
書式指示によって値として扱ったり,文字として扱ったりする。

練習4.2

次のプログラムの実行結果を予想しなさい。
List 4.3.5 自力追いかけプログラム  

#include <stdio.h>

int main()
{
    char mychar;
    for (mychar=65;mychar<68;mychar++) {
        printf("%d %x %c\n",mychar,mychar,mychar);
    }
    for (mychar=0x44;mychar<0x47;mychar++) {
        printf("%d %x %c\n",mychar,mychar,mychar);
    }
    for (mychar='G';mychar<'J';mychar++) {
        printf("%d %x %c\n",mychar,mychar,mychar);
    }
    return 0;
}

 

数値と数字の違い (試験でよくある間違え)

char ch;
に対して
ch=6;

ch='6';
は異なる。

ch='6';はch=0x36;またはch=54;と同じになる。

数値と数字の違い (ちょっとした工夫)

(1)数字→数値
char ch;
int value;
に対して
ch='6';
value=ch-'0';
でvalueの値は6になる。(文字 コード表参照)
この表現は「数値を表す文字コード」を「数値」に変換するのによく使われる。

(2)数値→数字
逆に
value=8;
ch='0'+value;
でchは文字'8'を表す値(8の文字コード)になる。(文字コード表参照)

(3)文字コードが数字を表しているかどうかの検査
「chが数字文字を表しているなら・・・・」は
if (ch=='0'||ch=='1'||ch=='2'||ch=='3'||ch=='4'||ch=='5'||ch=='6'||ch=='7'||ch=='8'||ch=='9') {
でも良いが,次のように表す。
if ('0'<=ch && ch<='9') {

「chが数字文字を表していないなら・・・・」は
次のように表す。
if (ch<'0' || '9'<ch) {

C言語の文字操作では,「C.ライブラリ関数の説明」 中の「C.3 文字操作関数」に便利な関数が紹介されており,よく使われる。
C言語の関数はまだ学んでいないので,この段階では,便利な命令がある程度の認識でよい。

 課題4 その3

(4)char型変数xを用い,xの値を65から74まで変化させ,
printf(" %c [%2x]\n",x,x)
を用いて部分文字コード表を作りなさい。(p04ex04.c)
ヒント char型変数であっても,for (ch=0x41; ch<=0x4a; ch++) のようにforループを作ることができる。
次のような実行結果が得られるようにしなさい。

A [41]
B [42]
C [43]
D [44]
E [45]
F [46]
G [47]
H [48]
I [49]
J [4a]

(5)char型変数xを用い,xの値を48から57まで変化させ,
printf(" %c [%2x]",x,x)
を用いて部分文字コード表を作りなさい。
ただし,改行位置を工夫し,1行に4データを表示しなさい。(p04ex05.c)

(6)char型変数xを用い,xの値を32から126まで変化させ,
printf(" %c [%2x]",x,x)
を用いて部分文字コード表を作りなさい。ただし,改行位置を工夫し,1行に8データを表示しなさい。(p04ex06.c)


 

  4.4 文字列

C言語には文字列型変数というのはないので,文字列はchar型の配列に格納する。
文字列は一単語のみで出来ているとは限らない。

printfで文字列を出力するには「%s」の書式を用い,char型の配列の名前を書く。

puts(char型の配列)でも文字列を表示出来る。この時は自動的に改行される。

List 4.4.1 文字列の出力

/*文字列の出力*/
#include <stdio.h>

int main()
{
    char mystring[20]="Hello, world.";
    char mystring2[]="Tomorrow is another day.";
    printf("mystring=<<%s>>\n",mystring);
    puts(mystring2); /*文字列mistring2を表示する*/
    puts(mystring2);
    return 0;
}

/******* 実行結果 **********
mystring=<<Hello, world.>>
Tomorrow is another day.
Tomorrow is another day.
****************************/

文字列は「"(ダブルクォーテーション)」で囲む。
(文字は「'(シングルクォーテーション)」で囲んだ。)

文字列の入力はgetsを用いる。ただし入力される文字数より大きな要素数の配列を用意しておかなければならない。

文字列の末尾には最終文字の後に「おわりのしるし」の0('\0'とも表現される)が格納されている。

この文字列の末尾の決まりを用いると,文字列のコピーが出来る。

List 4.4.2 文字列の入出力と文字列のコピー

/*文字列の入出力と文字列のコピー*/
#include <stdio.h>

int main()
{
    char mystring[200];
    char mystring2[200];
    int i;
    printf("文字列を入力してください\n");
    gets(mystring);        /*キーボードから文字列を入力してもらい,mystringに取り込む*/
    /*文字列のコピー開始*/
    i=0;
    while (mystring[i]!='\0') {
        mystring2[i]=mystring[i];
        i++;
    }
    mystring2[i]='\0';/*これを忘れるとコピー先に'\0'がない状態になる*/
    /*文字列のコピー終了*/
    puts("あなたが入力した文字列は");
    puts(mystring);
    puts("コピーした文字列は");
    puts(mystring2);
    return 0;
}

/****************** 実行結果 *********************
文字列を入力してください
The love you get is equal to the love you make.
あなたが入力した文字列は
The love you get is equal to the love you make.
コピーした文字列は
The love you get is equal to the love you make.
**************************************************/

注意 配列のコピーは要素ごとに行なう事になっているので次のような記述は出来ない。
    mystring2=mystring;

文字列を簡単にコピーする方法は「5.5文字列操作の関数」で述べる。

文字列の例を紹介しよう。

char mystring[20]="Hello, world.";
は次のように格納されている。

配列
要素

0
(号室)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

'H'

'e'

'l'

'l'

'o'

','

' '

'w'

'o'

'r'

'l'

'd'

'.'

'\0'

未使用

-

-

-

-

未使用


比較対照のために単なる文字コード配列では次のようになっている。

char mychararray[20]={
    'H','e','l','l','o',',',' ','w','o','r','l','d','.'
};
は次のように格納されている。

配列
要素

0
(号室)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

'H'

'e'

'l'

'l'

'o'

','

' '

'w'

'o'

'r'

'l'

'd'

'.'

未使用

-

-

-

-

-

未使用

単なる文字コード配列になっており,文字列の終わりのしるし'\0'がないので,次のようなprintfの表現ではうまく出力できない。
printf("%s\n",mychararray);

しかし
char mychararray[20]={
    'H','e','l','l','o',',',' ','w','o','r','l','d','.','\0'
};
のようになっていると
char mystring[20]="Hello, world.";
と同等になる。

次に,文字列の逆コピーをしてみよう。

List 4.4.3 文字列の入出力と文字列の逆コピー

/*文字列の入出力と文字列の逆コピー*/
#include <stdio.h>

int main()
{
    char mystring[200];
    char mystring2[200];
    int i,j;
    printf("文字列を入力してください\n");
    gets(mystring); /*キーボードから文字列を入力してもらい,mystringに取り込む*/

    j=0;
    while (mystring[j]!='\0') {
        j++;
    }

    /*この段階で,jは文字列mystringの文字数を表している*/

    mystring2[j]='\0';
    j--;
    i=0;
    while (mystring[i]!='\0') {
        mystring2[j]=mystring[i];
        i++;     /* i=i+1 と同じ意味*/
        j--;     /* j=j-1 と同じ意味*/
    }
    puts("あなたが入力した文字列は");
    puts(mystring);
    puts("逆コピーした文字列は");
    puts(mystring2);
    return 0;
}

/****************** 実行結果 *********************
文字列を入力してください
Boy, You're gona carry the weight for long time.
あなたが入力した文字列は
Boy, You're gona carry the weight for long time.
逆コピーした文字列は
.emit gnol rof thgiew eht yrrac anog er'uoY ,yoB
**************************************************/

練習4.3

次のプログラムの実行結果を予想しなさい。
List 4.4.4 自力追いかけプログラム
ヒント
「mystring[i-3]」と異なり,「mystring[i]-3」は文字コードから3を引いている
文字コード表を参照すること
 

#include <stdio.h>

int main()
{
    char mystring[200]="1xr|#hyro#L#nhhz#d#v|dg#wkjlH";
    char mystring2[200];
    char mystring3[200];
    int i,j;
    i=0;
    while (mystring[i]!='\0') {
        mystring2[i]=mystring[i]-3;
        i++;
    }
    mystring2[i]='\0';
    j=0;
    while (mystring2[j]!='\0') {
        j++;
    }
    mystring3[j]='\0';
    j--;
    i=0;
    while (mystring2[i]!='\0') {
        mystring3[j]=mystring2[i];
        i++;
        j--;
    }
    puts(mystring);
    puts(mystring2);
    puts(mystring3);
    return 0;
}

 

文字列についての補足
int型やdouble型の変数へのscanfでの読み込みでは変数の前に「&」を付けていた。
文字列に関してはこの「&」を付けない。

例えば次のようである。

int kk;
double xx;
char str[300];

scanf("%d",&kk);
scanf("%lf",&xx);
scanf("%s",str);

またscanfで文字列を入力した場合,文字列とは一単語のみ(スペース,タブ,改行などで区切られたところまで)が取得される。

コラム 「scanf+%s」と「gets」

「scanf+%s」を用いても「gets」を用いてもキーボードから文字列を入力することができるが,文字列の終わりの判断が異なる。
「scanf+%s」:「スペース」「タブ」および「改行」を文字列の区切り(文字列入力の終わり)とする。
「gets」:「改行」を文字列の区切り(文字列入力の終わり)とする。そのため「スペース」を含む文字列も入力可能となる。

scanf+%sのプログラム

#include <stdio.h>

int main()
{
    char str[1024];
    do {
        scanf("%s",str);
        printf("<<%s>>\n",str);
    } while(str[0]!='@');
    return 0;
}

実行結果

Here comes the sun.
<<Here>>
<<comes>>
<<the>>
<<sun.>>
@
<<@>>

getsのプログラム

#include <stdio.h>

int main()
{
    char str[1024];
    do {
        gets(str);
        printf("<<%s>>\n",str);
    } while(str[0]!='@');
    return 0;
}

実行結果

Here comes the sun.
<<Here comes the sun.>>
@
<<@>>


よくある誤り

int kk;
の時
kk='A';
は kk=0x41; あるいは kk=65; と同じなので正しいが
kk="A";
は "A"が'A'+'\0'を表すため正しくない
(正確にいうと,そのアドレスを表している)

逆に
char str[]="ABC";
は正しいが
char str[]='ABC';
は正しくない。

文字列は「"(ダブルクォーテーション)」で囲む。
文字は「'(シングルクォーテーション)」で囲む。

C言語の文字列の不便さ
 変数宣言時ではなく,実行時に文字型配列に文字列を代入したい場合は関数strcpy()を用いる。
C言語の文字列操作は不便なので,「C.ライブラリ関数の説明」 中の「C.4 文字列操作関数」に便利な関数が紹介されており,よく使われる。C言語の関数はまだ学んでいないので,この段階では,便利な命令がある程度の認識でよい。

char str[20]="Hello";
のように変数を宣言したときには変数に文字列を代入できる。

しかし
char str2[20];
と宣言した後に
str2="Hello";
のように文字列を代入できない。

後から文字列を代入したい場合は
strcpy(str2,"Hello");
とすればよい。ただし,プログラムの最初のところで
#include <string.h>
と書いておかなければいけない。


#include <stdio.h>
#include <string.h>

int main()
{
    char mystr[100];
    strcpy(mystr,"Hello world!");
    puts(mystr);
    return 0;
}

一般にstrcpy(str1,str2);と書いたら,文字列str2 を文字列str1にコピーするという意味になる.

文字列の終端を探す場合に良く使われる短縮表現

これまでの表現(List 4.4.2再掲 真偽判定では,非0を真,0を偽と判定することを利用して
次のような表現ができる
/*文字列の入出力と文字列の逆コピー*/
#include <stdio.h>

int main()
{
    char mystring[200];
    char mystring2[200];
    int i,j;
    printf("文字列を入力してください\n");
    gets(mystring);
    j=0;
    while (mystring[j]!='\0') {
        j++;
    }
    mystring2[j]='\0';
    j--;
    i=0;
    while ( mystring[i]!='\0' ) {
        mystring2[j]=mystring[i];
        i++;
        j--;
    }
    puts("あなたが入力した文字列は");
    puts(mystring);
    puts("逆コピーした文字列は");
    puts(mystring2);
    return 0;
}
/*文字列の入出力と文字列の逆コピー*/
#include <stdio.h>

int main()
{
    char mystring[200];
    char mystring2[200];
    int i,j;
    printf("文字列を入力してください\n");
    gets(mystring);
    j=0;
    while (mystring[j]!='\0') {
        j++;
    }
    mystring2[j]='\0';
    j--;
    i=0;
    while ( mystring[i] ) {
        mystring2[j]=mystring[i];
        i++;
        j--;
    }
    puts("あなたが入力した文字列は");
    puts(mystring);
    puts("逆コピーした文字列は");
    puts(mystring2);
    return 0;
}


 課題4 その4

(7)文字列をgets()で読み込み,その文字列に含まれる文字「e」の数を表示するプログラムを作りなさい。(p04ex07.c)
次の5例で検証し,実行結果の表示はこの例と同じになるようにしなさい。(青字は入力部分を表す。)

<<実行例1>>
keyin string => abcdefg
abcdefg => 1

<<実行例2>>
keyin string => eabcdefg
eabcdefg => 2

<<実行例3>>
keyin string => abcdefge
abcdefge => 2

<<実行例4>>
keyin string => I'm getting closer to my home.
I'm getting closer to my home. => 3

<<実行例5>>
keyin string => Tokyo Japan
Tokyo Japan => 0

(8)2つの文字列をgets()を2回使って読み込み,2つの文字列中,先頭の1文字が等しかったら,「equal」,等しくなかったら「not equal」と表示するプログラムを作りなさい。 (p04ex08.c)

ただし,2つの文字列を構成する文字数はそれぞれ1文字以上である。次の2例を含む5例ほどで検証し,実行結果の表示はこの例と同じにしなさい考え方

<<実行例1>>
str1= robot
str2= atom
robot atom => not equal

<<実行例2>>
str1= robot
str2= rocket
robot rocket => equal

(9)2つの文字列をgets()を2回使って読み込み,2つの文字列中,先頭の3文字が等しかったら,「equal」,等しくなかったら「not equal」と表示するプログラムを作りなさい。(p04ex09.c)
ただし,2つの文字列を構成する文字数はそれぞれ3文字以上である。次の3例を含む5例ほどで検証し
,実行結果の表示はこの例と同じにしなさい。 考え方

<<実行例1>>
str1= robot
str2= root
robot root => not equal

<<実行例2>>
str1= speaker
str2= speech
speaker speech => equal

<<実行例3>>
str1= time
str2= game
time game => not equal

(10)2つの文字列をgets()を2回使って読み込み,2つの文字列が完全に等しかったら,「equal」,等しくなかったら「not equal」と表示するプログラムを作りなさい。 (p04ex10.c)
ただし,2つの文字列を構成する文字数は等しいとする。次の2例を含む5例ほどで検証し
,実行結果の表示はこの例と同じにしなさい。 考え方

<<実行例1>>
str1= robot
str2= robit
robot robit => not equal

<<実行例2>>
str1= robot
str2= robot
robot robot => equal

(11)2つの文字列をgets()を2回使って読み込み,2つの文字列が完全に等しかったら,「equal」,等しくなかったら「not equal」と表示するプログラムを作りなさい。 (p04ex11.c)
ただし,2つの文字列を構成する文字数は等しいとは限らない。次の4例を含む5例ほどで検証し
,実行結果の表示はこの例と同じにしなさい。 考え方

<<実行例1>>
str1= robot
str2= robotics
robot robotics => not equal

<<実行例2>>
str1= robotics
str2= robot
robotics robot => not equal

<<実行例3>>
str1= robot
str2= robot
robot robot => equal

<<実行例4>>
str1= time
str2= game
time game => not equal

(12)文字列をキーボードから入力し,その文字列が2Jの学生の出席番号として正当かどうか調べ,正当か不正かを表示するプログラムを作成しなさい。  (p04ex12.c)
ただし2Jの学生の出席番号だったら「correct」,そうでなかったら「incorrect」と表示すること。番号は1から49までを正当とする。また2J1は正当とは認めず2J01を正当とする。
またプログラムへの入力と判定結果の出力は連続して出来るようにし,文字列「zzzz」が入力されたら,プログラムが終了するものとする。
以下の例でプログラムの動作を検証し
,実行結果の表示はこの例と同じにしなさい。 考え方2014.10加筆

実行の様子(必ずここに書いてある文字列 による実行結果をつけること)

2Jの出席番号を入力してください >> 2J01
2J01 => correct
2Jの出席番号を入力してください >> 2J13
2J13 => correct
2Jの出席番号を入力してください >> 2J29
2J29 => correct
2Jの出席番号を入力してください >> 2J30
2J30 => correct
2Jの出席番号を入力してください >> 2J49
2J49 => correct
2Jの出席番号を入力してください >> 2J1
2J1 => incorrect
2Jの出席番号を入力してください >> 2J9
2J9 => incorrect
2Jの出席番号を入力してください >> 2J50
2J50 => incorrect
2Jの出席番号を入力してください >> 2J123
2J123 => incorrect
2Jの出席番号を入力してください >> 2J00
2J00 => incorrect
2Jの出席番号を入力してください >> 2J
2J => incorrect
2Jの出席番号を入力してください >>
zzzz

(13)キーボードから入力された文字列に,母音字がそれぞれいくつ含まれているか表示するプログラムを作成しなさい。  (p04ex13.c)
入力される文字列の長さは300文字以下とします。なお大文字小文字は区別しないことにします。

答え合わせを以下のように行ないなさい。ただし,getsを用いると,リターンキーを押すまでが文字列として取り込まれるの
で,長い文章の入力途中でリターンキーを押さないようにする。

実行例(2回)

文字列を入力してください
hello!
入力された文字列は次のものです。
hello!
集計結果
number of vowels A,a= 0
number of vowels E,e= 1
number of vowels I,i= 0
number of vowels O,o= 1
number of vowels U,u= 0

文字列を入力してください
Over this year, I have seen many Japanese entertainment show on television that
are obviously supposed to make people laugh and have a good time. However, some
of these shows are not funny at all.

入力された文字列は次のものです。
Over this year, I have seen many Japanese entertainment show on television that
are obviously supposed to make people laugh and have a good time. However, some
of these shows are not funny at all.
集計結果
number of vowels A,a= 16
number of vowels E,e= 25
number of vowels I,i= 7
number of vowels O,o= 16
number of vowels U,u= 4


 4.5 二次元配列

これまでに学習した配列は,例えばプログラムの変数宣言部で

int arry[10]={987,654,321,963,852,741};

なら,配列変数の名前がarryで部屋番号が0号室から9号室(部屋数10)まである「長屋」のような整数型配列であり,最初の6部屋が利用されていて,残りの4室は未使用の状態あった。

部屋番号

0

1

2

3

4

5

6

7

8

9

987

654

321

963

852

741

未使用

未使用

未使用

未使用


また,プログラムの実行部でarry[0]は 987,arry[1]は654を示す。これは一次元配列と呼ばれる。
これに対し,「高層マンション」のように4階8号室とか12階6号室のように表現される配列は二次元配列と呼ぶ。
例えばプログラムの変数宣言部で


int darry[7][10]={
    {32,65,98,21,54,87},
    {96,63,85,52,74,41,10,20},
    {7,8,9,4,5,6},
    {10,20,30,40,50,60,70,80,90,100},
    {100,200,300}
};

なら,0階から6階(7フロアある)までの高層マンションで,各階には0号室から9号室 (部屋数10)
がある二次元配列である。
この二次元配列は,大きさ10のint型一次元配列が7個並んでいると考えられる。

値は次のように並んでいる。(使っていない部屋もある)

階番号部屋番号

0

1

2

3

4

5

6

7

8

9

0

32

65

98

21

54

87

未使用

未使用

未使用

未使用

1

96

63

85

52

74

41

10

20

未使用

未使用

2

7

8

9

4

5

6

未使用

未使用

未使用

未使用

3

10

20

30

40

50

60

70

80

90

100

4

100

200

300

未使用

未使用

未使用

未使用

未使用

未使用

未使用

5

未使用

未使用

未使用

未使用

未使用

未使用

未使用

未使用

未使用

未使用

6

未使用

未使用

未使用

未使用

未使用

未使用

未使用

未使用

未使用

未使用


またプログラムの実行部でdarry[2][3]は4,darry[4][2]は300を表す。

例題として,10人の学生の英語・数学・国語のテストの点(0100)の処理を考えよう。学生番号を09とし,英語・数学・国語の科目番号を0,1,2とする。科目ごとの平均と各学生の3科目合計点を求める事にする。学生番号 4の英語・数学・国語の得点をpoint[4][0],point[4][1],point[4][2]とし,合計点はpoint[4] [3]にしまうことにする。得点はあらかじめ2次元配列にしまっておくことにする。
処理内容は,個人の3科目の合計点,各科目の平均点および個人合計点の平均点の算出とする。

List 4.5.1 3科目の試験結果の処理

#include <stdio.h>

int main()
{
    int score[10][4]={ /*英・数・国・(合計)の順の個人得点*/
        {86,75,60},{75,68,81},{84,98,100},{87,76,48},{82,58,73},
        {100,87,98},{64,72,70},{87,68,99},{98,87,68},{87,72,84}
    };
    int sum[4];
    double average[4]; /*英・数・国・(合計)の順の平均値*/
    int i,j;
    for (i=0;i<10;i++) {
        score[i][3]=0;
        for (j=0;j<3;j++) {
            score[i][3]+=score[i][j];
        }
    }
    for (j=0;j<4;j++) {
        sum[j]=0;
        for (i=0;i<10;i++) {
            sum[j]+=score[i][j];
        }
        average[j]=sum[j]/10.0;
    }
    printf("学生番号 英語 数学 国語 合計\n");
    for (i=0;i<10;i++) {
        printf("%6d    %3d   %3d   %3d   %3d\n",
            i+1,score[i][0],score[i][1],score[i][2],score[i][3]);
    }
    printf("   平均    %.1f  %.1f  %.1f %.1f\n",
        average[0],average[1],average[2],average[3]);
    return 0;
}

/************実行結 果************
学生番号 英語 数学 国語 合計
     1     86    75    60   221
     2     75    68    81   224
     3     84    98   100   282
     4     87    76    48   211
     5     82    58    73   213
     6    100    87    98   285
     7     64    72    70   206
     8     87    68    99   254
     9     98    87    68   253
    10     87    72    84   243
   平均    85.0  76.1  78.1 239.2
*********************************/

追加説明
int score[10][4]={ /*英・数・国・(合計)の順の個人得点*/
    {86,75,60},{75,68,81},{84,98,100},{87,76,48},{82,58,73},
    {100,87,98},{64,72,70},{87,68,99},{98,87,68},{87,72,84}
};
は次のような2次元配列になる。
階番号部屋番号0
1
2
3
0
86
75
60
未使用
1
75
68
81
未使用
2
84
98
100
未使用
3
87
76
48
未使用
4
82
58
73
未使用
5
100
87
98
未使用
6
64
72
70
未使用
7
87
68
99
未使用
8
98
87
68
未使用
9
87
72
84
未使用


char型次元配列で文字列の配列を作る

複数の文字列を扱う場合は二次元配列になる。例えば変数宣言部で
char strings[5][16]={
    "Information","Technology","Programming","Language","Computer"
};
と書いた場合には次のような配列が出来る。この二次元配列は大きさ16のchar型の一次元配列が
5個並んでいると考えられる。

階番号部屋番号

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0

'I'

'n'

'f'

'o'

'r'

'm'

'a'

't'

'i'

'o'

'n'

'\0'

未使用

未使用

未使用

未使用

1

'T'

'e'

'c'

'h'

'n'

'o'

'l'

'o'

'g'

'y'

'\0'

未使用

未使用

未使用

未使用

未使用

2

'P'

'r'

'o'

'g'

'r'

'a'

'm'

'm'

'i'

'n'

'g'

'\0'

未使用

未使用

未使用

未使用

3

'L'

'a'

'n'

'g'

'u'

'a'

'g'

'e'

'\0'

未使用

未使用

未使用

未使用

未使用

未使用

未使用

4

'C'

'o'

'm'

'p'

'u'

't'

'e'

'r'

'\0'

未使用

未使用

未使用

未使用

未使用

未使用

未使用

この配列は次のように書いても全く同じ意味になる。

char strings[5][16]={
    {'I','n','f','o','r','m','a','t','i','o','n','\0'},
    {'T','e','c','h','n','o','l','o','g','y','\0'},
    {'P','r','o','g','r','a','m','m','i','n','g','\0'},
    {'L','a','n','g','u','a','g','e','\0'},
    {'C','o','m','p','u','t','e','r','\0'}
};

この配列を用いて,これらの文字列を表示するプログラムを作ってみる。

二次元配列においても,文字列終端は'\0'なのでList4.5.2内の表示方法test1のような表現が最初に思い浮かぶであろう。
test2,3のように文字列の場合は二次元配列なのだけれども表示する時に文字列の一次元配列のような扱いになる。
これは,stringsが
大きさ16のchar型の一次元配列が5個並んでいることに起因する。
string[i]は,
大きさ16のchar型の一次元配列のi番目(i番目の文字列)という意味になるからである。

List 4.5.2 複数文字列の扱い

#include <stdio.h>

int main()
{
    char strings[5][16]={
        "Information","Technology","Programming","Language","Computer"
    };
    char teststring[]="test string";
    int i,j;
    puts("** test1 **");
    j=0;
    while (teststring[j]!='\0') {
        printf("%c",teststring[j]);
        j++;
    }
    printf("\n");
    for (i=0;i<5;i++) {
        j=0;
        while (strings[i][j]!='\0') {
            printf("%c",strings[i][j]);
            j++;
        }
        printf("\n");
    }
    puts("\n** test2 **");
    printf("%s\n",teststring);
    for (i=0;i<5;i++) {
        printf("%s\n",strings[i]);
    }
    puts("\n** test3 **");
    puts(teststring);
    for (i=0;i<5;i++) {
        puts(strings[i]);
    }
    return 0;
}

/************実行結果************
** test1 **
test string
Information
Technology
Programming
Language
Computer

** test2 **
test string
Information
Technology
Programming
Language
Computer

** test3 **
test string
Information
Technology
Programming
Language
Computer
*********************************/


 課題 4 その5

(14)1週間の曜日が二次元char型配列に格納されているとする
     char dayofweek[7][20]={
         "Sunday","Monday","Tuesday",...,"Saturday"
     };
List 4.5.2 を参考にして,週の第何日目かをキーボードから読み込み,その曜日を表示するプログラムを作りなさい。(p04ex14.c)
全ての曜日が表示されることを検証し,実行結果の表示はこの例と同じにしなさい。
(表示方法についてList 4.5.2を参考にすること。そして,printfの書式に"%d: %s"を使いなさい。)

<<実行例1>>
number = 1
1: Sunday

<<実行例2>>
number = 3
3: Tuesday

(15)次の動作を行なうプログラムを作成しなさい。整数型の配列(配列要素の数を50としなさい)を宣言し,繰返し処理を用いて,要素の先頭から 1,4,9,16,25,36,49,64,81,...を格納しなさい。そして,先頭から,1つおきに,配列要素を画面に表示しなさい。(1 9 25 ...となる)
 (p04ex15.c)

(16)次のプログラムを作りなさい。(p04ex16.c)
int型配列testdata[20]を宣言し,すべての要素に0を代入しなさい。
次に要素番号が「0と2を除く2の倍数」になっている要素すべてに1を代入しなさい。
(ヒント)    for (i=2*2;i<20;i=i+2) testdata[i]=1;
そしてすべての要素の値を表示しなさい。表示にあたっては次の2行を用いなさい。

for(i=0;i<20;i++) printf("%d",testdata[i]);
printf("\n");

(17)次のプログラムを作りなさい。(p04ex17.c)
int型配列testdata[20]を宣言し,すべての要素に0を代入しなさい。
次に要素番号が「0と2を除く2の倍数」になっている要素すべてに1を代入しなさい。
(ヒント)    for (i=2*2;i<20;i=i+2) testdata[i]=1;
そして要素番号が「0と3を除く3の倍数」になっている要素すべてに1を代入しなさい。
(ヒント)    for (i=3*2;i<20;i=i+3) testdata[i]=1;
最後にすべての要素の値を表示しなさい。表示にあたっては次の2行を用いなさい。

for(i=0;i<20;i++) printf("%d",testdata[i]);
printf("\n");

(18)次の手順で「エラトステネスのふるい」を用いて2から1000までの素数を見つけ,表示するプログラムを作成しなさい。
またいくつ見つかったかも表示しなさい。
 (p04ex18.c)
(0,1は素数ではない)この手法では,2から1000までの数を素数候補としておき,2の倍数を候補からはずし,3の倍数を候補からはずし,4の倍数を 候補からはずし,5の倍数を候補からはずし,....とすると最後に残るのが素数になるという考え方を行なう。
まずint型の配列sieveを1001個宣言する。このとき配列要素は0から1000の設定となる。
配列要素sieve[0],sieve[1]には0を格納し,そのほかの要素には,1を格納する。(1が格納されているとこの要素の要素番号は素数候補を意味する。現時点では2以上の整数はすべて素数候補である。)この後次の作業に入る。
1)「2以外の2の倍数」の要素番号の要素に0を代入する。すなわちsieve[4],seive[6],seive[8],....に 0を代入。これらの要素番号は2の倍数なので素数候補からはずした。
2)「3以外の3の倍数」の要素番号の要素に0を代入する。すなわちsieve[6],seive[9],seive[12],.... に0を代入。これらの要素番号は3の倍数なので素数候補からはずした。
3)「4以外の4の倍数」の要素番号の要素に0を代入する。すなわち sieve[8],seive[12],seive[16],....に0を代入。これらの要素番号は4の倍数なので素数候補からはずし た。
4)「5以外の5の倍数」の要素番号の要素に0を代入する。すなわち sieve[10],seive[15],seive[20],....に0を代入。これらの要素番号は5の倍数なので素数候補からはず した。
 :
これを繰返して,999まで行って,1が残った要素が素数である。そして次のように表示できたらOK。
すこし工夫すると作業量が減少する。

なお,表示方法は次の実行例にならって,素数10個ごとに改行し,最後に見つかった素数の数を表示しなさい。

実行例

  2   3   5   7  11  13  17  19  23  29
 31  37  41  43  47  53  59  61  67  71
 73  79  83  89  97 101 103 107 109 113
127 131 137 139 149 151 157 163 167 173
179 181 191 193 197 199 211 223 227 229
233 239 241 251 257 263 269 271 277 281
283 293 307 311 313 317 331 337 347 349
353 359 367 373 379 383 389 397 401 409
419 421 431 433 439 443 449 457 461 463
467 479 487 491 499 503 509 521 523 541
547 557 563 569 571 577 587 593 599 601
607 613 617 619 631 641 643 647 653 659
661 673 677 683 691 701 709 719 727 733
739 743 751 757 761 769 773 787 797 809
811 821 823 827 829 839 853 857 859 863
877 881 883 887 907 911 919 929 937 941
947 953 967 971 977 983 991 997
168 prime numbers are found.


(文章課題)次の内容をまとめてレポートにしなさい。ただし,初めの2行(ファイル名,ID,出席番号,氏名)は,これまでのプログラムと同様な書式で書くこと。 (p04.txt)

1) List 4.1.6 を実行して,どのような実行結果になったのか説明しなさい。

2) List 4.1.7 を実行して,何が起こったか,説明しなさい。

3) List 4.1.8 でもし,配列の宣言が正しいとしたら,どのような実行結果が期待されていたのか,説明しなさい。

4) List 4.2.1 の補足のところで質問されている内容である。
変数aと変数bの内容を交換するときに,もう一つの変数をtmpを宣言して

        tmp=a;
        a=b;
        b=tmp;

としているが,

        a=b;
        b=a;

だけでは,交換できない。理由を説明しなさい。

5) List 4.2.6 で説明されている内容について,次のことを確かめて説明しなさい。
        for (i=0;i<4;i++)  b[i]=a[i];
のところを,
        b=a;
に変更して,実行して何が起こったか,説明しなさい。