■構造体へのポインタと関数

Last modified: Sun Jun 16 2024
このページは,基礎工学部システム科学科機械科学コース2年生向けの 「情報科学演習」における演習4の教材の1つです.

●なぜ構造体へのポインタを使うのか?

構造体へのポインタは関数の引数や戻り値によく使われる.なぜか?

復習: 関数が処理される前に,呼び出し側で引数に書いた内容(値)が, 関数側にある別の領域(仮引数)にコピーされ,また,関数の処理が終了した後に, 戻り値になる内容は,関数側から呼び出し側へコピーされる.

構造体自身を引数や戻り値にした場合は,コピーされる量が多くなりがちで, 関数の本来の処理量に比べてその処理量が無視できない. example43.c程度の構造体ならば気にしなくて良いが, もっと複雑な場合だと問題となる.

構造体を引数や戻り値にすると見通しの良いプログラムを書くことができるが, ある程度以上大きな構造体の場合は,通常,処理量の少なさ(=処理速度)を重視して, ポインタを用いてデータのやり取りを行う.構造体自身が大きくても, そのポインタの大きさは同じなので, ポインタを用いることで本来以外の処理の増加による効率の低下を避けることができる.

「値渡し+戻り値」と「ポインタによる参照渡し」の比較(double型の場合)

引数や戻り値の型がintやdoubleではなく,サイズがもっと大きくなると, データの引き渡しのための処理量が増える. 一方,ポインタを引き渡す場合は,指す型が変っても処理量はは同じ.

「値渡し+戻り値」と「ポインタによる参照渡し」の比較(complex型の場合)

●構造体へのポインタの宣言

変数の宣言
    struct タグ名   *ポインタ変数名;
    新型名          *ポインタ変数名;
    (例)
    struct vector   *pv;
    complex         *x, *y, *z;
関数の戻り値や引数の場合も同じように宣言できる.
    (例)
    complex *
    cAdd(complex *po, complex *p1, complex *p2) 
    {
        .....
    }

●ポインタを用いたメンバへのアクセス

頻繁に用いられるので,「->」を用いた特別な表現方法が用意されている.
    (*ポインタ).メンバ    ←(等価)→     ポインタ->メンバ
注意: 「*ポインタ.メンバ」と書くと「*(ポインタ.メンバ)」の意味.

    (例)
    typedef struct {
        double re;
        double im;
    } complex;

    complex a, *p;

    p = &a;
    p->re = 1.0;
    p->im = 2.0;
    printf("a = %f + %fi\n", a.re, a.im);

例4-4 example44.c

example43.cと同じ処理をする関数を, fraction型へのポインタを引数や戻値に用いて定義した.
int
main(void)
{
    fraction    x1, x2, x3, t1, t2;

    fracMake(&x1, 3,  4);
    fracMake(&x2, 5, -6);
    printf("x1 = "); fracPrint(&x1); printf("\n");
    printf("x2 = "); fracPrint(&x2); printf("\n");

    fracAdd(&x3, &x1, &x2);
    printf("x1 + x2 = "); fracPrint(&x3); printf("\n");

    fracMul(&x3, &x1, &x2);
    printf("x1 * x2 = "); fracPrint(&x3); printf("\n");

    fracAdd(&x3, fracMul(&t1, &x1, &x1), fracMul(&t2, &x2, &x2));
    printf("x1*x1 + x2*x2 = "); fracPrint(&x3); printf("\n");

    return 0;
}

●構造体へのポインタの型(オプション)

    struct タグ名 {
        型名1  メンバ名1;
        型名2  メンバ名2;
        ...;
     };
    typedef struct タグ名 * 型名;
    typedef struct {
        型名1  メンバ名1;
        型名2  メンバ名2;
        ...;
     } * 型名;

例4-5 example45.c

example44.cでは,関数の引数に「&」を多用する必要があった. そこで,「構造体へのポインタ」自身を新しい型として定義することも可能である. こうすると,プログラムの見通しが良くなる代りに, その型が指す領域を確保するという処理を追加する必要がある (関数fracAlloc()を新設).
typedef struct {
    int   n;    /* 分子 (numerator) */
    int   d;    /* 分母 (denominator) */
} *fractionp;


int
main(void)
{
    fractionp    x1, x2, x3, t1, t2;

    x1 = fracAlloc(1);
    x2 = fracAlloc(1);
    x3 = fracAlloc(1);
    t1 = fracAlloc(1);
    t2 = fracAlloc(1);

    fracMake(x1, 3,  4);
    fracMake(x2, 5, -6);
    printf("x1 = "); fracPrint(x1); printf("\n");
    printf("x2 = "); fracPrint(x2); printf("\n");

    fracAdd(x3, x1, x2);
    printf("x1 + x2 = "); fracPrint(x3); printf("\n");

    fracMul(x3, x1, x2);
    printf("x1 * x2 = "); fracPrint(x3); printf("\n");

    fracAdd(x3, fracMul(t1, x1, x1), fracMul(t2, x2, x2));
    printf("x1*x1 + x2*x2 = "); fracPrint(x3); printf("\n");

    fracFree(x1);
    fracFree(x2);
    fracFree(x3);
    fracFree(t1);
    fracFree(t2);

    return 0;
}

[演習4のインデックス] [前の教材(新しい型名の定義)] [次の教材(自己参照構造体)]