ページ 11

構造体配列

Posted: 2012年8月26日(日) 00:11
by 田中太郎

コード:

//mon_dat.h
#ifndef MON_DEF_H
#define MON_DEF_H

typedef struct
{
	int num;		//効果を呼び出すときのナンバー
	char numb[4];	//図鑑などで表示するときのナンバー
	char name[20];	//名前
	int atr;		//属性
	int cost;		//コスト
	int pow;		//攻撃力
	int life;		//体力
}Mon_t;

extern Mon_t mon[40];

#endif

コード:

//mon_dat.cpp
#include "../h/mon_dat.h"

void mod_dat()
{
	Mon_t mon[40] =
	{	//num, "numb", "name", atr, cost, pow, life
		{ 1, "001", "モンスター1", 1, 1, 1, 1 },
		//略
		{ 40, "040", "モンスター40", 4, 5, 3, 3 }
	};
}

コード:

//sample.cpp
#include "DxLib.h"
#include "../h/sample.h"
#include "../h/mon_dat.h"

void sample_main()
{
	//構造体がきちんと動作するか確認のためのサンプルプログラムです
	DrawFormatString( 280, 280, GetColor( 255 , 255 , 255 ), "%d", mon[0].pow );
}
これで動作すると思ったのですが
1>sample.obj : error LNK2001: 外部シンボル ""struct Mon_t * mon" (?mon@@3PAUMon_t@@A)" は未解決です。
1>C:\Users\core\Desktop\janken\Debug\janken.exe : fatal error LNK1120: 外部参照 1 が未解決です。
とエラーが出てしまいました。
何がいけないのか教えてください。お願いします。

環境:win7 VC++2010 DxLib 構造体は初めて使うので勉強しながらやっています。

Re: 構造体配列

Posted: 2012年8月26日(日) 00:58
by 赤鬼
田中太郎 さんが書きました:

コード:

//mon_dat.h
#ifndef MON_DEF_H
#define MON_DEF_H

typedef struct
{
	int num;		//効果を呼び出すときのナンバー
	char numb[4];	//図鑑などで表示するときのナンバー
	char name[20];	//名前
	int atr;		//属性
	int cost;		//コスト
	int pow;		//攻撃力
	int life;		//体力
}Mon_t;

extern Mon_t mon[40];

#endif

コード:

//mon_dat.cpp
#include "../h/mon_dat.h"

void mod_dat()
{
	Mon_t mon[40] =
	{	//num, "numb", "name", atr, cost, pow, life
		{ 1, "001", "モンスター1", 1, 1, 1, 1 },
		//略
		{ 40, "040", "モンスター40", 4, 5, 3, 3 }
	};
}

コード:

//sample.cpp
#include "DxLib.h"
#include "../h/sample.h"
#include "../h/mon_dat.h"

void sample_main()
{
	//構造体がきちんと動作するか確認のためのサンプルプログラムです
	DrawFormatString( 280, 280, GetColor( 255 , 255 , 255 ), "%d", mon[0].pow );
}
これで動作すると思ったのですが
1>sample.obj : error LNK2001: 外部シンボル ""struct Mon_t * mon" (?mon@@3PAUMon_t@@A)" は未解決です。
1>C:\Users\core\Desktop\janken\Debug\janken.exe : fatal error LNK1120: 外部参照 1 が未解決です。
とエラーが出てしまいました。
何がいけないのか教えてください。お願いします。

環境:win7 VC++2010 DxLib 構造体は初めて使うので勉強しながらやっています。
おそらく

コード:

//mon_dat.cpp
#include "../h/mon_dat.h"
//書くならグローバル領域に定義した方が良いと思います
/*つまりは此処とかですね*/

void mod_dat()
{
    //ローカル領域内で確保されているので関数を抜けると、値は保持されない
	Mon_t mon[40] =
	{	//num, "numb", "name", atr, cost, pow, life
		{ 1, "001", "モンスター1", 1, 1, 1, 1 },
		//略
		{ 40, "040", "モンスター40", 4, 5, 3, 3 }
	};
}
ここでローカル領域で実体を作成しているので関数を抜けると破棄されてしまいます。
保持しておきたいならグローバル空間におくか、static、もしくは動的確保して、ポインタ経由で扱うかのどちらかとなります。
LNK2001は基本、何か書き忘れたと覚えておくと後々楽になると思います。

Re: 構造体配列

Posted: 2012年8月26日(日) 01:01
by softya(ソフト屋)
これは、構造体の問題ではなく変数のグローバル・ローカルスコープの問題です。
mod_dat内にあるのは関数内ローカルスコープです。externされた変数はどこかの1つのcppにグローバルスコープ宣言しなくはいけません。
あとローカルとグローバルの変数は同じ名前で別変数として共存できるので注意してください。

もう一つの問題は、ローカル変数は関数が終わると消える寿命の問題です。
mod_datにあるmon変数の寿命はmod_dat()関数が呼び出されてreturnするまでの寿命となっています。

それと 赤鬼 さんが書いているstaticは変数に使うと関数外に変数を書いてもファイル内にスコープが限定されるのでextern参照時は使ってはいけません。
※ 誤解されそうなので補足させていただきました。

Re: 構造体配列

Posted: 2012年8月26日(日) 01:14
by 赤鬼
補足です。
自分の上の奴と、softyaさんのを見れば答えは分かると思います。
ポインタを学習していないと訳が分からないと思うので、その場合は流し読み程度で。
softya(ソフト屋) さんが書きました: それと 赤鬼 さんが書いているstaticは変数に使うと関数外に変数を書いてもファイル内にスコープが限定されるのでextern参照時は使ってはいけません。
※ 誤解されそうなので補足させていただきました。
補足感謝します、softyaさんの仰るとおりstaticの場合スコープが限定されます。
ですからポインタ経由で扱う必要があります。
この場合externでは参照できません。
変わりにポインタを取得する必要があります。

コード:

//mon_dat.h
#ifndef MON_DEF_H
#define MON_DEF_H

typedef struct
{
	int num;		//効果を呼び出すときのナンバー
	char numb[4];	//図鑑などで表示するときのナンバー
	char name[20];	//名前
	int atr;		//属性
	int cost;		//コスト
	int pow;		//攻撃力
	int life;		//体力
}Mon_t;

//extern Mon_t mon[40];
//変わりにポインタの宣言する必要あり
extern Mont_t* pmon;

#endif

コード:

//mon_dat.cpp
#include "../h/mon_dat.h"
//ポインタを返す関数
Mon_t* mod_dat()
{
	static Mon_t mon[40] =
	{	//num, "numb", "name", atr, cost, pow, life
		{ 1, "001", "モンスター1", 1, 1, 1, 1 },
		//略
		{ 40, "040", "モンスター40", 4, 5, 3, 3 }
	};
	//ポインタを返す
	return &mon[0];//monでもOK
}

コード:

//sample.cpp
#include "DxLib.h"
#include "../h/sample.h"
#include "../h/mon_dat.h"

//mod_datで取得したポインタを受け取るためのポインタ変数
Mon_t* pmon=NULL;

void sample_main()
{
	//構造体がきちんと動作するか確認のためのサンプルプログラムです
	/*pmonのポインタはこの前にどこかで取得しておいて下さい。
	一応これでも期待した動作が見込めると思います。但しソースコードの可読性が下がるのでお勧めはしません。
	そもそもstaticは外部に持ち越す物ではない。*/
	DrawFormatString( 280, 280, GetColor( 255 , 255 , 255 ), "%d",pmon->pow );
}
//ポインタ無しの場合

コード:

//mon_dat.h
#ifndef MON_DEF_H
#define MON_DEF_H

typedef struct
{
	int num;		//効果を呼び出すときのナンバー
	char numb[4];	//図鑑などで表示するときのナンバー
	char name[20];	//名前
	int atr;		//属性
	int cost;		//コスト
	int pow;		//攻撃力
	int life;		//体力
}Mon_t;

extern Mon_t mon[40];

#endif

コード:

//mon_dat.cpp
#include "../h/mon_dat.h"

//グローバル空間に実体+初期化
Mon_t mon[40] =
{	//num, "numb", "name", atr, cost, pow, life
	{ 1, "001", "モンスター1", 1, 1, 1, 1 },
	//略
	{ 40, "040", "モンスター40", 4, 5, 3, 3 }
};

/*
この関数は必要なくなります。
void mod_dat()
{
	Mon_t mon[40] =
	{	//num, "numb", "name", atr, cost, pow, life
		{ 1, "001", "モンスター1", 1, 1, 1, 1 },
		//略
		{ 40, "040", "モンスター40", 4, 5, 3, 3 }
	};
}*/

コード:

//sample.cpp
#include "DxLib.h"
#include "../h/sample.h"
#include "../h/mon_dat.h"

void sample_main()
{
	//構造体がきちんと動作するか確認のためのサンプルプログラムです
	DrawFormatString( 280, 280, GetColor( 255 , 255 , 255 ), "%d", mon[0].pow );
}
どちらにしても、グローバル空間に置かなくてはならないので、構造体そのものを置いても構わないと思います。
個人的にはポインタに慣れておいた方が後々楽になると思うので弄ってみると良いと思います。
ポインタを学習していないなら、何も考えずそのまま、構造体をグローバル空間に置いてしまっても構わないと思います。
後にポインタを学習したときに振り返って、変な人がこんなやり方してたな程度の認識で構いません。

それと、グローバルやローカルと言う言葉が分からなければ少し深く説明さて頂きます。

Re: 構造体配列

Posted: 2012年8月26日(日) 08:10
by へにっくす
赤鬼 さんが書きました:どちらにしても、グローバル空間に置かなくてはならないので、構造体そのものを置いても構わないと思います。
グローバル変数を用意しなくてもできるんでは?

コード:

//mon_dat.h
#ifndef MON_DEF_H
#define MON_DEF_H
 
typedef struct
{
    int num;        //効果を呼び出すときのナンバー
    char numb[4];   //図鑑などで表示するときのナンバー
    char name[20];  //名前
    int atr;        //属性
    int cost;       //コスト
    int pow;        //攻撃力
    int life;       //体力
}Mon_t;

//コメントにする
//extern Mon_t mon[40];
//代わりにプロトタイプ宣言。
Mon_t* mod_dat();
 
#endif

コード:

//mon_dat.cpp
#include "../h/mon_dat.h"
//ポインタを返す関数
Mon_t* mod_dat()
{
    static Mon_t mon[40] =
    {   //num, "numb", "name", atr, cost, pow, life
        { 1, "001", "モンスター1", 1, 1, 1, 1 },
        //略
        { 40, "040", "モンスター40", 4, 5, 3, 3 }
    };
    //ポインタを返す
    return &mon[0];//monでもOK
}

コード:

//sample.cpp
#include "DxLib.h"
#include "../h/sample.h"
#include "../h/mon_dat.h"
void sample_main()
{
    //mod_datで取得したポインタを受け取るためのポインタ変数
    Mon_t* pmon=mod_dat();
    //構造体がきちんと動作するか確認のためのサンプルプログラムです
    DrawFormatString( 280, 280, GetColor( 255 , 255 , 255 ), "%d",pmon->pow );
}
間違ってたらすみません

Re: 構造体配列

Posted: 2012年8月26日(日) 09:58
by 赤鬼
>>へにっくすさん
確かに出来ます。
わざわざ訂正有難うございます。
が、ゲームのように頻繁に必要になるので何度も関数を呼ぶ場合、速度に影響が出てくるかと思われますので前述の方が良いかなと思いました。
と言うよりこの場合staticで関数内に保持する時点で間違ってると思いますが。
外部からのアクセスを考慮する場合グローバル領域に保持か、動的確保かの方が一般的になるとおもいますし(何らかの理由で隠蔽したい場合は別ですが、そんな事普通は無いような)。

Re: 構造体配列

Posted: 2012年8月26日(日) 10:56
by softya(ソフト屋)
赤鬼さん。こういうのはGPUの件でISLeさんにも指摘されていたと思いますがケースバイケースです。
速度的にクリティカルになる場合は、ちょっとひねる必要があるかも知れませんが速度が許されるなら出来るだけ分かりやすくベタな書き方が望ましいです。
なので、へにっくすさんの方法がよりベターかと思います。
ただし、関数内staticが分りやすいかは考慮すべき所ですが間違っていると言うほどではありません。
それと構造体配列のまま戻り値にするよりは、要素だけ返すほうがよりよい設計だと私は思います。

>外部からのアクセスを考慮する場合グローバル領域に保持か、動的確保かの方が一般的になるとおもいますし(何らかの理由で隠蔽したい場合は別ですが、そんな事普通は無いような)。

これも誤解を招くので、
グローバル領域に保持 → staticを付けてファイルスコープで保持
動的確保 → staticを付けたポインタで保持だと思うので動的であるか無いかは関係無さそうです。
じゃないでしょうか?

Re: 構造体配列

Posted: 2012年8月26日(日) 14:52
by 田中太郎
たくさんのご回答ありがとうございます。
みなさんの回答を見ているといくつか疑問が出たので質問させて頂きます。

赤鬼さんが書いたポインタ無しの方が自分としては分かりやすいのですが、へにっくすさんの書いたプログラムの方がベターなのは何故ですか?
ポインタは苦手なのですが、やっぱりポインタを使った方がいいのでしょうか?

それと、みなさん extern Mont_t* pmon; のように 左側のほうにアスタリスクを付けるのでしょうか?
自分は Mont_t がintやcharのような型で、pmonの方を変数だと思っていたのですが間違っているのでしょうか?(int *a; とか char *b みたいな感じです)

softyaさんの、「それと構造体配列のまま戻り値にするよりは、要素だけ返すほうがよりよい設計だと私は思います。」というのは、
return &mon[0]; この部分を return &mon; の方がいいと言うことなのでしょうか?

関数内staticは、本来関数を抜けるとその中のプログラムは保持されないのをstaticを付けることで保持されるようにする、という理解でよろしいのでしょうか?

質問ばかりですみません。

Re: 構造体配列

Posted: 2012年8月26日(日) 15:14
by softya(ソフト屋)
グローバルな変数の公開はミスや安易な考えで内容を書き換るor 書き換られる原因となります。
その場合は厄介なバグの原因となりうるので極力避ける事が望ましいです。

その究極は変数を公開しないことで書き換えはチェックルーチンを経由して問題のある書き換えを阻止します。
値を戻す時は戻り値などでコピーを返すようにして、元の変数のポインタなどは決して返しません。
まぁ、やり過ぎると面倒なのと速度低下があるのでケースバイケースで処理します。

[訂正]比較的良い返し方。1要素のコピーを戻り値で返します。

コード:

Mon_t mod_dat(int index)
{
    static Mon_t mon[40] =
    {   //num, "numb", "name", atr, cost, pow, life
        { 1, "001", "モンスター1", 1, 1, 1, 1 },
        //略
        { 40, "040", "モンスター40", 4, 5, 3, 3 }
    };
    
    //	添字が範囲内かチェックする。
    assert( index >= 0 );
    assert( index < 40 );
    //	構造体の1要素のコピーを返す。
    return mon[index];
}
ポインタは高速化のためと実体の書き換えをするために使うべきものです。

>関数内staticは、本来関数を抜けるとその中のプログラムは保持されないのをstaticを付けることで保持されるようにする、という理解でよろしいのでしょうか?
そのとおりでstaticは変数の寿命を変更します。

>それと、みなさん extern Mont_t* pmon; のように 左側のほうにアスタリスクを付けるのでしょうか?
>自分は Mont_t がintやcharのような型で、pmonの方を変数だと思っていたのですが間違っているのでしょうか?(int *a; とか char *b みたいな感じです)

Mont_t*で1つの型ですからね。少なくともC言語/C++の内部ではそう扱われています。
int *a; だとintポインタ型のaです。

Re: 構造体配列

Posted: 2012年8月26日(日) 16:56
by ISLe
田中太郎 さんが書きました:赤鬼さんが書いたポインタ無しの方が自分としては分かりやすいのですが、へにっくすさんの書いたプログラムの方がベターなのは何故ですか?
保守性の問題もありますが、拡張性が上がるメリットもあります。
将来的に関数が外部ファイルからロードする仕様になっても、関数本体を書き換えるだけで良く、関数の呼び出し側を変更せずに済みます。
softyaさんのおっしゃるように要素毎にアクセスするようにしておくと要素が増えたり減ったりする場合にも柔軟に対応できます。

Re: 構造体配列

Posted: 2012年8月26日(日) 17:18
by 田中太郎
やっぱりグローバルは少ない方がいいんですよね。
みなさんのを参考にしてグローバルでないもので進めていこうと思います。
丁寧に説明して頂きありがとうございます。

Mont_t*で1つの型なんですか。知りませんでした。ありがとうございます。

>ISLeさん
今までグローバルは他のファイルによって書き換わったりするなどで危ないとは思ってましたが、拡張性については全く考えていませんでした。
やっぱり後々変更するのが簡単な方がいいですよね。
ご指摘ありがとうございます。


これで一通りの疑問は解消されたので、解決にさせて頂きます。
回答して頂いたみなさんに感謝致します。また質問するときはよろしくお願いします。
ありがとうございました。

Re: 構造体配列

Posted: 2012年8月26日(日) 17:38
by 赤鬼
softya(ソフト屋) さんが書きました: これも誤解を招くので、
グローバル領域に保持 → staticを付けてファイルスコープで保持
動的確保 → staticを付けたポインタで保持だと思うので動的であるか無いかは関係無さそうです。
じゃないでしょうか?
訂正有難うございます!!
やはりまだまだ勉強しなくてはいけませんね。
設計に付いてもしっかりと勉強する必要がありそうです。