C基础 数据序列化简单使用和讨论
前言
C中对序列化讨论少, 因为很多传输的内容都有自己解析的轮子. 对于序列化本质是统一编码, 统一解码的方式.
本文探讨是一种简单的序列化方案. 保证不同使用端都能解析出正确结果.
在文章一开始, 看一个最简单的序列化代码 如下
#include <stdio.h>
#include <stdlib.h> #define _INT_NAME (64)
#define _STR_TXT "student.struct" struct student {
int id;
char sex;
int age;
char name[_INT_NAME + ];
double high;
double weight;
}; // struct student 结构体序列化到文件的方法
static void _student_serialize(struct student* stu, FILE* txt) {
fprintf(txt, "%d %c %d %s %lf %lf ", stu->id, stu->sex,
stu->age, stu->name, stu->high, stu->weight);
} // struct student 结构体反序列化
static void _student_deserialize(struct student* stu, FILE* txt) {
fscanf(txt, "%d %c %d %s %lf %lf ", &stu->id, &stu->sex,
&stu->age, stu->name, &stu->high, &stu->weight);
} // 简单打印数据
static void _student_print(struct student* stu) {
static int _idx; printf("%d: %d %c %d %s %lf %lf \n", _idx++, stu->id,
stu->sex, stu->age, stu->name, stu->high, stu->weight);
} /*
* 一种最简单的通用序列化方法
*/
int main(int argc, char* argv[]) { FILE* txt = fopen(_STR_TXT, "wb+");
if (NULL == txt) {
fprintf(stderr, "fopen " _STR_TXT " error!\n");
return -;
} // 这里写入数据
struct student stu = { , , , "鸣人", 172.23, 64.05 };
_student_print(&stu); // 这里序列化并写入数据到文件
_student_serialize(&stu, txt); // 我们读取这个文件, 先设置文件指针到文件开头
fseek(txt, , SEEK_SET); // 开始读取数据
struct student ts;
_student_deserialize(&ts, txt);
_student_print(&ts); fclose(txt);
system("pause");
return ;
}
本质在 自定义编码解码,并利用 scanf和printf 对映关系
// struct student 结构体序列化到文件的方法
static void _student_serialize(struct student* stu, FILE* txt) {
fprintf(txt, "%d %c %d %s %lf %lf ", stu->id, stu->sex,
stu->age, stu->name, stu->high, stu->weight);
} // struct student 结构体反序列化
static void _student_deserialize(struct student* stu, FILE* txt) {
fscanf(txt, "%d %c %d %s %lf %lf ", &stu->id, &stu->sex,
&stu->age, stu->name, &stu->high, &stu->weight);
}
运行结果 如下:

通过这种实现, 是跨平台的. 因为C实现标准和自己定义协议支持
"%d %c %d %s %lf %lf "
最后我们还会讨论这种情况.
正文
1. 一次失败扩展 fscanf -> fread ; fprintf -> fwrite
测试如下, 在window上测试代码 main.c
#include <stdio.h>
#include <stdlib.h> #define _INT_NAME (64)
#define _STR_TXT "student.struct" struct student {
int id;
char sex;
int age;
char name[_INT_NAME + ];
double high;
double weight;
}; // struct student 结构体序列化到文件的方法
static void _student_serialize(struct student* stu, FILE* txt) {
fwrite(stu, sizeof(*stu), , txt);
} // struct student 结构体反序列化
static void _student_deserialize(struct student* stu, FILE* txt) {
fread(stu, sizeof(*stu), , txt);
} // 简单打印数据
static void _student_print(struct student* stu) {
static int _idx; printf("%d: %d %c %d %s %lf %lf \n", _idx++, stu->id,
stu->sex, stu->age, stu->name, stu->high, stu->weight);
} /*
* 一种最简单的通用序列化方法
*/
int main(int argc, char* argv[]) {
FILE* txt = fopen(_STR_TXT, "wb+");
if (NULL == txt) {
fprintf(stderr, "fopen " _STR_TXT " error!\n");
return -;
} // 这里写入数据
struct student stu = { , , , "鸣人", 172.23, 64.05 };
_student_print(&stu); // 这里序列化并写入数据到文件
_student_serialize(&stu, txt); // 我们读取这个文件, 先设置文件指针到文件开头
fseek(txt, , SEEK_SET); // 开始读取数据
struct student ts;
_student_deserialize(&ts, txt);
_student_print(&ts); fclose(txt);
system("pause");
return ;
}
核心是
// struct student 结构体序列化到文件的方法
static void _student_serialize(struct student* stu, FILE* txt) {
fwrite(stu, sizeof(*stu), , txt);
} // struct student 结构体反序列化
static void _student_deserialize(struct student* stu, FILE* txt) {
fread(stu, sizeof(*stu), , txt);
}
在 linux 上处理的代码 是 serialize.c
#include <stdio.h>
#include <stdlib.h> #define _INT_NAME (64)
#define _STR_TXT "student.struct" struct student {
int id;
char sex;
int age;
char name[_INT_NAME + ];
double high;
double weight;
}; // struct student 结构体反序列化
static void _student_deserialize(struct student* stu, FILE* txt) {
fread(stu, sizeof(*stu), , txt);
} // 简单打印数据
static void _student_print(struct student* stu) {
static int _idx; printf("%d: %d %c %d %s %lf %lf \n", _idx++, stu->id,
stu->sex, stu->age, stu->name, stu->high, stu->weight);
} /*
* 处理 window 上生成的内存文件, 看是否跨平台
*/
int main(int argc, char* argv[]) {
// 这里就简单读取 _STR_TXT
FILE* txt = fopen(_STR_TXT, "rt");
if (NULL == txt) {
fprintf(stderr, "fopen " _STR_TXT " error!\n");
return -;
} // 开始读取数据
struct student ts;
_student_deserialize(&ts, txt); // 打印读取数据测试
_student_print(&ts); fclose(txt);
return ;
}
编译 命令的是
gcc -g -Wall -o serialize.out serialize.c
将 window生成的 student.struct 文件传输到linux测试机上, 运行结果 如下:

期间进行了各种折腾
a. 考虑转码
b. 考虑 fopen 创建utf8 文件
c. 考虑代码转码
.......
还是以失败告终, 估计fread, fwrite是不同平台的直接内存文件. 差异大, 不适合跨平台, 但是同样平台是比较快的考虑方式.
扩展一下, 怎么得到文件字符长度
linux得到文件长度扩展 http://blog.csdn.net/yutianzuijin/article/details/27205121
2. 采用 protobuf - c google一种协议.
pbc 传输协议, 很多公司都在采用, 我看了一下, 网上实现版本比较多, 现在可能有官方版本了. 推荐一个
cloudwn pbc https://github.com/cloudwu/pbc
可能是最精简的一种实现, 源码写的很好, 但是觉得有点复杂了. 为了这么功能这么搞, 没意思.
最关键的是 pbc 需要生成中间协议文件, 占用内存也不少.
这也是一种解决方案.
3. 采用json协议
这个意思很明了, 大家都通过json来处理问题 这里推荐自己写的一个json引擎
源码在1000行左右,欢迎使用. 这也是一种解决方案. 最近和朋友在讨论问题, 越发觉得, 计算机软件开发无非
围绕 空间和时间来回搞, 通用还是针对.
真实生产环境中可能会更直白些, 快些, 没bug就行, 怎么爽怎么随便, 怎么快怎么来. 哈哈.
但是对于C, 还是有一套自己的哲学, 用最简单完成一场意外. C/C++ 老了, 但却是美的.
这种解决方案讲到这里了.
4. 开始就是结束. 还是从最简单的开始. 实现一个C 序列换流程
后面继续讲解通过 fscanf 和 fprintf 构建 C的序列化. 先看一种实现体, serialize_student.h
#ifndef _H_SERIALIZE_THREE_SERIALIZE_STUDENT
#define _H_SERIALIZE_THREE_SERIALIZE_STUDENT #include <assert.h>
#include <stdio.h>
#include <limits.h> // 1.0 定义序列换结构体
#define _INT_NAME (64)
struct student {
int id;
char sex;
int age;
char name[_INT_NAME + ];
double high;
double weight;
}; // 2.0 定义保存文件交换文件名 当前文件名去掉 .h
#define _STR_SERIALIZE_TXT_student "serialize_student" //3.0 定义转换读取协议, printf协议后面跟' ', printf后面跟的, scanf跟的
#define _STR_SERIALIZE_PBC_student "%d %c %d %s %lf %lf "
#define _F_SERIALIZE_PRINTF_student(p) \
p->id, p->sex, p->age, p->name, p->high, p->weight
#define _F_SERIALIZE_SCANF_student(p) \
&p->id, &p->sex, &p->age, p->name, &p->high, &p->weight // 3.0 定义序列换数据写入方法
static int serialize_student_printfs(void* data, int len) {
assert(data && len > ); FILE* txt = fopen(_STR_SERIALIZE_TXT_student, "wb");
if (!txt) return -; struct student* p = data;
for (int i = ; i < len; ++i) {
fprintf(txt, _STR_SERIALIZE_PBC_student, _F_SERIALIZE_PRINTF_student(p));
++p;
} fclose(txt);
return ;
} // 4.0 定义序列化数据读取方法
static int serialize_student_scanfs(void* data, int len) {
assert(data); FILE* txt = fopen(_STR_SERIALIZE_TXT_student, "rb");
if (!txt) return -; int ns = ;
struct student* p = data;
int nz = ;
const char* s = _STR_SERIALIZE_PBC_student;
while (*s) {
if (*s == '%')
++nz;
++s;
} while (ns < len && fscanf(txt, _STR_SERIALIZE_PBC_student, _F_SERIALIZE_SCANF_student(p)) == nz) {
++ns;
++p;
} fclose(txt);
return ns;
} #endif // !_H_SERIALIZE_THREE_SERIALIZE_STUDENT
这里看看注释容易明白, 这里讲解一下 头文件导入宏规则.
_H 开头 + _项目名 + _文件名(去掉后缀) 主要为了解决项目特别多的时候联编造成宏碰撞.
测试代码 main.c
#include <stdlib.h>
#include "serialize_student.h" /*
* 实现C的序列流程操作
*/
int main(int argc, char* argv[]) { struct student stu[] = {
{ , , , "鸣人", 172.23, 64.05 },
{ , , , "杀生丸", 178.23, 74.00 }
}; // 先序列化到文件
serialize_student_printfs(stu, sizeof(stu) / sizeof(*stu)); // 开始读取序列化内容
struct student sts[];
serialize_student_scanfs(sts, ); for (int i = ; i < ; ++i) {
printf("%d => %s\n", i, sts[i].name);
} puts("你喜欢吗, ... "); system("pause");
return ;
}
运行结果是

最后可能来点 封装, 减少以后的工作量. 可能有点复杂, 直接看代码, 能懂得就呵呵一笑而过.
再表述后面封装之前讲一个小知识, linux 上宏调试有个小技巧 通过 gcc -E 导出 *.i 文件, 查看宏命令.
同样 window 上 vs 需要这么 设置
加上 /EP /P 运行时候会生成 main.i

找到问题后再将其去掉. 编译运行.
我们先看一个 C宏模板 序列化注册头文件 serialize-base.h
#ifndef _H_SERIALIZE_THREE_SERIALIZE_BASE
#define _H_SERIALIZE_THREE_SERIALIZE_BASE #include <assert.h>
#include <stdio.h>
#include <limits.h> /*
* 宏模板, 为想实现序列化的结构注册函数
* name : 结构名称, 例如 student
* pbc : 定义的协议, 例如 "%d %c %d %s %lf %lf "
* ptf : printf 打印数据参数集, 例如 _->id, _->sex, _->age, _->name, _->high, _->weight | SERIALIZE_PTF
* scf : scanf 得到数据的参数集, 例如 &_->id, &_->sex, &_->age, _->name, &_->high, &_->weight | SERIALIZE_SCF
*/
#define SERIALIZE_BASE_REGISTER(name, pbc, ptf, scf) \
static int serialize_printfs_##name(void* data, int len) { \
assert(data && len > ); \
\
FILE* txt = fopen("serialize_"#name, "wb"); \
if (!txt) return -; \
\
struct name* _ = (struct name*)data; \
for (int i = ; i < len; ++i) { \
fprintf(txt, pbc, ptf); \
++_; \
} \
\
fclose(txt); \
return ; \
} \
\
static int serialize_scanfs_##name(void* data, int len) { \
assert(data); \
\
FILE* txt = fopen("serialize_"#name, "rb"); \
if (!txt) return -; \
\
int ns = , nz = ; \
struct name* _ = (struct name*)data; \
const char* s = pbc; \
while (*s) { \
if (*s == '%') \
++nz; \
++s; \
} \
\
while (ns < len && fscanf(txt, pbc, scf) == nz) { \
++ns; \
++_; \
} \
\
fclose(txt); \
return ns; \
} \ #endif // !_H_SERIALIZE_THREE_SERIALIZE_BASE
后面写一个结构 来实现序列化 serialize_person.h
#ifndef _H_SERIALIZE_THREE_SERIALIZE_PERSON
#define _H_SERIALIZE_THREE_SERIALIZE_PERSON // 必须导入(继承) 序列化基础实现模板
#include "serialize-base.h" // 1.0 定义序列换结构体
struct person {
int id;
char sex;
int age;
char name[];
double high;
double weight;
}; // 2.0 注册得到 ptf 结构
#undef SERIALIZE_PTF
#define SERIALIZE_PBC(id, sex, age, name, high, weight) \
_->id, _->sex, _->age, _->name, _->high, _->weight // 3.0 注册得到 sct 结构
#undef SERIALIZE_SCF
#define SERIALIZE_SCF(id, sex, age, name, high, weight) \
&_->id, &_->sex, &_->age, _->name, &_->high, &_->weight // 4.0 最后开始注册实现体
SERIALIZE_BASE_REGISTER(
person,
"%d %c %d %s %lf %lf ",
SERIALIZE_PBC(id, sex, age, name, high, weight),
SERIALIZE_SCF(id, sex, age, name, high, weight)
) #endif // !_H_SERIALIZE_THREE_SERIALIZE_PERSON
是不是很酷炫, 好测试一下 main.c
#include <stdlib.h>
#include "serialize_student.h"
#include "serialize_person.h" /*
* 实现C的序列流程操作
*/
int main(int argc, char* argv[]) { struct student stu[] = {
{ , , , "鸣人", 172.23, 64.05 },
{ , , , "杀生丸", 178.23, 74.00 }
}; // 先序列化到文件
serialize_student_printfs(stu, sizeof(stu) / sizeof(*stu)); // 开始读取序列化内容
struct student sts[];
serialize_student_scanfs(sts, ); for (int i = ; i < ; ++i) {
printf("%d => %s\n", i, sts[i].name);
} puts("你喜欢吗, ... "); struct person ps[] = {
{ , , , "日向雏田", 162.23, 51.05 },
{ , , , "玲", 158.23, 45.00 }
}; // 序列化数据
serialize_printfs_person(ps, sizeof(ps) / sizeof(*ps));
// 得到序列化数据 struct person tps[];
serialize_scanfs_person(tps, ); for (int i = ; i < ; ++i) {
printf("%d => %s\n", i, sts[i].name);
} system("pause");
return ;
}
测试结果如下, 一切正常

到这里基本都结束了. 主要核心就是上面注册的函数模板.
后记
这次后记我们在linux上测试一下 将刚生成的 serialize_person 上传到 linux平台
测试文件 main.c
#include <stdlib.h>
#include "serialize_person.h" /*
* 实现C的序列流程操作
*/
int main(int argc, char* argv[]) { puts("Play time game, writing code"); struct person tps[];
serialize_scanfs_person(tps, ); for (int i = ; i < ; ++i) {
printf("%d => %s\n", i, tps[i].name);
} return ;
}
最终测试结果

源码成功, 到这里基本上可以离开了.
关于C数据序列化的简单操作就到这里了. 错误是难免的, 拜~~~
C基础 数据序列化简单使用和讨论的更多相关文章
- C中级 数据序列化简单使用和讨论 (二)
引言 - 一种更好的方式 其实不管什么语言, 开发框架都会遇到序列化问题. 序列化可以理解为A 和 B 交互的一种协议. 很久以前利用 printf 和 scanf 的协议实现过一套序列化问题. C ...
- Python基础4 迭代器,生成器,装饰器,Json和pickle 数据序列化
本节内容 迭代器&生成器 装饰器 Json & pickle 数据序列化 软件目录结构规范 作业:ATM项目开发 1.列表生成式,迭代器&生成器 列表生成式 孩子,我现在有个需 ...
- Python-Day4 Python基础进阶之生成器/迭代器/装饰器/Json & pickle 数据序列化
一.生成器 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面 ...
- python基础6之迭代器&生成器、json&pickle数据序列化
内容概要: 一.生成器 二.迭代器 三.json&pickle数据序列化 一.生成器generator 在学习生成器之前我们先了解下列表生成式,现在生产一个这样的列表[0,2,4,6,8,10 ...
- 【原创】Hadoop的IO模型(数据序列化,文件压缩)
数据序列化 我们知道,数据在分布式系统上运行程序数据是需要在机器之间通过网络传输的,这些数据必须被编码成一个个的字节才可以进行传输,这个其实就是我们所谓的数据序列化.数据中心中,最稀缺的资源就是网络带 ...
- ORM取数据很简单!是吗?
简介 几乎任何系统都以某种方式与外部数据存储一起运行.大多数情况下,外部数据存储是一个关系数据库,并且在实现时通常将数据提取任务委托给某些 ORM. 尽管 ORM 包含很多 routine 代码,但是 ...
- python第四周:装饰器、迭代器、内置方法、数据序列化
1.装饰器 定义:本质是一个函数,(装饰其他函数)就是为其他函数添加附加功能 原则:不能修改被装饰函数的源代码,不能修改被装饰函数的调用方式 实现装饰器的知识储备: 函数即“变量”.每当定义一个函数时 ...
- 迭代器/生成器/装饰器 /Json & pickle 数据序列化
本节内容 迭代器&生成器 装饰器 Json & pickle 数据序列化 软件目录结构规范 作业:ATM项目开发 1.列表生成式,迭代器&生成器 列表生成式 孩子,我现在有个需 ...
- 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据
ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据 最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前 ...
随机推荐
- WEB 安全之 SQL注入 < 二 > 暴库
SQL注入是一个比较"古老"的话题,虽然现在存在这种漏洞的站点比较少了,我们还是有必要了解一下它的危害,及其常用的手段,知己知彼方能百战不殆.进攻与防守相当于矛和盾的关系,我们如果 ...
- conpot_usage简要说明
conpot是一个ICS(工业控制系统)蜜罐, 旨在收集攻击者针对工业控制系统的攻击方法和动机. 这篇文章主要用来说明conpot的用户定制相关的一些配置. (英文原文详见: https://gith ...
- Ajax.BeginForm 上传文件
在 Mvc 中上传文件时通常使用 Html.BeginForm 标签,同时对Form 添加属性 enctype = "multipart/form-data",前端代码如下: @H ...
- sublime Text及package control的安装
1.下载并安装sublime Text2http://www.baidu.com/s?wd=sublime&rsv_spt=1&issp=1&f=8&rsv_bp=0& ...
- 洛谷P1613 跑路
P1613 跑路 176通过 539提交 题目提供者该用户不存在 标签倍增动态规划 难度普及+/提高 提交该题 讨论 题解 记录 最新讨论 这个题的数据.. 题意问题 表意 题目描述 小A的工作不仅繁 ...
- noip2007 树网的核
P1099 树网的核 112通过 221提交 题目提供者该用户不存在 标签动态规划树形结构2007NOIp提高组 难度提高+/省选- 提交该题 讨论 题解 记录 题目描述 设T=(V, E, W) ...
- TCP/IP详解学习笔记(13)-- TCP连接的建立与终止
1.TCP连接的建立 设主机B运行一个服务器进程,它先发出一个被动打开命令,告诉它的TCP要准备接收客户进程的连续请求,然后服务进程就处于听的状态.不断检测是否有客户进程发起连续 ...
- Ov
- leetcode 13
罗马数字是阿拉伯数字传入之前使用的一种数码.罗马数字采用七个罗马字母作数字.即Ⅰ(1).X(10).C(100).M(1000).V(5).L(50).D(500). 记数的方法: 相同的数字连写,所 ...
- 1.4Linux内核版本号的定义规则
Linux内核版本号的组成: (1)主版本号: (2)次版本号: (3)修订版本号: (4)微调版本号: (5)为特定的Linux系统特别调校的描述: 例子:2.6.29.7-flykernel-12 ...