C言語の研究5

第5章 構造体

C言語でレコードを扱うにはどうしたらいいのだろうか。

レコードとは、たとえば学生番号、氏名、住所、各テストの得点、評価など型のそれぞれ異なるものが、ひとかたまりになって記録されているものである。この型の異なる構造体の構成要素をメンバと呼ぶ。

事務処理用プログラミング言語COBOLでは得意とする分野だが、C言語でも同様で実務には欠かせないアイテムである。

1 構造体とは

構造体の一般的な形は次のとおりである。

                                            struct  構造体タグ{

                                         型  メンバ名;

                                                :

                                    };

構造体タグとは宣言した構造体の型を代表する識別名であり、変数名ではない。たとえば、

struct seiseki {

        int  bango;

        int  sansu;

        int  eigo;

};

と構造体を定義した後で、構造体の実体を

struct seiseli akira; のように変数akiraを宣言する。

構造体変数の初期化は直接してもいいし、後でもいい。次の要領だ。

struct seiseki {
    int bango;
    int kokugo;
    int eigo;
    int sugaku;
    double ave;
    char hyoka;
} Akira = {1001, 50, 50, 50, 50.0, 'D'}; ←構造体を定義して、直接変数Akiraを初期化だ

struct seiseki Pell = {1002, 80, 80, 80, 80.0, 'A '}; ←おもむろに後でPellを初期化だ

さて、記憶域クラス指定子のtypedefを用いると、構造体タグをいちいち使わなくても簡単に構造体の型定義が出来て便利だ。次のようにしよう。

typedef struct seiseki {
    int bango;
    int kokugo;
    int eigo;
    int sugaku;
    double ave;
    char hyoka;
} DATA;

DATA Akira = {1001, 50, 50, 50, 50.0, 'D'};
DATA Pell = {1002, 80, 80, 80, 80.0, 'A'};

どうかな? 楽な感じだろう、というか分かりやすいね。

構造体のメンバを表現するには、次のようにする。

Akira.bango = 1001;

Pell.bango = 1002;

Akira.kokugo = 50;

このように変数の後にピリオドをつけて各メンバ名を書けばいい。

ちょっと今ので例を見よう。

#include <stdio.h>

void main()
{
typedef struct seiseki {
    int bango;
    int kokugo;
    int eigo;
    int sugaku;
    double ave;
    char hyoka;
} DATA;

DATA Akira = {1001, 50, 50, 50, 50.0, 'D'};
DATA Pell = {1002, 80, 80, 80, 80.0, 'A'};

printf("番号    国語    英語     数学    平均点    評価\n");
printf("%4d    %4d    %4d    %4d     %4.1f    %c\n"
    ,Akira.bango ,Akira.kokugo ,Akira.eigo ,Akira.sugaku ,Akira.ave ,Akira.hyoka);
}

こういう具合に、構造体変数の各メンバが単独で値を持っているんだ。

2 構造体のメンバ

構造体のメンバをここでとりあげるのは、それがポインタであったり、配列であったり、はたまた、構造体であったり、まさに多種多彩であるからであります。

(1) ポインタのメンバ

構造体メンバとしてポインタを指定する場合は、それが指す実体の型を明示しておくことは当然だろう。

次のようにしよう。

#include <stdio.h>

void main()
{
typedef struct seiseki {
    int bango;
    char *p_name; /*ポインタの宣言と同じだね*/
    int kokugo;
    int eigo;
    int sugaku;
    double ave;
    char hyoka;
} DATA;

DATA pell = {1002, "Pell" ,80, 80, 80, 80.0, 'A'};

printf("番号    名前    国語     英語    数学    平均点     評価\n");
printf("%4d    %s    %4d    %4d     %4d    %4.1f    %c\n"
    ,pell.bango ,pell.p_name ,pell.kokugo ,pell.eigo ,pell.sugaku ,pell.ave ,pell.hyoka);
}

(2) 配列のメンバ

今の例で、国語、英語、算数の各得点は配列にした方が簡潔かもネ。

#include <stdio.h>

void main()
{
int i;

typedef struct seiseki {
    int bango;
    char *p_name;
    int tokuten[3]; /*3科目を配列に*/
    double ave;
    char hyoka;
} DATA;

DATA pell = {1002, "Pell" ,80, 80, 80, 80.0, 'A'};

printf("番号    名前    国語     英語    数学    平均点     評価\n");
printf("%4d    %s    ",pell.bango ,pell.p_name);

for(i=0; i < 3; i++) printf("%4d    ",pell.tokuten[i]); /*順番に表示*/

printf("%4.1f    %c\n",pell.ave ,pell.hyoka);
}

うん、この方がスッキリしているね。

(3) 構造体のメンバ

構造体のメンバとして構造体をとることも多い。一見ややこしいが、良く考えればとどうってないことさ。

次に、評価は点数によって決める関数を追加して、構造体のメンバである構造体のメンバにアクセスする様子を見てみよう。

#include <stdio.h>

char hyoka(double);
void main()
{
    typedef struct kekka {
        int bango;
        char *pname;
        struct seiseki {
            int ten[3];
            double ave;
        }s1;
  /*メンバである構造体*/
        char hyoka;
    } DATA;
   
    DATA c1 = {1001,"Pell",98,99,94,0.0,'x'};

    DATA c2 = {1002,"Akira",89,78,98,0.0,'x'};

    int i;
   
    for(i=0; i<3; i++){
            c1.s1.ave += c1.s1.ten[i];  /*このメンバである構造体のメンバの書き方に注意*/
            c2.s1.ave += c2.s1.ten[i];
    }

    c1.s1.ave /= 3.0;
    c2.s1.ave /= 3.0;

    c1.hyoka = hyoka(c1.s1.ave);
    c2.hyoka = hyoka(c2.s1.ave);

    printf("番号    名前    平均     評価\n");
    printf("%4d    %s    %4.2f     %c\n",c1.bango,c1.pname,c1.s1.ave,c1.hyoka);
    printf("%4d    %s    %4.2f     %c\n\n",c2.bango,c2.pname,c2.s1.ave,c2.hyoka);
}

char hyoka(double ave)
{
    char r;
    if (ave < 60) r = 'D';
    else if (ave < 70) r = 'C';
    else if (ave < 80 ) r = 'B';
    else if (ave < 95) r = 'A';
    else    r = 'S';

    return r;
}

どうかな、同じことだろう。おそれるに足りないことなのさ。

(4) ビットフィールドのメンバ

これらのほかにもビットフィールドを構造体のメンバとする場合がある。処理系によってうまく行かない場合があるので注意を要するが、1ビットのときは0か1で区別し、2ビット以上のときはどのビットが1になるかで区別する方式だ。書き方が決まっている。

struct 構造体タグ名 {

            型  識別子:ビット数;

                        :

}

しかも、この場合の型はint かunsigned int かsigned intに限られる。

とにかく例を見た方が早いだろう。番号、氏名、性別、生年月日の個人情報をビットフィールドをメンバとする構造体で表示しよう。性別には1ビットを使い0が男性で、1が女性とし、年号には4ビットを使用して、左から順に明治、大正、昭和、平成の各ビットとして、どれかが1のときその年号をあらわすこととしよう。

#include <stdio.h>

#define MEN 0 /*1ビットを男女の区別を最初に定義*/
#define WOMAN 1

#define M 1 /*4ビットの各位置だけが1になった場合の数値を最初に定義*/
#define T 2
#define S 4
#define H 8

char *sex_check(int);
char *nengo_check(int);

void main()
{
    typedef struct Kojin_jyoho {
        int         bango;
        char    *name;
        unsigned int seibetu:1; /*この書き方に注意、コロンに続いてビット数を明記*/
        unsigned int nengo: 4;  
        unsigned int nen;
        unsigned int tuki;
        unsigned int hi;
    } DATA;
    char *sex;
    char *knengo;

    DATA x1={1001 ,"Pell" ,MEN ,S ,38 ,9,1};
    DATA x2={1002 ,"Akira" ,MEN ,S ,26 ,2 ,15};

    printf("番号    名前    性別     生年月日\n");

    sex = sex_check(x1.seibetu);
    knengo = nengo_check(x1.nengo);

    printf("%-5d    %s    %s     %s%2d年%2d月%2d日生\n",
        x1.bango ,x1.name ,sex ,knengo ,x1.nen ,x1.tuki ,x1.hi);

    sex = sex_check(x2.seibetu);
    knengo = nengo_check(x2.nengo);
    printf("%-5d    %s    %s     %s%2d年%2d月%2d日生\n",
        x2.bango ,x2.name ,sex ,knengo ,x2.nen ,x2.tuki ,x2.hi);
}

char *sex_check(int a)
{
    char *sex;
    if (a==MEN) sex="男";
        else sex="女";
    return sex;
}

char *nengo_check(int a)
{
    char *nengo;

    if (a==M) nengo="明治";
    else if (a==T) nengo="大正";
    else if (a==S) nengo="昭和";
    else nengo="平成";
    return nengo;
}

どうだろうか、ちょっとややこしかったかな? でも大丈夫、後で分かってくるから安心しよう。

3 構造体へのポインタ

ポインタが構造体を指す場合も当然ある。だってポインタは月でも指差せるんだから。

構造体を指すポインタは次のように宣言する。

struct タグ名 *変数名;

typedef文でDATAと識別指定した場合の構造体には、

DATA *変数名;

と書ける。このポインタは構造体の先頭のメンバのアドレスを指している。

たとえばさっきの例の、

    typedef struct Kojin_jyoho {
        int         bango;
        char    *name;
        unsigned int seibetu:1;
        unsigned int nengo:4;
        unsigned int nen;
        unsigned int tuki;
        unsigned int hi;
    } DATA;

    DATA x1={1001,"Pell",MEN,S,38,9,1};

この構造体変数x1へのポインタは、

    DATA *p_x1;

と宣言する。そして、アドレスをポインタp_x1に代入しよう、次のように

    p_x1=&x1;

さて、構造体を指すポインタがそのメンバを指定する場合は、

p_x1->seibetu のように、

構造体を指すポインタ->そのメンバ

という具合に書こう。

それじゃ早速、さっきの例を構造体を示すポインタを使って書いてみよう。

#include <stdio.h>

#define MEN 0
#define WOMAN 1

#define M 1
#define T 2
#define S 4
#define H 8

char *sex_check(int);
char *nengo_check(int);

void main()
{
    typedef struct Kojin_jyoho {
        int         bango;
        char    *name;
        unsigned int seibetu:1;
        unsigned int nengo :4;
        unsigned int nen;
        unsigned int tuki;
        unsigned int hi;
    } DATA;
    char *sex;
    char *knengo;

    DATA x1={1001,"Pell",MEN,S,38,9,1};
    DATA x2={1002,"Akira",MEN,S,26,2,15};

    DATA *p_x1;  /*構造体のポインタを宣言*/ 
    p_x1= &x1;

    DATA *p_x2;  /*構造体のポインタを宣言*/
    p_x2 = &x2;

    printf("番号    名前    性別     生年月日\n");

    sex=sex_check(p_x1->seibetu); /*構造体ポインタのメンバを関数に渡す */
    knengo=nengo_check(p_x1->nengo);
    printf("%-5d    %s    %s     %s%2d年%2d月%2d日生\n",
        p_x1->bango, p_x1->name, sex, knengo, p_x1->nen, p_x1->tuki, p_x1->hi);

    sex=sex_check(p_x2->seibetu);  /*構造体ポインタのメンバを関数に渡す */
    knengo=nengo_check(p_x2->nengo);
    printf("%-5d    %s    %s     %s%2d年%2d月%2d日生\n",
        p_x2->bango, p_x2->name, sex, knengo, p_x2->nen, p_x2->tuki, p_x2->hi);
}

char *sex_check(int a)
{
    char *sex;
    if (a==MEN) sex="男";
        else sex="女";
    return sex;
}

char *nengo_check(int a)
{
    char *nengo;

    if (a==M) nengo="明治";
    else if (a==T) nengo="大正";
    else if (a==S) nengo="昭和";
    else nengo="平成";
    return nengo;
}

はじめから長ったらしい例になってしまったね。ちょっと赤字に注目してほしい。構造体をポインタで表示した場合でもメンバはちゃんと実体になっているね。

これは、ポインタが指すのは構造体そのもので、メンバは各個独立した人格を持っているということかな。

この長ったらしいのをなんとかコンパクトにしようとするならば、構造体の x1と x2を2個の配列にすれば良いのじゃないかな。

そうすれば、ポインタの得意とするアドレスを1つずつインクリメントすれば次の構造体を指せるからね。さっそくやってみよう。

#include <stdio.h>

#define MEN 0
#define WOMAN 1

#define M 1
#define T 2
#define S 4
#define H 8

char *sex_check(int);
char *nengo_check(int);

void main()
{
    typedef struct Kojin_jyoho {
        int         bango;
        char    *name;
        unsigned int seibetu:1;
        unsigned int nengo:4;
        unsigned int nen;
        unsigned int tuki;
        unsigned int hi;
    } DATA;
    char *sex;
    char *knengo;
   
    DATA x[2] ={
                {1001,"Pell",MEN,S,38,9,1},
                {1002,"Akira",MEN,S,26,2,15}
    };

    DATA *p_x;
    int i;

    printf("番号    名前    性別     生年月日\n");

    p_x= &x[0];
   
    for (i=0; i<2; i++){
        sex=sex_check((p_x+i)->seibetu);
        knengo=nengo_check((p_x+i)->nengo);
        printf("%-5d    %s     %s    %s%2d年%2d月%2d日生\n",
            (p_x+i)->bango, (p_x+i)->name, sex, knengo,
            (p_x+i)->nen, (p_x+i)->tuki, (p_x+i)->hi );
    }
}

char *sex_check(int a)
{
    char *sex;
    if (a==MEN) sex="男";
        else sex="女";
    return sex;
}

char *nengo_check(int a)
{
    char *nengo;

    if (a==M) nengo="明治";
    else if (a==T) nengo="大正";
    else if (a==S) nengo="昭和";
    else nengo="平成";
    return nengo;
}

どうかな、ちょっとはスッキリしましたか?

それじゃ、構造体のメンバに別の構造体を指すポインタがある場合を考えてみよう。

    typedef struct seiseki {
        int ten[3];
        double ave;
    } SEIKA;

    typedef struct kekka {
        int bango;
        char *pname;
        SEIKA *p_s;
        char hyoka;
    } DATA;

たとえばこんな場合だとして、

SEIKA c1;

DATA d1;

がそれぞれ宣言されたとすると、d1からs1のメンバを参照するには、

d1.p_s = &c1; とアドレスを設定してやってから、 d1.p_s->ten[0]のようにして見られるね。

早速、例をやってみよう。

#include <stdio.h>

char hyoka(double);
void main()
{
    typedef struct seiseki {
        int ten[3];
        double ave;
    } SEIKA;

    typedef struct kekka {
        int bango;
        char *pname;
        SEIKA *p_s;
        char hyoka;
    } DATA;

    SEIKA s[2] = {
        {98,99,100,0.0},
        {87,95,97,0.0}
    };
   
    DATA c[2] = {
        {1001,"Pell",NULL,'x'},
        {1002,"Akira",NULL,'x'}
    };

   
    int i, j;

    printf("番号    名前    数学     英語    理科    平均     評価\n");

    for(i=0; i<2; i++) {
        c[i].p_s= s; /* = &s[0]でもよい*/
        printf("%4d    %s     ",c[i].bango,c[i].pname);
        for(j=0; j<3; j++){
            c[i].p_s->ave+=c[i].p_s->ten[j];
            printf("%3d     ",c[i].p_s->ten[j]);
        }
        c[i].p_s->ave/=3.0;
        c[i].hyoka=hyoka(c[i].p_s->ave);
        printf("%4.2f     %c\n",c[i].p_s->ave,c[i].hyoka);
    }
}

char hyoka(double ave)
{
    char r;
    if (ave < 60) r = 'D';
    else if (ave < 70) r = 'C';
    else if (ave < 80 ) r = 'B';
    else if (ave < 95) r = 'A';
    else    r = 'S';

    return r;
}

こんな具合に表示ができるね。いろいろ試してみよう。

また、こんなのはどうだろう。構造体のメンバの中に自分の構造体を指すポインタがあった場合は? なんだか再帰関数みたいだね。たとえば、こんな形だろうか。

    typedef struct chane {
        int bango;
        char *pname;
       struct chane *p; ←自分の構造体を指差している
    } DATA;

こいつを利用すれば、構造体が配列の場合、ポインタによってチェーンのように表示できそうだね。

ちょっと試してやろう。

#include <stdio.h>

void main()
{

    typedef struct chane {
        int bango;
        char *pname;
        struct chane *p;
    } DATA;

   
    DATA akr[5] = {
        {1001,"Pell",NULL},
        {1002,"Akira",NULL},
        {1003,"Dragon",NULL},
        {1004,"Bruce Lee",NULL},
        {1005,"END",NULL}
    };

    DATA *pdata;  /*構造体を指すポインタを準備しよう*/
    int i;

    for(i=0; i<4; i++)     akr[i].p = &akr[i+1]; /*akr[0]からakr[3]までのメンバpに次のアドレスを入れてやる*/
   
    printf("番号    名前\n");

    for(pdata=&akr[0]; pdata != NULL; pdata=pdata->p)
        printf("%4d     %s\n",pdata->bango, pdata->pname);
}

どうだろうか、構造体へのポインタがメンバであるポインタをたどって、次々と構造体配列akrを表示して行ったね。なかなか面白いね。

それじゃちょっと例題をやってみよう。

<例題>

ある喫茶店では、セット商品をセットA、セットBと2種類用意している。セットに組み込まれている商品、たとえばコーヒーとかエビフライとかの商品の数はどちらも4種類とする。また、セット価格は各単品価格を合計したものの1割引で10円未満は切り捨てている。次のプログラムはセットを構成する商品と単価を初期化によって設定し、セット価格を求めて表示させようとするものです。

ちょっとこれを研究してみよう。

#include <stdio.h>

void main()
{
    typedef struct tanpin {
        char *pname;
        int tanka;
        struct tanpin *poi;
    } TAN;

    typedef struct otoku_set {
        char *set_name;
        TAN    *poi;
        int set_kakaku;
    } SET;

    TAN a[4]={
        {"ハンバーガー",250,NULL},
        {"ポテトフライL",100,NULL},
        {"コーヒー",120,NULL},
        {"ツナサラダ",210,NULL}
    };

    TAN b[4]={
        {"エビフライ",900,NULL},
        {"ライス",80,NULL},
        {"コーヒー",120,NULL},
        {"ポテトサラダ",210,NULL}
    };

    SET s[2] ={
        {"Aセット",NULL,0},
        {"Bセット",NULL,0}
    };

    TAN *pt; /*単品へのポインタを準備*/

    int i;

    for(i=0; i<3; i++){ /*単品にポインタを設定しておく*/
        a[i].poi=&a[i+1];
        b[i].poi=&b[i+1];
    }
    s[0].poi= a; /*セット商品の単品へのポインタをセット &a[0]でもよい*/
    s[1].poi= b;

    for(i=0; i<2; i++) { /*セット価格の計算*/
        for(pt=s[i].poi; pt != NULL; pt=pt->poi) s[i].set_kakaku += pt->tanka;
        s[i].set_kakaku = (int)(s[i].set_kakaku * 0.9)/10*10;
    }

    for(i=0; i<2; i++) { /*セット商品と中身の単品を表示*/
        printf("-------%s-------\n",s[i].set_name);
        for(pt=s[i].poi; pt != NULL; pt=pt->poi)
            printf("%-10s     %5d\n",pt->pname,pt->tanka);
        printf("セット価格     %5d\n\n",s[i].set_kakaku);
    }
}

どうだろうか、お分かりいただけただろうか。こんな具合に配列にしておくと便利だね。

4 構造体と関数

さて、関数に構造体を渡して処理してもらうにはどうしたらいいのだろう。そのためには

・メンバをひとつずつ渡す

・構造体全体を渡す

・構造体へのポインタを渡す

・構造体を返す関数をつくる

とか、いろいろ考えられるね。

順番にやって行こう。

(1) 各メンバによる引数渡し

これは、関数側にとってみれば、構造体を意識せずに普通の変数を渡されたのと同様に処理できる。

たとえば、3科目の得点の構造体と学生の構造体があって、平均点や評価を関数で求めさせる場合、

#include <stdio.h>

#define KAMOKU 3
double get_ave(int *, int n);
char hyoka(double);
void print_out(int, char *,int *,int ,double ,char );

void main()
{
    typedef struct seiseki {
        int ten[3];
        double ave;
    } SEIKA;

    typedef struct kekka {
        int bango;
        char *pname;
        SEIKA *p_s;
        char hyoka;
    } DATA;

    SEIKA s[2] = {
        {98,99,100,0.0},
        {87,95,97,0.0}
    };
   
    DATA c[2] = {
        {1001,"Pell",NULL,'x'},
        {1002,"Akira",NULL,'x'}
    };

   
    int i;

    printf("番号    名前    数学     英語    理科    平均     評価\n");

    for(i=0; i<2; i++) {
        c[i].p_s = &s[i];
        c[i].p_s->ave = get_ave(&c[i].p_s->ten[0], KAMOKU);
        c[i].hyoka = hyoka(c[i].p_s->ave);
        print_out(c[i].bango,c[i].pname,&c[i].p_s->ten[0],KAMOKU,c[i].p_s->ave,c[i].hyoka);
    }
}

double get_ave(int *ten, int n)
{
    double ave;
    int j;

    ave=0.0;

    for(j = 0; j < n; j++) ave += *(ten + j );
    return ave/=3.0;
}

char hyoka(double ave)
{
    char r;
    if (ave < 60) r = 'D';
    else if (ave < 70) r = 'C';
    else if (ave < 80 ) r = 'B';
    else if (ave < 95) r = 'A';
    else    r = 'S';

    return r;
}

void print_out(int bango, char *name,int *ten,int n,double ave,char hyoka)
{
    int j;

    printf("%4d    %s     ",bango,name);
    for(j=0; j < n; j++)    printf("%3d     ",*(ten + j ));
    printf("%4.2f    %c\n",ave,hyoka);
}

こういうふうに、渡す変数が多くあって結構長いプログラムになっちゃったね。

(2) 構造体全体を渡す

それでは今と全く同じ例題を、今度は関数に構造体全体を渡す方法でやってみよう。

#include <stdio.h>
#define KAMOKU 3

typedef struct seiseki {
    int ten[3];
    double ave;
} SEIKA;

typedef struct kekka {
    int bango;
    char *pname;
    SEIKA *p_s;
    char hyoka;
} DATA;

double get_ave(DATA);
char hyoka(DATA);
void print_out(DATA);

void main()
{

    SEIKA s[2] = {
        {98,99,100,0.0},
        {87,95,97,0.0}
    };
   
    DATA c[2] = {
        {1001,"Pell",NULL,'x'},
        {1002,"Akira",NULL,'x'}
    };

   
    int i;

    printf("番号    名前    数学     英語    理科    平均     評価\n");

    for(i=0; i<2; i++) {
        c[i].p_s=&s[i];
        c[i].p_s->ave=get_ave(c[i]); /*構造体全体を関数に渡す*/
        c[i].hyoka=hyoka(c[i]);
        print_out(c[i]);
    }
}

double get_ave(DATA s)
{
    int j;

    s.p_s->ave=0.0;

    for(j=0; j<3; j++) s.p_s->ave += s.p_s->ten[j];
    return s.p_s->ave /= 3.0;
}

char hyoka(DATA s)
{
    if (s.p_s->ave < 60) s.hyoka = 'D';
    else if (s.p_s->ave < 70) s.hyoka = 'C';
    else if (s.p_s->ave < 80 ) s.hyoka = 'B';
    else if (s.p_s->ave < 95) s .hyoka = 'A';
    else s.hyoka = 'S';

    return s.hyoka;
}

void print_out(DATA s)
{
    int j;

    printf("%4d    %s     ",s.bango,s.pname);
    for(j=0; j<3; j++)    printf("%3d     ",s.p_s->ten[j]);
    printf("%4.2f    %c\n",s.p_s->ave,s.hyoka);
}

どうかな、これはアドレス渡しじゃないから、関数で引数の構造体と同じものをコピーしてから処理しているから、呼ぶ方としては赤色の代入文が必要になるわけだね。

(3) 構造体のアドレス渡し

私としては、シャレじゃないが、渡しとしてはこれが一番好きである。

ちょっと見てみよう。

#include <stdio.h>
#define KAMOKU 3

typedef struct seiseki {
    int ten[3];
    double ave;
} SEIKA;

typedef struct kekka {
    int bango;
    char *pname;
    SEIKA *p_s;
    char hyoka;
} DATA;

void get_ave(DATA *);
void hyoka(DATA *);
void print_out(DATA *);

void main()
{

    SEIKA s[2] = {
        {98,99,100,0.0},
        {87,95,97,0.0}
    };
   
    DATA c[2] = {
        {1001,"Pell",NULL,'x'},
        {1002,"Akira",NULL,'x'}
    };

   
    int i;

    printf("番号    名前    数学     英語    理科    平均     評価\n");

    for(i=0; i<2; i++) {
        c[i].p_s=&s[i];
        get_ave(&c[i]); /*構造体の関数へのアドレス渡し*/
        hyoka(&c[i]);
        print_out(&c[i]);
    }
}

void get_ave(DATA *s)
{
    int j;

    s->p_s->ave=0.0; /*構造体ポインタのメンバが
                               また構造体のポインタのためこんな書き方になる*/

    for(j=0; j<3; j++) s->p_s->ave += s->p_s->ten[j];
    s->p_s->ave /= 3;
}

void hyoka(DATA *s)
{
    if (s->p_s->ave < 60) s->hyoka = 'D';
    else if (s->p_s->ave < 70) s->hyoka = 'C';
    else if (s->p_s->ave < 80 ) s->hyoka = 'B';
    else if (s->p_s->ave < 95) s->hyoka = 'A';
    else s->hyoka = 'S';
}

void print_out(DATA *s)
{
    int j;

    printf("%4d    %s     ",s->bango,s->pname);
    for(j=0; j<3; j++)    printf("%3d     ",s->p_s->ten[j]);
    printf("%4.2f     %c\n",s->p_s->ave,s->hyoka);
}

どうかな、->と->が連続しているが、理屈はわかるだろう。

(4) 構造体型の関数

これは、一連の処理を全部関数に委託する方法だ。当然関数側では構造体のコピーを用意するから、代入文が必要だ。ちょっと見てみよう。

#include <stdio.h>
#define KAMOKU 3

typedef struct seiseki {
    int ten[3];
    double ave;
} SEIKA;

typedef struct kekka {
    int bango;
    char *pname;
    SEIKA *p_s;
    char hyoka;
} DATA;

DATA get_kekka(DATA); /*これが構造体を返す関数*/
void print_out(DATA);

void main()
{

    SEIKA s[2] = {
        {98,99,100,0.0},
        {87,95,97,0.0}
    };
   
    DATA c[2] = {
        {1001,"Pell",NULL,'x'},
        {1002,"Akira",NULL,'x'}
    };

   
    int i;

    printf("番号    名前    数学     英語    理科    平均     評価\n");

    for(i=0; i<2; i++) {
        c[i].p_s=&s[i];
        c[i]=get_kekka(c[i]);
        print_out(c[i]);
    }
}

DATA get_kekka(DATA s)
{
    int j;

    s.p_s->ave=0.0;

    for(j=0; j<3; j++) s.p_s->ave += s.p_s->ten[j];
    s.p_s->ave /= 3;

    if (s.p_s->ave < 60) s.hyoka = 'D';
    else if (s.p_s->ave < 70) s.hyoka = 'C';
    else if (s.p_s->ave < 80 ) s.hyoka = 'B';
    else if (s.p_s->ave < 95) s.hyoka = 'A';
    else s.hyoka = 'S';

    return s;
}

void print_out(DATA s)
{
    int j;

    printf("%4d    %s     ",s.bango,s.pname);
    for(j=0; j<3; j++)    printf("%3d     ",s.p_s->ten[j]);
    printf("%4.2f    %c\n",s.p_s->ave,s.hyoka);
}

ご覧のように赤色部分で関数の戻す値を代入している。処理効率は悪いが、一番コンパクトだね。

さて、ついでにget_kekka関数を構造体のポインタを返す関数に書き換えてみよう。

#include <stdio.h>
#define KAMOKU 3

typedef struct seiseki {
    int ten[3];
    double ave;
} SEIKA;

typedef struct kekka {
    int bango;
    char *pname;
    SEIKA *p_s;
    char hyoka;
} DATA;

DATA *get_kekka(DATA *); /*これが構造体のポインタを返す関数*/
void print_out(DATA);

void main()
{

    SEIKA s[2] = {
        {98,99,100,0.0},
        {87,95,97,0.0}
    };
   
    DATA c[2] = {
        {1001,"Pell",NULL,'x'},
        {1002,"Akira",NULL,'x'}
    };

   
    int i;

    printf("番号    名前    数学     英語    理科    平均     評価\n");

    for(i=0; i<2; i++) {
        c[i].p_s=&s[i];
        get_kekka(&c[i]);
        print_out(c[i]);
    }
}

DATA *get_kekka(DATA *s)
{
    int j;

    s->p_s->ave=0.0;

    for(j=0; j<3; j++) s->p_s->ave += s->p_s->ten[j];
    s->p_s->ave /= 3;

    if (s->p_s->ave < 60) s->hyoka = 'D';
    else if (s->p_s->ave < 70) s->hyoka = 'C';
    else if (s->p_s->ave < 80 ) s->hyoka = 'B';
    else if (s->p_s->ave < 95) s->hyoka = 'A';
    else s->hyoka = 'S';

    return s;
}

void print_out(DATA s)
{
    int j;

    printf("%4d    %s     ",s.bango,s.pname);
    for(j=0; j<3; j++)    printf("%3d     ",s.p_s->ten[j]);
    printf("%4.2f    %c\n",s.p_s->ave,s.hyoka);
}

どうだろうか、こうすると代入文は要らないわけだ。

それじゃここで、さっきの例題、つまり

<例題>

ある喫茶店では、セット商品をセットA、セットBと2種類用意している。セットに組み込まれている商品、たとえばコーヒーとかエビフライとかの商品の数はどちらも4種類とする。また、セット価格は各単品価格を合計したものの1割引で10円未満は切り捨てている。次のはセットを構成する商品と単価を初期化によって設定し、セット価格を求めて表示させるプログラム。

これを今度は関数を使って書き換えよう。

セット価格を計算する関数、フリントする関数を作成することとして、両方の関数とも構造体のアドレス渡しによる方法にしよう。

#include <stdio.h>

typedef struct tanpin {
    char *pname;
    int tanka;
    struct tanpin *poi;
} TAN;

typedef struct otoku_set {
    char *set_name;
    TAN    *poi;
    int set_kakaku;
} SET;

void cal_kakaku(SET *); /*セット価格を求める関数〜構造体のアドレス渡し*/
void print_out(SET *);  /*プリントする関数、やはり構造体のアドレス渡し*/  

void main()
{

    TAN a[4]={
        {"ハンバーガー",250,NULL},
        {"ポテトフライL",100,NULL},
        {"コーヒー",120,NULL},
        {"ツナサラダ",210,NULL}
    };

    TAN b[4]={
        {"エビフライ",900,NULL},
        {"ライス",80,NULL},
        {"コーヒー",120,NULL},
        {"ポテトサラダ",210,NULL}
    };

    SET s[2] ={
        {"Aセット",NULL,0},
        {"Bセット",NULL,0}
    };

    int i;

    for(i=0; i<3; i++){
        a[i].poi=&a[i+1];
        b[i].poi=&b[i+1];
    }
    s[0].poi=&a[0];
    s[1].poi=&b[0];

    for(i=0; i<2; i++){
        cal_kakaku(&s[i]);
        print_out(&s[i]);
    }
}

void cal_kakaku(SET *s)
{
    TAN *pt;

    for(pt=s->poi; pt != NULL; pt=pt->poi) s->set_kakaku += pt->tanka;
        s->set_kakaku = (int)(s->set_kakaku * 0.9)/10*10;
}

void print_out(SET *s)
{
    TAN *pt;
    printf("-------%s-------\n",s->set_name);
    for(pt=s->poi; pt != NULL; pt=pt->poi){
        printf("%-10s     %5d\n",pt->pname,pt->tanka);
    }
    printf("セット価格     %5d\n\n",s->set_kakaku);
}

どうだろうか。

両方の関数とも戻り値はないからvoidとなる。

5 共用体

構造体とは、型の異なるメンバを1つのレコードとして扱うものであるが、

共用体は型の異なるデータがメモリ空間の同じ領域を共有するものをいう。共有体と呼んでもいいような気がするが、共用体、英語ではunionという。

共有体を宣言するのは構造体と同じ方法で良い。つまり、次のとおりだ。

union 共用体タグ {

    型  メンバ;

          :

} ;                    

たとえば、

union  data {

        int  n;

        double d;

};

と書く。こうすると、整数n も実数d も同じメモリ空間を使用している訳だが、コンパイラはこの場合は実数dの方がサイズが大きいのでdにあわせた大きさのメモリ空間を確保する。

また、typedef文も当然使えるから、共用体のメンバ指定方法も構造体と全く同じだ。つまり、

typedef union data {

        int  n;

        double  d;

} UNI;

と書ける。ここで、

UNI  akr;  UNI  *p_akr; とポインタもあわせて定義しておくならば、

p_akr = &akr; とポインタを設定してやって

akr.n;  または、p_akr->n;  という書き方が可能なことは構造体とおなじだね。

この共用体は当然、構造体のメンバにもなることができるんだ。

それでは早速、例を見ることにしましょう。

#include <stdio.h>

typedef union kyoyotai{ /*共用体の定義*/
    int a;
    double b;
} KYO;

typedef struct pell { /*構造体の定義*/
    int m;
    char *n;
    KYO *pk; /*構造体のメンバであるKYO型共用体へのポインタ*/
} PELL;

void main()
{
    KYO f; /*共用体fを宣言*/
    f.b = 555.5; /*共用体fのメンバを設定*/
    f.a = 1234;

    PELL st = {1234, "C言語", NULL}; /*構造体stを宣言*/
    PELL *akr = &st; /*構造体stへのポインタakrの宣言と設定*/

    akr->pk= &f; /*構造体のメンバである共用体へのポインタを設定*/
   
    printf("%-2d    %s     %5.2f\n",akr->m, akr->n, akr->pk->b ); /*共用体メンバbの方を表示*/
    printf("%-2d    %s    %-7d\n", akr->m, akr->n, akr->pk->a );/*共用体メンバaの方を表示*/
}

どうだろうか、構造体で共用体へのポインタをメンバにしてやると、気に入った方をポインタで指定して表示させることが出来て便利だね。

さて、構造体の締めくくりに例題を見て終わりにしよう。

5人の学生の学生番号、名前、得点を初期化で与えておいて、5人の平均点を関数によって求めた上で、平均との差と評価を付けて表示させるようにしよう。ここで評価には共用体を利用して、平均以上の学生には文字定数の'Y'を、平均より下の学生には整数の0(ゼロ)を付けるようにちょっと工夫してみた。

#include <stdio.h>
#define NINZU 5

typedef struct seiseki{
    int bango;
    char *name;
    int ten;
    int sa;
} DATA;

typedef union hyoka{
    char ok;
    int ng;
} ADD;

int get_ave(DATA *,int);
void print_out(DATA *, ADD *);

void main()
{
    DATA s[NINZU] = {
        {1001,"Pell ",99,0},
        {1002,"Akira ",89,0},
        {1003,"Kurazo ",96,0},
        {1004,"Dragon ",100,0},
        {1005,"Sanshiro",78,0}
    };

    ADD hy;
    ADD *ph = &hy;

    DATA *ps = s;

    int ave = get_ave(s,NINZU);
    int i;

    printf("平均点は%3d\n\n",ave);

    printf("番号    氏名         得点    差     評価\n");
    for(i=0; i<NINZU; i++,ps++){
        ps->sa= ps->ten - ave;
        print_out(ps, ph);
    }
}

int get_ave(DATA *a, int n)
{
    int i;
    int ave=0;
    for(i=0;i<n;i++,a++) ave+=a->ten;
    return (int)(ave/n);
}
void print_out(DATA *a, ADD *h)
{
    printf("%d    %s    %3d     %3d     ",a->bango,a->name,a->ten,a->sa);

    if(a->sa >= 0){
        h->ok = 'Y';
        printf("%c\n",h->ok);
    }
    else{
        h->ng = 0;
        printf("%d\n",h->ng);
    }
}

いかがだろうか、何もコメントは付けなかったが、これが理解できるようになれば、もはや君のC言語の能力は相当なところまで到達している。もうすぐ達人の域に達するぞ。

ちょっと理解できない君も大丈夫だ。続けることによって絶対に分かってくる。/*Trust me.*/

許されないのは、あきらめることだ      ただ前進あるのみ!

back  next


HOME