カテゴリー
C

C(Step5-1)

構造体-データをまとめて管理する

大量のデータをまとめて扱う時に便利なものとして配列を説明してきました。配列は「10人の身長」や「100人の10科目の点数」といったデータをまとめて扱う時に、繰り返し処理と組み合わせて利用して便利さを発揮するものでした。つまり、「整数」や「実数」や「文字」といった同種類のデータが膨大にあり、それらを1つにまとめて扱いたいときに配列を使ってきました。次のようなデータを扱うときを考えてみましょう。

サンプルデータ

このようなデータを配列で扱う場合は、

1人の氏名の文字型1次元配列×10 → 文字型2次元配列
1人の生年月日の整数型変数 ×10 → 整数型1次元配列

1人の体重の実数型変数     ×10 → 実数型1次元配列

というように個々のデータをまとめて管理できます。しかし、サンプルデータの図のように個人のデータを一つにまとめられません。配列は同じ型のデータのみをまとめて扱う方法だからです。このようなデータをまとめるときに用いるのが構造体(structure type)です。

実際にデータをまとめてプログラミング

実際に大きなプログラムを作る時は、大人数で分割して作っていきます。そのため、どのようなプログラムを作るか決めたら、データをどう扱うかというとデータ構造(data structure)をまず決めなければあとでつじつまが合わなくなります。

まずは、最も基本的な構造体について例題を見ながら、考えていきましょう。

例題:A君とB君のデータとして次の表があります。それぞれの項目名を選択し入力すると、二人のデータが同じか異なっているかを判定し、結果を表示するプログラムを作成します。

項目A君B君
血液型AB
出身地長野長野
年齢2122
アルバイト経験(月)1512
時給(円)800800
A君B君の個人データ

このプログラムを作るにあたり、データをどのように変数に格納するかデータ構造を考えます。まず、1人分のそれぞれの項目にどの型を使いどれだけの大きさが必要になるかまとめると以下のようになります。

血液型文字型(1文字)
出身地文字型(5文字)
年齢整数型
アルバイト経験(月)整数型
時給(円)整数型
個人データのまとめ方

ここで血液型はAB型がないので、1文字、出身地は全角2文字なので、その倍の「4文字+何もない文字\0」で5文字として準備します。

  • 二人分のデータなので、A君を0番、B君を1番とした配列で準備するとします。
血液型char blood[2]
出身地char area[2][5]
年齢int old[2]
アルバイト経験(月)int exp[2]
時給(円)int wage[2]
配列でのデータのまとめ方

このようにするとデータをまとめることが出来ますが、普段考える時にはA君のデータを1つのグループ、B君のデータを1つのグループと考える事は多いのではないでしょうか?

  • A君をAdata、B君をBdataというデータで表すとします。
構造体構造体を使ったデータのまとめ方

こちらのデータ表現の方がしっくりくると思います。構造体を使うことにより、データのまとまりを作ることが出来ます。

次にこの例題のアルゴリズムを考えます。

  1. A君のデータを既定値で初期化する
  2. B君のデータを既定値で初期化する
  3. どの項目を比較するかを数値で入力できるように、「何で比較しますか? 1:血液型 2:出身地 3:年齢 4:アルバイト経験 5:時給」という質問を表示する
  4. 入力された項目でA君とB君のデータを比較し、同じならば「二人は同じです」、異なれば「二人は違います」と表示する

ソースプログラムは以下のように記述します。

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

struct private_data{ //①
    char blood;
    char area[5];
    int old, exp, wage;
};

int main(){
    struct private_data Adata, Bdata; //②
    int input;
    int same;

    /*③A君の既定データの代入*/
    Adata.blood = 'A';
    strcpy(Adata.area, "長野");
    Adata.old = 21;
    Adata.exp = 15;
    Adata.wage = 800;

    /*③B君の既定データの代入*/
    Bdata.blood = 'B';
    strcpy(Bdata.area, "長野");
    Bdata.old = 22;
    Bdata.exp = 12;
    Bdata.wage = 800;

    /*質問の表示と選択結果の入力*/
    printf("何で比較しますか?\n");
    printf("1:血液型 2:出身地 3:年齢 4:アルバイト経験 5:時給\n");
    scanf("%d", &input);
    same = 0;

    /*判定と結果表示*/
    switch(input){
    case 1:if(Adata.blood == Bdata.blood) same = 1;
        break;
    case 2:if(strcmp(Adata.area, Bdata.area) == 0) same = 1;
        break;
    case 3:if(Adata.old == Bdata.old) same = 1;
        break;
    case 4:if(Adata.exp == Bdata.exp) same = 1;
        break;
    case 5:if(Adata.wage == Bdata.wage) same = 1;
        break;
    default:printf("そんな項目番号はありません\n"); same = 2;
        break;
    }

    if(same == 0){
        printf("二人は違います\n");
    }else{
        if(same == 1){
            printf("二人は同じです\n");
        }
    }
    return(0); 
}

①どんな構造体かを記述する

この部分は、構造体がどんな変数のまとまりなのかを定義している部分です。構造体は、複数の変数をまとめて1つの名前で管理します。struct(ストラクト)のあとに、「何という名前の構造体であるか」、その中にまとめて扱うのは「どんな変数で何という名前なのか(変数の宣言)」を記述します。最後に「;」がついてることにも注意しましょう。

■構造体の定義

struct 定義する構造体の名前
{
      変数の型 変数名(配列名[])
};

ただし、ここでは、private_dataがどんなデータを扱うのか、その型を定義しているだけであって、構造体の実体を作っているわけではありません。

②構造体を使った変数宣言

この部分での記述は、①で決めた構造体の名前を使って、実際に構造体のデータを記憶する場所を用意しています。この記述を構造体を使った変数宣言といいます。

■構造体の変数宣言

struct [構造体の名前] [実際に利用する変数名]

struct private_data Adata, Bdata;

③構造体でまとめたそれぞれの変数に値を代入

これらの記述は、構造体でまとめたそれぞれのデータに対して、値の代入や、値の参照を行っています。以下のようなAdataのまとまりの中にある変数bloodに値を代入する時は、「Adata.blood = [値]」のようにしてピリオド(.)をつけて記述します。

データの代入の仕組み

「Adataのまとまりの中のblood」→ Adata.bloodのように「.」を「~のまとまりの中の」と読み替えるとわかりやすいでしょう。

構造体と配列の利用

「構造体=いくつかの変数をまとめて扱うもの」というイメージができてると思います。しかし、上のような例では構造体を使ってもプログラムの記述が複雑になるだけで、構造体でまとめるメリットをなかなか感じられないと思います。構造体と配列を両方利用してはじめてメリットを感じ取れるはずです。

次の例題で考えていきましょう。

例題:50人の個人データが記述されているテキストファイルがある。このとき、年齢・月収の項目について、データの分布を調べ、グラフにまとめる。

データ集計プログラムのデータ構成
  • データ構成を考える

まずは、データをどのように表現するかというデータ構造を考えてみましょう。この問題では、「名前」「年齢」「誕生月」「郵便番号」「月収」の5つのデータが1人のデータとしてあるので、これを構造体としてまとめると以下のようになります。

データ構造のまとめ方

さらに、この問題では、50人分のデータを取り扱うため、この構造体を50個まとめた1次元配列で表します。そのため、構造体の配列を準備する変数宣言は、先のdatatypeの構造定義を利用して次のように記述できます。

struct datatype data[50];

構造体の配列で「struct 構造定義名 配列名 [n][m]…」と書きます。この構造体の配列で、0番目のデータである名前を扱う時は「data[0].name」といった形で記述します。

これらを含め、プログラムにすると次のように記述できます。

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

struct datatype{
    char name[30];
    int old, birth;
    char zip[8];
    int salary;
};

/*graph[]の内容をグラフ表示する関数*/
int write_graph(int graph[5]){
    int i, j;

    /*軸の表示*/
    printf("-----|");
    for(i=0; i<5; i++){
        for(j=0; j<9; j++)
            printf("-");
        printf("+");
    }
    printf("\n");

    /*ヒストグラム・グラフの表示*/
    for(i=0; i<5; i++){
        if(i!=4)
            printf("%2d-%2d|", i*2*10, (i*2+2)*10);
        else
            printf("80---|");
        for(j=0; j<graph[i]; j++)
            printf("*");
        printf("\n");
    }

    /*軸の表示*/
    printf("-----|");
    for(i=0; i<5; i++){
        for(j=0; j<9; j++)
            printf("-");
        printf("+");
    }
    printf("\n");
    return(0);
}

int main(){
    struct datatype data[50];
    FILE *FP;
    int graph[5];
    int i;

    /*ファイルの読み込みモードでのオープン*/
    if((FP=fopen("personal_data.txt", "r"))==NULL){
        printf("ファイルが開けません\n");
        return(1);
    }

    /*ファイルからデータを読み込む*/
    for(i=0; i<50; i++){
        /*nameの読み込み*/
        fgets(data[i].name, 29, FP);
        data[i].name[strlen(data[i].name)-1]='\0';

        /*oldの読み込み*/
        fscanf(FP, "%d\n", &data[i].old);

        /*birthの読み込み*/
        fscanf(FP, "%d\n", &data[i].birth);

        /*zipの読み込み*/
        fgets(data[i].zip, 8, FP);
        data[i].zip[7]='\0';

        /*salaryの読み込み*/
        fscanf(FP, "%d\n", &data[i].salary);
    }

    /*graphの初期化*/
    for(i=0; i<5; i++){
        graph[i]=0;
    }

    /*oldの集計*/
    for(i=0; i<50; i++){
        if(data[i].old>=0){
            if(data[i].old<20){
                graph[0]=graph[0]+1;
            }else{
                if(data[i].old<40){
                    graph[1]=graph[1]+1;
                }else{
                    if (data[i].old<60){
                        graph[2]=graph[2]+1;
                    }else{
                        if(data[3].old=graph[3]+1){
                            graph[3]=graph[3]+1;
                        }else{
                            graph[4]=graph[4]+1;
                        }
                    }                    
                }
            }
        }
    }

    /*graph[]をグラフ化して表示*/
    printf("\n年齢分布\n");
    write_graph(graph);

    /*グラフの初期化*/
    for(i=0; i<5; i++){
        graph[i]=0;
    }

    /*salaryの集計*/
    for(i=0; i<50; i++){
        if(data[i].salary>=0){
            if(data[i].salary<20){
                graph[0]=graph[0]+1;
            }else{
                if(data[i].salary<40){
                    graph[1]=graph[1]+1;
                }else{
                    if (data[i].salary<60){
                        graph[2]=graph[2]+1;
                    }else{
                        if(data[3].salary=graph[3]+1){
                            graph[3]=graph[3]+1;
                        }else{
                            graph[4]=graph[4]+1;
                        }
                    }                    
                }
            }
        }
    }

    /*graph[]をグラフ化して表示*/
    printf("\n年齢分布\n");
    write_graph(graph);

    return(0);
}

これまでよりソースプログラムのサイズは大きくなりますが、まとまりごとに慎重に見れば、理解できるでしょう。

構造体の基本的な使い方は以上ですが、「構造体の基本は簡単でもどう使うかが勝負」です。センスの悪い構造体を作ってもプログラムが見づらくなるだけです。プログラムを書きながら、適切な使い道を模索していきましょう。

カテゴリー
C

C(Step4-3)

ポインタの利用

まず、ポインタは、変数のアドレスを記憶する変数を指します。アドレスは、メモリ上の住所みたいなもので、「0014DF7C」などの番号を与えらています。今まで色々な変数を宣言してきましたが、その変数にはアドレスが割り当てられていたということです。また、アドレスにアクセスすることができ、そのアドレスの変数の値を取得することも可能です。アドレスは基本的に16進数で表されます。

ポインタの使い方

続いて、ポインタの使い方について説明していきます。ポインタはポインタ型変数として以下のように宣言します。

   型名 *変数名;
例: int *pt;

ポインタ変数は、変数名の前に「*(アスタリスク)」を付けて宣言します。加えて、変数のアドレスは変数名の前に「&(アンパサンド)」を付けて表します。以下のサンプルを実行して確かめてみましょう。

#include <stdio.h>
 
int main(void) {
    int num = 5; // int型普通の変数
    int *ptr_num; // int型ポインタ変数
    ptr_num = &num; // ポインタ変数ptr_numに変数numのアドレスを代入
    
    printf("int型変数numの値:%d\n", num);
    printf("int型ポインタ変数ptr_num:%p\n", ptr_num);
 
    return 0;
}
sample4-2.1実行結果

このプログラムでは、int型のポインタ変数「ptr_num」を宣言しています。ポインタ変数「ptr_num」にint型の変数「num」のアドレス「&num」を代入しています。ちなみに同じ型でなければ、エラーとなります。ポインタ変数をprintf()で出力表示するには「%p」の変換子を利用し、アドレスを表示しています。「%p」 はアドレスを16進数で表示する変換子です。

ここで、上記のプログラムに以下の文を追加して実行してみましょう。

printf("int型ポインタ変数p_numの参照先の値:%d\n", *ptr_num);
sample4-2.2実行結果

実行結果を見て分かる通り、printf()で、「*(アスタリスク)」を付けることでポインタ変数の参照先の値を表示することができます。また、「*(&num)」と記載しても同様の結果を得ることができます。

ポインタと配列

C言語では、配列はメモリの連続した区間を使うという約束になっています。そのため、ポインタとは深く関係しています。次のプログラムを使って配列の要素をポインタで扱ってみましょう。

#include <stdio.h>
 
int main()
{
    int a[5] = {10, 20, 30, 40, 50}; //配列
    int *p; //ポインタ変数
 
    p = &a[0]; //配列の先頭のアドレスを代入する
 
    /*配列の中身表示*/
    printf("繰り返し処理による配列内の値表示:");
    for(int i=0; i<5; i++){
        printf("%d ", a[i]);
    }
    printf("\n");

    /*ポインタを使った配列の中身表示*/
    printf("ポインタを使った配列内の値表示:");
    for(int i=0; i<5; i++){
        printf("%d ", *p);
        p++;
    }

    return(0);
}
sample4-2.3実行結果

ポインタ変数のpに注目しましょう。プログラム内では始めに配列の1番目のアドレスを代入しています。その後、ポインタ変数p++することで、アドレスを位置を順番に進めています。このアドレスの位置は配列の順番と同じことがわかると思います。つまり、ポインタの位置と配列の順番は連動しているとわかります。

ポインタのポインタ

前の説明で、ポインタとは変数のアドレスを記憶する変数と説明しました。しかし、ポインタ自体もある種の変数です。そのため、ポインタ変数のアドレスもメモリの何処かにあり、アドレス番号が与えられているということです。ポインタ自体にポインタのアドレスを格納することも出来ます。これをポインタのポインタと言います。宣言するには以下のように、さらに「*(アスタリスク)」を付けます。

型名 **変数名;

次のサンプルを実行して確認してみましょう。

#include <stdio.h>
 
int main() {
    int num = 5; //int型変数
    int *p_num; //int型ポインタ変数
    int **pp_num; //int型ポインタ変数のポインタ
    
    p_num = &num; //ポインタ変数p_numに変数numアドレスを代入
    pp_num = &p_num; //ポインタ変数のポインタpp_numにポインタ変数p_numのアドレスを代入
    
    printf("int型変数numの値:%d\n", num);
    printf("int型ポインタ変数p_num:%p\n", p_num);
    printf("int型ポインタ変数のポインタpp_num:%p\n", pp_num);
    
    return 0;
}
sample4-2.4実行結果

プログラム内では、int型の変数numのアドレスを格納するポインタ変数p_numをまず宣言、定義しています。さらに、このポインタ変数p_numのアドレスを格納するために、ポインタ変数のポインタpp_numを次に宣言、定義しています。実行結果を見て分かる通り、ポインタに対してもポインタを扱うことができます。

関数ポインタの使い方

続いて、関数ポインタの使い方について説明します。関数ポインタと変数のポインタとの違いは関数が格納されたメモリアドレスを取得することです。関数を呼び出す時に関数ポインタの内容を別の関数のアドレスに変更することで、呼び出す関数を変更することができます。それによって、呼び出す関数を動的に変更することができます

利用するには、「typedef」を用いて、関数ポインタの型を宣言すると簡潔に記述できます。

typedef 戻り値の型 (*関数ポインタ型名)(引数);

そして、この関数ポインタ型名のオブジェクトを生成して使用します。

関数ポインタ型名 オブジェクト名;

以下のサンプルを実行して確認してみましょう。

#include <stdio.h>
 
// 関数ポインタの宣言
typedef int (*foAO)(int x, int y);
 
/*関数ポインタに代入する関数を定義
、関数ポインタの引数の型と引数の数を統一*/

//足し算を行う関数
int sum(int x, int y){
    return x + y;
}

//引き算を行う関数
int mns(int x, int y){
    return x - y;
}
 //掛け算を行う関数
int mul(int x, int y){
    return x * y;
}

//割り算を行う関数
int div(int x, int y){
    return x / y;
}
 
int main() {
    int x, y, result;
    foAO c; //foAO型関数ポインタのオブジェクトを生成
    
    x = 10;
    y = 2;
    printf("x:%d, y:%d\n", x, y);
    
    c = &sum; //関数ポインタに関数アドレスを代入
    result = c(x, y); //関数ポインタより関数の呼び出し
    printf("x + y = %d\n", result);
    
    c = &mns;
    result = c(x, y);
    printf("x - y = %d\n", result);

    c = &mul;
    result = c(x, y);
    printf("x * y = %d\n", result);

    c = &div;
    result = c(x, y);
    printf("x / y = %d\n", result);
 
    return 0;
}
sample4-2.5実行結果

プログラムでは、四則演算を行う関数を作成し、各関数のアドレスを渡すことで、目的の関数呼び出しています。このようにして、ポインタを用いることで関数であっても有効に利用することができます。

カテゴリー
C

C(Step4-2)

関数の利用②

関数の利用①に続いて、関数についての知識を深めていきましょう。

ライブラリ関数

C言語ではプログラミングを簡単にするために、あらかじめ多くの関数が用意されていると説明しました。 このようなにあらかじめ用意されている関数をライブラリ関数といいます。printf() もライブラリ関数のひとつですが、他の例として数値計算を行う際によく使う関数を以下に挙げておきます。

関数名演算内容
sin(x)正弦。引数xはラジアンで角度を示したもの。
cos(x)余弦。引数xはラジアンで角度を示したもの。
tan(x)正接。引数xはラジアンで角度を示したもの。
fabs(x)xの絶対値。
sqrt(x)xの平方根。
数値計算に使う関数

これらの関数を呼び出すためには、「stdio.h」と同じように以下の記述をしなければなりません。

#include <math.h>

次のプログラムを実行して確認してみましょう。

#include <stdio.h>
#include <math.h>

int main() {
    printf("sin(0.0)     = %f\n", sin(0.0));
    printf("cos(0.0)     = %f\n", cos(0.0));
    printf("tan(0.0)     = %f\n", tan(0.0));
    printf("fabs(-1.41)  = %f\n", fabs(-1.41));
    printf("sqrt(4.0)    = %f\n", sqrt(4.0));

    return 0;
}

実行結果は、以下のようになります。

<math.h>の利用実行結果

関数の再帰呼び出し

再帰というのは、関数が自分自身を呼び出すことで、このような関数を再帰関数(recursive function)と呼びます。特に漸化式で表される数学的な関数など繰り返し行う処理を直接プログラミングすることができます。以下のような書式で、記述します。

戻り値の型 関数名(仮引数の宣言付きリスト)
{
    関数名(実引数リスト);

        return 式;
}

また、典型的な書き方と再帰関数を扱う際の注意点を下に示しておきます。

int 関数名(int n)
{
    if(n == 0){   //①
       /*再帰関数の土台*/
       ans = ??????
    }else{
       /*再帰部分②*/
       ans = 関数名(n - 1);
    }
    return ans;
}
  1. 必ず、再帰の土台部分、抜け道を作って置くこと。
  2. 再帰呼び出しをするときは、必ず抜け道に近づくようにすること。

上のことを守らないと再帰関数は、終わらないプログラムになってしまうので、注意が必要です。

次のプログラムを実行して確認してみましょう。

#include <stdio.h>

int fcl(int n)
{
  int m;

  if (n == 0)
    return 1; //0! = 1
  /*nが0でないとき*/
  m = fcl(n - 1); //(n-1)!を求めてそれをmとおく。ここのfcl(n-1)が再帰呼出し。
  return n * m;   //n! = n * m
}

int main()
{
  int i, num;

  scanf("%d", &i);
  num = fcl(i);
  printf("%dの階乗は%dです。\n", i, num);
  return 0;
}
再帰関数を使った階乗の実行結果

一番、簡単な例として階乗のプログラムを作りました。書いている中で思ったかもしれませんが、再帰の書き方は本質的には繰り返し処理と似ています。再帰の呼び出しでしていることは、for文やwhile文などの繰り返しの処理で実現できます。そのため、どちらを使うのか適切に判断しなければなりません。

voidを使った関数

ここまでの関数は「int 関数名()」という記述をしています。しかし、他のサイトや本で、関数を「void 関数名()」として紹介している場合もあります。このように書く時は「何もない型」として表しています。これがどうして必要なのかというと、関数を定義するときは関数の「型」を定義する必要があります。しかし、returnで何も値を返さない関数を作りたいときは、関数の定義でも「何もない型=void」として利用すれば良いです。記述する際は以下のような形になります。

void 関数名(…)
{
     :
}
void main()
{
     :
  関数名(…);
     :
}

実際にプログラム内で使うと以下のようになります。

#include <stdio.h>

void putText(){
  printf("Hello World!\n");
}
 
int main(){
 
  /*関数の呼び出し*/
  putText();
 
  return 0;
}
void関数実行結果

上のようになにも知らずにvoidだけを使った方がはじめはわかりやすいかもしれませんが、大きいプログラムを開発するときを考えると。「int 関数名()」などの記述に慣れていた方が良いです。

値渡しと参照渡し

まず、値渡しとは変数などのオブジェクトの値を引数や戻り値とすることです。対して、オブジェクトのアドレスを引数や戻り値とすることを参照渡しといいます。参照渡しでは関数の引数や戻り値としてアドレスを用います。そのため、関数内の処理では引数や戻り値のアドレスを操作することになります。詳しくは後のポインタの説明で行いますが、簡単に触れておきます。

ここでは、複数のオブジェクトの値を変更する際に、引数で参照渡しにすれば、その複数のオブジェクトの値を処理変更できると覚えておきましょう。

次のサンプルを実行して確認してみましょう。

#include <stdio.h>
 
float pi = 3.14;
  
//関数の定義 
void calc(float r, float *area, float *len) {
    *area = pi * r * r;
    *len = pi * 2 * r;
}
 
void prt(float r, float s, float l) {
    printf("半径%.1fの円の面積は%.2f、円周の長さは%.2f\n", r, s, l);
}

//関数の呼び出し、実行 
int main() {
    float r = 10.0, s = 0, len = 0; //変数の初期化
    calc(r, &s, &len);
    prt(r, s, len);
 
    return 0;
}
参照渡し実行結果

引数や戻り値に配列を指定

引数や戻り値に配列を指定したい場合がある場合はどうしたらいいのか?ただ、C言語では、配列を直接指定できません。ポインタを使って配列のアドレスを指定しなければなりません。

次のサンプルを実行して確認してみましょう。

#include <stdio.h>
 
float pi = 3.14;

//関数の定義
float *data(float *nums) {
    static float data[3];
    data[0] = nums[0];
    data[1] = pi * nums[0] * nums[0];
    data[2] = pi * 2 * nums[0];
    return data;
}
 
void prt(float r, float s, float l) {
    printf("半径%.1fの円の面積は%.2f、円周の長さは%.2f\n", r, s, l);
}

//関数の呼び出し、実行
int main() {
    //引数:ポインタ型(配列)
    float nums[3] = {10.0, 0, 0,};
    float *new_nums = data(nums);
    prt(new_nums[0], new_nums[1], new_nums[2]);
 
    return 0;
}
配列を用いた参照渡し実行結果

配列を戻り値とする関数には、関数名に「*(アスタリスク)」を付けます。関数は配列自体を返せず、配列のポインタを返却します。また、関数は配列を引数とすることはできません。配列を引数にするには配列のポインタを引数にしなければなりません。

ここで出てきたポインタの使い方については次のStepで説明しますので、参考にしてください。

カテゴリー
C

C(Step4-1)

関数の利用①

「関数」という言葉は、ここまでの中で何度か出てきています。例えば、次のようなものです。

  • 結果を画面に表示する関数 printf()
  • データを入力する関数 scanf()
  • ファイルを開く関数 fopen()
  • ファイルに書き込む関数 fprint()

などの関数を使ってきましたが、ここでは、特に自分で関数を作ることを行っていきましょう。

C言語と関数

プログラムに新しい機能を加えるたび、C言語に標準で用意されている関数や、最初に#include<stdio.h>#include<string.h>と記述することで利用できるようになる関数を使ってきました。また、main()も関数にあたります。これは、プログラムを実行する時に一番初めに実行される部分という意味があります。

このように考えると、四則演算や括弧などの記号、変数宣言、制御文を除くと、C言語の記述は全て関数で成り立っています

自分で関数を作る

ここからは自分で実際に関数を作っていきましょう。関数を作るケースは、以下のような場合です。

  • 何度も使う記述は1回だけにまとめる
  • プログラム機能別に見やすくまとめる
  • 他のプログラムでも利用可能な資源を作る

このような関数を作る場面を次の例題で考えてみましょう。

例題:価格表の作成として、商品5個の原価を入力すると、以下の要素をまとめて表示するプログラムを作成してください。

  • 原価に30%を加えた原価(小数切り捨て)
  • 売価の10%の消費税(小数切り捨て)
  • 売価に消費税を加えた税込み価格

このプログラムを関数を使って記述したのが次のプログラムになります。

#include <stdio.h>

/*①配列の初期化関数*/
int initial_array(int price[5][4]){
	int i, j;

	for(i=0; i<5; i++){
		for(j=0; j<4; j++){
			price[i][j] = 0;
		}
	}

	return(0);
}

/*②商品5個の原価入力*/
int input_mod(int price[5][4]){
	int i;

	for(i=0; i<5; i++){
		printf("%d個目の商品の原価を入力してください\n", i+1);
		scanf("%d", &price[i][0]);

	}
	return(0);
}

/*③売価・消費税・税込み価格の計算*/
int calc_mod(int price[5][4]){
	int i;
	for(i=0; i<5; i++){
		price[i][1] = (int)(price[i][0] * 1.3);
		price[i][2] = (int)(price[i][1] * 0.1);
		price[i][3] = price[i][1] + price[i][2];
	}
	return(0);
}

/*④価格表の表示*/
int print_price(int price[5][4]){
	int i;

	printf("原価      :");
	for(i=0; i<5; i++){
		printf("%6d :", price[i][0]);
	}

	printf("\n売価      :");
	for(i=0; i<5; i++){
		printf("%6d :", price[i][1]);
	}

	printf("\n消費税    :");
	for(i=0; i<5; i++){
		printf("%6d :", price[i][2]);
	}

	printf("\n税込み価格:");
	for(i=0; i<5; i++){
		printf("%6d :", price[i][3]);
	}
	printf("\n");

	return(0);
}

int main(){
	int price[5][4];

	/*初期化*/
	initial_array(price);

	/*原価入力*/
	input_mod(price);

	/*売価・消費税・税込み価格の計算*/
	calc_mod(price);

	/*価格表の表示*/
	print_price(price);

	return(0);
}
sample4-1.1実行結果

プログラムを見ると、main()関数内では、「初期化」「原価入力」「各種計算」「価格表の表示」というアルゴリズムを簡単にしたスッキリとした記述になっていると思います。それぞれ、処理に対応する関数が記述されているからです。

関数の定義部で配列宣言をする

以前のプログラムとの違いは次の部分です。

int 関数名(配列宣言)  //関数の定義
{
       :
   return(0);
}

int main()
{
   配列宣言
     :
  関数名(配列名)  //関数の呼び出し
       :
   return(0);
}

ここでは、main()の中で、「関数名(配列名)」として関数を呼び出し、関数の定義では、「int 関数名(配列宣言)」と記述しています。配列を一緒に記述すると、関数の呼び出しで使っている配列が、関数の中でそのまま利用できます

①②③④の関数内部について

プログラム内の4つの関数は、内部でそれぞれ、i や j といった変数の宣言をしています。それぞれの関数は独立しているので、ある関数で使用していた変数を別の関数でも使いたいときは、再び宣言しないと使用できないです。そのため、それぞれの関数で、他の関数と値の関連性がない、その関数内だけで必要な関数を宣言しなくてはいけません。4つの関数で、i という変数を使っていますが、同一の名前でも全く別物として扱われます。

様々な関数を作ってみよう

「どのように関数を記述すればよいのか?」という部分でイメージがまだできないかと思います。ここでは、小さなプログラムを例として、そのプログラムの一部を関数に書き換える方法を説明していきます。

変数を渡さない関数

次のプログラムの一部を関数function1に書き換え、同じ機能のプログラムにするにはどうすれば良いか考えます。

#include <stdio.h>

int main(){
    printf("##################\n");
    printf("##Sample Program##\n");
    printf("##################\n");

    return(0);
}

上のプログラムは変数を何も使ってない記述です。この場合は、次の方法が簡単に関数に置き換えれます。

#include <stdio.h>

int function1(){
    printf("##################\n");
    printf("##Sample Program##\n");
    printf("##################\n");

    return(0);
}

int main(){
    function1(); //定義した関数を呼び出す

    return(0);
}

このような種類の関数は「タイトルの表示」など、特定のメッセージの表示などに利用すると良いです。

変数を渡すが、変数の値は変更しない関数

次のプログラムの一部を関数function2に書き換え、同じ機能のプログラムにするにはどうすれば良いか考えます。

#include <stdio.h>

int main(){
    int a,b,c;
    a = 10; b = 20; c = 40;

    printf("a + b + c = %d\n", a+b+c);
    printf("c - a - b = %d\n", c-a-b);

    return(0);
}

ここでは、変数の値を初期値のまま変更していません。この場合には、次の方法で関数に置き換えられます。

#include <stdio.h>

int function2(int a, int b, int x){
    printf("a + b + c = %d\n", a+b+x);
    printf("c - a - b = %d\n", x-a-b);

    return(0);
}

int main(){
    int a,b,c;
    a = 10; b = 20; c = 40;

    function2(a, b, c);

    return(0);
}

変数の値を参照する関数を作るときは、

  • 関数の呼び出し部では、関数名(変数名*, 変数名, …)
  • 関数の定義部では、int 関数名(変数宣言, 変数宣言, …)

という記述をします。ここで注意することは、呼び出し定義部では、変数の名前は同じものである必要はなく、変数を記述する順番だけが意味を持つということです。

変数を渡し、変数の値を変更する関数

次のプログラムの一部を関数function3に書き換え、同じ機能のプログラムにするにはどうすれば良いか考えます。

#include <stdio.h>

int main(){
    int a, b;
    a = 10; b = 20;

    a = a + 5;
    printf("a + b = %d\n", a+b);
    printf("a = %d\n", a);
    return(0);
}

最初に代入した変数の値を+10として変更しています。この場合この処理を関数にするには注意が必要です。なぜなら、「定義されている関数の中で変数の値を変えても、その関数の呼び出しを終了して呼び出した関数に戻ってきたときには、関数を呼び出す前の値に戻る」からです。

そこで、次のような記述にしなければいけません。

#include <stdio.h>

int function3(int *a, int b){
        *a = *a + 5;
        printf("a + b = %d\n", *a+b);

        return(0);
}

int main(){
    
    int a, b;
    a = 10; b = 20;

    function3(&a, b);
    printf("a = %d\n", a);
    return(0);
}

この例からわかるように「変数の値を変更する関数」では、変更する変数を渡すときには「&」を変数名の先頭につけ、呼び出された関数のほうでは、「*」をつけて宣言し、取り扱わなければなりません。このような操作をcall by referenceと言います。

配列を関数に渡して利用する

次のプログラムの一部を関数function4に書き換え、同じ機能のプログラムにするにはどうすれば良いか考えます。

#include <stdio.h>

int main(){
    int x[2];
    x[0] = 10; x[1] = 20;

    x[0] = x[0] + 10;
    printf("x[0] + x[1] = %d\n", x[0] + x[1]);

    printf("x[0] = %d\n", x[0]);
    return(0);
}

配列x[0]の要素の値を+10として変更しています。この処理を関数にするには注意が必要です。

#include <stdio.h>

int function4(int a[2]){
    a[0] = a[0] + 10;
    printf("x[0] + x[1] = %d\n", a[0] + a[1]);

    return(0);
}

int main(){
    int x[2];
    x[0] = 10; x[1] = 20;
    function4(x);

    printf("x[0] = %d\n", x[0]);
    return(0);
}

ここでは、「配列の値を変更する関数」を記述するときに使った「&」や「*」を利用していません。しかし、ちゃんと関数内では更新した値が反映されます。配列の場合は、配列名を渡して関数を呼び出すだけで、その関数の中で更新した値が呼び出した関数側でも反映されます。

カテゴリー
C

C(Step3-3)

文字列の扱い②

文字の取り扱いの詳細

ここまでで、基本的な文字の取り扱い方の詳細についてまとめてみます。

  • 一般に文字を取り扱うときには、何文字かのつながり(文字列)で利用する
  • 文字列を記憶するときには、文字型1次元配列を利用する
  • 文字列を記憶する文字型1次元配列の大きさは、想定される文字長の最大+1で用意する
  • 文字列を記憶させるときには、文字列の最後に何もない文字’\0’を記憶させる
  • 文字はコンピュータの内部で数値で表されているので、その数値の大小関係を利用して文字の比較をすることができる

また、これらを踏まえた上で、文字列の読み込み・表示方法についても学習しました。

文字列の読み込み、ファイル入力方法の詳細

文字列の読み込みでは、実行時の読み込みとファイルからの読み込みで似た記述をします。処理の内容には大きな違いがあるので、注意が必要です。

■gets()の使い方

gets(配列名);
  • 配列名は、「char 配列名[n];」として宣言された、n個の要素を持つ文字型1次元配列を意味する。
  • 入力された文字の長さが、nよりも小さいときには入力された文字の最後に「何もない文字(’\0‘)」を入れたものが1次元配列に記憶される
  • 入力された文字の長さが、nよりも大きい時には、n+1番目以降は無視して、先頭からn番目の文字までが、1次元配列に記憶される

■fgets()の使い方

fgets(文字配列名,[読み込む文字数],[ファイルポインタ]);
  • ファイルポインタとは、「FILE *変数名」として変数宣言された変数を意味し、fopen()を行ったあとのファイルポインタを利用する
  • 配列名は、「char 配列名[n];」として変数宣言された、n個の要素を持つ文字型1次元配列を意味する。
  • ファイルの1行の長さが、[読み込む文字数]よりも小さいときは、改行を含む先頭の文字列が記憶される
  • ファイルの1行の長さが、[読み込む文字数]よりも大きい時には、行の途中までの[読み込む文字数]分だけの文字列が配列に記憶され、次回の読み込みは前回の読み残した残りの文字からはじまる。
  • nよりも大きな値を[読み込む文字数]として指定したときは、先頭からn文字だけが配列に記憶され、残りは切り捨てられる

文字列の読み込み、ファイル入力方法の詳細

次に文字列の表示方法についてまとめておきましょう。文字列の表示は、ファイルの出力の記述とよく似た記述をします。

■printf()の使い方

printf("%c", 変数名);
printf("%s", 配列名);
  • 1文字を表示する時は、「char 変数名;」として宣言されたものに対して、printf()に「%c」の変換仕様を利用する
  • 文字列を表示する時は、「char 配列名[n];」として宣言されたものに対して、printf()に「%s」の変換仕様を利用する
  • 文字列を表示するときは、表示する配列の先頭から何もない文字(‘\0’)までのすべての文字が左詰めで表示される(空白文字を含む)

■fprintf()の使い方

fprintf([ファイルポインタ], "%c", 変数名);
fprintf([ファイルポインタ], "%s", 配列名);
  • ファイルポインタとは「FILE *変数名;」として宣言された変数を意味し、fopenを行ったあとのファイルポインタを利用する
  • 文字列をファイルに書き出すときは、「char 配列名[n];」として配列宣言されたものに対して、fprintf()「%s」の変換仕様を利用する
  • 文字列を書き出す時は、書き出す配列の先頭から何もない文字(’\0’)までのすべての文字が左詰めで表示される(空白文字を含む)

文字列をコピーする

文字列をコピーしたいときは、#include <string.h>でstring.hファイルをインクルードしたあと、strcpy()(ストリングコピー)を使って次のように記述します。

strcpy(moji2, moji);

このように記述することで、mojiの配列内の文字をmoji2の配列にコピーします。このとき、moji2(コピー先)の文字列の長さ >= moji(コピー元)の文字列の長さ でないといけません。また、文字列を初期化したいときにも使うことができます。その場合は、以下のように記述します。

strcpy(moji, "Hello World");

この記述では、「”」(ダブルクォーテーション)で文字列(Hello World)を囲み、その文字列が文字列の1次元配列であるかのように扱っています。

文字列を繋ぐ

文字列を連結するにも関数があります。文字列のコピーと同様に#include <string.h>でstring.hファイルをインクルードしたあと、strcat()ストリングキャット)という関数を使い、次のように記述します。

strcat(moji2, moji);

この記述では、moji2の後ろにmojiを追加することを表します。たったこれだけで、1文字ずつ調べながら代入するといった手間が省け簡単に2つの文字列を繋ぐことができます。

文字列を比較する

続いて文字列の比較です。文字列のコピーと同様に#include <string.h>でstring.hファイルをインクルードしたあと、strcmp()(ストリングコンペア)という関数を使い、次のように記述します。

if(strcmp(input, moji) == 0)

この記述では、inputとmojiの配列内の文字を比べ、等しければ0を返すという処理をしています。何十文字と膨大な文字を比較する時など有効に使える場面は数多くあります。

次のサンプルプログラムを実行して確認してみましょう。

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

int main(){
	char input[3];

	/*質問の表示と解答入力*/
	printf("質問、正しいと思えばYes,誤っていると思えばNoと入力してください\n");
	gets(input);

	/*正解判定と結果表示*/
	if(strcmp(input, "Yes") == 0){
		printf("正解\n");
	}else{
		if(strcmp(input, "No") == 0){
			printf("不正解\n");
		}else{
			printf("入力が不適切\n");
		}
	}
	return(0);
}
strcmp()を使ったサンプル実行結果

何文字あるか調べる

文字列の長さを調べるには、strlen()(ストリングレングス)という関数を用います。これは、文字列を調べた結果を数値として返します。以下のように記述します。

n = strlen(moji);

この記述では、文字配列mojiの文字列の長さを調べ、その数値を変数nに代入する処理をしています。

次のサンプルプログラムを実行して確認してみましょう。

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

int main(){
	char input1[10], input2[10];

	/*質問の表示と解答入力*/
	printf("1つめの文字列を入力");
	gets(input1);
	printf("2つめの文字列を入力");
	gets(input2);

	/*文字列を繋ぎ合わせるか判定*/
	if((strlen(input1) + strlen(input2)) <= 10){
		/*文字列を繋ぎ合わせ、結果表示*/
		strcat(input1, input2);
		printf("%s\n", input1);
	}else{
		/*結果の表示*/
		printf("入力した文字列が長いので連結できません\n");
	}
	return(0);
}
strlen()を使ったサンプル実行結果

文字列を使った応用場面

実際に現実的な例題で文字列処理の応用場面を見ていきましょう。

例題:アドレス帳の検索 10人の名前、電話番号が記述されているテキストファイルがあるとき、名前を入力したら、該当する電話番号が表示されるプログラムを作成します。

この例題に沿って、プログラムを作成していきましょう。

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

int main(){
	char name[10][22]; //①
	char phone[10][13];
	char input[21];
	int i;
	FILE *FP;

	/*ファイルのオープン*/
	if((FP = fopen("address.txt", "r")) == NULL){
		printf("ファイルが開けません\n");
		return(1);
	}

	/*ファイルからデータを読み込む*/
	for(i = 0; i < 10; i++){
		fgets(name[i], 22, FP);
		/*②読み込んだ文字の最後にある改行を消す*/
		name[i][strlen(name[i])-1] = '\0';
		printf("名前%d:%s\n",i+1 ,name[i]);
		/*③電話番号の読み込み*/
		fgets(phone[i], 13, FP);
		/*読み込んだ文字の最後にある改行を消す*/
		phone[i][strlen(phone[i])-1] = '\0';
	}

	/*電話番号を検索したい名前の入力*/
	printf("電話番号を検索したい名前を入力してください\n");
	gets(input);
	/*検索と表示*/
	for(i = 0; i < 10; i++){  //④
		if(strcmp(name[i], input) == 0){
			/*電話番号表示*/
			printf("%sさんの電話番号は:%s\n", input, phone[i]);
		}
	}
	fclose(FP);
	return(0);
}
アドレス帳検索プログラム実行結果

プログラム内の番号が付いている部分について順番に説明していきます。

①ファイルから名前と電話番号を読み込むための2次元配列と、名前入力の1次元配列を用意

ここで、注意するのは以下の点です。

  • 文字列を扱う際に、関数gets()を利用するときには改行文字が文字列に読み込まれないため、用意する文字列の大きさは、「最大文字+1(‘\0’の分)」必要です。
  • 文字列を扱う際に、関数fgets()を利用する時は、改行文字が文字列に読み込まれるため、用意する文字列の大きさは、「最大文字+2(‘改行文字と’\0’の分)」必要です。

②③文字列をファイルから読み込む

この部分は、どちらもファイルから読み込む記述です。ただ、fgets()で読み込んだ文字列は改行文字も含まれるので、strlen()を利用して文字列の長さを調べ、最後の改行文字を何もない文字’\0’に置き換えてます。

④読み込まれた文字列と入力された文字列の比較

ここでは、2つの文字列、ファイルから読み込んだ文字列name[X]とinputが同じであるかの比較をしています。

ファイルの中身を全て読み出す

ここまででは、ファイルからデータを読み出す時には、何行書かれているかあらかじめ知っておく必要がありました。実際には、ファイルに何行書かれているかわからないことがよくありますよね。次の例題から考えていきましょう。

例題:「read.txt」というテキストファイルに、何らかの情報が書かれています。これを「copy.txt」というファイルにコピーするプログラムを作成します。

ここで、問題となるのは、どれだけ繰り返せば良いのかです。コンピュータで扱っているテキストファイルは、ファイルの終わりが検出できるような、目に見えない特殊な文字が入っています。それは、EOF(End Of File)と呼ばれる文字です。プログラムでファイルを呼び出す場合には、このEOFを検出したり、検出する関数を利用したりしてプログラミングします。

次のサンプルを実行して確認してみましょう。

#include <stdio.h>

int main(){
	char input[81];
	FILE *FP1, *FP2;

	/*ファイルから読み込みできるようにする*/
	if((FP1 = fopen("read.txt", "r")) == NULL){
		printf("読み込みファイルが開けません\n");
		return(1);
	}

	/*ファイルに書き込み出来るようにする*/
	if((FP2 = fopen("copy.txt", "w")) == NULL){
		printf("書き込みファイルが開けません\n");
		return(1);
	}

	/*繰り返し処理*/
	while(fgets(input, 81, FP1) != NULL){
		fprintf(FP2, "%s", input);
	}
	fclose(FP1);
	fclose(FP2);
	return(0);
}

これまで利用してきた関数ばかりなので、目当たりことはないですが、以下の部分に注目しましょう。

fgets(‥‥)!= NULL

この表現は、「もし、fgets()が、何もない文字を文字を出力しないならならば」という条件を表しています。今まで、利用してきたfgets()という関数は、

  • ファイルの終端でないときには、NULLではない文字を出力する
  • ファイルの終端であれば、NULLを出力する

という機能を持っている関数だったのです。この表現は重要なものなので、覚えておきましょう。

カテゴリー
C

C(Step3-2)

文字列の扱い①

ここまででは、プログラムで文字を扱う方法は、ソースプログラムに書かれている文字をそのまま画面に表示することしかできません。ここでは、以下のようなことを取り扱えるようにしていきます。

  • 文字を入力して、それを表示する
  • 質問の答えを数値ではなく、文字で答えられるようにする

C言語での文字列

まず、文字を記憶する変数宣言はchar型を使います。

char  変数名;

この記述で、1文字を記憶する変数を用意できます。ただ、1つの変数に1つの文字しか記憶できません。日本語のひらがな等記憶するには、2文字分の変数が必要です。

文字列には配列を使う

数多くの数値を取り扱うときには、配列を用いる事を以前説明しました。文字列の場合でも同様に考えます。例えば、5文字の「Hello」という文字列を記憶するための配列mojiは、次のようにchar型を使って宣言し、代入して記憶させます。

char moji[6];

moji[0] = 'H';
moji[1] = 'e';
moji[2] = 'l';
moji[3] = 'l';
moji[4] = 'o';
moji[5] = '\0';

ここで、注意するのは、文字を代入するときは「’」シングルコーテーションで文字を囲むことです。また、最後の行に注目しましょう。文字列を扱う時は、必ず’\0’を入れる必要があります。これは空白文字Nullを意味します。そのため、配列は「記憶させたい文字数+1」の大きさで用意する必要があります。

文字列の代入

上記のように、文字を1文字ずつ代入していては、文字数が多い時に大変になります。そこでもっと便利な代入方法があります。次の例から説明していきます。

例題:入力された英字小文字を大文字に変換するプログラムを作成します。英数字は1行80文字まで入力でき、日本語や空白は入力しないものとする。

次のプログラムを作成していきます。

#include <stdio.h>

int main(){
    
    char input[81]; //①文字列を記憶する1次元配列
    int i;

    /*初期化*/
    for(i=0; i<81; i++){
        input[i] = '\0'; //②配列をNullで初期化
    }
    
    /*入力*/
    scanf("%s", input); //③文字列の入力を受付け

    /*出力*/
    printf("入力された文字\n%s\n", input); //④配列inputの文字列表示

    /*1文字ずつ検査と表示*/
    for(i=0; i<81; i++){
        switch (input[i]){
            case 'a':printf("A"); break;
            case 'b':printf("B"); break;
            case 'c':printf("C"); break;
            case 'd':printf("D"); break;
            case 'e':printf("E"); break;
            case 'f':printf("F"); break;
            case 'g':printf("G"); break;
            case 'h':printf("H"); break;
            case 'i':printf("I"); break;
            case 'j':printf("J"); break;
            case 'k':printf("K"); break;
            case 'l':printf("L"); break;
            case 'm':printf("M"); break;
            case 'n':printf("N"); break;
            case 'o':printf("O"); break;
            case 'p':printf("P"); break;
            case 'q':printf("Q"); break;
            case 'r':printf("R"); break;
            case 's':printf("S"); break;
            case 't':printf("T"); break;
            case 'u':printf("U"); break;
            case 'v':printf("V"); break;
            case 'w':printf("W"); break;
            case 'x':printf("X"); break;
            case 'y':printf("Y"); break;
            case 'z':printf("Z"); break;
            default :printf("%c", input[i]);
        }
    }
    return(0);
}
sample3-2.1実行結果
①文字列を記憶するための1次元配列を用意

文字列を記憶する時には、①のように1次元配列として取り扱います。ここでは、1行最大80文字まで扱い、最後に付ける「何もない文字(’\0’)」の分も入れて81個の要素を用意します。

②配列の初期化

ここまででも、説明しましたが、変数や配列を宣言した時はそこに何の値が入っているかは決められていません。数値の変数や配列の時は0を代入して初期化をしてきました。文字列の場合、「何もない文字」を代入するという初期化をします。何もない文字をあらわす記述が「’\0’」になります。

③文字列の入力受け付け

ひと続きの文字列をまとめて読み込むための便利な記述方法が③の部分です。ひと続きの文字列を変換仕様で「%s」として表します。しかし、「Hello World」のように文字の間に空白が入ってしまったときには、文字が続いているはじめの部分のみが変数に記憶されます。以下の入力例をご覧ください。

入力表示結果記憶されなかった文字
Hello WorldHelloWorld
I’m happyI’mhappy
3-years ago3-yearsago
%s入力例

現実にもっと良く使われる場面を考えると、文字列では空白が入ることが多々あります。そのようなときに次の文字専用の入力関数 gets()を使います。

gets (文字配列名);

例えば、scanf(“%s”, input);の所をgets(input);と書き換えることができます。空白も正しく配列に記憶できます。

④配列に記憶されている文字列を画面に表示

この部分は、配列inputに記憶されている文字列を画面に表示する記述です。ここのprintf()では、「%s」を使っています。文字列の1次元配列を意味しており、「%c」では1文字だけだったのがまとめて表示できるようになっています。

文字と数字の関係

C言語での文字の扱いを知るため、以下のサンプルを作成してみてください。

#include <stdio.h>

int main(){
    
    char input;

    /*入力*/
    scanf("%c", &input);

    /*出力*/
    printf("入力された文字[%c][%d]", input, (int)input);

    return(0);
}

このプログラムでは、次の2つの処理をしています。

  1. 文字を入力し、文字型の変数inputに記憶する
  2. 変数inputに記憶されている文字と、inputに保存されている文字を「数値に変換した値」を表示する

ここでは、文字型の変数inputに対して「(int)input」として「%d」という変換仕様で表示しています。「文字型変数→整数型変換」と変換して値を取り出して整数値に変換したものを表示するという意味になります。コンピュータの内部では、文字も全て数値としてあらわすことができるのです。

上のプログラムの結果は以下の表のようになります。

入力文字数値入力文字数値入力文字数値
a97A65048
b98B66149
c99C67250
::::::
z122Z90957
文字の数値変換表

この結果から次のことがわかります。

  • アルファベットの小文字、大文字は違う数値で表される。アルファベットの「a」と「A」、」「z」と「Z」のように、小文字、大文字で違う数値で表されます。
  • アルファベットの小文字、大文字はそれぞれ数値で表すと、順番に並んでいる。アルファベットの小文字は、「a」「b」「c」…は97、98、99…と順番になります。同じように、大文字についても65、66、67…と順番になります。文字を表す数値も、アルファベットの順に並びます。ただし、小文字の表す数値の方が大文字の数値よりも大きい値になります。

関係性としては、
小文字の数値 = 大文字の数値 + 32
になります。

  • 数字の文字と文字が表す数値は一致しない。文字として入力した「0」「1」「2」…は、数値で表すと48、49、50となります。つまり、数字を表す文字は、表す数値と一致しません。ただ、数字の順には並んでいます。

関係性としては、
文字を表す数値 – 48
になります。

このように、コンピュータの中では、文字も全て数値として扱われていると考えることができます。以下の表のようにまとめることが出来ます。

10の位\1の位0123456789
0[無,\0]
1
2
3[空白]!#$%&
4()*+,./01
523456789:;
6<=>?@ABCDE
7FGHIJKLMNO
8PQRSTUVWXY
9Z[\]^_`abc
10defghijklm
11nopqrstuvw
12xyz{}~
文字と数値の対応表

上の表を利用して「数値→文字」に変換して表示するプログラムを作成してみましょう。

#include <stdio.h>

int main(){

    int num;

    /*入力*/
    scanf("%d", &num);

    /*出力*/

    printf("入力された数値[%d][%c]\n", num, (char)num);

    return(0);
}

大切なのは、「(char)num」の部分です。この記述が「数値→文字」のキャストを行っています。例えば、「65」と入力すれば、「A」が表示されるはずです。文字は数値で表すことができ、その数値は順番に並んでいるということを理解することが重要です。

文字を比較する

プログラムでは、数字と同じように文字も「どちらが大きいか?どちらが小さいか?」という比較もできます。まずは、以下のプログラムを書いて実行してみましょう。

#include <stdio.h>

int main(){

    char input;

    /*初期化*/
    input = '\0';

    /*入力*/
    scanf("%c", &input);

    /*小文字英文字の判定*/
    if((input >= 'a') && (input <= 'z')){  //①
    	printf("小文字英字です。\n");
    }else{
    	/*大文字英字の判定*/
    	if((input >= (char)65) && (input <= (char)90)){  //②
    		printf("大文字英字です。\n");
    	}else{
    		printf("英字以外の文字です。\n");
    	}
    }
    return(0);
}
sample3-2.2実行結果

①は、入力された文字(input)が’a’以上’z’以下であるかというif文の記述です。次のように書き換えることも可能です。

if((input >= (char)97) && (input <= (char)122))

②は、入力された文字(input)が文字を表す数値で65以上90以下であるかというif文の記述になります。次のように書き換えることも可能です。

if((input >= 'A') && (input <= 'Z'))

これらから以下のことが言えます。

  • 文字も数字と同じように大小比較することができる
  • 文字の大小関係は、文字を表す数値によって決まる
カテゴリー
C

C(Step3-1)

ファイルの入出力

ここでは、C言語では、どのようにしてファイルを操作し、保存・読み込みを行えるようにすればよいか説明していきます。

コンピュータにおけるファイルの種類

コンピュータ上のファイルは大きく分けて、テキストファイルとバイナリファイルの2種類に分類されます。ここでは、扱いが簡単なテキストファイルについて詳しく説明していきます。

テキストファイルとは、エディタなどで、そのファイルを開いたときに文字や記号が人間にわかるように記述されているファイルです。人の目に見える文字や記号の他にも「改行」「スペース」など見えない記号が書かれてることがあります。取り扱うときは、これらの「改行」や「スペース」などの記号も1文字として取り扱います。さらに日本語の1文字は2文字として取り扱わなくてはいけません。

ファイルにおける文字

全角文字が半角文字2文字分であることはC言語だけでなく、コンピュータで日本語を扱う時の一般的規則です。

ファイルを利用してのデータ入力

大量のデータをプログラムに入力しなくてはならない場合、どのようにファイルを利用してデータを入力できるか見ていきます。

例題:野球選手10人の打者の成績を評価するために1塁打1ポイント、2塁打2ポイント、3塁打3ポイント、ホームラン4ポイントとして過去30打席の成績をもとに得点を計算する。そこで、10人それぞれの打席の結果をポイントで入力し、10人の全打席のポイント数と合計した成績を表示するプログラムを作成する。

ここで、どのようにプログラムを作成するかと考え、アルゴリズムを考えます。

  • 30打席のデータをまとめて、1次元配列にする
  • さらに10人のデータがあるので、1次元配列をまとめて2次元配列にする

入力するデータをテキストファイルに

膨大な得点を手入力で入力するわけにはいかないので、入力するデータをあらかじめテキストファイルに入れておき、プログラムはこのテキストファイルからデータを読み込むようにすれば、データは何度でも使えますし、入力の手間を省けます。

入力するデータは以下のようにテキストファイルとして作って置くことにします。それぞれの行には整数値しか記入しません。(空白もありません)

選手の得点データ

テキストファイルを利用してプログラムに渡すようにしたプログラムを次に考えていきます。

#include <stdio.h>

int main(){
    int point[10][30];
    int total[10];
    int i, j;
    FILE *FP; //①ファイルを扱うための変数宣言

    /*ファイルを読み込み可能状態にする*/
    FP = fopen("data1.text", "r"); //②用意してあるテキストファイルを読み込み可にする
    
    /*ポイントの入力*/
    for(j=0; j<10; j++){
        for(i=0; i<30; i++){
            printf("背番号%2dの%d打席目のポイント入力\n", j, i+1);
            fscanf(FP, "%d", &point[i][j]); //③テキストファイルからデータを読み込む
        }
    }

    /*ファイルの使用を終了する*/
    fclose(FP);

    /*合計得点の表示*/
    for(j=0; j < 10; j++){
        total[j] = 0;
        for(i=0; i<30; i++){
            total[j] = total[j] + point[j][i];
        }
    }

    /*結果の表示*/
    printf("----結果----\n");
    for(i=0; i< 30; i++){
        for(j=0; j<10; j++){
            printf("%3d", point[j][i]);
        }
        printf("\n");
    }
    printf("---|---|---|---|---|---|---|---|---|---|\n");
    for(j=0; j<10; j++){
        printf("%3d", total[j]);
    }
    return(0);   
}
①ファイルを扱うための変数宣言

この部分は、変数宣言になります。ファイルを扱うためにはFILE(大文字表記)という型の変数を用意します。ただし、「*」が名前の前につきます。

■ファイルポインタの宣言
FILE *変数名;

C言語では、このように「*」をつけた変数をポインタ変数といい、FILE型のものはファイルポインタ(file pointer)と呼びます。これは、ファイルを操作しているときに、今ファイルのどこを読み込んでいるのかを示し、その場所を保存しておくポインタ型の変数です。

②ファイルのオープン

この部分は、ファイルのオープンと呼ばれる処理で、C言語でファイル操作するときには必須の部分です。fopen()という関数を使います。

■ファイルのオープン
・ファイルを読み込み可能にする
・ファイルポインタ(ファイルのどこを見ているか)をファイルの先頭で初期化する

ファイルポインタ = fopen("ファイル名", "r");
③ファイルからデータを読み込む

ここでは、fscanf()が出てきました。キーボードからのデータ入力を扱うのが、scanf()であるのに対して、ファイルから読み込むのがfscanf()になります。使い方は似ていて、違いは、はじめの引数にファイルポインタが加えられているだけです。

④ファイルのクローズ

この部分は、ファイルのクローズと呼ばれる部分で、ファイルをオープンして使用した後は必ず必要な処理です。fclose()という関数を使います。ファイルのクローズを忘れても実行できることがありますが、コンピュータ上の他のファイルを破壊したり、実行に異常がある場合もあるので、記述しておきましょう。

ファイルへのデータ出力

次に「ファイルに結果を出力」を例題を通して説明していきます。

例題:サッカーチームA、Bの10試合の対戦結果を入力すると、得点をグラフで表示するプログラムを作成する。

プログラムに起こすと以下のようになります。

#include <stdio.h>

int main(){
    int score[10][2];
    int i, j;
    FILE *FP; //①ファイルポインタの宣言

    /*得点結果の入力*/
    for(i=0; i<10; i++){
        printf("A対Bの第%d試合結果を数字2つで入力してください\n", i+1);
        scanf("%d %d", &score[i][0], &score[i][1]);
    }

    /*②ファイル書き込み可能状態にする*/
    FP = fopen("result.text", "w");

    /*③結果表示*/
    fprintf(FP, "----A----|----B----\n");
    for(i=0; i<10; i++){
        for(j=0; j<(10-score[i][0]); j++){
            fprintf(FP, " ");
        }
        for(j=0; j<score[i][0]; j++){
            fprintf(FP, "*");
        }
        fprintf(FP, "|");
        for(j=0; j<score[i][1]; j++){
            fprintf(FP, "*");
        }
        fprintf(FP, "\n");
    }
    fclose(FP); //④
    return(0);
}

このプログラムを実行し、対戦結果の数字を入力し終えても画面には何も表示されません。しかし、「result.txt」というファイルが作られ、ファイル内にぐらふが書かれていることがわかります。

②以外の部分は、前のプログラムと同じ処理を行います。ファイルのオープンのみ異なります。

■ファイルのオープン
・ファイルを書き込み可能にする
・ファイルポインタをファイルの先頭で初期化する

ファイルポインタ = fopen("ファイル名", "w");

ファイルからの読み込み処理では、readの1文字をとって「r」、ファイルへの書き込みはwriteの1文字をとって「w」となっています。

実用的なファイルのオープン方法

ユーザーにとってより使いやすいプログラムを提供するには、エラー処理も大切になります。エラー処理の第一歩として「ファイルからデータを読み込もうとしたのにそのファイルが存在しなかった」という場合に、「ファイルが開けません」というメッセージを表示してみましょう。

#include <stdio.h>

int main()
{
    FILE *FP;
    
    /*エラー処理を含むファイルのオープン*/
    if((FP = fopen("file.txt", "r")) == NULL){
        printf("ファイルが開けません\n");
        return(1);
    }

    /*ファイルが開けたときのみ実行される*/
    printf("ファイルが開けました\n");
    /*ファイルクローズ*/
    fclose(FP);
    
    return(0);
}

このサンプルを実行すると、ファイルが存在しなければ、「ファイルが存開けません」存在すれば、「ファイルが開けました」と表示します。ファイルが開けない時もプログラムをコントロールできます。

ファイルの利用方法の詳細

ここまでのファイルの使い方についてまとめておきます。

ファイルを操作できる状態、操作を終える
■fopen、fcloseの使い方
if((FP = fopen("file.txt", "X")) == NULL){ 
        [ファイルを開けない時の処理];
        return(1); 
} 

[ファイルを利用した様々な処理];

fclose([ファイルポインタ]);
  • ファイルポインタとは「FILE *変数名;」として変数宣言された変数を意味する
  • ファイル名にはディレクトリ名を含めることができる
  • Xには、どのようにファイルをオープンするか決める文字が入る
「r」…ファイルの先頭から読み込むとき
「w」…ファイルの先頭から書き込むとき
「a」…現在あるファイルの最後から書き込むとき
  • ファイルをオープンしたら、必ずファイルのクローズをする。ただし、開くのに失敗したときはいらない
ファイルから読み込む、書き込む
■fscanf()の使い方

fscanf([ファイルポインタ], "XXXXXX", Y1, ..., \n);
  • ファイルポインタとは「FILE *変数名;」として変数宣言された変数を意味する
  • XXXXXには、読み込みたい値の書式を記述する
  • 読み込みたい値の書式には、printf()で利用した%dなどの変換仕様を用いる
  • Yには、変数のポインタ(コンピュータ上で変数が用意されている場所の情報)を記述する。ここまでに説明した変数に値を読み込む場合には、変数名の先頭に&をつけたものを記述する
■fprintf()の使い方

fprint([ファイルポインタ], "XXXXXX", Y1, ..., \n);
  • ファイルポインタとは「FILE *変数名;」として変数宣言された変数を意味する
  • XXXXXには、書き込みたい値の書式を記述する。ここに文字や数値をそのまま記述すれば、それがファイルポに書き込まれる
  • 変数を書き込むときは、書き込みたい値の書式に、printf()で利用した%dなどの変換指定を用い、Yには変数を記述する
カテゴリー
C

C(Step2-3)

配列の利用

配列とは値を入れる箱(変数)をまとめて棚をつくることです。C言語では、複数の変数をまとめて管理する方法の1つを配列(array)といいます。配列が利用できるのは、同じ種類の変数をまとめて管理するときだけです。

1次元配列

まずは、データを一列に並べて取り扱う方法を次の例で考えてみましょう。

例題:商品が1列に値段順に10個並んでいます。この時、何番目の商品かを入力すれば、値段を表示するようにプログラムを作成します。入力された値が1~10であれば、その番号の値段を表示し、入力に戻ります。それ以外の値ならプログラムを終了します。

上の例をプログラムにすると以下のようになります。

#include <stdio.h>

int main(){
    int price[10]; //①
    int number;

    /*値段の初期設定②*/ 
    price[0]=80; price[1]=100; price[2]=120;
    price[3]=140; price[4]=150; price[5]=160;
    price[6]=200; price[7]=198; price[8]=220;
    price[9]=280; 

    /*繰り返し処理*/
    do{
        printf("値段を知りたい番号を入力してください");
        printf("[1-10以外なら終了]\n");
        scanf("%d", &number);
        /*値段表示*/
        if((number >= 1) && (number <= 10)){ 
            printf("%d番の棚は、%d円です\n", number, price[number-1]);
        }
    }while((number>=1) && (number <= 10));
    return(0);
}
sample2-3.1実行結果

上のプログラムでは、簡単に記述する方法として配列が用意されています。①と②に注目してみましょう。

配列を使った変数宣言と値の代入(①、②)

配列を使わない変数宣言では、「price0, price1, ‥‥」と変数をそれぞれ別々に記述しますが、ここでは、price[10]と記述しています。このように、

変数の型 変数名[変数の個数(n)];

として変数宣言すると、変数がn個並んでいる配列を用意できます。これを配列の宣言といい、名づけた変数名は配列名となります。配列の宣言をすれば、1行の記述で、n個の値を記憶できる場所を用意できることになります。ひとつひとつの入れ物を配列の要素といい、[ ]で囲んだ番号がつきます。これをインデックス(index)といいます。

②では、配列の1つ1つに商品の値段を代入しています。int price[10];として用意された配列に値を代入するときは以下のようになります。

配列の1番目の要素に値を代入するときprice[0]=80;
配列の2番目の要素に値を代入するときprice[1]=100;
: :
配列の最後の要素に値を代入するときprice[9]=280;
配列の代入の仕方

配列の要素それぞれには、[0]~[19]までの番号がつきます。ここで注意することは、配列の一番初めの番号は[0]であるということです。20個の値を入れる配列であれば、0~19までの番号がついた箱を並べていることになります。

2次元配列

前項では、変数を並べて管理する1次元配列を扱いましたが、配列にはより拡張した使いかたがあります。次の例題で考えていきましょう。

例題:3人で4種類のゲームを行ったときの得点をゲームごとにそれぞれ入力し、各人の得点と合計得点を次のように表示できるようにしてください。

ゲーム1ゲーム2ゲーム3ゲーム4合計
A104072885
B230132790
C540112985
得点表

次に、これをプログラムに記述していきます。

#include <stdio.h>

int main(){
    int point[3][4]; //A,B,Cの得点を記憶する配列①
    int total[3];
    int i, j;

    /*得点の入力②*/
    for(i=0; i<4; i++){
        for(j=0; j<3; j++){
            switch(j){
                case 0:printf("A"); break;
                case 1:printf("B"); break;
                case 2:printf("C"); break;
            }
            printf("のゲーム%dの得点を入力してください\n", i+1);
            scanf("%d", &point[j][i]);
        }
    }

    /*合計得点の計算③*/
    for(j=0; j < 3; j++){
        total[j]=0;
        for(i=0; i<4; i++){
            total[j] = total[j] + point[j][i];
        }
    }

    /*表の表示④*/
    for(j=0; j < 3; j++){
        for(i=0; i<4; i++){
            printf("%4d", point[j][i]);
        }
        printf(" :%d\n", total[j]);
    }
    return(0);
}
sample2-3.2実行結果

上のプログラム①、②、③、④について解説していきます。

2次元配列の用意(①の部分)

①の部分では、

配列の型 配列名[X][Y];

という記述をしています。これは、「配列名[Y]」という1次元配列を、さらに1次元配列のようにまとめて用意するという配列の宣言です。このように1次元配列をさらに1次元配列として記述したものを2次元配列といいます。

2次元配列+繰り返し処理へと書き換え、分岐処理を行う(②の部分)

②では、「変数の並び→1次元配列+繰り返し処理」と同じように「1次元配列の並び→2次元配列+繰り返し処理」という書き方をしています。そのため、繰り返しが2重になっています。

また、switch文によって表示する文字の分岐処理をしています。配列を使えば、変数をまとめて「配列名[番号]」として番号で管理できますが、「A」「B」「C」という文字の並びで管理できません。文字を使いたい場合などは、分岐処理が必要になります。

2次元配列+繰り返し処理への書き換え

③、④の部分でも、「1次元配列の並び→2次元配列+繰り返し処理」という書き方をしています。そのため、繰り返しが2重になっています。

このようにして、1次元配列の並びをさらにまとめて、2次元配列で表現することで、プログラムがすっきり記述できます。

配列の使用方法詳細

ここでは、配列の扱い方について詳細な説明をしていきます。

配列の宣言の記述方法

2つ以上の変数をまとめて、番号をつけて1つの配列名で管理する1次元配列は、次のような配列の宣言で記述することで利用できます。

記憶する値1次元配列の配列宣言の記述
整数int 配列名[並べて扱う変数の数]
整数long int    配列名[並べて扱う変数の数]
整数short int   配列名[並べて扱う変数の数]
整数unsigned int 配列名[並べて扱う変数の数]
実数float     配列名[並べて扱う変数の数]
実数double    配列名[並べて扱う変数の数]
文字char     配列名[並べて扱う変数の数]
1次元配列の配列宣言の記述方法

たとえば、int array[10]と宣言すれば、20個の整数値をまとめて管理することが可能です。2次元配列として宣言する場合は以下のようになります。

1次元配列: int array0[10], array1[10], ……, array50[10];
   ↓
2次元配列: int array[50][10];

1次元配列→2次元配列とまとめたのと同じように、2次元配列→3次元配列とまとめることもできます。このように何重にもまとめた配列を多次元配列と呼びます。

※大きさがばらばらな配列や異なる種類の変数をまとめてひとつの配列にはできません。

配列の扱い方

配列宣言を行い、値を記憶する場所を用意した配列をどのようにしてプログラム内で取り扱うかまとめます。

1次元配列をint array[100];として配列宣言した場合は、

array[0] = 20; array[1] = 100; ‥‥‥ array[99] = 80;

のようにして配列の1つ1つの要素に代入できます。つまり、「配列名[n]」とすることで、これまでに利用してきた変数と同じように1つずつの値を取り扱うことができます。

ここで注意するのは、「配列名[n]」のnの部分の整数は、0から始まります。int array[100];では、array[0]~array[99]の100個の値を記憶できます。

2次元を超える配列の場合も、[整数]を重ねて記述すれば値の代入、取り出しを行えます。

カテゴリー
C

C(Step2-2)

繰り返し処理

同じ処理を何度も繰り返す「繰り返し処理」は、コンピュータの意味のある利用に欠かせない処理です。人間がやたら時間がかかる大量な作業もプログラムでコンピュータにさせることができるようになります。

決まった回数の繰り返し(for文)

まずは、最も基本的な繰り返し処理を理解しましょう。次のプログラムを実行してみましょう。

#include <stdio.h>

int main(){
    double inch, cm;
    int i; //100まで数えるための変数を用意
    inch = 2.54;

    for(i = 1; i <= 100; i++){
        cm = i*inch;
        printf("%3d inch : %.2lf\n", i, cm);
    }
    return(0);
}
sample2-2.1実行結果

このプログラムでは、1inchから100inchまでの対応表を作成しています。しかし、このプログラムでは、100inchまでを少ない処理で実現しています。繰り返し処理の記述内を見ていきましょう。

for(i = 1; i <= 100; i++){
        cm = i*inch;
        printf("%3d inch : %.2lf\n", i, cm);
    }

この記述内には計算1回、表示1回という記述しかないですが、実行すると100回分の処理を行ってくれます。これは、for(フォア)文を使っているためです。この記述によって繰り返し処理を実現できます。

繰り返し処理の詳細(for文)

■for文の書き方
for(初期値; 条件; 変化)
{
     [処理];
}
  • 多くの場合、初期値には「X = 0」、条件には「X < Xの最大値」、変化には「X++」と記述し、Xの最大値の回数だけ[処理]をX回繰り返す
  • はじめに条件を満たさない時には、[処理]は1度も実行されない
  • 条件を省略した場合、[処理]を無限に繰り返す「無限ループ」となる
  • [処理]の内容が1つの演算式、またはひとまとまりの文だけの場合{ }を省略できる

for文の記述はどのように実行されていくか処理の流れも以下にまとめておきます。

[処理1] -①
for(i = 0; i < 5; i++) -②
{
    [処理2]; -③
}
[処理3] -④

○○となるまで繰り返す(while文)

繰り返し処理の中には、繰り返しの初期条件や繰り返しの中で利用する変数の変化を自由に決めることができる記述方法があります。「条件が○○となるまで何度でも繰り返す」という記述です。

次のプログラムを実行し、理解を深めましょう。

#include <stdio.h>

int main(){
    int num, total;

    /*それぞれの変数に初期値を代入する*/
    num = -1;
    total = 0;

    /*条件式が成り立っている間、処理を繰り返す*/
    while (num!=0){
        printf("整数値を入力してください\n");
        scanf("%d", &num);

        total = total + num;
        printf("これまでの合計:%d\n", total);
    }
    return(0);
}
sample2-2.2実行結果

サンプルプログラム中の以下の部分が繰り返し処理を記述している部分です。

/*条件式が成り立っている間、処理を繰り返す*/
    while (num!=0){
        printf("整数値を入力してください\n");
        scanf("%d", &num);

        total = total + num;
        printf("これまでの合計:%d\n", total);
    }

whileという記述を使って繰り返し処理を実現しています。これをwhile(ホワイル)文といいます。for文と似た構造ですが、それよりも記述が少なくなっています。上の処理では、numの値が0でない限り繰り返す記述になっています。すなわち、while文では、繰り返しをする条件だけを与えることで繰り返し処理を記述することができます。

繰り返し処理の詳細(while文)

■while文の書き方
while(条件)
{
     [処理];
}
  • 条件が成り立つ間、何度でも[処理]を繰り返す
  • [処理]の内容が1つの演算式、またはひとまとまりの文だけの場合には、{ }を省略できる

while文の記述はどのように実行されていくか処理の流れも以下にまとめておきます。

[処理1] -①
while(条件) -②
{
    [処理2]; -③
}
[処理3] -④

次のことを繰り返す、ただし○○となると終了する(do while文)

これまでのfor文、while文では、処理1を繰り返す際に、条件判断→処理1と条件判断を行って繰り返すかどうか判断していました。ここで紹介するのは、処理1→条件判断と処理を先に行うプログラムについて説明していきます。

まずは、次のプログラムを実行してみて下さい。

#include <stdio.h>
int main(){
    int num, total;

    total = 0; //①
    do{        //②
        printf("整数値を入力して下さい\n");
        scanf("%d", &num);

        total = total + num;
        printf("これまでの合計: %d\n", total);
    }while(num != 0);
}
sample2-2.3実行結果

1つ前のwhile文との違いは、2か所あります。

  • ①の部分で「num = -1」の初期化の記述がない
  • ②の部分でwhileは最後に記述し、その場所にdoを記述している

この記述をdo while(ドゥ ホワイル)文といいます。これを利用すると、numの初期化が不要になり、繰り返しをする条件の判断が1度処理をしたあとにはじめておこなわれます

繰り返し処理の詳細(do while文)

■do while文の書き方
do
{
    [処理];
}while(条件);
  • 上から順に実行していき、whileの条件を満たした場合は、doの位置に戻って再び[処理]を続け、繰り返しをする
  • [処理]の内容が1つの演算式、またはひとまとまりの文だけの場合は、{ }を省略できる

do while文の記述はどのように実行されていくか処理の流れも以下にまとめておきます。

[処理1] -①
do -②
{
    [処理2]; -③
}while(条件); -④
[処理3] -⑤

カテゴリー
C

C(Step2-1)

分岐処理

ここでは、条件判断を行い、それによって次に行う処理を決定する分岐処理について説明していきます。

If else文を用いた分岐処理

「もし~だったら○○という処理を行い、そうでない場合△△という処理を行う」ということを指示したいときに使う条件文(conditional statement)になります。if と else を用いて分岐処理を表すことができ、if else(イフ エルス)文といいます。

次のプログラムを見ながら、理解を深めましょう。

#include <stdio.h>

int main(){
    int ans1, ans2;
    int total;

    printf("通勤時は(1:車 2:自転車 3:歩き)で行きますか?\n");
    scanf("%d", &ans1);

    printf("運動は(1:していません 2:たまにする 3:良くする)\n");
    scanf("%d", &ans2);

    total = ans1+ans2;

    if(total < 4){
        printf("運動不足です\n");
    }else{
        printf("良く運動しています\n");
    }

    if(total == 2){
        printf("もっと運動してください\n");
    }
    return(0);
}
sample2-1.1実行結果

上のプログラムのアルゴリズムは以下のようになっています。

  1. 1つ目の質問を表示する
  2. 答えを入力する、数値は変数ans1に記憶する
  3. 2つ目の質問を表示する
  4. 答えを入力する、数値は変数ans2に記憶する
  5. 変数totalにans1とans2の合計を代入する
  6. もし、totalが4よりも小さいならば、「運動不足です」と表示、そうでなければ、「良く運動しています」と表示する
  7. もし、totalが2であれば、「もっと運動してください」と表示する

注目すべきは以下の記述です。

 if(total < 4){
        printf("運動不足です\n");
    }else{
        printf("良く運動しています\n");
    }

    if(total == 2){
        printf("もっと運動してください\n");
    }

totalの値を条件に処理内容を変えています。totalが4未満かどうか判断しています。その後、totalが2であるかどうかを比較しています。値の比較には比較演算子を用います。値の大小や等しい、等しくないかを比較する記号でC言語では、「> より大きい」「< より小さい」「>= 以上」「<= 以下」「== 等しい」「!= 等しくない」で指定します。

分岐処理の詳細(if else文)

■if else文の使い方
if(条件)
{
     [処理1]条件が真(成り立っている)時の処理
}else
{
     [処理2]条件が偽(成り立っていない)時の処理
}
  • 条件が成り立っているときは[処理1]に分岐し、成り立っていないときは[処理2]に分岐する
  • 条件には、大小(>, <, >=, <=)や同じ(==)、異なる(!=)などの比較演算子を記述できる
  • 比較演算子には、論理演算子を2つ以上組み合わせて条件を増やして記述できる
  • [処理1][処理2]の内容が1つの演算式、またはひとまとまりの文だけなら{ }を省略できる
  • [処理2]に記述するものが無い時は、else以下を省略できる

switch case文を用いた分岐処理

「もし、A==○ならば処理1を、A==△ならば処理2を、A==□ならば処理3を行う…」といったif else文とは違う方法で分岐処理を行うこともできます。これをswitch 文といいます。

次のプログラムがその例です。

#include <stdio.h>

int main(){
    int ans;

    printf("一日の睡眠時間を入力して下さい\n");
    scanf("%d", &ans);

    switch (ans)
    {
        case 8:
        case 9: printf("健康的です\n"); break;
        case 10: printf("十分な睡眠です\n"); break;
        default: printf("規則正しい生活をしましょう\n");
    }
}
sample2-1.2実行結果

上のプログラムのアルゴリズムは以下のようになっています。

  1. 睡眠時間の質問をする
  2. 時間を入力、ansに記憶
  3. 8,9時間なら「健康的です」と表示する
  4. 10時間なら「十分な睡眠です」と表示する
  5. それ以外なら「規則正しい生活をしましょう」と表示する

注目は以下の記述です。

switch (ans)
    {
        case 8:
        case 9: printf("健康的です\n"); break;
        case 10: printf("十分な睡眠です\n"); break;
        default: printf("規則正しい生活をしましょう\n");
    }

caseの後の数値になった際にその後の処理が実行されます。defaultは、caseの条件に当てはまらなかった場合に実行される処理になります。それぞれのcaseに一致した場合、break以降の処理は実行されません。

分岐処理の詳細(switch文)

■switch文の使い方
switch(判定する変数 X)
{
      case X1:[処理1];
              break;
      case X2:[処理2];
      case X3:[処理3];
              break;
              :
            :
      default:[処理4];
}
  • 判定する変数Xの値がX1なら[処理1]から順に処理を行い、breakまで処理をしたら、switch文の分岐処理を抜け出す。同様にX2なら、[処理2]から順に処理を行う。
  • 定めたXnに当てはまるものがなければ、そのままswitch文の処理を抜け出す。dafaultの記述がある場合、[処理4]を行い、分岐処理を抜け出す。