C++ Windows プログラミング 書いて覚えるための初心者自己中記事
Windowsデベロッパーセンターで初心者向けの1からの説明ページがあったから、そこで勉強する。
まず開発環境を準備する。
Windows SDKを入れましょう、ダウンロードはここから、みたいなのあるけど、よくわからない。リンク先はwindows7 とか書いてあるし、visual studioってそのへんどうなってるんだ?
visual studio のインストーラ起動すると、windows10 SDK とか入ってるからこれでいいってことだよね?
入ってるということにしておこう。
Windowsにおけるコーディング規約
WinDef.h で型がtypedefされまくっているからびっくりしないでね、って書いてる。
データ型 | サイズ | 符号の有無 |
---|---|---|
BYTE | 8 ビット | 符合なし |
DWORD | 32 ビット | 符合なし |
INT32 | 32 ビット | 符号付き |
INT64 | 64 ビット | 符号付き |
LONG | 32 ビット | 符号付き |
LONGLONG | 64 ビット | 符号付き |
UINT32 | 32 ビット | 符合なし |
UINT64 | 64 ビット | 符合なし |
ULONG | 32 ビット | 符合なし |
ULONGLONG | 64 ビット | 符合なし |
WORD | 16 ビット | 符合なし |
上記以外でBOOL
#define FALSE 0
#define TRUE 1
こうなっている。
BOOLは整数型でC++ のboolと入れ替えが出来ないと書いてある。
??C++のboolは違うんだっけ?
ポインター型もtypedefされててプレフィクスにP-やらLP-やらが付いてる。
LP-は16ビットWindows時代の名残で、今は区別ないらしい。
ポインター精度の型
- DWORD_PTR
- INT_PTR
- LONG_PTR
- ULONG_PTR
- UINT_PTR
これらはポインターの長さと同じになる。???
32ビットアプリケーションの場合は32ビット
64ビットの場合は64ビット
というかポインターの長さってなんだ?
とりあえず、これらの型は整数値からポインタへのキャスト時に使われる。
visual C++コンパイラはワイド文字用のデータ型 wchar_t をサポートしている。WinNT.hでは
typedef wcha_t WCHAR;
としている。
ワイド文字を宣言する場合、リテラルの前に L をつける。
wchar_t a = L'a';
wchar_t *str = L"hello";
Typedef | 定義 |
---|---|
CHAR | char |
PSTR または LPSTR | char * |
PCSTR または LPCSTR | const char* |
PWSTR または LPWSTR | wchar_t* |
PCWSTR または LPCWSTR | const wchar_t* |
ウィンドウのタイトルバーテキストを設定する関数は2種類ある。
#ifdef UNICODE #define SetWindowText SetWindowTextW #else #define SetWindowText SetWindowTextA #endif
ウィンドウとは
アプリケーションウィンドウ(メインウィンドウ)
フレームは非クライアント領域(OSが管理)
フレームの内側がクライアント領域(プログラムで管理)
コントロールウィンドウ
単独で存在できない。
コントロールウィンドウとアプリケーションウィンドウは相互対話が可能。
親ウィンドウとオーナーウィンドウ
コントロールウィンドウはアプリケーションウィンドウの子
アプリケーションウィンドウはコントロールウィンドウの親
Dialog Boxって表示されてるウィンドウはモーダルダイアログ
この場合、アプリケーションウィンドウはオーナー
モーダルダイアログは「オーナーに所有されている」
所有されているウィンドウは、常にオーナーの前面に表示されオーナーが最小化されると追従する。オーナーが破棄されると同時に破棄される。
上の画像はアプリケーションウィンドウがモーダルダイアログウィンドウを所有していて、モーダルダイアログウィンドウは2つのボタンウィンドウの親。
ウィンドウはオブジェクト。
C++のクラスではない。
プログラムはハンドルと呼ばれる値でウィンドウを参照する。
ウィンドウハンドルのデータ型はHWND
ウィンドウを作成する関数の戻り値がHWND型
HWND CreateWindow(.........)
HWND CreateWindowEx(.........)
画面上でウィンドウを再配置する関数は
MoveWindow
BOOL MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint);
これらのようにHWND型の引数がある関数でウィンドウは操作できる。
WinMain アプリケーションエントリポイント
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);
まず、第一引数
HINSTANCE hInstance
インスタンスへのハンドル、またはモジュールへのハンドル
OSはこの値を使用してメモリに読み込まれた実行可能ファイルを特定する。?
第二引数
HINSTANCE hPrevInstance
16ビットWindows時代に使われていた。現在は無意味。
第三引数
PWSTR pCmdLine
第四引数
int nCmdShow
メインアプリケーションウィンドウの最大化・最小化・通常表示を指定するフラグ。
このwWinMain関数の戻り値は別のプログラムのステータスコードとして使用できる。
WINAPIは呼び出し規約。呼び出し側からのパラメータをどう受け取るかを定義。
それ以外は同じ。
コマンドライン引数のUnicodeコピーはGetCommandLine関数で取得できる。
コンパイラがmain関数ではなくwWinMain関数を起動するかというとMicrosoft C ランタイムライブラリ(CRT)がwWinMainを呼び出すmainを実装しているから。
慣れない。
次は実際にウィンドウを出せる。
ここまで。
C++ STL algorithm 書いて覚えるための初心者自己中記事
標準ヘッダファイルのことについて全然知らないので、勉強していく。
テキストの終わりに標準テンプレートライブラリ(STL)の一覧があったから上から見ていく。
algorithm 各種アルゴリズム
どう使うのかさっぱり。
algorithm はあれか、要素に対して何かしらちょっかいが出せる関数の集まりなのか。
アルゴリズムライブラリは、要素の範囲に対して作用するさまざまな目的の (例: 検索、ソート、計数、操作) 関数を定義しています。ひとつの範囲は [first, last)
のように定義され、last
は参照したり変更したりする最後の要素の次の要素を指します。
なるほど。
じゃあ、何でもいいから配列を作ってalgorithmの関数を使ってみればいいのかな?
というかその前に、イテレータってのは何かあれだったような、コンテナなら全員持ってるのか?種類もあったような。
その辺があいまいだからそこからやったほうがいい気がしてきた。
イテレータの種類は以前テキストで習った。
あ、リファレンスのサイト見ると乗ってるのか。
難しな。
ダメだ、情報が多すぎて把握できない。
こんな時は少しずつだな。
とりあえず、vector クラステンプレートで配列を作って、それをalgorithm の関数で操作してみて様子を見よう。
vector単体でもいろいろ出来るけど、それ以上に出来るということかな。
おー、レファレンスのコンストラクタとか見るといろんな引数で作れるとか分かるのか。
ちょいちょい出てくるAIlocatorってなんだ?
STLコンテナがメモリ確保するときには皆Allcator を使っているのか。
Allocator がメモリの確保と解放の役割を一手に担っていると。
なるほど、デフォルト引数になってるけど、自分で用意して引数に渡すこともできるのか。うん、まだ早すぎるな。
vectorのコンストラクタを見ているんだった。
あー、見てたらきりがないな。
今やりたいことは細かい部分じゃなくて全体的な使い方。
std::vector<int> vintA = { 0,1,2,3,4,5,6,7,8,9, };
とりあえず、vectorで配列作った。
で、algorithmの関数でfor_eachってのが面白そう。
指定した範囲内に別の関数を適用できる。で、あってるのかな?
for_each関数の第一第二引数はInputIteratorって書いてある。
ここにさっき作ったvintA のイテレータを渡せばいんだな。
あ、そういうことか、ここでvectorのイテレータはランダムアクセスイテレータだからこの関数は使用できるということか。
typedef std::vector<int>::iterator Iterator; std::vector<int> vintA = { 0,1,2,3,4,5,6,7,8,9, }; Iterator f = vintA.begin(); Iterator e = vintA.end(); std::for_each(f,e,);
関数はどうしよう。
戻り値とか引数とかどうなってるんだ?
template<class InputIt, class UnaryFunction> UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f) { for (; first != last; ++first) { f(*first); } return f; }
これみると、関数の引数には配列の中身をイテレータの*で渡してるから、関数で配列の中身を書き換えることが出来るのか。
戻り値が関数の戻り値をそのまま使ってるけど、配列分繰り返される関数の戻り値の型ってどうなってるんだ・・・?
まずは中身を書き換えてみよう。
ん、第三引数に指定する関数の入れ方がわからないぞ。
void twice(int& n) { n *= n; } int main() { typedef std::vector<int>::iterator Iterator; std::vector<int> vintA = { 0,1,2,3,4,5,6,7,8,9, }; Iterator f = vintA.begin(); Iterator e = vintA.end(); std::for_each(f,e,twice);
カッコはいらないのか。関数名だけでよかった。
count
count_if
bool Under(int& n) { return 5 > n; } int main() { typedef std::vector<int>::iterator Iterator; std::vector<int> vintA = { 0,1,2,3,4,5,6,7,8,9, }; Iterator f = vintA.begin(); Iterator e = vintA.end(); std::cout << count(f, e, 5) << std::endl; std::cout << count_if(f, e, Under) << std::endl;
count_ifはbool返す関数でtrue の数を数えるのか。
関数を引数にするときに別で引数渡したいときはどうするのかな。
struct test { test(int i):i(i){} bool operator()(int& n) { return i > n; } int i; };
std::cout << count_if(f, e, test(7)) << std::endl;
なるほど。演算子オーバーロードを使って構造体を一つの関数のようにしてしまうのか。
関数を引数にとるalgorithm関数内部で
p(*first)
この状態になるから出来るのか。
count_ifに指定するときはコンストラクタとして()が使われて、
count_if内部の処理の時は()がオーバーロードされた状態のやつってのが。
初期化されるまではオーバーロードも有効にならないということなのだろうか。うん、そうだな。きっと。
STL algorithmがどういうものかが少しわかった気がする。
ここまで。
C++ ゲーム作りの為の勉強2 書いて覚えるための初心者自己中記事
表示された何かをキーボードで動かしたい。
全然わからないけど、GetKeyState関数を使ったらキーボードに反応した。
if (GetKeyState('D') < 0)
~~~~~~~~
翌日
~~~~~~~~
とりあえず、ビーム出して標的を破壊してみたかったから作ってみた。
ビームと標的(ブロック)はdeque を利用。
ビームはクラスを作ってそれをdeque に入れて処理、ビームが消えるタイミングでdeque の要素も削除。
標的はテンプレートクラスpair を使って標的の有る座標をpair<int,int>に登録、それをdeque に座標の数だけ入れた。
ビームが標的のいる座標に当たったら、ビーム、・標的両方消す。dequeの要素も消す。
画面左上にビームと標的のdeque要素数が出ていて、要素が作られたり消されたりがわかる。
deque の要素を操作するためにイテレータを使ったんだけど、for文の中で条件に合うdequeの要素を削除していこうとすると、イテレータがダメになる。
for文で回すたびにイテレータでbegin()とend() 取得し直すようにした。
//天井まで行っていたら、または壁を壊したら要素削除 if (!ray->empty()) { Iterator e = ray->end(); for (Iterator f = ray->begin(); f < e ; ++f) { if (f->wallatack) { //壁破壊爆発消火 setCursorPos(f->x, f->y); std::cout << ' '; } if (f->y == 0 || f->wallatack ) { ray->erase(f); if (!ray->empty()) {//要素が残ってたらイテレータ取得しなおし f = ray->begin(); e = ray->end(); } else {//要素が無い場合breakしないと++fでエラーになるっぽい break; } } } }
ビームのクラス
class Ray { public: Ray(const int& x, const int& y); public: static void rayShot(const int& x, const int& y, std::deque<Ray>* ray, std::deque<std::pair<int, int>>* wall); private: int x;//ビームX座標 int y;//ビームY座標 char r = '!';//ビーム本体 bool wallatack = false;//標的に当たったビームのフラグ };
dequeは使えた。
次は別の何かを使って、使い方を覚えよう。
ここまで。
C++ ゲーム作りのための勉強1 書いて覚えるための初心者自己中記事
ゲーム作りたいので最初はコンソールゲームというものから勉強していきます。
HANDLE hCons = GetStdHandle(STD_OUTPUT_HANDLE);
コンソールでゲームするのに表示とかどうなっているのかさっぱりわからなくて調べたら、windowsで作る場合はwindowsのウィンドウを操作するコンソールハンドルなるものを呼び出すらしい。
#include <windows.h> にwindows関連のいろいろが入ってるのか。
ハンドルは、「取り扱う」とかの意味で覚えればいいのかな。
GetStdandle関数でSTD_OUTPUT_HANDLE を指定すると戻り値でコンソールハンドルが取得できる。
これを保持するのはHANDLE型
HANDLE型って何だろうと思ったら、void* をtypedefしただけらしい。
WORD attr = 0;
色を指定するためのビットフラグを登録する変数。
WORD型ってなんぞやと思ったら、これもunsigned short をtypedefしただけだった。なんなんだろうね。
色の指定について。
赤
青
緑
高度?
の組み合わせで作られるのかな?
それぞれがビットフラグになってて、組み合わせで色が作られるみたいな。
コンソールフォントというのか。
attr |= FOREGROUND_INTENSITY; attr |= FOREGROUND_RED; attr |= FOREGROUND_GREEN; attr |= FOREGROUND_BLUE;
SetConsoleTextAttribute(hCons, attr);
SetConsoleTextAttribute関数で、第一引数には扱うハンドル第二引数にWORD型でビットフラグ指定したの。
これでSTD_OUTPUT_HANDLE(標準出力ハンドル)のフォントカラーを変更できるのか。
一連の流れを関数で簡単に設定できるようにしたりするのね。
FOREGROUND_~~ がフォントカラー
BACKGROUND_~~ が背景色
次、表示される文字の場所を指定する。
さっきの色と同じ流れだな。
HANDLE hCons = GetStdHandle(STD_OUTPUT_HANDLE);
ハンドル取得。
COORD pos;
表示場所の指定をするからx軸とy軸を指定する。
そのための型がCOORD型
COORD型はなんだろう。
typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD;
こんなん書いてあった。
SHORTはtypedefしただけのshort型でした。
_COORD構造体、中身short X / Y
をCOORDとしてるのか。
pos.X = x; pos.Y = y; SetConsoleCursorPosition(hCons, pos);
関数でxとy をshort型で引数にしてさっきみたいに入ればいいのか。
表示位置を指定するには
SetConsoleCursorPosition関数(扱うハンドル,COORD型)
ランダムしたいときに
#include <random>
random_device rnd;
呼び出しは rnd();
オーバーロードされてるっぽい。
デバイスの状態をもとにランダム作成してるらしい。
動く*が壁にぶつかったら反射するようなコードを書いてみましょう、っていうのを頑張ってやった。出来たからニヤニヤしてたら、解答欄でものすごく短いコードで実現してた・・・。
計算させるって大事だなぁ。すごいわ。
初めて尽くしだから、いったん区切ろう。
ここまで。
C++ terminate 書いて覚えるための初心者自己中記事
例外が最後までキャッチされないときに呼ばれる関数terminate
terminate関数が呼ばれるとプログラムは異常終了する。
正確にはterminate関数はabort関数を呼び出して異常終了する。
exit関数は呼ばれないのでデストラクタも呼ばれない、正常な終了処理が行われない。
set_terminate関数を使うと、強制終了する前に行う処理を追加できる。
void OnTerm() { cerr << "異常終了です" << endl; } int main() { set_terminate(OnTerm); throw exception(); system("pause"); }
この後にabort関数が呼ばれる。
キャッチできない例外
unexpected関数が呼ばれたとき
二重例外が発生したとき
静的変数のコンストラクタやデストラクタで例外が発生したとき
catchブロック外で例外の再送出を行おうとしたとき
atexit関数で登録された処理の中で例外が投げられたとき
これらの場合はset_terminate関数で終了処理を登録する。
ここまで。
C++ volatile 書いて覚えるための初心者自己中記事
メモリ上にある値を使って計算を行うにはメモリからCPUへデータが読み込まれる。
CPUにもメモリとは別にデータを置いておく場所があり、この作業領域をレジスタという。レジスタは非常に高速なアクセス。CPUとレジスタの話?
メモリからレジスタはそれなり。
なのでメモリ~レジスタの行き来をなるべく少なくしたい。
void Hoge() { int n = 1; if (n) { cout << n << endl; } };
この関数内の変数n は値が変化していない。
さらにif文は必ずtrueになる。
そのため
void Hoge() { cout << 1 << endl; };
コンパイラはこのように最適化をしてしまう場合がある。
変数n そのものがなくなってしまった。
void Hoge() { int n = rand() % 2; if (n) { cout << n << endl; };
この場合はn は消されない。
このn の値をずっとレジスタを使って扱えばメモリへのアクセスが必要なくなります。???
とにかく、最適化によって変数が削除されたり、数値の値がメモリ上に置かれない状態になったりすることがある。
なんかわからないけど、それが困る状況があるらしい。
そのために限定的に最適化をキャンセルさせる方法があって、それが
volatile
これを変数宣言の先頭につけるだけ。
この変数を使用する個所では必ずメモリにアクセスするようになる。
volatile はconstの同類。
const外すのがconst_cast
そのままvolatile 外すのも const_cast
volatile がよく使われるのはマルチスレッドプログラミングだそう。
全然知らない。
マルチコアCPUの話かな?
とにかく処理が複数ってことで、スレッド1とスレッド2の両方から使われる変数とかで必要らしい。
スレッド1では変化なしだけどスレッド2で変更される可能性があるとかを考慮しないで最適化してしまうかもしれない。ってことであってるかな。
また、メモリマップド I / O というメモリへのアクセスが外部機器への入出力に相当するような機能があって、最適化でメモリへのアクセスが省略させたりすると機能しなくなったり、アクセスの順番が変わってしまったりしても操作準が変わってしまう結果になったりする。
ここまで。