C++ with Windows API講習/構造体

概要

構造体、特にPODと呼ばれるC言語互換形式のものについて解説します。

重要語

構造体

複数の変数などをまとめた型を作るもの

メンバ

構造体の中にある変数などのこと

構造体の宣言

構造体の名前を定義すること

構造体の定義

構造体の名前とメンバを定義すること

構造体変数の宣言

定義した構造体の変数を宣言すること

ドット演算子

構造体のメンバにアクセスする演算子

アロー演算子

(*).の糖衣構文

エイリアス

英語で「別名、通称」などの意味の単語。

エイリアス宣言

型名の別名を宣言する言語機能

typedef

型の別名を宣言するキーワード

必要語

変数の宣言

変数の型と名前を定義すること

ポインタ変数

アドレスを値として持つ変数

ポインタ型

ポインタ変数の型

間接参照

アドレスを変数のように扱うこと

糖衣構文/シンタックスシュガー

長い、又は複雑な構文の簡単な書き方

構造体基本

構造体の宣言/定義/初期化/メンバアクセスについてです。
次のサンプルコードを実行してみましょう。
構造体基本
#include <Windows.h>
#include <string>

struct item;

struct item {
    int number;
    std::wstring name;
};

void show_item(item a) {
    MessageBoxW(
        NULL,
        a.name.c_str(),
        std::to_wstring(a.number).c_str(),
        MB_OK
    );
    return;
}

void show_item_p(item* a) {
    MessageBoxW(
        NULL,
        a->name.c_str(),
        std::to_wstring(a->number).c_str(),
        MB_OK
    );
    return;
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    item a;
    a.number = 1;
    a.name = L"Shibuya";

    item b{ 3,L"Komabatodaimae" };

    show_item(a);
    show_item_p(&b);

    return 0;
}

構造体の宣言

構造体は、関数と同様に宣言と定義を区別します。
宣言はstructというキーワードを使い、struct 構造体名;という構文を用います。
後述する、構造体の定義の前におかれている宣言を、前方宣言と呼びます。

構造体の定義

関数と同様に、構造体の宣言に波括弧を付けると定義となります。
構造体の定義はstruct 構造体名 { メンバ並び };という構文を用います。
構造体は複数の変数をまとめる機能が主なので、メンバ並びに変数宣言を記述していきます。

構造体変数の宣言/初期化

宣言した構造体を使うには、組み込み型と同様の形で変数の宣言を行います。
宣言した構造体を型として、宣言された変数を構造体変数と呼ぶことがあります。
初期化は波括弧を使い、メンバ並びの記述順にコンマ区切りで値を渡して行います。

メンバアクセス

構造体のわかりにくいところ、構造体が持つメンバへのアクセス方法についてです。

メンバアクセス - ドット演算子

当然ですが、構造体変数が持っている変数にアクセスすることが出来ます。
構造体のメンバにアクセスする際には、変数名.メンバ名という構文を使用します。
なお、変数名.メンバ名は1つの変数を指すので、まとめて1つの変数の名前と見なせます。

メンバアクセス - ポインタ変数から

組み込み型にポインタ変数があるように、構造体にもポインタ変数が存在します。
もちろんポインタ型は構造体名*で、ポインタ変数の宣言やアドレス演算子は変わりません。
ただし、ポインタ変数からのメンバアクセスは(*変数名).メンバ名となります。
これは、ドット演算子の方が間接参照演算子よりも優先されてしまうからです。

メンバアクセス - アロー演算子

先述したように、ポインタ変数からメンバアクセスする際は(*変数名).メンバ名となります。
しかし演算子を2つ使い、丸括弧も付けないといけないのは間違いの元です。
そのため、アロー演算子というシンタックスシュガーが存在します。
アロー演算子を用いると、変数名->メンバ名と記述することが出来ます。

コード「構造体基本」解説

コード「構造体基本」の解説です。

item構造体の定義

構造体の定義にはstruct 構造体名 { メンバ並び };の構文を使います。
intstd::wstringをもつ構造体itemを宣言しています。
そして、それぞれnumbernameという名前を付けています。

item構造体変数の宣言と初期化

構造体も組み込みと同じ変数宣言の形を用いりました。
ただし、初期化は波括弧を用いて、リスト並びで行うのでした。
wWinMain関数内では、item構造体の変数abを宣言しています。
aはドット演算子でメンバアクセスして値を与えています。
対してbは、波括弧の初期化をしています。

show_item、show_item_p関数

show_item関数はitem構造体を値渡しで受け取ります。
対して、show_item_p関数はitem構造体へのポインタを受け取っています。
処理はどちらもitem構造体の内容をメッセージボックスに出力しています。
メンバアクセスはshow_item関数では、ドット演算子を使います。
対してshow_item_p関数はポインタなので、アロー演算子を使っています。

エイリアス

型名の別名、エイリアスについてです。以下のサンプルコードを実行してみましょう。
エイリアス
#include <Windows.h>
#include <string>

struct tagITEM;

using show_func = void(tagITEM*);
using show_func_ptr = show_func*;

typedef struct tagITEM {
    int number;
    std::wstring name;
    show_func_ptr show;
} ITEM, * LPITEM;

void show_item(LPITEM item) {
    MessageBoxW(
        NULL,
        item->name.c_str(),
        std::to_wstring(item->number).c_str(),
        MB_OK
    );
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    ITEM a{ 1,L"hanauta",show_item };

    a.show(&a);

    return 0;
}

エイリアス宣言

エイリアス宣言は、型名の別名を定義する言語機能です。
using 別名 = 型名;という構文で、型名別名を定義します。
この型名にはポインタ型なども指定でき、1つの型として宣言できます。
また、別名はエイリアス宣言後にあらゆる場所で通常の型と同様に使用できます。

補足 - 変数宣言

実は、変数宣言の型名 変数名には、コンマ区切りで変数名を追加できます。
ここで問題になるのが、ポインタ変数宣言でのアスタリスクは変数名に係ることです。
そのため、コンマ区切りの各変数名にアスタリスクを付けないとポインタ変数になりません。
すなわち型名* 変数名A,変数名B;とすると、変数名A型名*ですが、変数名B型名です。

typedef

typedefは、typedef 変数宣言という形で変数の別名を定義するためのキーワードです。
すなわち変数宣言変数名が、別名として宣言、定義されることになります。
サンプルコードでは構造体の宣言と同時に構造体変数を宣言する形で別名を宣言しています。
実は、構造体の宣言時の波括弧の後に変数名を変数宣言の変数名以下同様に記述できます。
なお、typedefは旧来の方法なのでC++ではエイリアス宣言を使うことが推奨されています。

コラム - LPWSTRの正体

wWinMain関数の引数に、LPWSTRがありました。
これは、LPがポインタ、Wがワイド文字、STRが文字列を意味します。
ワイド文字というのは、Visual StudioにおいてはUTF-16LEの文字のことです。
つまり、UTF-16LEを扱える型の文字列へのポインタということになります。
厳密に言うと、UTF-16LEを扱える型の文字へのポインタです。
配列を学ぶとここで言っていることがわかるようになるでしょう。
実際には、LPWSTRtypedef WCHAR *LPWSTR;で、WCHARtypedef wchar_t WCHAR;です。

コード「エイリアス」解説

コード「エイリアス」の解説です。

show_func、show_func_ptr

双方とも、エイリアス宣言によって宣言されている、型の別名です。
ただ、構造体tagITEMの構造体名を使いたいので、前方宣言をしています。 show_funcは、返り値void、引数は構造体tagITEMへのポインタを取る関数の型です。
show_func_ptrshow_funcへのポインタ、即ちvoid(*)(tagITEM*)を表わします。

構造体tagITEMの定義

構造体tagITEMは、typedefと組み合わせています。
そして、tagITEMの別名ITEMと、tagITEMへのポインタ型LPITEMを定義しています。
tagITEMtagというのはC言語では構造体名は型名でなく、タグ名だったことに由来します。
LPITEMLPは、Windows APIがポインタ型の接頭辞でLPを使っていることに由来します。

補足 - C言語での構造体

実は、C言語だと構造体名はタグ名であって、単体では型名にならなかったのです。
そのため、構造体変数を宣言するとき、struct 構造体名 変数名;と言う構文でした。
ただし互換性から、C++でもこの構造体変数の構文を用いても良いことになっています。

show_item関数

show_item関数は、構造体ITEMのメンバshowに渡すために用意した関数です。
LPITEM、すなわちITEMへのポインタを引数に取ります。
処理は構造体ITEMの内容をメッセージボックスに表示させています。

a.show(&a)

これは、構造体変数aのメンバのshowを経由してshow_item関数を呼び出しています。
show_item関数は、LPITEMを引数に取るので、構造体変数aのアドレスを渡しています。

練習問題

以下のコードのwWinMain関数を変更せず、書き加える形でプログラムとして完成させてください。
難しいので、ヒントを複数用意しています。始めに行き詰る時には、ヒントを見てしまいましょう。
解答の一部
#include <Windows.h>
#include <string>

void ass(int a, LPFUNC p) {
    p->a = a;
    return;
}

void show(LPFUNC p) {
    MessageBoxW(
        NULL,
        std::to_wstring(p->a).c_str(),
        L"after",
        MB_OK
    );
    return;
}

void call_struct_func(int a, LPFUNC p) {
    p->ass(a, p);
    p->show(p);
    return;
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    FUNC object{ 0,ass,show };
    MessageBoxW(
        NULL,
        std::to_wstring(object.a).c_str(),
        L"before",
        MB_OK
    );

    call_struct_func(10, &object);
    return 0;
}
ヒント1 wWinMain関数内のFUNCは構造体の名前です。
また、ass関数の引数のLPFUNCFUNCへのポインタです。
ヒント2 ヒント1より、構造体の定義とエイリアスの宣言が必要があります。
その構造体のメンバの型は初期化から推察が出来ます。
ヒント3 構造体のメンバの型は上から、0よりint
ass関数よりvoid(*)(int,LPFUNC)
show関数よりvoid(*)(int,LPFUNC)です。
また、構造体のメンバの名前はass関数とcall_struct_func関数からわかります。
ヒント4 構造体のメンバ名は、それぞれinta
void(*)(int,LPFUNC)の片方はass、もう片方はshowです。
解答
練習問題解答
#include <Windows.h>
#include <string>

typedef struct tagFUNC {
    int a;
    void(*ass)(int, tagFUNC*);
    void(*show)(tagFUNC*);
}FUNC, * LPFUNC;

void ass(int a, LPFUNC p) {
    p->a = a;
    return;
}

void show(LPFUNC p) {
    MessageBoxW(
        NULL,
        std::to_wstring(p->a).c_str(),
        L"after",
        MB_OK
    );
    return;
}

void call_struct_func(int a, LPFUNC p) {
    p->ass(a, p);
    p->show(p);
    return;
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    FUNC object{ 0,ass,show };
    MessageBoxW(
        NULL,
        std::to_wstring(object.a).c_str(),
        L"before",
        MB_OK
    );

    call_struct_func(10, &object);
    return 0;
}