WisdomSoft - for your serial experiences.

7.2 構造体へのポインタ

構造体の値もアドレスを持つため、構造体をポインタで扱うことができます。構造体へのポインタから、構造体が持つメンバにアクセスする方法を解説します。

7.2.1 構造体のメンバに間接参照する

構造体のメンバがポインタの場合は、通常のポインタとその扱いは変わりません。しかし、構造体型のポインタを扱う場合は、参照方法に注意する必要があります。構造体型のポインタとは、構造体変数(インスタンス)のメモリアドレスを保存しています。これは、「6.1 ポインタ」で解説した通常の変数へのポインタと同じです。次のような構造体の宣言を考えていください。

struct Point *pointer = &pt;

pointer は Point 型構造体変数へのアドレスを格納するポインタ型変数です。ポインタを理解していれば、この宣言と初期化については特に疑問はないはずです。問題はこのポインタが指す構造体メンバに間接参照を行うにはどうすればよいかということです。単純に構造体のインスタンスを取得する場合は関節演算子を使うだけです。

struct Point pt = *pointer;

しかし、構造体の役割を考えれば、通常は構造体のインスタンスを返す目的にではなく、構造体のインスタンスのメンバにアクセスする目的で間接参照が行われることでしょう。最初は、次のような方法でアクセスを試みるかもしれません。

int x = *pointer.x ;

一見すると、pointer ポインタ変数が示す構造体の x メンバにアクセスしているかのように思えますが、残念ながら違います。この場合、構造体の x メンバを間接参照していることを表しています。構造体のメンバがポインタ型のときに、こうした構文か用いられます。例えば、次のような構造体です。

struct Point {
	int *x;
	int *y;
};

しかし、今回の目的はポインタ型のメンバから間接参照を行うのではなく、構造体のポインタから構造体のインスタンスに間接参照することです。この場合は、演算子の優先順位の関係から次のように記述しなければなりません。

int x = (*pointer).x;

こうすることで、構造体型のポインタから間接参照することができます。上の文は、(*pointer) が構造体へ間接参照を行い構造体のインスタンスを返し、その x メンバにアクセスしていることを表しています。

コード1
#include <stdio.h>

struct Point {
	int x;
	int y;
};

int main() {
	struct Point pt = { 100 , 200 };
	struct Point *ppt = &pt;

	printf("pt.x = %d : pt.y = %d\n" , (*ppt).x , (*ppt).y);
	return 0;
}
実行結果
コード1 実行結果

コード1では、Point 型のポインタ ppt に構造体変数 pt のアドレスを代入しています。printf() 関数でポインタから構造体のメンバの値を表示するために、(*ppt).x というような形で間接参照を行っていることに注目してください。

構造体へのポインタは、実践のプログラミングでは頻繁に用いられます。巨大なシステムでは、これでもかというほど構造体が用いられますし、この構造体の情報の受け渡しや生成、加工を関数で行う手段として、ポインタによる参照渡しが使われるのです。構造体のポインタを間接参照するとき、わざわざ上記したように (*pointer).~ と記述するのは少し面倒ですね。

そこで、メンバアクセス演算子 "->" を使うことができます。この演算子は機能的に "." 演算子と同じですが、構造体のポインタからメンバを参照するという点で性質が異なります。メンバアクセス演算子 -> は、しばしばアロー演算子と呼ばれます。

アロー演算子
構造体へのポインタ名 -> メンバ名

この -> 演算子はマイナス記号 - と > 記号の組み合わせで構成されています。(*pointer).member と pointer->member はまったく同じものであると考えることができます。多くのプログラマは (*pointer).member よりも pointer->member という記法を好むため、特別な意図がない限り -> 演算子を用いる方が推奨されます。

コード2
#include <stdio.h>

struct Point {
	int x;
	int y;
};

int main() {
	struct Point pt = { 100 , 200 };
	struct Point *ppt = &pt;

	printf("pt.x = %d : pt.y = %d\n" , ppt->x , ppt->y);
	return 0;
}
実行結果
コード2 実行結果

このプログラムの動作はコード1とまったく同じですが、構造体へのポインタ ppt からメンバを参照するときに -> 演算子を使っている点で異なっています。

7.1 構造体 コード7」で、関数に構造体の値渡しは行うべきではないと解説しました。理由は、値を丸ごとコピーするため、合成体の値渡しはメモリやCPUに高い負荷をかける可能性があるためです。通常、配列や構造体などはポインタによる参照渡しを採用します。今回解説した構造体のポインタを扱う方法が理解できれば、これを実現することができるでしょう。

コード3
#include <stdio.h>

struct Point {
	int x;
	int y;
};
void SizeToPoint(struct Point *offsetPoint , struct Point *target , int width , int height) {
	target->x = offsetPoint->x + width;
	target->y = offsetPoint->y + height;
}

int main() {
	struct Point location = { 100 , 100 } , target;
	SizeToPoint(&location , &target , 200 , 40);

	printf(
		"Rectangle\n\t"
		"Left = %d : Top = %d\n\t"
		"Right = %d : Bottom = %d\n" ,
		location.x , location.y , target.x , target.y
	);
	return 0;
}
実行結果
コード3 実行結果

これは「7.1 構造体 コード7」の SizeToPoint() 関数を改良したものです。情報源となる基点を格納した構造体へのアドレスを offsetPoint に指定し、新しい座標を格納する構造体へのアドレスを target に指定します。width と hieght はこれまで同様に offsetPoint に対する幅と高さを指定します。この関数は offsetPoint パラメータから相対的な幅と高さの位置を表す座標を target パラメータに間接参照で格納します。

SizeToPoint() を呼び出すとき、構造体のインスタンスではなくポインタを渡しているため、合成体を丸ごと複製する負荷を避けることができます。さらに、戻り値として構造体を返すのではなく、target を間接参照して値を代入しているため、戻り値は使われていません。例えば、関数が成功したか、失敗したかを呼び出し元に通達する手段として使うなど、異なる用途に戻り値を使うことができるようになります。