c,c++におけるポインタについて
まだ加筆予定
目標は
char (*p)[5]を完全に理解するぐらいまでにはポインタを理解すること。
ポインタと配列の違いは一言でいうならば、書き換えができるか否かである。
ポインタと配列の混乱の元
1:配列名aを読み込む場合、aは&a[0]と解釈される。
2:ポインタbの参照先が配列の場合、でアクセスできる。(もちろん書き込み、読み込み両方の意味で)
3:文字を''で囲めばchar型になるし、""で囲めば、char* 型になる.
(ただし、char型配列を""で初期化(代入ではない)するときは""で囲まれた文字列をいったん別のところにコピーしてから配列に
入れなおしている。)
ようするにchar型,char*型のときポインタと配列の規則がかなりややこしい。
4:プロトタイプ宣言では、引数にchar sと配列型を指定したとしても、ポインタとして扱う。
int a[5] = {0,1,2,3,4}; int* b; int c[5] = {6,7,8,9,10}; int d = 10; b; //bはint *型 b = &d; a = &d; //できない。 a = c; //できない。
これは普通にわかると思うが、仕組みを解説する。(後で)
決定的な違いはbは箱が用意されているので、書き込みできるが、aは箱が用意されていないので、上のように書き込みできない。
vc++の「式は変更可能な左辺値である必要があります」というのがそれ。
b = a; //aは読み込み可能なので、あたかもaがポインターであるかのようにふるまう。 b++; //++演算子はb(=&a[0]:つまりアドレス)をインクリメントする。(つまり、bの値を書き込む) a++; //aは配列名なので、書き込めない。よってこういう風にはかけない。
aは値を書き込めないが、1:厄介なことにaをload(読み込み)することはできる。このばあい、aは&a[0]と解釈される。
ここでよくわからなくなってきそうだが、ポインターbは書き込み可能だが、配列aは書き込みできないことを常に念頭に置こう。
b=a; b[2] = 10; //b++でアクセスするのはめんどくさい。
さらに、厄介なことに 2:ポインターbは[]演算子でアクセスできる。
//#include <stdlib.h> //C言語でのmalloc,free関数用にインクルード int a[5]; //このままつかっていいよ// int* b; int num = 5; b = new int[num]; //領域を手動で確保する。 // b = (int*)malloc(num); Cならこういう書き方。 //これでbは自由に動かせる。(配列と同じようにつかってよい) delete[] b; //bは配列として領域を格納したので、deleteではなくdelete[]で解放させる。 // free(b); C言語なら //ちなみに、calloc---cfreeは覚えなくても、malloc---freeペアを使ったほうがよい。
配列の場合、宣言すれば自動的に領域が確保されるが、ポインタの場合、宣言しても領域を確保してくれることはない。
bは自由に書き換え可能なので、なんども値を変更するときは自分で後始末しろよ、ということである。
対して、配列aは書き換えできないので、簡単に管理が可能であり、かっこを抜ければ自動的に領域が解放される仕様になっている。
ポインタの場合、手動で領域を確保しなければならないので、めんどくさいが、配列数を定数にする必要がなくなる。
newとかの基準について、
例をあげると、bというポインタが、「別の家に引っ越したい」とき、新しく「家」を作らなくてよい(newで領域を確保 しなくて良い)から、newはいらない。 一方、「家を持たない」bというポインタが、「新しく家を建てたいとき」、新しく「家」を作る必要があるので、 newする必要がある。
まあ、実際やればわかる。
int型はもういいとして、char型のポインタ関連の話に移る。これがかなりややこしい。
考えるのがめんどくさい人は、文字列を扱うのは const char* str2 = "yoro"; //constは必ず付けること で char str[5] = "yoro"; もしくは、 char str2[5] = {'y', 'o', 'r', 'o', '\0'}; //char* str2とすると、領域を確保していないのでダメ。 char* str2; //初期化をしないとき の略記バージョンだとおもってくれればよい。どうしても文字列の書き換えを許したければ、後半のように書く。
ここで、いろいろ納得できないことが出てくる。気になったら読むといい。
char* str = "yoro"; 、一番上はポインタなのに、エラーにならないんだろうか。 さっき、領域を手動で確保しなきゃいけないって言ったのに矛盾しないか? 答え:"yoro"(""で囲まれた文字列を文字列リテラルという。)自体がポインタなのだ。 文字列は一旦文字列専用の領域(メモリ上の静的な領域のどこか)に書き込まれる.(だから、constをつける) (規格上、文字列リテラルの書き換えは未定義) アドレスの値は、'y', 'o', 'r', 'o','\0' と連続してメモリ上に置かれた時の'y'へのポインタである。 ということで、 char* str; str = "yoro"; とかいても問題ない。(もっとも、constがついていない。文字列リテラルを書き換える可能性があるので、 const char str2 = str; //として隠ぺいできるがいずれにしろ、strが残るので、素直に、 char* str = "yoro"; //でいこう。 char* str; str = new char[50]; みたいなのは見たことないはずだ。
文字列リテラルがポインタなら、 char str[5] = "yoro"; は、なんでOKなの? int* b; b = new int[5]; b={1,2,3,4,5}; int a[5] = b; ではエラーなのに? 答え:配列をリテラル文字列("で囲まれた文字列")で初期化しても普通の配列になる。 char型配列を""で初期化(代入ではない)するときは ""で囲まれた文字列(文字列リテラル)をいったん別のところにコピーしてから配列に入れなおしているらしい。(未確定) だから、constはいらない。 char str2[5] = {'y', 'o', 'r', 'o', '\0'}; //とおなじこと。 char str[5]; str = "yoro"; はstrがchar[5]型の配列の配列名なので、書き込みできず、ダメ。
まとめると、
実際には、 char str[5] = "yoro"; const char* str = "yoro"; の二つの書き方を覚えておけばよい。
それでは
char *p[5]; char (*p)[5];
の説明。
http://www.geocities.jp/kuronishi_kakiteru/C/c_31.htmlのはじめ
くらいに詳しめに書いてある。
char *p[5];
char (*p)[6];
//演算子の結合順序がわからなければ、かっこを付けてしまおう。 p; //ポインタなので、書き換え可能 char(*)[6]型 &p; //アドレスは書き換えできない。安全だが、制約でもある。 *p; //(*p)はchar[6]型の配列の配列名なので、当然書き換えできない。 *(p++); //かっこを忘れずに! ポインタが移動している点以外は上と全く同じ。 *p++; //(*p)++;と読む。(*p)はchar[6]型の配列の配列名なので、書き換え不可。 //よって、++演算子(値が変更されてしまう:書き換え操作)を置けない。こういう形はありえない! (*p)[1]; //char型なので、書き換え可能。p[0][1]と同じです。 *p[1]; //結合の順序から(*p)[1]と同じ。 //ポインタは書き換え可能なので、p++とかいうアクセスのほかにp[num]みたいなアクセスもOKとしている。 p[1]; //p[1]はpをインクリメントした後の*pと同じ。
補足:constについて、
const int* pNum; //ポインタの中身が定数でなければならない。 int* const pNum = pUtint; //ポインタ自体が定数でなければならない。なので、必ず初期化が必要になる。 //配列とできることとしては変わらなくなる。 const int* const pNum = pUtint; //ポインタの中身もポインタ自体も定数でなければならない。
補足:[]について
初期化を伴う宣言のとき、第一次元をかかなくてよい
int a[] = { 1, 2, 3 }; は int a[3] = {1,2,3}; とおなじ。 おなじく、 char a[][ 2 ] = { { 1, 2 }, { 3, 4,}, { 5, 6 } }; は第一次元に3を指定したものと同じになる。
このように、は省略された配列型を思っていいが、例外がある。
4:プロトタイプ宣言では、引数にchar sと配列型を指定したとしても、ポインタとして扱う。
その前に関数についての用語を改めて確認、定義する。
定義 仮引数... 実引数... プロトタイプ宣言...
4:プロトタイプ宣言では、引数にchar s[]と配列型を指定したとしても、ポインタとして扱う。 つまり、 void func(char s[]); と void func(char* s); は全く同じ。 また、 void func( char * p[] ); は void func(char** p); と同じだし、 void func (char p[][3]); は、 void func (char (*p)[3]); とおなじ。
ちなみに、プロトタイプ宣言では配列を引数に置けないのはなぜだろうか? void func(char s[5]);という書き方も認められているが、実はこれは void func(char s[]); と全く同じになる。(ということは、配列を引数におけないし、配列の個数を5に指定しても無意味である。) なぜか、というのはもし、仮引数が配列でもOKだとするなら、 int main(void) { int s[5] = {1,2,3,4,5}; func(s); return 0; } void func(int ms[5]) { //仮に配列でプロトタイプ宣言できるとする。 // } 仮引数に値を渡すとき、int ms[5] = s; という操作が行われると考えるのが自然である。 しかし、このコードはありえない!なぜなら、msというのは書き換えできないから、&s[0](sを読み込んだときこう解釈される) という値を書き込めない。 だから、プロトタイプ宣言ではポインタを引数に置く仕様になっているのだ。 void func(char p[5][3]); //と宣言したとしても、ポインタを引数に置くという決まりにより、 void func(char p[][3]); //と全く同じになる。 関数を呼ぶときにはポインタ、もしくは配列変数ではない変数を引数にとる。
しかし、どうしても、配列しか引数に渡さない場面はそれなりにあるだろう。その時は、 配列の要素数を別の引数で指定する(sizeofで図ることはできない。) というやり方が一番簡単だ。 void func(int a[5], int n); //nは配列の要素数 aは配列の形をしているが、3番目の意味と全く同じ. void func(int a[], int n); void func(int* a, int n); //この3つは全く同じ。
配列の一次元目の要素数は、 1. 要素数を渡すのではなく、配列の最後にそれと分かるデータを入れる 2. 配列の要素数を別の引数で指定する のほうほうがあるが、2が一番良い。 void func( char * p[] ); //なら、2つ指定する必要があるし、 void func (char (*p)[3]); //なら、一つでよい。 ((*p)[3]は[3]がついているから、配列じゃないか、と混乱するかもしれないが、p自体はポインタである。 図解を参照すること。)
int aa[9]; //配列 //aa = hin; aaは書き換え可能ではないので、このコードはかけない。 //書き換えはできないが、参照はできる。この参照というのが、ポインタとの混同を引き起こさせる原因。 //参照の時はaa = &aa[0]と同じになる。 &aa; //参照時の型はint (*)[6] 書き換え不可 aa; //参照時の型はint * 書き換え不可 aa[1]; //参照時の型はint constを付けない限り書き換え可能
&という演算子について。-->後で
クラスが引数のとき、値渡しだと、クラスが大きいとき、まるまる一つコピーすることになるので、めちゃくちゃ遅くなる。 書き換えるときはポインタ渡し、書き換えないときは参照渡し。にすればよい。
swapを寝渡し、ポインタ渡し、参照渡しで実装してみよう。
win32 part2 描画編
赤い色の直線を描く
赤のペンを作成
赤のペンをデバイスコンテキストにせんたくする
現在位置を直線の視点にセットする
終点まで直線を描く
デバイスコンテキストからの赤のペンを選択削除する
赤のペンを削除する
WM_PAINTメッセージではBeginPaintで。(更新が必要とシステムが認識している領域以外には描画ができない)
デバイスコンテキスト:ディスプレイとかプリンタとかメモリの「画用紙」を抽象化したもの。(デバイスに依存しない処理ができる。) GDI:Windowsが提供するグラフィック描画機能 GDIオブジェクト(グラフィックスオブジェクト):GDI機能を使って描画するときに使う「道具」(「ペン」) ビットマップ、ブラシ、フォント、論理パレット、パス、ペン、リージョンの7種類 関連付け:デバイスコンテキストに〜を関連付けるとは、「ペン」を持つこと。 一つのデバイスコンテキストに一つの「ペン」しか持てない。SelectObject APIで切り替える。 GDIオブジェクトも、デバイスコンテキストもハンドルを持っていてそれでもって操作する。
HGDIOBJ SelectObject( HDC hdc, // デバイスコンテキストのハンドル HGDIOBJ hgdiobj // オブジェクトのハンドル ); ビットマップを扱うなら、戻り値の型HGDIOBJから、HBITMAPに
BOOL BitBlt( HDC hdcDest, // コピー先デバイスコンテキストのハンドル int nXDest, // コピー先長方形の左上隅の x 座標 int nYDest, // コピー先長方形の左上隅の y 座標 int nWidth, // コピー先長方形の幅 int nHeight, // コピー先長方形の高さ HDC hdcSrc, // コピー元デバイスコンテキストのハンドル int nXSrc, // コピー元長方形の左上隅の x 座標 int nYSrc, // コピー元長方形の左上隅の y 座標 DWORD dwRop // ラスタオペレーションコード );
例のやつ
//ch1gam.cpp #include <iostream> #include <cstdio> #include "Object.h" using namespace std; /* 0000 .po# の順に */ int main() { Stage stage1(5,8); char s1map[5][8] = { {1,1,1,1,1,1,1,1}, {1,0,8,8,0,4,0,1}, {1,0,2,2,0,0,0,1}, {1,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1}, }; stage1.SetMap(s1map); stage1.Create(); int key; while((key = getchar()) != EOF) { if(key !='\n') { stage1.Move(key); stage1.Create(); } if(stage1.IsClear()) { printf("OKOK\n"); break; } } // for(int i=0;i<stage1.GetRow();i++) { // delete[] s1map[i]; // } return 0; } //Object.h /* class Object { protected: int x,y; //位置 char** stage; int row, col; bool Rule(int x, int y); public: virtual void Init() {;} void Show(); void Move(char key); }; */ class Stage { private: char map[5][8]; int pos_x,pos_y; int dotcount; int row; int col; public: int GetRow(); int GetCol(); void SetMap(char map[5][8]); void Create(); void Move(int key); bool IsClear(); Stage(int row, int col); // virtual ~Stage(); }; //Object.cpp #include "Object.h" #include <stdlib.h> #include <cstdio> /*ビットの扱い方* 0000 .po# とする bool b; int ans; int qt; //要素の属性 b = qt&4 //qtにp(player)が含まれていればb=true; ans = qt|4 //qtにp(player)の属性を加える ans = qt&(~4) //qtにp(player)の属性を除く */ Stage::Stage(int row, int col) { this->row = row; //左側のrow:クラス内のrow this->col = col; //右側のcol:クラス内のcol //thisをつけたほうが無難。なぜなら、複数で開発する際にrowが使われているかわからないので、仮に引数を変えたとしても、 //クラス内のメンバと一致する可能性があるから。 } /* Stage::~Stage() { for(int i=0;i<row;i++) { delete[] map[i]; } } */ int Stage::GetRow() { return this->row; } int Stage::GetCol() { return this->col; } void Stage::SetMap(char m[5][8]) { this->dotcount = 0; for(int i=0;i<row;i++) { for(int j=0;j<col;j++) { map[i][j] = m[i][j]; if(map[i][j]&4) { this->pos_x = i; this->pos_y = j; } if(map[i][j]&8) { this->dotcount++; } } } } void Stage::Create() { //system("cls"); for(int i=0;i<5;i++) { for(int j=0;j<8;j++) { switch (map[i][j]) { case (1): putchar('#'); break; case (2|8): putchar('O'); break; case (2): putchar('o'); break; case (4|8): putchar('P'); break; case (4): putchar('p'); break; case (8): putchar('.'); break; default: putchar(' '); break; } } putchar('\n'); } printf("%d\n", dotcount); } void Stage::Move(int key) { int dir[5][2] = { {0,-1}, {1,0}, {0,1}, {-1,0}, {0,0} }; int m; int x,y,nx,ny, f_nx, f_ny; switch (key) { case 'a': m = 0; break; case 's': m = 1; break; case 'd': m =2; break; case 'w': m = 3; break; default: m=4; break; } x = this->pos_x; y = this->pos_y; nx = x+dir[m][0]; ny = y+dir[m][1]; if(nx >= 0 && nx < 5 && ny >= 0 && ny < 8) { if(!(map[nx][ny]&3) ) { map[x][y] = map[x][y]&(~4); map[nx][ny] = map[nx][ny]|4; pos_x = nx; pos_y = ny; } else if(map[nx][ny]&2) { f_nx = x+2*dir[m][0]; f_ny=y+2*dir[m][1]; if(map[f_nx][f_ny]&3) { ;//移動できない } else { if(map[f_nx][f_ny]==8) { this->dotcount--; } if(map[nx][ny]==(2|8)) { this->dotcount++; } map[f_nx][f_ny] = map[f_nx][f_ny]|2; map[nx][ny] = (map[nx][ny]|4)&~2; map[x][y] = map[x][y]&(~4); pos_x = nx; pos_y = ny; } } } } bool Stage::IsClear() { if(dotcount==0) { return true; } return false; }
c++
コンストラクタ
mallocもnewも必要なメモリ領域をヒープからもらってくるが、mallocには、コンストラクタを呼び出す機能はない。 mallocは仮想関数テーブル迄もを初期化させる。
コピーコンストラクタ:
まず、代入と、初期化との区別ができているかどうか? 初期化とは、変数を定義すると同時に代入する行為のこと。 int a = 4; //初期化 a = 5; //代入 仮引数が作られる時も初期化がされる。 int main(void) { //;;; func(a); //func(a)が呼ばれる } int num = a; という形で初期化が行われ、funcが実行される。 void func(int num) { // } 違いが顕著にでるのは、コンストラクタでの振る舞いである。 class C { public: C(int num) : mNum(num) { //初期化 mFlag = ( (mNum>0) ? 1 : 0 ); //代入 } private: int mNum; bool mFlag; };
コピーコンストラクタは初期化が起こるときに呼び出される。
(自作クラスをnewする時は、コンストラクタとコピーコンストラクタの2つが一緒に呼び出される。
特にクラスをシングルトンにしたいときは必ず、コピーコンストラクタの定義を明示的に書くこと。(何もしない処理を書く)
↑
はまったので注意。
シングルトンの時はインスタンスの生成が特殊なので、constを付けることは基本的にはできない。
コピーコンストラクタをprivate:側で書くと、もし、引数の受け渡しが行われてもコンパイルエラーになる。
何も処理させたくないときに書くと堅牢なコードを書ける。
つまり、仮引数が作られる時と、変数を定義すると同時に代入するとき。
メンバ変数がポインタの場合にはコピーコンストラクタを明示的に書いてあげないといけない。
仮引数がクラスで値渡しのとき注意が必要。(メンバ変数がポインタの場合)
また、
Class C;
C a = b; //初期化しているので、この時コピーコンストラクタが呼ばれる。(メンバ変数がポインタの時に注意。)
コピーコンストラクタの引数が参照である理由
引数が参照でなければ、コピーコンストラクタで値のコピーが行われることになる。
つまり、コピーコンストラクタを呼び出すためにまたコピーコンストラクタを呼び出さなければならなくなる。-->無限ループ
コピーコンストラクタの浅いコピーとディープコピー
浅いコピー Class C { public: C(C& m) { ptr = m.ptr; } } ディープコピー(ポインタもその実体も別に新しく作る) Class C { public: C(C& m) { ptr = new char[mNum]; for(int i=0;i<mNum;i++) { this->ptr[i] = m.ptr[i]; } } private: int* ptr; int mNum; }
フレンド関数
クラス側でfriendを付けて宣言する。
フレンド関数はそのクラスのあらゆるメンバを利用できる。(その関数はクラスのメンバ関数ではないことに注意。
フレンド関数は複数のクラスで定義可能。
2つの別のクラスを比較したいとき、
二項演算子の第一引数は同じクラスのオブジェクトに限定されるが、friend指定すればその制限が取れ、
自由な型を指定できる。
インライン
インラインなんていnらiん。速度を気にする時だけ使えばよいと思う。
静的メンバ関数:
静的メンバ関数の場合も、性的メンバ変数と同じく、インスタンスを生成する前から存在します。 そのため、インスタンスを生成しなくても、呼び出すことが可能。
これが原因で、静的メンバ関数からは、静的でないメンバ変数にアクセスすることができません。
関数の仮引数を配列形式で書いていたら,それはポインタ変数となります.
aは,「3個のintの配列」のポインタで,a = b;としてよい.
a[0]は,3個のintの配列
a[0][0]は,1個のintの値
switch文 caseのあとに変数宣言をしたい場合には、{}をいれるとうまくいく。
二次元配列を扱う場合,二種類の方法があります。 (i) int (*ptr_array)[3]; /* 宣言 */ ptr_array = malloc((sizeof(int) * 3) * 2); /* 確保 */ これは,int array[2][3];と同等のことを,動的に確保した場合です。 最高次以外の配列の数が,コンパイル時に既知である必要があります。 (ii) int i; int **ptr_array; /* 宣言 */ /* 確保 */ ptr_array = malloc(sizeof(int *) * 2); for (i = 0; i < 2; ++i) ptr_array[i] = malloc(sizeof(int) * 3); C++では int data=4; char** ppA; ppA = new char*[data]; for(int i=0;i ppA; } delete ppA; これは,たいちうさんの書かれた方法で, 動的に2次元配列を作成するときに一般的に使われている方法です。 コンパイル時に配列の数が決定している必要はありません。 ptr_array[0]とptr_array[1]で含まれる配列の数を変えることもできます。 関数は,単一の方法で両方を受け取ることはできません。 void func (int array[][3]); または void func (int (*array)[3]); であれば,(i)またはarray[2][3]を渡せます。 一次元目の要素数はどの様にして関数に知らせれば良いか? 1. 要素数を渡すのではなく、配列の最後にそれと分かるデータを入れる 2. 配列の要素数を別の引数で指定する 3. 配列の要素数をマクロ定義する void func (int **array); であれば,(ii)のみを渡せます。 関数内でのみ使うのであれば,使いやすい方を使えばいいですが, 他の関数に渡す場合には,その関数に合わせてメモリを確保する必要があります。
もし2次元配列を以下の関数に渡すと int array[NROWS][NCOLUMNS]; f(array); 関数の宣言は以下のどちらかでないといけない。 f(int a[][NCOLUMNS]) { ... } あるいは f(int (*ap)[NCOLUMNS]) /* apは配列へのポインター */ { ... } char str[6] = "zedse"; char foo[6] = "abcde"; char (*p)[6]; p = (char (*)[6])malloc((sizeof(*p) * 6) * 2); p=&foo; ++p = &str;
参照 初期化が必要 さすものを変えられない 添え字はつけられないし、数字を足して先の要素をさすようにしたりすることもできない。 => 絶対に何かをさしているので、初期化忘れバグが起こらない 指しているものを変えることができないので、間違いが起こらない。 配列アクセスができないので、間違いが起こらない
constについて const で定義された変数は、定数なので、値を変更できませんが、 最初の定義(初期化時)のときにだけは、値を設定することができる const int* const m = &iA; 前半のconstはポインタの指す変数が定数であることを示す。 後半のconstはポインタ自体が定数(変更できない)ことを示す。 クラスでの挙動 自前で定義するクラスのオブジェクトを const にしようとする場合 メンバ関数の定義に const キーワードをつけて、 コンパイラに「この関数はメンバ変数を変更しない」ということを知らせるようにします。 ->メンバ変数を変更しないとはっきりわかっている関数を作ったとき (Get〜()関数など)はconst指定するように心がける。 クラスが適切に const 付のメソッドを提供していないと, そのクラスを利用する側も連鎖的に const を使用できない const std::string& get_name() const { return name_; } //戻り値は初期化するとき以外は変更できないというのが、一つ目のconstの意味。 const string n_name = get_name() //としか書けない。 void func(const int a) { Show(a); //Show(a)がconstメンバになってなきゃいけない。(aの値を変えるかどうか不明なので) } void func(const string* str) { str->????; //????の部分の関数はconstメンバ関数でなければならない。 } constメンバ関数は:メンバ変数がポインタの場合「定数ポインタ」でなければならない。 -->定数ポインタがメンバ変数のアドレスを保持していたら変更できる! void Map::drawCell(int x, int y, int size) const{ //mImageがポインタであれば、drawがconstメンバ関数でなくても問題ない。(constでないメンバ変数にもアクセスできる。) mImage->draw(size*i,0,x*size,y*size,size,size); //mImageがポインタであれば、drawがconstでなくても問題ない。 }
クラステンプレートの設定例 template<class T> class Array2D { private: T* mArray; int mSize0; int mSize1; public: Array2D(); //: mArray(0) ~Array2D(); void setSize(int size0, int size1); //関数呼び出し演算子を再定義。 T& operator()(int index0, int index1); const T& operator()(int index0, int index1) const; //const修飾子のArray2Dインスタンス対策 }; template<class T> Array2D<T>::Array2D() : mArray(0) { } template<class T> Array2D<T>::~Array2D() { delete[] mArray; mArray = 0; } //Array2D<T>の<T>を書くのを忘れずに!! template<class T> void Array2D<T>::setSize(int size0, int size1) { if(mArray) { delete[] mArray; mArray = 0; } mSize0 = size0; mSize1 = size1; mArray = new T[size0 * size1]; } template<class T> T& Array2D<T>::operator()(int index0, int index1) { return mArray[index1 * mSize0 + index0]; } template<class T> const T& Array2D<T>::operator()(int index0, int index1) const { return mArray[index1 * mSize0 + index0]; } 列挙型はクラスのように宣言と定義を分けることができない。
「実体」を持つ場合、クラスの「定義」が必要になる。一方、「ポインタ」を持つ場合は、クラスの「宣言」だけでいい。 また、関数の引数や戻り値に使う場合も宣言だけでよい。 名前なし名前空間 namespace{ int foo; } なら、 namespace a4823678430 { int foo; } using namespace a4823678430; というのと同じ。 効果:そのcpp以外では使わないとわかっているようなクラス、関数を他から(どういう方法であれ)絶対に見えない形で 定義することができる。 privateに宣言してもいいが、名前なし名前空間を設定することで以下のメリットがある。 (1):名前なし名前空間に関数がセットされている場合、 その関数の仕様がしょっちゅう変わるときは(たとえば、引数を加えたいとき) ヘッダを更新する必要があり、ほかのcppでそのヘッダを使用している場合には、そのcppもコンパイルしなおさなければならない。 extern を用いても、他のファイルから無名名前空間にアクセスすることはできない。 いちおう、staticの代わりに名前なし名前空間を使うことが推奨されている。
staticについて
クラス内のstatic
private staticとする場合はシングルトンパターンが思いつくが、
そのほかにメンバ変数にprivate staticを使ったら、生成されたインスタンス経由でしかアクセスできない?
(シングルトンパターンを使う場合、必ず、createとdestroyは一回だけしか呼べないことに注意すること
(いつの間にか呼んでいて間違いに気づかない場合がある)
テンプレートについて テンプレートクラスの関数を使う場合は、その関数の中身が見えていないといけない つまり、cppの中に中身を書くと、ほかのcppから見ることができなくなってしまうので、 ヘッダファイルの中に書かなければならない。 つまり、宣言を~~.hに書いて、内容を~~.cppに書くとlinkエラーになる。 (コンパイルは通ってしまうが、mainにテンプレートで作成された関数を置けなくなる)<−−−はまったので注意。
ファイル関係について テキストデータにおいては実は文字コード&制御コードの割り振りが一通りではないため、 パソコンやその他のコンピュータにデータを移す場合にコードの変換が行われる必要があるのです。 非テキストデータにおいては勝手な変換を行われると困るわけで、無変換に転送される 非テキストデータのことをバイナリデータと呼ぶ
VC++のことについて
プロジェクト作成 プロジェクト内部にディレクトリが構築されているとき プロジェクト->プロパティ->VC++[全般]->追加のインクルードディレクトリでパスを追加。手動で こうすることにより、cppの場所により (内部にディレクトリがないばあいは、vc++が自動的にcppと同じディレクトリを 自動でインクルードパスに含めてくれていたので、includeファイルを開けません、ということにはならない。) ソリューションの中にプロジェクトが複数あるとき ビルドするときはプロジェクト右クリック->スタートアッププロジェクトに設定でビルドしたいプロジェクトが太字になる 原因がよくわからないが、フォルダわけしたいときは、エキスプローラから直にファイルを移動するのではなく、 ソリューションエキスプローラを利用してファイルを移動したほうが、 projファイルの内容がおかしくならないので、そっちのほうがよさそう。 やりかたとしては、ソリューションエキスプローラのアイコン「すべてのファイルを表示」をOnの状態にして、ファイルを作成すればよい。
win32
//説明 //アプリケーション(.exe)実行でまず、呼び出される //2 int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) /* HINSTANCE hInst:実行中のアプリケーションを識別するための整数値を受け取るもの。(もっとも、一意に決められるものではない) HINSTANCE hPrevInst: 昔の名残 LPSTR lpCmdLine:コマンドライン int nCmdShow:メインウィンドウの最初の表示状態を表す値がここに格納される。 */ { //2−1 WNDCLASS wndcls; //ウインドウの初期状態、属性を登録する HWND hWnd; //ウインドウハンドル(ウィンドウを操作するにはウィンドウハンドルが 必要)整数値。識別するため。 MSG msg; //クリックとか、ユーザーがアクションを起こした時に必要になってくるもの。構造体で、 typedef struct tagMSG { HWND hwnd; UINT message; //メッセージ(たとえば、WM_QUIT,WM_PAINTなど) WPARAM wParam; //メッセージに属するパラメタ LPARAM lParam; //同じく。 DWORD time; //起きた時間 POINT pt; //起こった位置 } MSG , * PMSG; //という形になっている。 //2−2 //ウインドウクラスの登録 //2−3 //メイン・ウィンドウの作成 作成するだけで、本来ウィンドウを表示するための関数ではない。 WS_VISIBLEとかあるけど、それはまた別箇と //考える。 //2−4 //引数にしたがって,ウィンドウの初期表示状態を指定する //ShowWindowはnCmdShowがWinMainの第4引数と対応している。 //UpdateWindowはウィンドウの更新をする関数。起動時はすぐに更新する必要があるので、明示的に呼び出している。 //WM_PAINTメッセージをメッセージキューに送らず、直接送る。 ShowWindow( hWnd, nCmdShow); UpdateWindow( hWnd); /***********WM_QUITメッセージを受け取った場合以外、ループが延々と続く。***********/ //2−5 // メッセージ・ループ //GetMessageでメッセージキューからメッセージを取出し、msgにメッセージ情報が格納される。 //WM_QUIT以外のメッセージをうけとったときは1,WM_QUITのメッセージを受け取ったときは0となる。 /*補足*/ //GetMessageとPeekMessageの違いはメッセージキューが空の時ちがう。 //GetMessageはメッセージキューが空の時でも待機する。 //(よって、WM_QUITのメッセージを受け取らない限りはこのループは永久に続く!) //PeekMessageはメッセージキューが空の時に待機せず、while文を抜け出してしまう。 //PeekMessageはゲームプログラミング(アクション、シューティングなどの動きが大切なアプリケーション)に向いているといえる。 while( GetMessage( &msg, 0, 0, 0) ){ TranslateMessage(&msg); // DispatchMessage( &msg); } //PeekMessageを使いたい場合は、次のようにコードを書くとよい。 while (1) { //PM_NOREMOVEを指定すれば、キューからメッセージを取り出すことなく //メッセージの内容を見ることができる。 if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE) ) { if(msg.message) break; //WM_QUITのときだけwhile文を抜けて、アプリケーションを終了する。 DispatchMessage(&msg); } else { //アイドル時の処理を行う。 } } //TranslateMessage関数はキーボードのメッセージを文字メッセージに変換する関数。 //DispatchMessage関数はウインドウプロシージャーに送る。 //このばあいは、ウィンドウ・クラスのlpfnWndProcにWndProc関数(メッセージ処理関数)を登録したから、 //その関数に処理が移る。 /*補足*/ //以上はユーザーがクリックしたなど、システムでの動作により、messageが送られる仕組みであるが、 //アプリケーション自体がメッセージを送るようにしたいときはどうすればよいだろうか? //たとえば、ユーザーが右クリック->閉じるメッセージとか、最小化するメッセージを送りたい 時 //ユーザー名義のメッセージ(たとえば、この時だけファイルを操作していいですよというメッセージ)を送りたい 時など //そのときはPostMessageや、SendMessage関数を使用する。 //違いは、SendMessage関数はメッセージを直接(メッセージキューに追加せずに)送って、処理するのを見届ける関数。 //リアルタイムで操作を反映したいときに使うとよい。 //PostMessage関数はメッセージをメッセージキューに送るだけの関数。送るだけなので、そのメッセージが処理しないうちに、 //PostMessage関数が終わっていることがある(というかそのほうが多い) //Windowプログラムの仕組み(キューで受け取る)を考慮すれば、PostMessage APIを使うほうが望ましい。 //1 //1-1 //uMsg(メッセージ定数)を受け取って、それぞれの場合に処理される。 //1−2 case WM_DESTROY://右上の×ボタンが押されたときとかにこのメッセージがuMsgに格納される。 PostQuitMessage( 0); //WM_QUITメッセージをメッセージキューに追加する関数。引数にWM_QUITに通知したいデータを格納 return 0; } //1-3 // 処理しない(大部分の)メッセージはシステムに任せる return DefWindowProc( hWnd, uMsg, wParam, lParam); /***********WM_QUITメッセージを受け取った場合以外ループが延々と続く。***********/ //2-6 // WM_QUITメッセージのときは2-5のループを抜けるから、wParamをプログラムの終了コードにする。 return msg.wParam; //SendMessageのメッセージ送信に関連して… //一般的にメッセージはメッセージキューに送られるが、例外がいくつかあって、 //たとえば、UpdateWindow( hWnd);によるWM_PAINTメッセージの送信 //WM_ACTIVATE,WM_SETFOCUS,WM_SETCURSORメッセージ //SendMessage関数による、好きなメッセージの送信 //などがあげられる。
#include <windows.h> // ウィンドウ・クラス名 #define MYWNDCLSNAME "MyWindowClass" //1 LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { //1−1 switch( uMsg){ //1−2 case WM_DESTROY: PostQuitMessage( 0); return 0; } // 処理しないメッセージはシステムに任せる①−3 return DefWindowProc( hWnd, uMsg, wParam, lParam); } //2 int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { //2−1 WNDCLASS wndcls; HWND hWnd; MSG msg; // ウィンドウ・クラスの登録 2−2 ZeroMemory( &wndcls, sizeof(wndcls) ); wndcls.lpfnWndProc = WndProc; wndcls.hInstance = hInst; wndcls.hIcon = LoadIcon( 0, IDI_APPLICATION); wndcls.hCursor = LoadCursor( 0, IDC_ARROW); wndcls.hbrBackground = (HBRUSH)COLOR_BACKGROUND; //wndcls.hbrBackground = (BBRUSH)GetStockObject(WHITE_BRUSH);と書くことも。 wndcls.lpszClassName = MYWNDCLSNAME; //#define MYWNDCLSNAME "MyWindowClass" if( RegisterClass( &wndcls) == 0) return -1; // メイン・ウィンドウの作成 2−3 hWnd = CreateWindow( MYWNDCLSNAME, "My Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInst, NULL); if( hWnd == 0) return -2; // 引数にしたがって,ウィンドウの初期表示状態を指定する 2−4 ShowWindow( hWnd, nCmdShow); UpdateWindow( hWnd); // メッセージ・ループ 2−5 while( GetMessage( &msg, 0, 0, 0) ){ TranslateMessage(&msg); DispatchMessage( &msg); } // WM_QUITメッセージのwParamをプログラムの終了コードにする 2−6 return msg.wParam; }
stdafx.hは windows.h stdlib.h malloc.h memory.h tchar.h という5つのヘッダーファイルがインクルードされている。
本
最近、絵を描くのにはまっているので。
- 作者: ジャック・ハム,島田照代
- 出版社/メーカー: 嶋田出版
- 発売日: 1987/07/01
- メディア: 単行本
- 購入: 55人 クリック: 209回
- この商品を含むブログ (23件) を見る
- 作者: 染森健一,ビーコム,面出和子
- 出版社/メーカー: オーム社
- 発売日: 2009/06/01
- メディア: 単行本
- 購入: 6人 クリック: 15回
- この商品を含むブログ (2件) を見る
初めて学ぶ遠近法
- 作者: フィリップ・W・メッツガー,森屋利夫
- 出版社/メーカー: エムディエヌコーポレーション
- 発売日: 2012/04/20
- メディア: 単行本
- 購入: 1人 クリック: 2回
- この商品を含むブログを見る
- 作者: 伊原達矢,角丸つぶら
- 出版社/メーカー: ホビージャパン
- 発売日: 2010/12/24
- メディア: 大型本
- 購入: 24人 クリック: 203回
- この商品を含むブログ (7件) を見る
drawing on the right side of the brain 道具
おもしろくていい本なんだけど、
道具揃えんのがめんどい
ええい、ままよ。
紙:画用紙が一番いいが、普通の紙でも全く問題ない。
ペン:黒に塗りつぶすのに、4Bの"Graphite stick"がいるらしいが、4Bの鉛筆で問題ないだろう。(ぬりつぶすだけなので。)
Graphite stickとかいちいち買うのめんどくさいし、アマゾンで買おうとすると送料が大変なことになった。
non-permanent black(ホワイトボードなんかで使用するペン(水性) or サインペン)
ホワイトボードペンとそれ用の消しゴムを拝借
permanent black(油性ペン)
シャーペン:なんでもよい
消しゴム:なんでもよい。
Masking tape:viewfindersに対する仮止め用のテープとしてしか使わない。医療用のテープでいいんじゃないか??普通のテープでも粘着性さえ気にしなければかわらない。
clip:備品から拝借(笑)
drawing board:いるのか、これ?いまのところは机がざらざらしていないので、用意しないでおこう。
picture plane: 8 inch * 10 inch のガラス製のボートか、プラスチック製のやつかって指定されているけど、
透明の下敷きで全く問題ないんではないんでしょうか
viewfinders:この本には、黒と書かれているが、別に必然的な理由はないようだ。よって、アマゾンの段ボールにしよー
a small mirror:100均で買えばよし。
第6章時点でgraphite stickとかがいると書いてあったので、p13をもう一度見返し。
Keys to Drawingに食指がわいてきた。