プログラミングおよび演習 NO.8

Last-Modified: 2018.10.02


一次元文字列配列

配列は数値以外に、文字列データを格納するための変数として使います。
このとき、1つの配列要素に1文字の文字型データを格納します。
文字列データは、複数の文字データと、その最後に終了コード'\0'がついたデータです。
配列に文字列データを設定する方法には、宣言文の変数の初期化のところで行う方法と、実行文の中で行う方法があります。

表1 一次元の文字列配列データ


宣言文の中で設定する方法(プログラムの最初に初期値として設定する)
(1)方法1

 char a[5]="book";  //文字列データ"book"をa[5]に代入。

a[5]は"book"の文字数+1(=5)の数に合わせています。データ数を指定せずに

 char a[]="book";

のように書くこともできます。この場合、コンピュータは配列の長さを文字列データ"book"に自動的に合わせてくれます。一方で、

 char a[4]="book"; //こう書いてはいけません。

のように、実際の文字列データの長さより短い長さで宣言すると間違いです。
(上記のようにしてもエラーメッセージは出ません。しかし、上記のプログラムを走らせると、場合によってコンピュータがフリーズします)

(2)方法2

 char a[5]={'b','o','o','k','\0'}; //1文字づつ値を指定している。

最後の\0は文字列の終了コード。'\0'を最後につけないといけない。

実行文の中で設定する方法(プログラムの途中で書き込むことで設定する)

(3)方法3 本文の実行文の中で、次のように1文字づつ代入操作を行う。

 a[0]='b'; a[1]='o'; a[2]='o'; a[3]='k'; a[4]='\0';

(4)方法4 次のように文字列コピー関数 strcpy()を用いる。

 strcpy(a,"book"); //配列aに文字列"book"をコピーする。

ただし、(3),(4)の方法を用いる場合は、以下の宣言文が必ず必要です。

 char a[5];

注意:実行文の中で、

 a="book"; //こう書いてはいけません。

のような文字列の代入演算はできません。必ず、(4)のような方法を用いて下さい。



以下は一次元配列に文字列を代入しその結果を表示するプログラムです。

例8-1(世界のあいさつ1)
#include<stdio.h>

int main(void)
{
    char USA[]= "Hello";
    char Japan[]= "こんにちは";
    char France[]= "Bonjour";
    char Germany[]= "Guten Tag";
    char China[]= "ウーアン";
    char Korea[]= "アンニョン";
    
    printf("%s\n",USA);
    printf("%s\n",Japan);
    printf("%s\n",France);
    printf("%s\n",Germany);
    printf("%s\n",China);
    printf("%s\n",Korea);
    
    return(0);

}



二次元文字列配列

一つの文字列を、複数のます目に文字が入っているデータとして扱うと、表1に示したように文字数分のます目が横方向に並んだ一次元のデータと考えることができます。文字列が複数ある場合は、横方向に並んだ1つの文字列データが、縦の方向にも複数行にわたって並んでいる二次元データとして考えることができます。

例8-1のプログラムにおいて、6カ国のあいさつの文字列を一まとめにして、全体をhelloという配列名の二次元文字列配列データに格納することにします。
例えば、6カ国のあいさつの文字列を、二次元の配列(横方向に並んだ一次元の文字列配列を複数、縦方向に並べたデータ)に格納する場合、表2のようなイメージになります。この場合、一次元配列を6個用意するのに比べて、二次元配列を1個用意するだけで済み、プログラムが見やすくなることや、より便利にプログラムを作ることができるようになります。

表2 二次元文字列配列データ




[補足説明] 英数字(ASCII文字)1文字を1Byte(配列要素1個)、日本語全角文字1文字を2Byte(配列要素2個: シフトJIS、JIS、EUCコードの場合)または3Byte(配列要素3個: UTF-8コードの場合)で扱うことに注意してください。端末室で使用しているEclipse付属のgccコンパイラーは、全角文字をUTF-8コードで扱っています。このため、全角文字1文字を格納するのに、配列要素3個が必要です。すなわち、英数字の場合は、(英数字の文字数+1)、全角文字の場合は、(全角文字の文字数×3+1)の要素数の配列を宣言してください。

以上のような考え方にたって、例8-2は二次元の文字列配列を使って書き換えたプログラムです。ここでは、6カ国の国名も表示するようにするために、6カ国のあいさつ文をデータにもつhelloという変数名の二次元配列に加えて、6カ国の国名をデータにもつcountryという変数名の二次元配列を使っています。

例8-2(世界のあいさつ2) (Revised : 2018/10/02)
#include<stdio.h>

int main(void)
{ 
    char country[6][8]={"USA", "Japan", "Germany",
    "France","China","Korea"};
    char hello[6][16]= {"Hello", "こんにちは", "Guten Tag","Bonjour"
           ,"ウーアン","アンニョン"};

    int i;
    
    for(i=0;i<6;i++)
        printf("%8s : %16s\n",country[i],hello[i]);

    return(0);
}

 例8-1と例8-2の両者をよく見比べてください。例8-2の方が簡潔で自由度の高いプログラムらしいプログラムになっていることを見てください。



演習問題 8-1 (Revised : 2014/10/06)
前回例7-3の成績表示プログラムで、

  char name[7][7]={"Ito","Kato","Tanaka","Suzuki","Yamada","Takada","Sato"};
  char hyodai[5][7]={"NAME","EIGO","SUGAKU","BUTURI","GOKEI"};


のような文字列データを用いることで、各人の名前がついた点数表が表示されるようにプログラムを作りかえてください。

構 造 体

前回の例7-3に新たに学生番号を付け加えて、以下のような項目から成り立っている成績データを考えます。

 ①学生番号(整数型)、②名前(文字列型配列)、③三科目の点数(整数型配列)、④合計点(整数型)

今までは、これらの項目ごとに別々の変数を用意してプログラムを作りました。
その場合、各々の項目データはお互いにバラバラなため、見通しが悪く取り扱いが厄介です。
①~④を一つにまとめた変数名で表わしたほうが、込み入った複雑なデータをわかり易く簡単に扱うことができるようになります。
構造体は、このような目的のために使います。
以下にその使い方を説明します。

(1) 構造体の型枠(テンプレート)の宣言
構造体変数を使う前に、予め構造体の型枠を宣言します(型を定義する働きをする)。

   struct 構造体タグ名{メンバ変数の宣言文の並び};

構造体タグ名は、{メンバ変数の宣言}の中に宣言している複数のメンバ変数を
ひとまとめにした名前を表わします。そして、メンバ変数の宣言文は

 
   変数の型 変数名1;
   変数の型 変数名2;
  ‥‥‥‥}

のように、複数のメンバ変数の宣言文を列挙します。
成績データの場合、

 ①学生番号を入れる整数型変数    ‥‥   num
 ②名前を入れる文字列配列変数    ‥‥   
name[20]
 ③3科目分の点数を入れる整数型配列 ‥‥  
ten[3]
 ④各科目の合計点を入れる整数型変数 ‥‥ 
total

の4つのメンバ変数を、一つにまとめて表わすための構造体タグ名 slistを次のように宣言します。(型枠宣言は、mainの前のプログラムの先頭(大域変数領域)に置きます)

   struct slist{
 int num;
 char name[20];
 int ten[3];
 int total;
}

(2) 構造体変数の宣言
  構造体を実際に使いたい所で(例えばmain関数の中)、構造体変数を宣言します(メモリ領域の確保が行われる)。

  struct 構造体タグ名 構造体変数名;

  次のように構造体変数を、配列で宣言することもできます。
  (以下の成績データの場合には、人数分の要素数の構造体配列を宣言します)

  struct 構造体タグ名 構造体配列名[要素数];

  成績処理プログラムの場合、slist型の構造体配列変数 seiseki を次のように宣言します。
  (この例では、学生の数が7人なので、7個の要素からなる配列変数にしている。)

   struct slist seiseki[7];

  通常の変数と同じように、宣言文の所で例えば次のようにして、構造体配列変数 seiseki  に初期値を設定することができます。

  struct slist seiseki[7]={ {1, "Ito",  {70, 60, 80}, 0},
                            ‥‥}};

(3) 構造体メンバの参照(使用)
 プログラムの実行文の中で、構造体変数の中の各要素変数を参照する場合、

  構造体名.構造体メンバ名

のように、ドットを使って、構造体名を構造体メンバ名で修飾します。
構造体変数が配列の場合には、参照方法は、

 構造体配列名[要素番号].構造体メンバ名

のようになります。さらに、構造体メンバも配列の場合、

 構造体配列名[要素番号].構造体メンバ名[要素番号]

となります。上記の構造体配列seisekiの例では、メンバ変数を次のように参照します。

  seiseki[i].num     i番の学生の学生番号
  seiseki[i].name    i番の学生の名前
  seiseki[i].ten[0]   i番の学生の、0番の科目の点数
  seiseki[i].ten[1]   i番の学生の、1番の科目の点数
  seiseki[i].ten[2]   i番の学生の、2番の科目の点数
  seiseki[i].total    i番の学生の合計点

例 8-3 (Revised : 2012/10/01)

次は構造体変数を用いて、例7-3 を書き直した成績データ処理プログラムです。
プログラムリストをとおして構造体変数の使い方を勉強してください。

 /* 構造体を使った成績データの並び替え */
#include<stdio.h>
#include<string.h>

struct slist{
    int num; /* 番号 */
    char name[20]; /* 名前 */
    int ten[3]; /* 各科目の点数(3科目) */
    int total; /* 合計点 */
};

int main(void){ 
    /*変数宣言と配列の初期値設定*/ 
    int i,j;
    int N=7,M=3,K=6; /* N:学生数, M:科目数 K:項目数*/
    struct slist seiseki[7] = {{1, "Ito", {65, 55, 80}, 0},
                              {2, "Kato", {30, 40, 50}, 0},
                              {3, "Tanaka",{65, 85,100}, 0},
                              {4, "Suzuki",{85,90, 65}, 0},
                              {5, "Yamada",{20, 50, 80}, 0},
                              {6, "Takada",{90, 30, 40}, 0},
                              {7, "Sato",{50, 70, 55}, 0}};
    char hyodai[6][7]={"no.","name","eigo","sugaku","buturi","total"};
/*表題の表示*/ printf("------------------------------------------------\n"); printf(" Original Data\n"); printf("------------------------------------------------\n"); for(i=0;i<K;i++){ printf("%8s",hyodai[i]); }; printf("\n"); /*各学生の3科目の合計(行の計算)*/ for(i=0;i<N;i++){ printf("%8d%8s",seiseki[i].num,seiseki[i].name); for(j=0;j<M;j++){ printf("%8d", seiseki[i].ten[j]); seiseki[i].total += seiseki[i].ten[j]; } printf("%8d\n",seiseki[i].total); } return(0); }

演習問題 8-2 (Revised : 2014/10/06)

例8-3を以下のような動作をするプログラムに変更してください。
レポートには、このプログラムの動作説明等を含む考察を書いてください。

①画面に、>1:Ito, 2:Kato, 3:Tanaka, 4:Suzuki, 5:Yamada, 6:Takada, 7:Sato
     >学籍番号を入力してください(1-7)?  
と表示した後、成績表示したい学生の番号をキーボードから入力すると、該当する学生についてのみ、番号、名前、3科目の素点、および合計点を表示。

②画面に、>1:EIGO, 2:SUGAKU, 3:BUTURI
        >科目番号を入力してください(1-3)?  
と表示した後、成績表示したい科目の番号をキーボードから入力すると、該当する科目についてのみ、平均点、最高点、最低点、標準偏差を表示。


並び替え(ソーティング)

例えば、7人の学生の学生番号と合計点が 

 番号 合計点
  1   210
  2   160
  3   260
  4   240
  5   190
  6   210
  7   180

のように並んでいたとします。合計点の多い順に

 番号 合計
  3   260
  4   240
  1   210
  6   210
  5   190
  7   180
  2   160

のように並べ替えるにはどうしたらよいでしょうか?

ソーティングアルゴリズムには、①選択ソート、②挿入ソート、③バブルソート、④クイックソート、などがあります。①②③の処理回数は、データ個数nの二乗に比例するため、データ数が多くなるにつれて処理時間が長くなってしまう欠点があります。ただし、②③の方法には、安定なソート(同順位のデータの順番が、並び替え前の順番と同じになること)を行うことができる長所があります。これに対して、④のクイックソートは安定でない欠点がありますが、処理回数を少なくできる(平均的な回数は、n logn に比例する) ため実用的によく使われます。ここでは、学習目的のために、アルゴリズムが単純でプログラムが理解しやすい①の選択ソート法を取り上げることにします。

(注: ソーティングのアルゴリズムについて詳しく知りたい人は、

  http://ja.wikipedia.org/wiki/Category:%E3%82%BD%E3%83%BC%E3%83%88

などを参考にしてください。)

例えば、ソートしたい数が

  (原データ) 210 160 260 240 190 210 180

のように並んでいるとします。最初の繰り返し処理において、最大値のデータとその番号を調べます。その後、最大値のデータ(260)を1番目のデータと交換して、

  (1回目) 260 160 210 240 190 210 180

とします。次に、2番目以降のデータに対して、上記の場合と同様に、最大値を見つける処理ならびに、最大値(240)のデータと2番目のデータを交換する作業を行って

  (2回目) 260 240 210 160 190 210 180

と並べ替えます。以下同様に、以上の処理を、残り2つのデータになるまで繰り返します。その結果、

  (3回目) 260 240 210 160 190 210 180
  (4回目) 
260 240 210 210 190 160 180
  (5回目) 
260 240 210 210 190 160 180
  (6回目) 
260 240 210 210 190 180 160

のように6回の並び替えの後に、降順(点数の高い順)にデータを並べかえることができます。以上の処理過程において、最大値ではなく、最小値を捜すように変更を加えれば、昇順(点数の低い順)に並べることもできます。

例 8-4 (Revised : 2006/10/09)
/* 成績データのソーティング*/
#include<stdio.h>

int main(void)
{
    /*変数宣言と配列の初期値設定*/

    int i,j;
    int N=7,jmax,totalmax;
    int total[7]={210,160,260,240,190,210,180};
  
    /*並び替え前の合計点の表示 */
    printf("--------------------------\n");
    printf("原データ\n");
    printf("--------------------------\n");
    for(i=0;i<N;i++){
        printf("%8d\n", total[i]);
    }

    for(i=0;i<N-1;i++){
    
        totalmax=total[i];
        jmax=i;
    
        /* 最大値を求める*/
        for(j=i+1; j<N; j++){
            if(total[j] > totalmax){
                jmax=j;
                totalmax=total[j];
            }
        }

        /* 最大値(jmax番目)のデータとi番目のデータを交換*/
        total[jmax]=total[i];
        total[i]=totalmax;
    }

    /*並ぎ替え後の合計点の表示 */
    printf("--------------------------\n");
    printf("並び替えデータ\n");
    printf("--------------------------\n");
    for(j=0;j<N;j++){
        printf("%5d\n", total[j]);
    }

    return(0);
}

演習問題 8-3 (Revised : 2014/10/06)

原データを

int total[8]={46,52,97,13,46,78,46,108};

としたとき、以下のように小さい順から出力するよう、例8-4のプログラムを改良してください。また、並び替え後の?に入る番号を答えなさい。

--------------------------
原データ
--------------------------
番号 合計
 1 46
 2 52
 3 97
 4 13
 5 46
 6 78
 7 46
 8 108

--------------------------
並び替えデータ
--------------------------
番号 合計
 4 13
 ? 46
 ? 46
 ? 46
 2 52
 6 78
 3 97
 8 108


演習問題 8-4 余力がある人へ (Revised : 2014/10/06)
例8-3で練習した構造体変数を用いて、次のような結果を表示するプログラムを作ってください
(合計点の高い順にソーティングするプログラム)。
------------------------------------------------
 Original Data
------------------------------------------------
 No. Name   Eigo Sugaku Buturi Total
 1  Ito   65  55   80   200
 2  Kato   30  40   50   120
 3  Tanaka  65  85   100   250
 4  Suzuki  85   90   65   240
 5  Yamada  20  50   80   150
 6  Takada  90    30      40   160
 7  Sato   50    70      55   175

------------------------------------------------
 Sorted Data
------------------------------------------------
  No.  Name    Eigo Sugaku  Buturi  Total
  3    Tanaka  65  85   100   250
  4    Suzuki  85   90   65   240
  1    Ito   65  55   80   200
  7    Sato   50    70      55   175
  6    Takada  90    30      40   160
  5    Yamada  20  50   80   150
  2    Kato   30  40   50   120