初心者の私にはコードはこう見えます。。。(その2)

きょーちゃん
記事: 11
登録日時: 15年前

初心者の私にはコードはこう見えます。。。(その2)

投稿記事 by きょーちゃん » 14年前

前回の続き。

CODE:

//-----------------------------------------------------------------------------
// マップが開かれているか
//-----------------------------------------------------------------------------
BOOL CFmfMap::IsOpen() const
{
	return m_pLayerAddr != NULL;
}

NULLでなければレイヤーデータへのポインタ(m_pLayerAddr)を返すということか?
constの位置がちょっと不思議な気がする。
よく分からん。パス。

CODE:

//-----------------------------------------------------------------------------
//	マップメモリを開放
//-----------------------------------------------------------------------------
void CFmfMap::Close(void)
{
	if (m_pLayerAddr != NULL)
	{
		delete [] m_pLayerAddr;
		m_pLayerAddr = NULL;
	}
}
delete:
new演算子を使って確保したメモリを解放する演算子。
どうやらnewとセットで使うのが決まりらしい。
なんでそのあとにNULLを入れてるのかとか
深く考えないことにする。

CODE:

//-----------------------------------------------------------------------------
//	指定レイヤの先頭アドレスを得る
//	引数:	レイヤ番号
//	戻り値:	正常終了	= レイヤデータのアドレス
//			エラー		= NULL
//	各レイヤデータは連続したメモリ領域に配置されてるので
//	指定レイヤデータのアドレスを計算で求める。
//-----------------------------------------------------------------------------
void* CFmfMap::GetLayerAddr(BYTE byLayerIndex) const
{
	// メモリチェック、範囲チェック
	if ((m_pLayerAddr == NULL) || (byLayerIndex >= m_fmfHeader.byLayerCount))
		return NULL;

	BYTE bySize = m_fmfHeader.byBitCount / 8;
	return m_pLayerAddr + m_fmfHeader.dwWidth * m_fmfHeader.dwHeight * bySize * byLayerIndex;
}
このあたりから具体的な感じで面白みが増してきますね。
byLayerIndexは、この後出てくるのですがレイヤーの番号です。
m_fmfHeader.byLayerCountはレイヤーの数。

レイヤーデータがきちんと読まれてない、もしくは読み込もうとするレイヤー番号が実際のレイヤー数より大きい場合はエラー。

で、その下。m_fmfHeader.byBitCountはレイヤデータのビットカウントらしい。
ビットカウント…よくわからんが、platinumで新規作成するとき、8ビットと16ビットを選択できる。たぶんあれのことだと思う。
bySizeは8ビットなら1、16ビットなら2になる。
8ビットで作ったデータでは、1つのマップチップ情報を保存するのに1バイト、16ビットなら2バイト必要ってことだろう。
レイヤーデータは連続したメモリ領域に配置されてると見出しに書かれている。

たぶんこんな感じだ。

<レイヤー0>
123   
456
789
<レイヤー1>
a b c
d e f
g h i

という風な配置のマップデータがあったとして、メモリ領域では

123456789abcdefghi

という風に配置してるんだと思う。
ここでの計算は、(byLayerIndex)番のレイヤー情報を読むにはどこから読み始めたらいいの~?ってのを計算しているようだ。
その式が最後の一行。言い換えると、
レイヤー情報の先頭の位置 + マップの横幅 * マップの縦幅 * ビット数 * レイヤー番号 

ビット数ってのがややこしいけど、上の例でレイヤー1の情報の先頭の位置を知りたければ、「a」の情報が入っている場所が分かれば良い。
ぱっと見では10番目。
だけどメモリ上では、「1」の情報を保存するのに8ビット使われている(8ビットで作った場合)。
つまり「a」の情報が入っている場所は10番目ではなく、10*8=80番目ということになる。
ビットでそのまま計算すればいいのに、なぜわざわざバイトに直すのかと思うけれど、
コンピュータは情報の記憶や処理、伝達をバイト単位で行うことが多い、ってことらしい。たぶんそれでだ。

CODE:

//-----------------------------------------------------------------------------
// レイヤ番号と座標を指定して直接データを貰う
// 引数:
// 	byLayerIndex	= レイヤ番号
// 	dwX				= X座標(0~m_fmfHeader.dwWidth - 1)
// 	dwY				= Y座標(0~m_fmfHeader.dwHeight - 1)
// 戻り値:
// 	正常終了	= 座標の値
//	エラー		= -1
//-----------------------------------------------------------------------------
int CFmfMap::GetValue(BYTE byLayerIndex, DWORD dwX, DWORD dwY) const
{
	int nIndex = -1;

	// 範囲チェック
	if (byLayerIndex >= m_fmfHeader.byLayerCount ||
		dwX >= m_fmfHeader.dwWidth ||
		dwY >= m_fmfHeader.dwHeight)
		return nIndex;

	if (m_fmfHeader.byBitCount == 8)
	{
		// 8bit layer
		BYTE* pLayer = (BYTE*)GetLayerAddr(byLayerIndex);
		nIndex = *(pLayer + dwY * m_fmfHeader.dwWidth + dwX);
	}
	else
	{
		// 16bit layer	
		WORD* pLayer = (WORD*)GetLayerAddr(byLayerIndex);
		nIndex = *(pLayer + dwY * m_fmfHeader.dwWidth + dwX);
	}

	return nIndex;
}
ますます盛り上がってきました!
さっきの関数でほしいレイヤーの先頭のアドレスは分かったので、
今度ははそのレイヤー上の、座標(dwX、dwY)のマップのデータを取り出すための関数。

もうエラー処理はいいかな…飛ばします。

8ビットの場合から見てみる。
まず、pLayerにレイヤーの先頭のデータが書かれているアドレスを入れます。
で、ほしい座標のデータはそこから数えて何番目~?ってのを計算してます。
m_fmfHeader.dwWidthはマップの横幅です。
さっきも言ったとおり、マップ情報はずらーっと一列に並んでいるので、
n行目のデータを見るにはマップの横幅×n個分データを進める必要があるってことですね。

16ビットも式が同じで、処理同じじゃ~んって思ったら、データ型がWORDになってる。
WORDとは16ビット符号なし整数の型らしい。
う~ん、いろいろ決まりごとがあるんだろう。

CODE:

//-----------------------------------------------------------------------------
// レイヤ番号と座標を指定してデータをセット
//-----------------------------------------------------------------------------
void CFmfMap::SetValue(BYTE byLayerIndex, DWORD dwX, DWORD dwY, int nValue)
{
	// 範囲チェック
	if (byLayerIndex >= m_fmfHeader.byLayerCount ||
		dwX >= m_fmfHeader.dwWidth ||
		dwY >= m_fmfHeader.dwHeight)
		return;

	if (m_fmfHeader.byBitCount == 8)
	{
		// 8bit layer
		BYTE* pLayer = (BYTE*)GetLayerAddr(byLayerIndex);
		*(pLayer + dwY * m_fmfHeader.dwWidth + dwX) = (BYTE)nValue;
	}
	else
	{
		// 16bit layer	
		WORD* pLayer = (WORD*)GetLayerAddr(byLayerIndex);
		*(pLayer + dwY * m_fmfHeader.dwWidth + dwX) = (WORD)nValue;
	}
}
う~んここちょっと分からん。
さっきとまったく同じ処理をしてnValueに代入している気がするんだけど・・・
データを貰うのとセットするのって何が違うんだ・・・
読み進めたらわかるかな。とりあえずパス。

CODE:

//-----------------------------------------------------------------------------
// マップの横幅を得る
//-----------------------------------------------------------------------------
DWORD CFmfMap::GetMapWidth(void) const
{
	return m_fmfHeader.dwWidth;
}
//-----------------------------------------------------------------------------
// マップの高さを得る
//-----------------------------------------------------------------------------
DWORD CFmfMap::GetMapHeight(void) const
{
	return m_fmfHeader.dwHeight;
}
//-----------------------------------------------------------------------------
// チップの横幅を得る
//-----------------------------------------------------------------------------
BYTE CFmfMap::GetChipWidth(void) const
{
	return m_fmfHeader.byChipWidth;
}
//-----------------------------------------------------------------------------
// チップの高さを得る
//-----------------------------------------------------------------------------
BYTE CFmfMap::GetChipHeight(void) const
{
	return m_fmfHeader.byChipHeight;
}
//-----------------------------------------------------------------------------
// レイヤー数を得る
//-----------------------------------------------------------------------------
BYTE CFmfMap::GetLayerCount(void) const
{
	return m_fmfHeader.byLayerCount;
}
//-----------------------------------------------------------------------------
// レイヤーデータのビットカウントを得る
//-----------------------------------------------------------------------------
BYTE CFmfMap::GetLayerBitCount(void) const
{
	return m_fmfHeader.byBitCount;
}
なんやかんやいろいろ。
なんでいちいち関数にするのか。代入でいいんじゃないのと思ってしまうのはきっと素人考え。
あと順序もよくわからん。こいつら最初に書けばよさそうなのに…そこは好みの問題なのか?


って、そんなことを言ってる間にcppファイルは終わってしまった。
あれ??SetValue関数何に使うの???


なるほど分からん。
よし、次からマップ描画のためのコード書きに挑戦だ♪

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前

Re: 初心者の私にはコードはこう見えます。。。(その2)

投稿記事 by softya(ソフト屋) » 14年前

おっハイペースですね!
今からⅠ回目から解析結果を眺めてみます。
気になるところがあったらコメントしますね。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前

Re: 初心者の私にはコードはこう見えます。。。(その2)

投稿記事 by softya(ソフト屋) » 14年前

>NULLでなければレイヤーデータへのポインタ(m_pLayerAddr)を返すということか?
>constの位置がちょっと不思議な気がする。
>よく分からん。パス。

constメンバ関数ですね。
やがて理解できるかと。

>new演算子を使って確保したメモリを解放する演算子。
>どうやらnewとセットで使うのが決まりらしい。
>なんでそのあとにNULLを入れてるのかとか
>深く考えないことにする。

NULLでメモリ確保済みか未確保か判断しているのでNULLにしないとまだ確保済みと判断されてしまいます。

>123456789abcdefghi

それで合ってますよ。

>コンピュータは情報の記憶や処理、伝達をバイト単位で行うことが多い、ってことらしい。たぶんそれでだ。
まぁ、配列がバイト単位ですからね。

>16ビットも式が同じで、処理同じじゃ~んって思ったら、データ型がWORDになってる。
>WORDとは16ビット符号なし整数の型らしい。う~ん、いろいろ決まりごとがあるんだろう。

WORD型のポインタとBYTE型のポインタの動作が違います。
ポインタ式では型のサイズ分自動的に式に計算結果が補正されますので、BYTE型だと1バイトづつ変化しますがWORD型だと2バイトづつ変化しますよ。

>う~んここちょっと分からん。
>さっきとまったく同じ処理をしてnValueに代入している気がするんだけど・・・
>データを貰うのとセットするのって何が違うんだ・・・
>読み進めたらわかるかな。とりあえずパス。

GetValueはマップデータの取得、SetValueはマップデータの書き換え処理用です。

>なんでいちいち関数にするのか。代入でいいんじゃないのと思ってしまうのはきっと素人考え。

クラスのカプセル化ですね。

>あと順序もよくわからん。こいつら最初に書けばよさそうなのに…そこは好みの問題なのか?

これは好みの問題かな。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前

Re: 初心者の私にはコードはこう見えます。。。(その2)

投稿記事 by Dixq (管理人) » 14年前

softyaさんの華麗なご返答で十分だと思いますが、補足すると・・

> どうやらnewとセットで使うのが決まりらしい。
> なんでそのあとにNULLを入れてるのかとか深く考えないことにする。

メモリは確保したら解放しないといけないってのはお分かりかと思います。
「私が使うから誰も使わないでね!」って占有する領域を不必要になっても持ち続けるとドンドンリソースが足らなくなりますから、
newで作って、いらなくなったらdeleteで解放します。

そこで、何故NULLを入れるか・・、それはsoftyaさんが仰る通りです。
ポインタがNULLじゃなかったら解放、って処理

if( p != NULL ){
delete p;
}

これ、pの値は変わらないので、何度も重複deleteしてしまうかもしれません。
deleteしたら、変数に自動的にNULL入ってくれればいいのに、と思うかもしれませんが、そうはいかないのです。
ここから、頭がこんがらがるかもしれませんが、流し読み程度にご覧ください。

例えば

int *p;

が指している p に入っているアドレス先のデータは p に入っているアドレスを渡すことで変更可能です。
しかし、p自身の値は、pそのもののアドレスを渡さなければ変更できません。
Cだと

free( p );

で解放しますよね。
解放する時、関数にはpに入っているアドレスを渡しているのであって、p自身のアドレスではないため、p自身は変更できません。
そこで、 p=NULL; やらないとpにNULLが入らないのです。

ポインタは関数に渡すとsizeofで大きさが取れなくなったり、型によって p++; した時に変化するアドレスの移動量が変わったり
混乱するかもしれませんが、今はイミフでも、
「あ、いつか、こんな時問題が生じるって聞いたような・・」
って思えるか思えないかはすごく大きな違いですので、全然イミフでもいいので、色々と聞いておくと、トラブルが起こった時の対応がスムーズになるかもしれません。

難しいでしょうけどファイトです!

ISLe
記事: 2650
登録日時: 15年前

RE: 初心者の私にはコードはこう見えます。。。(その2)

投稿記事 by ISLe » 14年前

きょーちゃん さんが書きました:NULLでなければレイヤーデータへのポインタ(m_pLayerAddr)を返すということか?
!=(不等比較演算子)は両辺の値が等しくないときに1(真)、等しいときに0(偽)と評価されます。

きょーちゃん
記事: 11
登録日時: 15年前

Re: 初心者の私にはコードはこう見えます。。。(その2)

投稿記事 by きょーちゃん » 14年前

>Dixqさん
分かりやすい解説ありがとうございます!
softyaさんのコメ読んで、メモリの開放=NULLじゃないんだな~とはわかったんですけど、
あとになってよくよく考えてみたら、じゃあ開放したアドレスには何が入ってるのか???と不思議に思ってたところです。
やっぱポインタは難しいですね。概念はなんとなく分かるけど、思うように使えない・・・
でもやればやるほど楽しくなってきますね。がんばります^^

>ISLeさん
ご指摘ありがとうございます。
if (m_pLayerAddr != NULL)
return m_pLayerAddr;

ってのを1つの式で書いたのかと勘違いしてました^^;
BOOLだからポインタは返せないですよね・・・