構造体ポインタ演算 (構造体メンバー演算子との使い分け)

次の記述は、構造を定義している設計書でした。

struct data_t {
	char name[10];
	int  idata;
};

この場合、例えば、
struct data_t a;

という記述の、struct data_tの部分が変数の種類を意味し、 aは、設計書で作成された記憶域を表現する変数です。

aのように、構造体全体を表現する式に対して、その内部のメンバを指定する場合は、 構造体メンバー演算子のドットを使ってアクセスしました。
a.idata = 85; /* 例 */

他の型と同じように * 宣言子を使うと、ポインタ変数が用意できます。
ポインタ変数なので、実際のデータを記憶する記憶域のアドレスを設定してからでないと使えません。
struct data_t *p = &a;

これで、pを介してaの変数内容を変更できます。
ここで、a.idataを変更する場合、次の表現は間違いです。
*p.idata = 85; /* エラー */
エラーになる理由は、優先順位で*のポインタ演算子より、 . の演算子が働こうとして、 pが、構造体全体を表現するものでなく、ポインタなので、 . が使えないためです。
ですから、*演算をカッコで先に働くようにして、構造体全体を表現させてから . を使う次の表現であれば正しく実行できます。
(*p).idata = 85; /* 正しい */
または、[]配列演算子で、構造体全体を表現させてから . を使う指定も可能です。
p[0].idata = 85; /* 正しい */

しかし、カッコがあって記述が長くなりがちなので、構造体ポインタに対してメンバを指定する演算子が用意されています。
それは -> の表現で、左に構造体のポインタ書き、右にメンバ名を指定します。
これは、構造体ポインタ演算子とよばれ、 . と同に、 優先順位が最高位です。それを使った表現で書くと次ようになります。

p->idata = 85; /* 正しい */


なお、意味がないですが、次の表現でも、aを正しく変更できます。
( & a)->idata = 85; /* 正しい */

構造体ポインタ介した簡単な例

2つの構造体変数 a と b があり、オペレータの選択で、処理対象の変数をポインタに管理させ変更させるプログラムです。

#include <stdio.h>
#include <stdlib.h>

struct data_t {
	char name[10];
	int  idata;
};

main()
{
	struct data_t a = { "suzuki" , 70};
	struct data_t b = { "yamada" , 80};
	struct data_t *p;	/* 構造体へのポインタ変数 */
	int op;				/* オペレータの操作入力用 */

	printf("aの変更なら 1、bの変更なら 2 を入力>");
	fscanf(stdin, "%d", &op);
	if(op == 1) {
		p = & a; /* a を管理させる */
	} else {
		p = & b; /* b を管理させる */
	}
	
	printf("%sの変更>" , p->name );
	fscanf(stdin, "%s" , p->name );
	printf("\t%dの変更>" , p->idata );
	fscanf(stdin, "%d" , & p->idata );
	printf("aの記憶内容 :%sのデータは、%dです\n" , a.name , a.idata );
	printf("bの記憶内容 :%sのデータは、%dです\n" , b.name , b.idata );
}

実行例を以下に示します。

aの変更なら 1、bの変更なら 2 を入力>2
yamadaの変更>sasaki
        80の変更>65
aの記憶内容 :suzukiのデータは、70です
bの記憶内容 :sasakiのデータは、65です

関数の仮引数にポインタ変数を使う。

仮引数をポインタ変数にすれば、配列などのポインタを実引数とした関数の実行ができます。
この場合の、関数側におけるポインタを介した記憶域のアクセスは、呼び出し側のローカル 変数や配列でも変更可能になります。
以下では、上記と同じ処理を行なわせるプログラムで、関数change_dataで変更しています。

#include <stdio.h>
#include <stdlib.h>

struct data_t {
	char name[10];
	int  idata;
};

void change_data(struct data_t *p)	/* 構造体へのポインタで仮引数を宣言 */
{
	printf("%sの変更>" , p->name );
	fscanf(stdin, "%s" , p->name );
	printf("\t%dの変更>" , p->idata );
	fscanf(stdin, "%d" , & p->idata );
}
main()
{
	struct data_t a = { "suzuki" , 70};
	struct data_t b = { "yamada" , 80};
	
	int op;				/* オペレータの操作入力用 */

	printf("aの変更なら 1、bの変更なら 2 を入力>");
	fscanf(stdin, "%d", &op);
	if(op == 1) {
		change_data(& a); /* a を変更する */
	} else {
		change_data(& b); /* b を変更する */
	}
	
	printf("aの記憶内容 :%sのデータは、%dです\n" , a.name , a.idata );
	printf("bの記憶内容 :%sのデータは、%dです\n" , b.name , b.idata );
}

なお、ポインタ宣言でない仮引数で、引数を宣言することがもできます。 しかし、その場合は呼び出し側の情報を変更することができません。
ですが、表示などに利用することはできます。 この場合は構造体全体が仮引数にコピーされるので、 大きなサイズの構造体を指定するとオーバヘッドが大きくなり好ましくありません。