打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
C++中固定长度数字的序列化与反序列化处理

在实际应用中,我们可能会遇到处理固定长度数字的情况,比如像邮政编码、身份证号这样的编号,这些编号可能是由几段有意义的数字组成,在序列化为字符串时需要逐段输出,而在反序列化时又需要逐段读入。

这里假设我们有如下一种编码,它由8位数字组成,前2位为省份编号,接着2位为城市编号,最后4位为实例号,其数据结构为:

struct SomeCode
{
    SomeCode() {}
    SomeCode(char pn, char cn, short in)
    {
        province_no = pn;
        city_no = cn;
        inst_no = in;
    }
    
    char    province_no; // 省份编号,2位
    char    city_no;     // 城市编号,2位
    short   inst_no;     // 实例编号,4位
};

在C++中,我们可以使用ostream提供的一些方法来实现固定长度的输出,如下:

#include <iostream>
#include <iomanip>
using namespace std;

int main(int argc, char** argv)
{
    SomeCode code(1, 2, 3); // 编码为01020003
    cout << setw(2) << setfill('0') << (int)code.province_no
        << setw(2) << setfill('0') << (int)code.city_no
        << setw(4) << setfill('0') << code.inst_no
        << endl;
}

setw用于设置宽度,setfill用于设置填充字符。这两个设置都是一次性的,即完成一次输出后会被重置,因此我们不得不每次都进行设置。另外,上面将province_no和city_no转换为int型是为了避免将其作为字符类型输出。

对于反序列化,C++就不太方便了,setw对于istream的输入是没有效果的,我们可以尝试下面的代码,但结果会让人失望。

std::stringstring ss("01020003");
char province_no;
ss >> setw(2) >> (int&)province_no;

其实,我觉得istream也应该被赋予setw这一能力,这不是什么难事,可惜STL的设计者没有往这方面考虑。

由于直接使用STL并不能简洁的处理上述问题,因此以前我都采用C方式来实现序列化与反序列化:

char buf[16];
sprintf(buf, "%02d%02d%04d", code.province_no, code.city_no, code.inst_no); // 序列化

int province_no = 0, city_no = 0, inst_no = 0; // 注意变量类型均为int,否则会引起stack corrupt错误,当然也可以选用long类型
sscanf(buf, "%2d%2d%4d", &province_no, &city_no, &inst_no); // 反序列化

上述代码很简洁,但并不优雅,而且容错性不好,因此,我还是打算在C++范畴内实现。于是,我设计了一个模版类:

template<typename NumberType, size_t Length, bool Rollback = true>
class fixed_length_number;

我先给出其用法,对于上述例子,我们可以这样处理:

#include "fixed_length_number.h"
#include <iostream>
#include <iomanip>
#include <sstream>
using namespace std;

typedef fixed_length_number<char, 2> ProvinceNo;
typedef fixed_length_number<char, 2> CityNo;
typedef fixed_length_number<short,4> InstNo;

struct SomeCode2
{
    SomeCode2() {}
    SomeCode2(ProvinceNo pn, CityNo cn, InstNo in)
    {
        province_no = pn;
        city_no = cn;
        inst_no = in;
    }

    ProvinceNo province_no;
    CityNo city_no;
    InstNo inst_no;
};

inline std::ostream& operator<<(std::ostream& os, const SomeCode2& code)
{
    return os << code.province_no << code.city_no << code.inst_no;
}

inline std::istream& operator>>(std::istream& is, SomeCode2& code)
{
    return is >> code.province_no >> code.city_no >> code.inst_no;
}

int main(int argc, char** argv)
{
    SomeCode2 code2(1, 2, 3);
    cout << code2 << endl; // 序列化,输出01020003
    std::stringstream ss("12345678");
    ss >> code2; // 反序列化
    cout << code2 << endl; // 输出12345678
}

可以看出,这种方式是很完美的,下面给出fixed_length_number的具体源码:

/**
 * Copyleft 2006-12-26 by freefalcon
 */

#ifndef __FIXED_LENGTH_NUMBER_H__
#define __FIXED_LENGTH_NUMBER_H__

#include <iosfwd>
#include <iomanip>


template<typename NumberType, size_t Length, bool Rollback = true>
struct fixed_length_number
{
public:
    fixed_length_number(NumberType num = 0)
    {
        m_num = num;
    }

    operator NumberType&()
    {
        return m_num;
    }

    operator NumberType() const
    {
        return m_num;
    }

private:
    NumberType m_num;
};

template<typename NumberType, size_t Length, bool Rollback>
inline std::ostream& operator<<(std::ostream& os, const fixed_length_number<NumberType, Length, Rollback>& num)
{
    return os << std::setw(Length) << std::setfill('0') << (unsigned long)(NumberType)num;
}

inline int char_to_int(char ch)
{
    if(ch >= '0' && ch <= '9') return ch - '0';
    if(ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
    if(ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
    return 0xff;
}

template<typename NumberType, size_t Length, bool Rollback>
inline std::istream& operator>>(std::istream& is, fixed_length_number<NumberType, Length, Rollback>& num)
{
    if(Length == 0)
    {
        is >> (NumberType&)num;
    }
    else
    {
        int base = 10;
        switch(is.flags() & std::ios_base::basefield)
        {
        case std::ios_base::dec: base = 10; break;
        case std::ios_base::hex: base = 16; break;
        case std::ios_base::oct: base = 8; break;
        }

        char ch;
        (NumberType&)num = 0;
        int length = Length;
        int tmp;
        while(length--)
        {
            if(!(is >> ch && (tmp = char_to_int(ch)) < base))
            {
                std::ios_base::iostate state = is.rdstate();
                if(Rollback)
                {
                    is.clear();
                    is.seekg(state ? length-Length+1 : length-Length, std::ios_base::cur);
                    num = 0;
                }
                is.setstate(state|std::ios_base::badbit);
                return is;
            }
            (NumberType&)num = num*base + tmp;
        }
    }
    return is;
}

#endif//__FIXED_LENGTH_NUMBER_H__

代码其实很简单,这里对三个模版参数作以说明,NumberType为数字类型,Length为数字长度,Rollback表示当从istream读取失败时是否对读指针进行“回滚”处理(类似于事务处理)。另外,上述代码还考虑了8进制和16进制的情况,应该能够满足我们的大部分需求。


本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
std::string的用法
系统的方法就一定是最好的?
使用std::unique
实现 void delete_char(char * str, char ch);把str...
输入一个整数,输出为对应的大写汉字 如输入456,输出:四百五十六
Char* 与 LPCTSTR 类型的互相转换
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服