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 構造体名 { メンバ並び };
の構文を使います。
int
とstd::wstring
をもつ構造体item
を宣言しています。
そして、それぞれnumber
とname
という名前を付けています。
item
構造体変数の宣言と初期化
構造体も組み込みと同じ変数宣言の形を用いりました。
ただし、初期化は波括弧を用いて、リスト並び
で行うのでした。
wWinMain
関数内では、item
構造体の変数a
とb
を宣言しています。
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を扱える型の文字へのポインタです。
配列を学ぶとここで言っていることがわかるようになるでしょう。
実際には、LPWSTR
がtypedef WCHAR *LPWSTR;
で、WCHAR
がtypedef wchar_t WCHAR;
です。
コード「エイリアス」解説
コード「エイリアス」の解説です。
show_func、show_func_ptr
双方とも、エイリアス宣言によって宣言されている、型の別名です。
ただ、構造体tagITEM
の構造体名を使いたいので、前方宣言をしています。
show_func
は、返り値void
、引数は構造体tagITEM
へのポインタを取る関数の型です。
show_func_ptr
はshow_func
へのポインタ、即ちvoid(*)(tagITEM*)
を表わします。
構造体tagITEM
の定義
構造体tagITEM
は、typedefと組み合わせています。
そして、tagITEM
の別名ITEM
と、tagITEM
へのポインタ型LPITEM
を定義しています。
tagITEM
のtag
というのはC言語では構造体名は型名でなく、タグ名だったことに由来します。
LPITEM
のLP
は、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
関数の引数のLPFUNC
はFUNC
へのポインタです。
ヒント2
ヒント1より、構造体の定義とエイリアスの宣言が必要があります。
その構造体のメンバの型は初期化から推察が出来ます。
ヒント3
構造体のメンバの型は上から、0
よりint
、
ass
関数よりvoid(*)(int,LPFUNC)
、
show
関数よりvoid(*)(int,LPFUNC)
です。
また、構造体のメンバの名前はass
関数とcall_struct_func
関数からわかります。
ヒント4
構造体のメンバ名は、それぞれint
はa
、
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;
}