strtokで分割した文字列のポインタへの格納について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ゴンマサ

strtokで分割した文字列のポインタへの格納について

#1

投稿記事 by ゴンマサ » 10年前

質問を閲覧頂ありがとうございます。
前回の質問では大変お世話になりました。
今回も宜しくお願い致します。

 以下のコードでは、テキストファイルにマップのサイズ、マップチップ配列、
そして移動先のマップの書かれたテキストファイル名を記述し、
特定のマップチップに接触した場合、あらかじめポインタに設定しておいた、
移動先のマップの書かれたテキストファイル名を呼び出し、
そのマップデータを描画するというものです。
使用するテキストファイルの内容は一番下に記載させていただきました。


ビルドは通りますが、デバッグ中に移動するためのチップに接触すると
致命的なエラー、またはハンドルされていない例外が発生してしまいます。

原因としては、80行目のstrtokで、移動先のマップが書かれたテキストファイル名を
読み込めていないのではないかと思います。
また、293行目で文字列を表示させようとすると文字化けが起きてしまうため、
そもそものポインタの扱いが間違っているのかもしれません。


そこで質問なのですが、ポインタで文字列を扱う場合、配列を使用すると思うのですが、
strtokで分割したトークンをさらに1文字ずつに分けて、配列に格納していくことは可能なのでしょうか。
それともそこまでややこしいことをする必要はなく、単にポインタの扱いを間違っているだけなのでしょうか。

また文字化けの原因についてもご教授いただけたらと思います。
宜しくお願い致します。


コード:


#include "DxLib.h"
#include "stdio.h"
#include "string.h"
#include "stdlib.h"

//ウインドウの横と縦の大きさ
#define GAME_WINDOW_WIDTH 640//32*20
#define GAME_WINDOW_HEIGHT 480//32*15
#define MAPCHIP_SIZE 32//マップチップサイズ
#define LINE_1 256//動的擬似に次元配列の実験用定数

int mapSizeY,mapSizeX;//読み取ったマップのサイズ
int make_sure_of_memori;//メモリ確保の可否

//メモリ確保のための変数
int **MapData;//ダブルポインタの宣言
int i,j;//LoadMapFile内で使用、forのカウント用、メモリの確保用
int l,k;//Mapchipdraw内で使用、forのカウント用
int pox,poy;//配列を代入するときのカウント用

//マップデータ読み込み許可の有無
int load;//0:on,1:off

//マップデータ変更許可の有無
int MapChengAct;//0:on,1:off

int x,y;//マップチップ描画の際のカウント用
int tate,yoko;//プレイヤーの座標
int Direction;//移動ボタンの方向別適用動作
int MFC;//メモリ開放の際のカウント用

//どの出口から出たかを決める
//これによりどのマップへ移動するか(どのマップを読み込むか)を決める
int InChip;

//マップチップ描画時のプレイヤー座標の再設定の可否
int PlayerPositionFirstSetting;//0:on,1:off

//各出口の行き先を格納する変数の実体の宣言
//マップデータの読み込みと割り当て時に使用
char *P_ExitChip[1];//ポインタ。[1]を追加

//マップデータを入れる場所
char *P_DataFilePointer[1];//ポインタ。[1]を追加

char *SCRIPT_FILENAME;

//****************************関数一覧*********************
//プレイヤーの描画
void PlayerDraw()
{
	DrawBox(yoko,
			tate,
			yoko+MAPCHIP_SIZE,
			tate+MAPCHIP_SIZE,
			GetColor(100,100,100),
					 TRUE);
}

//ファイルの読み込み、振り分け、メモリの確保
void ReadMapDataFile( char* Filename)
{
	char line[LINE_1];
	poy = 0;//DATAの行数
	int make_sure_of_memori;//メモリの確保の可否
	make_sure_of_memori= 0;
	FILE*mapFile;
	mapFile = fopen(Filename,"r");
	while (fgets(line,LINE_1,mapFile))
	{
		char*words =strtok(line,",");
		if(strcmp("MAP_SIZE",words) == 0 )//マップサイズの読み込み
		{
			mapSizeY = atoi(strtok(NULL,","));
			mapSizeX = atoi(strtok(NULL,","));
		}
		else if(strcmp("ExitChip",words) == 0 )//出入り口の行き先
		{
			P_ExitChip[0] = strtok(NULL,",");//[0]を追加
		}
		else if(strcmp("DATA",words) == 0 )//マップチップ配列の読み込み
		{
			if(make_sure_of_memori == 0)
			{
				//y軸のメモリの確保
				MapData = new int *[mapSizeY];

				//x軸のメモリの確保
				for(i = 0; i < mapSizeY; i++)
				{
					MapData[i] = new int[mapSizeX];

					//値の設定
					for (j = 0;j < mapSizeX; j++)
					{
						MapData[i][j] = 0;
					}
				}
				 //二回目以降はメモリの確保を繰り返さない
				 make_sure_of_memori = 1;
			}
			//x軸に列の内容をstrtokで読み込ませる
			for(pox = 0; pox <  mapSizeX; pox++)
			{
				MapData[poy][pox] = atoi(strtok(NULL,","));
			}
			poy++;
		}
	}
	//ファイルを閉じる
	fclose(mapFile);
}

//プレイヤーの移動・上下のみ
void Player_Act()
{
	int idou;//プレイヤーの移動距離は1
	idou = 1;xn--u9ja4nvhmc2b4gv462a8g1a8n0b2yqgr8cwrya
	int idou_count;
	int P_Idou_Stetus;

	Direction = 0;//プレイヤーを静止状態にする

	if(CheckHitKey( KEY_INPUT_UP ) == 1 )//上ボタン
	{
		Direction = 3;
		P_Idou_Stetus = 8;//歩数
	}
	else if(CheckHitKey( KEY_INPUT_DOWN ) == 1 )//下ボタン
	{
	    Direction = 4;
		P_Idou_Stetus = 8;//歩数
	}

//////////////////////////////////////////////////////////////
	//上ボタンが押されたら
	if( Direction == 3 )//if( CheckHitKey( KEY_INPUT_UP ) == 1 )
	{
		for(idou_count = 0;idou_count<P_Idou_Stetus;idou_count++)
		{
			//ここから移動チップに対する記述
			//上は北、チップは4
			if(MapData[(tate-idou)/32][yoko/32] == 4 ||MapData[(tate-idou)/32][(yoko + 31)/32] == 4 )
			{
				//マップ移動先に北側のマップデータを設定する
				//どの出口から出たか
				InChip = 1;
				
				//マップ変更関数を許可
				MapChengAct = 0;//0:on,1:off

				//マップ移動後の初期座標の設定の許可
				PlayerPositionFirstSetting= 0;//0:on,1:off

			}
			//マップチップ0以外の場合
			else if(MapData[(tate - idou)/32][yoko/32] != 0 && MapData[(tate - idou)/32][(yoko + 31)/32] != 0)
			{
				tate -= idou;
			}

		}
	}
////////////////////////////////////////////////////////
	////下ボタンが押されたら
	if( Direction == 4 )//if( CheckHitKey( KEY_INPUT_DOWN ) == 1 )
	{
		for(idou_count = 0;idou_count<P_Idou_Stetus;idou_count++)
		{
			//ここから移動チップに対する記述
			//下は南、チップ5
			if(MapData[(tate+31+idou)/32][yoko/32] == 5 ||MapData[(tate+31+idou)/32][(yoko + 31)/32] == 5 )
			{					
				
				InChip = 2;//どこの出口から出たか

				//マップ変更の許可
				MapChengAct = 0;//0:on,1:off

				//マップ移動後の初期座標の設定の許可
				PlayerPositionFirstSetting= 0;//0:on,1:off

			}
			//////////////////////////////////////////////////
			 //進む先の座標が0、または2ではないか && となりの座標にかぶってないか
			else if(MapData[(tate + 31 + idou)/32][yoko/32] !=0 && MapData[(tate + 31 + idou)/32][(yoko + 31)/32] != 0)
			{
				tate += idou;   
			}			
		}
	}
///////////////////////////////////////////////////////////		
}//

//マップチップの描画
void MapChipDraw()
{
for(y = 0; y < mapSizeY; y++)
 {
	 for(x = 0; x < mapSizeX;x++)
	 {
		 if(MapData[y][x]==0)
		 {
			 DrawBox(x*MAPCHIP_SIZE,
                         y*MAPCHIP_SIZE,
                         x*MAPCHIP_SIZE+MAPCHIP_SIZE,
                         y*MAPCHIP_SIZE+MAPCHIP_SIZE,
                         GetColor(255,255,255),
                         TRUE);
		 }
		 //マップ移動時のプレイヤーの初期座標設定
		 //ここでのマップごとの初期座標(出口)の設定はデータを読み込んだ初回のみとする
		 if(PlayerPositionFirstSetting == 0)
		 {
			 //どこから入ったか、そしてどこから出るか
			if( InChip == 2 && MapData[y][x]==5)//南口、北マップからの移動、移動先は北口
			{
				tate = y*MAPCHIP_SIZE-MAPCHIP_SIZE;
				yoko = x*MAPCHIP_SIZE;
			}
			//InChip == 2(北口から)
			else if( InChip == 1 && MapData[y][x]==4)//北口、南マップからの移動、出口は南口
			{
				tate = y*MAPCHIP_SIZE+MAPCHIP_SIZE;
				yoko = x*MAPCHIP_SIZE;
			}
			//スイッチをoffにする
			PlayerPositionFirstSetting = 1;
		 }
	 }
 }
}

//確保したメモリの開放
void MapMemorieClear()
{
	for(MFC=0;MFC<mapSizeY; MFC++)
	{
		delete[]MapData[MFC];
	}
	delete[]MapData;
}
		

//メイン
int WINAPI WinMain( HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
	
//**********************セッティング***********************
	ChangeWindowMode( TRUE );//ウィンドウモードで起動
	//画面の大きさは800 * 600
    SetGraphMode( GAME_WINDOW_WIDTH, GAME_WINDOW_HEIGHT, 16 ) ;
	//DxLib初期化
    if( DxLib_Init() == -1 )
    {
		return -1;
	}
	SetDrawScreen( DX_SCREEN_BACK );
//*********************************************************

//**********************初期設定***************************
	tate = 64;
	yoko = 64;
	load = 0;//0:on,1:off
	MapChengAct=1;//0:on,1:off
	Direction = 0;

	SCRIPT_FILENAME = "map.txt";

	P_DataFilePointer[0] = SCRIPT_FILENAME;//アドレスの格納、[0]を追加

//*********************************************************



	while(CheckHitKey(KEY_INPUT_ESCAPE) == 0)
	{
		ClearDrawScreen();
		//1-******************マップデータの読み込みと設定******************
		if(load == 0)
		{
			ReadMapDataFile(P_DataFilePointer[0]);//[0]を追加
			load = 1;
		}
		//2-*****************データ内容の描画*********************
			//マップチップの描画
			MapChipDraw();//マップ変更時のプレイヤーの初期座標設定
			Player_Act();//プレイヤーの移動、および座標計算
			PlayerDraw();//プレイヤーの描画

			DrawFormatString(0,80,GetColor(255,100,255),"Now%s",P_DataFilePointer[0]);
			DrawFormatString(0,100,GetColor(255,150,255),"Next%s",P_ExitChip[0]);

			//P_ExitChipが文字化け

		//3-*****************読み込むマップデータの差し替え******************
		if(MapChengAct==0)
		{
			MapMemorieClear();//メモリのクリア
			*P_DataFilePointer[0] = *P_ExitChip[0];//訂正した
			/*
			致命的なエラーが検出される
			*/

			//strcpy(P_DataFilePointer[0],P_ExitChip[0]);
			/*
			こちらのstrcpyを使うとハンドルされていない例外が発生する
			*/
			MapChengAct=1;//差し替えスイッチのオフ
			load = 0;//マップデータの読み込みを許可
		}
	    ScreenFlip();
	    WaitTimer(20);
	}
	DxLib_End();
	return 0;
}

使用する2つのマップテキストファイルの内容

***********map.txt***********

MAP_SIZE,5,5,

ExitChip,map2,

DATA,0,0,4,0,0,
DATA,0,1,1,1,0,
DATA,0,1,1,1,0,
DATA,0,1,1,1,0,
DATA,0,0,0,0,0,


***********map2.txt**********

MAP_SIZE,10,10,

ExitChip,map,

DATA,0,0,0,0,0, 0,0,0,0,0,
DATA,0,1,1,1,1, 1,1,1,1,0,
DATA,0,1,1,1,1, 1,1,1,1,0,
DATA,0,1,1,1,1, 1,1,1,1,0,
DATA,0,0,5,0,0, 0,0,0,0,0,
*/

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: strtokで分割した文字列のポインタへの格納について

#2

投稿記事 by みけCAT » 10年前

ゴンマサ さんが書きました:ビルドは通りますが、デバッグ中に移動するためのチップに接触すると
致命的なエラー、またはハンドルされていない例外が発生してしまいます。
P_DataFilePointer[0]には271行目で文字列リテラルへのポインタが格納されており、これが指す内容(文字列リテラル)を書き換えようとするとエラーになる可能性があります。
たとえエラーにならない環境であっても、文字列リテラルを書き換えるべきではありません。
ゴンマサ さんが書きました:原因としては、80行目のstrtokで、移動先のマップが書かれたテキストファイル名を
読み込めていないのではないかと思います。
一旦strtok(またはそれを扱う標準ライブラリ)に記憶させているので隠れていますが、
ここでは静的でないローカル変数lineのデータへのポインタをP_ExitChip[0]に代入しているため、lineを宣言したスコープ(≒ReadMapDataFile関数)を抜けると無効になります。
1. strtokの戻り値を(一時)変数に格納する
2. その文字列の長さ(strlenの戻り値)+1文字(終端文字用)の領域をnewなどで確保し、P_ExitChip[0]に代入する
3. P_ExitChip[0]にstrtokから返された文字列をstrcpyする
という手順で保存すればいいと思います。
もちろん、確保した領域を(例えばReadMapDataFile関数の終わりなどで)無駄に開放してはいけません。
ゴンマサ さんが書きました:また、293行目で文字列を表示させようとすると文字化けが起きてしまうため、
そもそものポインタの扱いが間違っているのかもしれません。
前述の通り、無効なデータを表示しようとしているからでしょう。
ゴンマサ さんが書きました:strtokで分割したトークンをさらに1文字ずつに分けて、配列に格納していくことは可能なのでしょうか。
もちろん可能です。C++はチューリング完全です。(あまり関係なさそう)
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ゴンマサ

Re: strtokで分割した文字列のポインタへの格納について

#3

投稿記事 by ゴンマサ » 10年前

みけCAT様
いつも回答ありがとうございます。返信が遅くなり申し訳ありませんでした。
strtokの扱いにばかり目が行ってしまってました。

なんとかなりそうです。
ありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る