パソコン活用研究シリコンバレー(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の本を見ると「構造体のポインタ変数は構造体を関数に受け渡す場合によく使います」
などと書かれています。


1980年代のCの本には、こんな風に書かれていました。↓



もっとも、今どきのCコンパイラではそんな制約はなくなっていますので、a=b; で構造体の丸ごとコピー
はできます。
なお、構造体のポインタ変数の扱いについてはコンパイラにより若干の差異があるようです。
コンパイラ間の差異については「構造体(ポインタ変数)のコンパイラ間の差」で調べてみた結果を書きました
のでご参照下さい。


----------------------------------------------------------------------------------------------------
T 構造体のポインタ変数とメンバーの参照
U 構造体のコピー(代入)
V 構造体を関数で受け渡し
----------------------------------------------------------------------------------------------------

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


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


(1)構造体のポインタ変数
構造体の変数として、ポインタ変数を宣言して使うことができます。

例えば、平凡な例ではありますが、よくある個人データ集(住所録)のようなものを例にあげて
みてみます。以下は personal というデータ型の構造体です。メンバーに名前、住所、電話番号を持ちます。
この構造体変数としてポインタ変数の*psを宣言するには以下のように書きます。

struct personal {
char name[20];
char adress[40];
char tel[15];
} *ps;

(2) 構造体のポインタ変数のメンバーの参照

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

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

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


2 構造体のポインタ変数の値の代入と参照

それでは、上述のpersonal型で、構造体のポインタ変数への値の入力と参照をしてみます。
この例では、あえて構造体のポインタ変数を使うメリットはありませんが、一番わかりやすい例として
あげましたので見てください。
構造体の各メンバー、name,adress,telに値の入力をした後に、3パターンの方法で各メンバーの
値を参照して表示しています。

#include <stdio.h>


struct personal {
char name[20];
char adress[40];
char tel[15];
};

int main(void){
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);}
return 0; }
【解説】
(1) personal型として ポインタ変数 *ps 配列 ps_data を宣言します。
(2) 構造体のポインタ変数 *ps は、直接初期化したり、値の代入をすることができません。
    従って、構造体の配列 ps_data の先頭アドレスを ps に渡してやる必要があります。
    ps = ps_data; の行を抜くと、コンパイル時に「psの値が未定義」というエラーがでます。
    これは、構造体のポインタ変数 *ps を宣言しただけでは、その器だけが定義された状態で
    実態(psの値 = *psのポインタ、すなわち先頭アドレス)がない、ということです。
(3) *psの各メンバーに値を代入します。
(4) 以下3つのやり方で、構造体の各メンバーを参照して表示させています。
実行例
C:\C>structa
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 構造体のコピー(代入)

int型のメンバー変数x,yをもつstarという型の構造体を作って、3つのやり方で構造体変数のコピー(代入)を
してみます。

#include <stdio.h>

struct star{
int x;
int y;
};

int main(void)  {
struct star st, *p,q;  /* @  */

st.x=100; st.y=200;
q=st;                 /* A  */
  printf("q:%d   st:%d \n",&q,&st);
  printf("q.x:%d, q.y:%d\n",q.x,q.y);
        
st.x=1; st.y=2;
*p=st;                /* B */
  printf("p:%d   st:%d \n",p,&st);
  printf("p->x:%d, p->y:%d\n",p->x,p->y);
st.x=-5; st.y=0;      /* C */
  printf("st.x:%d, st.y:%d, p->x:%d, p->y:%d\n",st.x,st.y,p->x,p->y);
         
st.x=10; st.y=15;
p=&st;                /* D */
  printf("p:%d  &st:%d \n",p,&st);
  printf("p->x:%d, p->y:%d\n",p->x,p->y);
st.x=0; st.y=50;      /* E */
  printf("st.x:%d, st.y:%d, p->x:%d, p->y:%d\n",st.x,st.y,p->x,p->y);
return 0;
}

@ 構造体star型の変数として、st, *p, q を宣言します。
stはコピー元、*p, q はそれぞれコピー先(代入先)の構造体変数として使います。

A まずはオーソドックスにq=stという構造体を丸ごとコピー(代入)する方法でやっています。
※冒頭の余談で書いた通り、大昔のCではできなかった方法です。

今どきのコンパイラではなんの制約もなく、構造体を丸ごとコピー(代入)できます。
これで、構造体の全メンバーがコピー(代入)されます。

下の実行例Aの通り、構造体変数stとqは別のアドレス(&q:1703728 &st:1703736)に領域を持っており
構造体変数stのメンバの値は構造体変数qのメンバ変数にコピーされている(値渡しで渡された)。

B 構造体変数stを構造体のポインタ変数*pにコピー(代入)しています。
下の実行例Bの通り、stと*pは別のアドレス(p:3301376 &st:1703736)に領域を持っており、
構造体変数stのメンバの値は構造体のポインタ変数*pのメンバ変数にコピーされている(値渡しで渡された)。

C stのメンバ変数の値を、st.x=5; st.y=0;に変更していますが、当然ながら*pのメンバ変数の値は変わっていません。
※Eと比較してみると違いがわかります。

D 構造体変数stのポインタ&stをpに代入しています。これで、*pのアドレスはstと同じアドレスを参照することになり、
pと&stは同じアドレスを指すことになります。

E stのメンバ変数の値をst.x=0; st.y=50;に変更すると、当然ながらp->xもp->yも変更されます。

構造体変数のコピー(代入)の仕方でその後の状況が違うことがわかります。





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



1 構造体を関数に渡す
上の「U構造体のコピー(代入)」例では、構造体のポインタ変数を使う意義はあまりありませんでしたが、
構造体をまるごと関数に渡し、構造体の各メンバー値を呼び出し元にリターンしたいときに、構造体のポインタ変数の出番があります。
では、プログラム例を見てください。特に@の使い方が肝です。

@の関数input()では、呼び出し側では、構造体配列 ps_data[ ]の先頭アドレス(ポインタ) である ps_data を
引数として関数に渡しています。関数input()側では、構造体のポインタ変数 *ps で構造体のデータを受け取っています。
(つまり、参照渡しをしている)、
関数input()において、メンバ変数 ps->name, ps->adress, ps->tel に 値を代入すると、ps_data[]も同じ値を
指すことになります。

参照渡しについては「変数の有効範囲とポインタ、関数のデータの受け渡し」をご参照下さい。

A、Bは表示のための関数ですが、Aは構造体のポインタ変数を使って、Bは構造体変数を使って引数を
受け渡ししています。

#include <stdio.h>

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

struct personal {
char name[20];
char adress[40];
char tel[15];
};

int main(void){
struct personal ps_data[2];

input(ps_data);
display(ps_data);
display2(ps_data[0]);
return 0;
}

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

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


void display2(ps)  /* B */
struct personal ps;
 { printf("%s  :  %s  : %s\n",ps.name,ps.adress,ps.tel);}


実行例
c:\bcc55\Bin>struct2b
name? -Yamada Taro
Yamada Taro
adress? -Nerima-Ku, Tokyo, Japan
Nerima-Ku, Tokyo, Japan
tel? -999-333-1234
999-333-1234
name? -John Bonn
John Bonn
adress? -NY, US
NY, US
tel? -000-000-0000
000-000-0000
Yamada Taro  :  Nerima-Ku, Tokyo, Japan  : 999-333-1234
John Bonn  :  NY, US  : 000-000-0000
Yamada Taro  :  Nerima-Ku, Tokyo, Japan  : 999-333-1234

c:\bcc55\Bin>


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

TopPage