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;
}