采用最一般的序列化方法生成有特殊序列化顺序要求的格式文件的方法

#if 0
封装操作在创建文件格式上面的作用
#endif

#if 0
封装操作,我的理解是封装了函数调用并且能够延迟调用的一个变量!这种方式在C/C++里
面就有好多种:函数指针,仿函数。实际上我们编写程序的过程就是一个设计函数调用顺
序的过程。
#endif

#if 0
通常的情况是我们将函数调用顺序以静态编码的形式写死了:
#endif

#ifdef SAMPLE1
#include <iostream>
using namespace std;
void func1(){cout<<"func1()"<<endl;}
void func2(){cout<<"func2()"<<endl;}
void func3(){cout<<"func3()"<<endl;}
int main()
{
typedef void (*Func)();
Func funcs[3]={func1,func2,func3};
for(int i=0;i<3;++i)funcs[i]();
return 0;
}
#endif//SAMPLE1

#if 0
其实在C++函数库中有着大量动态函数调用的过程,在各种API中都有着函数指针的形式。
另外在C++标准库中还有着大量的函数实体(仿函数)存在。所谓的函数实体就是不仅仅封
装了函数调用而且还封装了其他的一些信息,例如参数信息,当然也可以保存任意的信息
,保存不同的信息还可以设计到一些特殊的应用啊!例如:使用非常广泛地Undo,Redo也
就是撤销和重做功能的实现:)
#endif

#ifdef SAMPLE2
#include <iostream>
using namespace std;
struct Func
{
Func(int m):member(m){}// 构造函数就是封装接口
void operator()(){cout<<"func"<<member<<"()"<<endl;}
int member;
};
int main()
{
// 利用封装接口实现了一个整型变量的封装
Func funcs[3]={Func(1),Func(2),Func(3)};
for(int i=0;i<3;++i)funcs[i]();
return 0;
}
#endif//SAMPLE2

#if 0
sample1和sample2的代码都完成了一模一样的工作,但是做法却完全不同,此外还可以看
出Func结构体不仅仅封装了一个函数调用,而且还封装了一个整型变量,其实说到这里,
有一点C++编程基础的人都可以进行任意的数据封装啦:)所以在此不赘述啦。
#endif

#if 0
另外有一点需要注意的是:sample1和sample2都是用C/C++数组的形式保存函数调用的!其
实完全可以采用任何其它合法的C/C++容器来盛放这些封装起来的操作。既然说起容器,自
然就要想到C++标准模版库的各种容器。最令我觉得惊奇的是map容器和这里的封装操作的
混用,竟然产生了惊人的效果!例如:实现脚本引擎,使得程序有一定的智能,降低编码
的思维负担等等!
#endif

#if 0
在此我将从降低编码的思维负担着手,来阐释如何利用封装操作的方法来降低思维负担:
#endif

#ifdef SAMPLE3
#include <iostream>
#include <map>
using namespace std;
struct Func
{
Func(int m):member(m){}
void operator()(){cout<<"func"<<member<<"()"<<endl;}
int member;
};
int main()
{
// 利用map容器来盛放Func对象
typedef map<int,Func> FuncT;
FuncT funcs;
funcs.insert(make_pair(0,Func(1)));// 执行顺序由键值0决定
funcs.insert(make_pair(1,Func(2)));// 执行顺序由键值1决定
funcs.insert(make_pair(2,Func(3)));// 执行顺序由键值2决定
for(FuncT::iterator it=funcs.begin();it!=funcs.end();++it)
{// 执行顺序从小到大执行,而不是依据加入map容器的先后顺序
it->second();
}
return 0;
}
#endif//SAMPLE3

#if 0
从sample3可以看出,利用了map容器之后就可以通过调整map的键值来动态的调整程序的执
行顺序。如果这个键值是通过计算得出的,那么就可以实现更加强大的应用!其实从sample3
的代码还可以看出,通过修改map容器的键值就可以在不大量修改代码的同时完全改变代码
的执行顺序,从而改变程序功能!
#endif

#if 0
在代码sample3中,Func封装了一个成员变量member,实际上它还封装了一个函数调用,也
就是operator()的函数体。现在就是考虑如何利用上面的封装函数操作的方法来实现有特
定顺序的序列化方法,当然需要利用一般的序列化方法啦!那么我们先看看一般的序列化
方法是什么样子的,如sample4所示:
#endif

#ifdef SAMPLE4
#include <fstream>
#include <vector>
#include <functional>//mem_fun bind2nd
#include <algorithm>//for_each
using namespace std;
typedef std::ofstream archive;
struct DATA1
{
int member1;
int member2;
};
struct DATA2
{
float member1;
float member2;
};
struct HEADER
{
unsigned int data1Cnt;
unsigned int data1Ofs;
unsigned int data2Cnt;
unsigned int data2Ofs;
};
class Data1:public DATA1
{
public:
void save(archive*ar)
{
ar->write(reinterpret_cast<const char*>(static_cast<DATA1*>(this)),sizeof(DATA1));
}
};
class Data2:public DATA2
{
public:
void save(archive*ar)
{
ar->write(reinterpret_cast<const char*>(static_cast<DATA2*>(this)),sizeof(DATA2));
}
};
class Header:public HEADER
{
public:
void save(archive*ar)
{
// 第一次写出头结构体是为了在文件中将HEADER写在文件的头部固定位置,起着一个占位的作用
ar->write(reinterpret_cast<const char*>(static_cast<HEADER*>(this)),sizeof(HEADER));
// 下面的两个变量就是可以以任意顺序编写的代码
data1Cnt = static_cast<unsigned int>(data1.size());
data2Cnt = static_cast<unsigned int>(data2.size());
// 下面的两个变量就是不可以以任意顺序编写的代码
data1Ofs = ar->tellp();
for_each(data1.begin(),data1.end(),bind2nd(mem_fun_ref(&Data1::save),ar));
data2Ofs = ar->tellp();
for_each(data2.begin(),data2.end(),bind2nd(mem_fun_ref(&Data2::save),ar));
// 绝对不可以写成下面的顺序,否则就出错啦:) (到底有没有办法可以避免这一点呢?)
//data1Ofs = ar->tellp();
//data2Ofs = ar->tellp();
//for_each(data1.begin(),data1.end(),bind2nd(mem_fun_ref(&Data1::save),ar));
//for_each(data2.begin(),data2.end(),bind2nd(mem_fun_ref(&Data2::save),ar));
// 在HEADER结构体中的数据被填写好了之后再一次重写到文件中,要保证就是第一次写入的HEADER
ar->seekp(0,ios::beg);
ar->write(reinterpret_cast<const char*>(static_cast<HEADER*>(this)),sizeof(HEADER));
}
private:
vector<Data1> data1;
vector<Data2> data2;
};
int main()
{
archive ar("sample.bin");
Header header;
header.save(&ar);
return 0;
}
#endif//SAMPLE4

#if 0
从sample4的代码中可以看出:序列化函数save被调用的时候,其中的文件操作是立即执行
的,也就是说,这里面的save函数的调用过程直接决定了文件中的写出顺序,但是我们需
要注意上面已经列举出来的问题:sample4代码中已经给我们提出了问题!就是在编写
sample4的序列化代码的过程中还要额外的考虑代码的特殊顺序问题。然而,人脑一次性的
考虑过多的问题很容易出错!为了降低思维负担,我们考虑一次将所有的同类型成员数据
集中输出,而把这种特殊的顺序问题放到另外一个地方集中进行处理,从而降低了认得思
维负担,是的代码的可维护性大大提高,见sample5所示:
#endif

#ifdef SAMPLE5
#include <fstream>
#include <vector>
#include <map>
#include <functional>//mem_fun bind2nd
#include <algorithm>//for_each
using namespace std;
typedef std::ofstream archive;
struct DATA1
{
int member1;
int member2;
};
struct DATA2
{
float member1;
float member2;
};
struct HEADER
{
unsigned int data1Cnt;
unsigned int data1Ofs;
unsigned int data2Cnt;
unsigned int data2Ofs;
};

// 下面就是实现上面的代码编写顺序无关的核心代码
enum PRIORITY{
HEADER_FIRST = 0,

DATA1OFS = 100,
DATA1DAT = 199,

DATA2OFS = 200,
DATA2DAT = 299,

HEADER_LAST = 999,
};
// 下面的SAVE结构体就是前面所说的仿函数,也就是在这里封装了真正的文件操作
struct SAVE
{
public:
// 封装了结构体数据的文件写入操作
template <class T>
SAVE(archive*_ar,T*_data,bool _rewind=false)
{
init();
ar = _ar;
data = reinterpret_cast<const char*>(_data);
size = sizeof(T);
rewind = _rewind;
}
// 封装了获取文件当前指针位置的操作
SAVE(archive*_ar,unsigned int*_ofs)
{
init();
ar = _ar;
ofs = _ofs;
}
// 下面是真正的文件操作功能代码
void operator()()
{
if(rewind)
{
ar->seekp(0,ios::beg);// 将文件当前写指针至于文件开始位置
}
if(NULL != ofs)
{
*ofs = ar->tellp();// 获得文件当前写指针位置
}
if(NULL != data && 0 != size)
{
ar->write(data,size);// 写出数据到文件
}
}
private:
void init()// 需要将成员变量初始化
{
ar = NULL;
data = NULL;
size = 0;
ofs = NULL;
rewind = false;// 写入文件头必须
}
archive* ar;
const char* data;
unsigned int size;
unsigned int* ofs;
bool rewind;// 写入文件头必须
};
// 利用map容器的自动排序功能实现文件写入延迟操作,从而将无序的数据自动排列成为
// 有序数据
typedef map<PRIORITY,SAVE> SERIALIZE;
static SERIALIZE serialize;

class Data1:public DATA1
{
public:
void save(archive*ar)
{
serialize.insert(make_pair(DATA1DAT,SAVE(ar,static_cast<DATA1*>(this))));
}
};
class Data2:public DATA2
{
public:
void save(archive*ar)
{
serialize.insert(make_pair(DATA2DAT,SAVE(ar,static_cast<DATA2*>(this))));
}
};
class Header:public HEADER
{
public:
void save(archive*ar)
{
// 下面的所有代码的顺序都可以任意安排了,无需我们来关心顺序,由serialize来处理
// 这样sample4中有特定顺序的编码要求已经不必要啦,可以集中操作数据啦:)
serialize.insert(make_pair(HEADER_FIRST,SAVE(ar,static_cast<HEADER*>(this),true)));
serialize.insert(make_pair(HEADER_LAST ,SAVE(ar,static_cast<HEADER*>(this),true)));

data1Cnt = static_cast<unsigned int>(data1.size());
data2Cnt = static_cast<unsigned int>(data2.size());

serialize.insert(make_pair(DATA1OFS,SAVE(ar,&data1Ofs)));
serialize.insert(make_pair(DATA2OFS,SAVE(ar,&data2Ofs)));

for_each(data1.begin(),data1.end(),bind2nd(mem_fun_ref(&Data1::save),ar));
for_each(data2.begin(),data2.end(),bind2nd(mem_fun_ref(&Data2::save),ar));
}
private:
vector<Data1> data1;
vector<Data2> data2;
};
int main()
{
archive ar("sample.bin");
Header header;
header.save(&ar);
for(SERIALIZE::iterator it=serialize.begin();it!=serialize.end();++it)
{
it->second();// 真正的文件操作在这里执行,不过顺序已经自动排列啦:)
}
return 0;
}
#endif//SAMPLE5

#if 0
从代码sample5中可以看出,在编写序列化程序中将顺序相关的代码,修改成为顺序无关的
代码是多么的方便啊,可以极大的降低编码负担。希望这篇文档对你有用:)
#endif
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值