CSV读取类,C++ STL实现

本文介绍了一种CSV文件解析方法,并展示了如何将其应用于游戏配置数据的管理中,包括CSV文件的读取、数据结构设计及自动生成代码的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CSV文件作为一种简单易用的信息存储文件,有着非常广泛的用途,在游戏开发行业的应用尤为普遍,它比普通ini文件记录信息更加方便,具有更加严谨的格式信息,同时又没有数据库存储数据那么复杂。
它的记录形式通常以逗号为字段间分隔符,以回车为每条记录间分割符的简单文本格式,通过Excel打开如下所示:

通过普通通文本编辑器打开,如下图所示:


通过上面的图示,可知csv记录的信息实际上就是一个二维信息集,所以第一步应该是有一个将csv文件转换成二维信息集的类,如下:

#ifndef _CSV_FILE_
#define _CSV_FILE_

#include <fstream>
#include <vector>
#include <string>
#include <iostream>

using namespace std;

typedef vector< string > VecString;
typedef vector< VecString > MatrixString;

class CVSFile
{
public:
	CVSFile();

	//以文件名初始化表格数据
	bool Init(const char* szFileName, char cSpliteToken = ',');

	//以指定分隔符从一行数据中获取关键字列表
	bool Splite(const string& strLine, VecString& vecString);

private:
	// 检测读出的数据是否合法
	bool _CheckDataValid();

protected:
	MatrixString m_content;
	char m_cSpliteToken;
};

#endif

#include "CVSFile.h"


CVSFile::CVSFile()
{
	m_content.clear();
}

bool CVSFile::Init( const char* szFileName, char cSpliteToken)
{
	ifstream inFile(szFileName);

	if (!inFile)
	{
		return false;
	}

	//获取分隔符
	m_cSpliteToken = cSpliteToken;

	// 每次读取一行文本内容直至文件结尾
	string strLineContext;
	while (getline(inFile, strLineContext))
	{
		// 以指定分隔符获取每行信息列表
		VecString vecValue;
		Splite(strLineContext, vecValue);
		m_content.push_back(vecValue);
	}

	// 检验数据是否符合要求
	if (!_CheckDataValid())
	{
		return false;
	}
	
	return true;
}

bool CVSFile::Splite( const string& strLine, VecString& vecString )
{
	int nBegin = 0;
	int nEnd = 0;

	while ((nEnd = strLine.find_first_of(m_cSpliteToken, nBegin)) != string::npos)
	{
		vecString.push_back(strLine.substr(nBegin, nEnd - nBegin));
		nBegin = nEnd + 1;
	}

	if ((nBegin = strLine.find_last_of(m_cSpliteToken, strLine.length() - 1)) != string::npos)
	{
		vecString.push_back(strLine.substr(nBegin + 1, strLine.length() - 1));
	}
	
	return true;
}

bool CVSFile::_CheckDataValid()
{
	int nNumInOneLine = 0;

	MatrixString::const_iterator itRowData = m_content.begin();
	for (; itRowData != m_content.end(); itRowData++)
	{
		const VecString& curVecString = *itRowData;
		nNumInOneLine = curVecString.size();
		if (nNumInOneLine == 0)
		{
			return false;
		}		
	}

	return true;
}

第二步,如何运用这个二维数据集是根据特定的应用需求有关的,有点像数据库中表结构的设计,需要规定一些规则,比如是以行还是以列为视角记录数据,每种数据的名称、类型、说明等。这里的代码是假如数据如前面excel表格中展示的,第一行定义为数据说明,第二行为数据名称,第三行为数据类型。
#ifndef _GAME_CSV_FILE_
#define _GAME_CSV_FILE_

#include <vector>
#include <string>

#include "CVSFile.h"

using namespace std;

enum KEY_TYPE
{
	KEY_TYPE_NULL	= -1,		// 未知类型
	KEY_TYPE_EMPTY,			// 空类型
	KEY_TYPE_CHAR,			// 字符类型
	KEY_TYPE_DWORD,		// 整形类型
	KEY_TYPE_FLOAT,			// 浮点类型
	KEY_TYPE_STRING,		// 字符串类型
	KEY_TYPE_MAX				
};

// 字段类型名称表
static char* KeyTypeName[] = { "",
										"char",
										"dword",
										"float",
										"string"
										};

//字段类型信息结构
struct KEY
{
	string description;			//字段说明
	string name;					//字段名称
	KEY_TYPE eType;			//字段类型
};


const unsigned int DESCRIPTION_ROW_INDEX = 0;		//字段说明在csv表中的行号
const unsigned int NAME_ROW_INDEX = 1;					//字段名称在csv表中的行号
const unsigned int KEY_TYPE_ROW_INDEX = 2;			//字段类型在csv表中的行号
const unsigned int KEY_TYPE_ROW_NUM = KEY_TYPE_ROW_INDEX + 1;			//字段类型在csv表中的行数

class GameCSVFile : public CVSFile
{
public:
	GameCSVFile();

	// 通过文件名,获得字段类型信息
	bool Format(const char* szFileName);

private:
	// 通过字符串获得字段类型
	KEY_TYPE _GetKeyType(const string& strKeyType);

protected:
	vector< KEY > m_vecKeyType;	// 字段信息记录
};

#endif

#include "GameCSVFile.h"

GameCSVFile::GameCSVFile()
{
	m_vecKeyType.clear();
}

bool GameCSVFile::Format( const char* szFileName )
{
	if (!Init(szFileName))
	{
		return false;
	}

	// 字段定义行数不足,返回失败
	if (m_content.size() < KEY_TYPE_ROW_INDEX + 1)
	{
		return false;
	}

	// 形成字段说明数组
	for (unsigned int i = 0; i < m_content[DESCRIPTION_ROW_INDEX].size(); i++)
	{
		KEY curKey;

		curKey.description = m_content[DESCRIPTION_ROW_INDEX][i];
		curKey.name = m_content[NAME_ROW_INDEX][i];
		curKey.eType = _GetKeyType(m_content[KEY_TYPE_ROW_INDEX][i]);

		m_vecKeyType.push_back(curKey);
	}
	
	return true;
}

KEY_TYPE GameCSVFile::_GetKeyType( const string& strKeyType )
{
	for (int i = 0; i < sizeof(KeyTypeName); i++)
	{
		if (strKeyType.compare(KeyTypeName[i]) == 0)
		{
			return static_cast<KEY_TYPE>(i);
		}
	}
	
	return KEY_TYPE_NULL;
}

第三步,为某个特定用途的csv表格的信息获取生成代码(这里建议通过自动代码生成工具生成代码,避免重复的工作),这里以上面excel展示的数据为例,代码如下:
#ifndef _CSV_FILE_MGR_ITEM_
#define _CSV_FILE_MGR_ITEM_

#include <hash_map>

using namespace stdext;

#include "GameCSVFile.h"

#define IN
#define OUT

#define CSV_FILE_MGR_MAX_STRING_LEN	255

namespace _CVSFileMgr
{

//每行内容对应的信息包装成结构,便于访问使用
struct ItemInfo
{
	unsigned int ID;
	char Type;
	char Name[CSV_FILE_MGR_MAX_STRING_LEN];
	unsigned int Function;
	float Xpos;
	float Ypos;
	float Zpos;

	ItemInfo()
	{
		Clear();
	}

	void Clear()
	{
		ID = 0;
		Type = 0;
		memset(Name, 0, sizeof(Name));
		Function = 0;
		Xpos = 0;
		Ypos = 0;
		Zpos = 0;
	}
};

class CSVFileMgr_Item : public GameCSVFile
{
	typedef hash_map< unsigned int, int > MapIDToRow;

public:
	CSVFileMgr_Item();

	// 获取表格中数据的行数,不包括前三行的数据定义行
	unsigned int GetRows();

	// 通过行首ID获取信息
	bool GetInfoByID(unsigned int ID, ItemInfo& rItemInfo);

	// 通过行号获取信息,不包括前三行的数据定义行
	bool GetInfoByRow(unsigned int Row, ItemInfo& rItemInfo);

private:
	// 通过行号获取信息,不包括前三行的数据定义行
	bool _GetInfoByRow(unsigned int Row, ItemInfo& rItemInfo);

	// 获得指定字段的数据内容
	bool _GetField(OUT void *pData, const string& strData, unsigned int uFieldIndex);

private:
	bool m_bOpenSucc;					//文件是否成功打开标志
	MapIDToRow m_mapIdToRow;	//行首ID和行号对应Hash表
};


}

#endif

#include "CSVFileMgr_Item.h"

namespace _CVSFileMgr
{

CSVFileMgr_Item::CSVFileMgr_Item()
{
	if (Format("item.csv"))
	{
		m_bOpenSucc = true;

		m_mapIdToRow.clear();
		for (unsigned int i = KEY_TYPE_ROW_INDEX + 1; i < m_content.size(); i++)
		{
			int nID = atoi(m_content[i][0].c_str());
			m_mapIdToRow[nID] =  i - KEY_TYPE_ROW_NUM;
		}
	}
	else
	{
		m_bOpenSucc = false;
	}
	
}

unsigned int CSVFileMgr_Item::GetRows()
{
	if (!m_bOpenSucc)
	{
		return 0;
	}

	return m_content.size() - KEY_TYPE_ROW_NUM;
}

bool CSVFileMgr_Item::GetInfoByID( unsigned int ID, ItemInfo& rItemInfo)
{
	if (!m_bOpenSucc)
	{
		return false;
	}

	MapIDToRow::const_iterator it = m_mapIdToRow.find(ID);
	if (it == m_mapIdToRow.end())
	{
		return false;
	}

	return GetInfoByRow(it->second, rItemInfo);
}

bool CSVFileMgr_Item::GetInfoByRow( unsigned int Row, ItemInfo& rItemInfo)
{
	if (!m_bOpenSucc)
	{
		return false;
	}

	if (Row < 0 || Row > GetRows())
	{
		return false;
	}

	return _GetInfoByRow(Row + KEY_TYPE_ROW_INDEX + 1, rItemInfo);
}

bool CSVFileMgr_Item::_GetInfoByRow( unsigned int Row, ItemInfo& rItemInfo )
{
	rItemInfo.Clear();

	if (!_GetField((void*)&rItemInfo.ID, m_content[Row][0], 0))
	{
		return false;
	}

	if (!_GetField((void*)&rItemInfo.Type, m_content[Row][1], 1))
	{
		return false;
	}

	if (!_GetField((void*)&rItemInfo.Name, m_content[Row][2], 2))
	{
		return false;
	}

	if (!_GetField((void*)&rItemInfo.Function, m_content[Row][3], 3))
	{
		return false;
	}

	if (!_GetField((void*)&rItemInfo.Xpos, m_content[Row][4], 4))
	{
		return false;
	}

	if (!_GetField((void*)&rItemInfo.Ypos, m_content[Row][5], 5))
	{
		return false;
	}

	if (!_GetField((void*)&rItemInfo.Zpos, m_content[Row][6], 6))
	{
		return false;
	}

	return true;
}

bool CSVFileMgr_Item::_GetField( OUT void *pData, const string& strData, unsigned int uFieldIndex )
{
	if (uFieldIndex >= m_vecKeyType.size())
	{
		return false;
	}

	switch (m_vecKeyType[uFieldIndex].eType)
	{
	case KEY_TYPE_NULL:
		break;
	case KEY_TYPE_EMPTY:
		break;
	case KEY_TYPE_CHAR:
		{
			char c = strData[0];
			memcpy(pData, &c, sizeof(c));
		}
		break;
	case KEY_TYPE_DWORD:
		{
			int i = atoi(strData.c_str());
			memcpy(pData, &i, sizeof(i));
		}
		break;
	case KEY_TYPE_FLOAT:
		{
			float f = static_cast<float>( atof( strData.c_str() ) );
			memcpy(pData, &f, sizeof(f));
		}
		break;
	case KEY_TYPE_STRING:
		{
			memcpy(pData, strData.c_str(), strData.length());
		}
		break;
	default:
		break;
	}

	return true;
}

}

读取信息演示代码如下:
#include "CSVFileMgr_Item.h"

using namespace _CVSFileMgr;

int main( int argc, char* argv[] )
{
	CSVFileMgr_Item item;

	ItemInfo itemInfo1;
	item.GetInfoByID(300001, itemInfo1);

	ItemInfo itemInfo2;
	item.GetInfoByRow(1, itemInfo2);


	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值