C言語 アロー演算子の使い方【ポインタから構造体を使う】

C言語
この記事は約11分で読めます。

こんにちは、ナナです。

ポインタから構造体を扱う時は、特別な演算子である「アロー演算子」が必要となります。どのような時にアロー演算子が必要となるのかを学んでいきましょう。

ポインタの全貌を学びたい方は『C言語 ポインタを使いこなせ【身に付けるための9の極意】』の記事から順に読むことをお勧めします。

本記事では次の疑問点を解消する内容となっています。

本記事で学習できること
  • ポインタ変数から構造体にアクセスする方法とは?
  • ポインタ変数と間接参照演算子を付与したときの注意点とは?

では、ポインタを使った構造体の扱い方を学んでいきましょう。

スポンサーリンク

ポインタを使った構造体メンバへのアクセス方法

ポインタは構造体とも関係性があると聞きました。ポインタで一体何が変わるんですか?この筋肉も知りたがっています。

ナナ
ナナ

ポインタから構造体メンバにアクセスするときには、特別な演算子が必要になるよ。それが注意するところだね。

ポインタはとあるメモリを参照しますが、参照先の情報が皆さんの定義した構造体であることもあり得ますね。

構造体変数から構造体メンバにアクセスするときには、「ドット演算子」を使用しました。

しかし、ポインタ変数を使った構造体メンバへのアクセスは、「アロー演算子」を使用する必要があります。

変数から構造体メンバのアクセス

#include <stdio.h>

typedef struct
{
    double lon;  // 経度
    double lat;  // 緯度
} S_Coordinate;

int main(void)
{
    // 構造体の変数定義
    S_Coordinate pos;


    // 変数によるメンバへのアクセス
    pos.lon = 139.7459914;
    pos.lat =  35.6568407;

    return 0;
}

ポインタから構造体メンバへのアクセス

#include <stdio.h>

typedef struct
{
    double lon;  // 経度
    double lat;  // 緯度
} S_Coordinate;

int main(void)
{
    // ポインタの照準設定 pPos --> pos
    S_Coordinate pos;
    S_Coordinate * pPos = &pos;

    //  ポインタによるメンバへのアクセス
    pPos->lon = 139.7459914;
    pPos->lat =  35.6568407;

    return 0;
}

アロー演算子は次の書式にて利用します。

アロー演算子

演算子と書き方
 ポインタ変数->構造体メンバ名

使用例
 pPos->lon

説明
 構造体を参照しているポインタから、構造体メンバへアクセスする。

ポインタ変数から構造体メンバへのアクセスは、このアロー演算子を使わないとビルドエラーとなります。

ナナ
ナナ

「アロー(arrow)」って「矢」という意味なんです。「->」って矢のように見えますよね。

本サイトで「ポインタ」は「弓矢」のイメージで語られていますが、まさしくイメージ通りなんです。

スポンサーリンク

ポインタに間接参照演算子を使った場合の注意点

ポインタの扱いにまだ不慣れな部分があるんですが、ポインタ変数から構造体メンバを使いたいときは「アロー演算子」と覚えればいいんですか?

ナナ
ナナ

うーん、そこは一概にそうだと言いづらいんだよね。

ポインタは「間接参照演算子」によって、参照先メモリへと実体化してしまうんだね。そうすると「アロー演算子」が使えなくなってしまうんだよ。そこは注意が必要だね。

ポインタは間接参照演算子を付与することで、参照先のメモリに変化します。

そのため、次のように間接参照演算子を使った場合は、ドット演算子を使って構造体メンバにアクセスすることになります。

#include <stdio.h>

typedef struct
{
	double lon;  // 経度
	double lat;  // 緯度
} S_Coordinate;

int main(void)
{
	// ポインタの照準設定 pPos --> pos
	S_Coordinate pos;
	S_Coordinate * pPos = &pos;

	//  ポインタの場合はアロー演算子を使う
	pPos->lon = 139.7459914;
	pPos->lat = 35.6568407;

	//  間接参照の場合はドット演算子を使う
	(*pPos).lon = 139.7459914;
	(*pPos).lat = 35.6568407;

	return 0;
}

(*pPos)とすることで、この部分はpos変数と等しくなります。そのため、ドット演算子で構造体メンバにアクセスすることになるのです。

ナナ
ナナ

番地からアクセスするときは「アロー演算子」、実体からアクセスするときは「ドット演算子」となります。

間違っているとビルドエラーが出るため、気づきやすいのが救いです。

スポンサーリンク

課題:ポインタから構造体を使う方法が学べたかを確認しよう

ナナ
ナナ

もしも、プログラムが上手く動かなくて困ったときは、答えを見るのではなく「デバッガ」の使い方を学びましょう。

この記事を見ると問題の解決技術が身に付きます。困ったときのオススメ記事です!

課題1

課題内容

次の構造体を定義せよ。

課題1_1

次の関数を定義せよ。

課題1_2

次のプログラムに上記の構造体と関数を追加し、コメントで指定した処理を追加せよ。実行した結果、出力期待結果が表示されることを確認せよ。

main.c

#include <stdio.h>

int main(void)
{
    //  S_Coordinate構造体変数の定義


    //  getTokyoTowerPosition関数の呼び出し


    //  関数呼び出しの結果
    //  正常なら緯度経度座標を表示
    //  異常なら"ERROR"を表示


    return 0;
}

出力期待結果

getTokyoTowerPosition関数の呼び出しが正常終了したとき

緯度:35.658581 経度:139.745433

getTokyoTowerPosition関数の呼び出しが異常終了したとき

ERROR

main.c

#include <stdio.h>

typedef struct
{
    double  latitude;   // 緯度
    double  longitude;  // 経度
} S_Coordinate;

int getTokyoTowerPosition(S_Coordinate * pPos)
{
    if (pPos == NULL)
    {
        return -1;
    }

    // 東京タワーの緯度/経度
    pPos->latitude  = 35.658581;
    pPos->longitude = 139.745433;

    return 0;
}

int main(void)
{
    // S_Coordinate構造体変数の定義
    S_Coordinate pos = {0};
    int ret = -1;

    //  getTokyoTowerPosition関数の呼び出し
    ret = getTokyoTowerPosition(&pos);
//  ret = getTokyoTowerPosition(NULL);

    //  関数呼び出しの結果
    //  正常なら緯度経度座標を表示
    //  異常なら"ERROR"を表示
    if (ret < 0)
    {
        printf("ERROR");
    }
    else
    {
        printf("緯度:%lf 経度:%lf", pos.latitude, pos.longitude);
    }

    return 0;
}
ナナ
ナナ

ポインタ変数から構造体メンバへアクセスするときは「アロー演算子」を使うのでしたね。これは一番基本になります。

課題2

課題内容

次の構造体を定義せよ。

課題2_1

次の関数を定義せよ。

課題2_2

次のプログラムに上記の構造体、関数を追加し、出力期待結果が表示されるようにせよ。main関数には適宜、変数定義や関数呼び出しを追加するものとする。

#include <stdio.h>

int main(void)
{
    //  学生の教科毎のテスト結果
    S_Subject   point[] = {
        {72, 85, 54, 61}, //  学生A
        {50, 43, 38, 49}, //  学生B
        {89, 92, 87, 78}, //  学生C
        {72, 25, 36, 98}, //  学生D
    };

    //  getAverageSubject関数の呼び出し


    // 関数呼び出しの結果
    // 正常なら平均点を表示
    // 異常なら"ERROR"を表示


    return 0;
}


出力期待結果

getAverageSubject関数が正常終了したとき

国語:70
算数:61
理科:53
社会:71

getAverageSubject関数が異常終了したとき

ERROR

main.c

#include <stdio.h>

typedef struct
{
    long japaneseLanguage; // 国語
    long arithmetic;       // 算数
    long science;          // 理科
    long socialStudies;    // 社会
} S_Subject;

int getAverageSubject(S_Subject pSub[], int num, S_Subject * pAve)
{
    S_Subject ave = {0};
    int i;

    //  教科毎の総点数を算出
    for (i=0 ; i < num ; i++)
    {
        ave.japaneseLanguage += pSub[i].japaneseLanguage;
        ave.arithmetic       += pSub[i].arithmetic;
        ave.science          += pSub[i].science;
        ave.socialStudies    += pSub[i].socialStudies;
    }

    //  教科毎の平均点を算出
    ave.japaneseLanguage /= num;
    ave.arithmetic       /= num;
    ave.science          /= num;
    ave.socialStudies    /= num;

    //  引数へデータコピー
    *pAve = ave;

    return 0;
}

int main(void)
{
    //  学生の教科毎のテスト結果
    S_Subject   point[] = {
        {72, 85, 54, 61}, //  学生A
        {50, 43, 38, 49}, //  学生B
        {89, 92, 87, 78}, //  学生C
        {72, 25, 36, 98}, //  学生D
    };
    S_Subject ave = {0};  //  平均点
    int       ret;

    //  getAverageSubject関数の呼び出し
    ret = getAverageSubject(point, sizeof(point) / sizeof(point[0]), &ave);

    // 関数呼び出しの結果
    // 正常なら平均点を表示
    // 異常なら"ERROR"を表示
    if (ret < 0)
    {
        printf("ERROR");
    }
    else
    {
        printf("国語:%d\n", ave.japaneseLanguage);
        printf("算数:%d\n", ave.arithmetic);
        printf("理科:%d\n", ave.science);
        printf("社会:%d\n", ave.socialStudies);
    }

    return 0;
}

ポインタ、構造体、配列の組み合わせた使い方を学ぶ。

ナナ
ナナ

「ポインタ」「構造体」「配列」を組み合わせたプログラミングになります。規模の大きなシステムでは普通に出てきますよ。