パソコン活用研究シリコンバレー(C、C++、の活用研究)



構造体(ポインタ変数)

今回は、構造体のポインタ変数について軽く触れてみます。
さて、昔のC言語の規約では(K&Rなどでは)、構造体について以下のような制約がありました。
(1) 構造体(まるごと)を直接関数に渡したり、返したりできない。
(2) 構造体をまるごと(全メンバーのデータ)を、一度に同じ型の構造体に代入することはできない。

すなわちpersonal型の構造体変数 a,b が以下のようにある時

struct personal {
char *name;
char *adress;
char *tel;
} a,b;

構造体(まるごと)の代入 a = b; はできないということです。
(古いCの規約では、メンバー単位でのデータの代入 a.name = b.name; しかできないことになっています。)
このような場合には、構造体のポインタを使って関数との受け渡しや、代入を行う必要がでてきます。
もっとも、最近の多くのC言語ではその制約がなくなっていますが。

 T 構造体のポインタ変数の受け渡し


1 構造体のポインタ変数とメンバーの参照

Cのプログラムを見ていると、ときどき person->name のような妙な記述を見かけませんか。
初めてみた人には、何をしているのか見当もつかないかも知れませんが、これが構造体のポイイタ変数
の各メンバーへの参照の書式です。こんな演算子「->」はBasicにはないので、Basicプログラマーは
めんくらってしまうかもしれません。

それでは、平凡な例ではありますが、よくある個人データ集(住所録)のようなものを例にあげて
みてみます。以下は personal というデータ型の構造体です。メンバーに名前、住所、電話番号を持ちます。

struct personal {
char *name;
char *adress;
char *tel;
} *ps;

さてこの構造体のポイインタ変数のメンバーの参照の仕方ですが、2つの書式があります。
構造体メンバー演算子の「 . 」を使う方法がひとつです。この書式だと
(*ps).name
のように記述します。
この書式では、n 番めのデータを参照しようとすると
(*(ps+n)).name
のように少々厄介な記述になります。(メンバー演算子の優先順位は高いので、このような記述になります)

それでは面倒なので、冒頭に書いた通り、-> というメンバー演算子を使うのが通常です。
この場合は 
ps->name
のように記述します。n番めのデータを参照するときは
(ps+n)->name
のようになります。


2 構造体のポインタ変数の受け渡し

それでは、上述のpersonal型で、構造体のポインタ変数の受け渡しをしてみます。
この例では、あえて構造体のポインタ変数を使うメリットはありませんが、一番わかりやすい例として
見てください。
#include <stdio.h>


struct personal {
char *name;
char *adress;
char *tel;
}

main(){
struct personal *ps, ps_data[2];   /* (1) */
int i;

/* 下の ps=ps_data; がないと、コンパイル時に「PSの値が未定義」というエラーになる*/  
ps=ps_data;    /* (2) */
for(i=0;i<2;i++,ps++){      /* (3) */       
printf("name?  -"); gets(ps->name);
printf("adress?  -"); gets(ps->adress);
printf("tel?  -"); gets(ps->tel); printf("\n");
}

ps=ps_data;    /* (4) */
for(i=0;i<2;i++){
printf("%s : %s : %s\n",(*(ps+i)).name,(*(ps+i)).adress,(*(ps+i)).tel);
}

ps=ps_data;
for(i=0;i<2;i++){
printf("%s : %s : %s\n",(ps+i)->name,(ps+i)->adress,(ps+i)->tel);
}

ps=ps_data;
for(i=0;i<2;i++,ps++){
printf("%s : %s : %s\n",ps->name,ps->adress,ps->tel);
}}
【解説】
(1) personal型として ポインタ変数 *ps 配列 ps_data を宣言します。
(2) 構造体のポインタ変数 *ps は、直接初期化したり、代入をすることができません。
    従って、構造体の配列 ps_data の先頭アドレスを ps に渡してやる必要があります。
    ps = ps_data; の行を抜くと、コンパイル時に「psの値が未定義」というエラーがでます。
    これは、構造体のポインタ変数 *ps を宣言しただけでは、その器だけが定義された状態で
    実態(psの値 = *psのポインタ、すなわち先頭アドレス)がない、ということです。
(3) *psの各メンバーに値を代入します。
(4) 以下3つのやり方で、構造体の各メンバーを参照して表示させています。
実行例
C:\C>struct
name? -田中
adress? -東京都世田谷区
tel? -999-666-333

name? -Ron Bon
adress? -NY, US
tel? -777-999-999

田中 : 東京都世田谷区 : 999-666-333    
Ron Bon : NY, US : 777-999-999
田中 : 東京都世田谷区 : 999-666-333
Ron Bon : NY, US : 777-999-999
田中 : 東京都世田谷区 : 999-666-333
Ron Bon : NY, US : 777-999-999

C:\C>

U 構造体を関数で受け渡し



1 構造体を関数に渡す
上の例では、構造体のポインタ変数を使う意義はあまりありませんでしたが、
先述の通り、構造体をまるごと関数に渡したいときに、構造体のポインタ変数の出番があります。
では、プログラム例を見てください。構造体を関数に渡すには、構造体のアドレス(ポインタ)を引数と
して渡します。
#include <stdio.h>

void input(struct personal *);
void display(struct personal *);

struct personal {
char *name;
char *adress;
char *tel;
}

main(){
struct personal ps_data[2];

input(ps_data);
display(ps_data);
}

void input(ps)
struct personal *ps;
{int i;
for(i=0;i<2;i++,ps++){
printf("name? -");  gets(ps->name);
printf("adress? -");  gets(ps->adress);
printf("tel? -"); gets(ps->tel); printf("\n");
}}

void display(ps)
struct personal *ps;
{int i;
for(i=0;i<2;i++,ps++){
printf("%s  :  %s  : %s\n",ps->name,ps->adress,ps->tel);
}}

データの入力、出力用の関数 input, display を用意し、main でコールするという単純なプログラムです。
input, display では構造体配列 ps_data[ ]の先頭アドレス(ポインタ) である ps_data を引数として関数
に渡します。それぞれの関数側では、構造体のポインタ変数 *ps で構造体のデータを受け取っています。

実行例
C:\C>struct2
name? -kasai tsuyoshi
adress? -setagaya,Tokyo
tel? -000-888-999

name? -bon
adress? -NY,US
tel? -888-333-222

kasai tsuyoshi : setagaya,Tokyo : 000-888-999  
bon : NY,US : 888-333-222


構造体のポインタ変数は、若干わかりにくいところがあると思いますが、自分でプログラムしてみると
徐々に理解できるようになるでしょう。

TopPage