CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
实验题目
数据表示实验
实验目的
掌握结构数据的保存和读取的方法。
参考资料
- C语言函数分类:http://msdn.microsoft.com/zh-cn/library/2aza74he(v=vs.110).aspx
- C语言字符串函数:http://msdn.microsoft.com/zh-cn/library/f0151s4x(v=vs.110).aspx
实验环境
- Windows + VS 2012 http://172.18.187.11/netdisk/default.aspx?vm=16net
- Linux + gcc
这里我使用的环境是Windows 10 + VSCode + gcc version 8.1.0 (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project)
实验内容
结构数据保存和读出
实验要求
循环输入员工(Person)的信息,每输入一个员工的信息,立即写入文件(Persons.stru),直到输入的姓名为空时跳出循环。然后,读出该文件,显示每个Person的信息。 Person的信息表示:
1
2
3
4
5
6
7
8
struct Person
{
char username[USER_NAME_LEN]; // 员工名
int level; // 工资级别
char email[EMAIL_LEN]; // email地址
DWORD sendtime; // 发送时间
time_t regtime; // 注册时间
};
源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long DWORD;
struct CSV //按照CSV格式保存。
{
vector<vector<string>> v;
void get(istream &is)
{
string s;
for (v.clear(); getline(is, s);)
{
v.push_back({});
for (istringstream sin(s); getline(sin, s, ',');)
v.back().push_back(s);
}
}
void put(ostream &os)
{
for (int i = 0; i < v.size(); ++i)
for (int j = 0; j < v[i].size(); ++j)
os << v[i][j] << (j + 1 < v[i].size() ? ',' : '\n');
}
};
struct Person
{
string username; // 员工名
int level; // 工资级别
string email; // email地址
DWORD sendtime; // 发送时间
time_t regtime; // 注册时间
bool get(istream &is, ostream &os) //从is中读入,并将提示信息写入os;若读入终止返回false
{
os << "Input username: ";
getline(is, username);
while (!username.empty() && username.back() == ' ')
username.pop_back(); //删除多余空格
if (username.empty()) //读入空串,说明已经终止
return 0;
os << "Input level: ";
is >> level;
os << "Input email: ";
is >> email;
os << "Input sendtime: ";
is >> sendtime;
os << "Input regtime: ";
is >> regtime;
string s;
return getline(is, s), 1; //要读掉上一行的行末的回车,以免对下一次读入产生影响
}
void getCSV(vector<string> &v) //从CSV的某一行读入信息
{
username = v[0];
level = stoi(v[1]);
email = v[2];
sendtime = stoull(v[3]);
regtime = stoull(v[4]);
}
string putFileCSV() //返回CSV格式的信息
{
return username + "," + to_string(level) + "," + email + "," + to_string(sendtime) + "," + to_string(regtime) + "\n";
}
friend ostream &operator<<(ostream &os, Person &p) //打印p的信息到os
{
return os << "username: " << p.username << "\n"
<< "level: " << p.level << "\n"
<< "email: " << p.email << "\n"
<< "sendtime: " << p.sendtime << "\n"
<< "regtime: " << p.regtime << "\n\n";
}
};
int main()
{
ofstream fout("Persons.stru");
for (Person p; p.get(cin, cout);) //从屏幕读入Person直到空
fout << p.putFileCSV(); //以CSV格式写入文件
fout.close();
ifstream fin("Persons.stru");
CSV csv;
csv.get(fin); //读入CSV文件
fin.close();
for (auto line : csv.v) //对于读入的每一行信息
{
Person p;
p.getCSV(line);
cout << p;
}
}
多文件合并保存和读出
实验要求
循环输入多个文件名(不超过200MB,可以自己确定),每输入一个,就把该文件的文件名(最多300字节)、文件大小(long)和文件内容写入文件FileSet.pak中,输入文件名为空时跳出循环。然后,读FileSet.pak,每读出一个文件就把它原来的文件名加上一个序号保存起来。
合并时可以先取得文件大小,然后边读边写。
源代码
数据的交换格式格式使用JSON,正文内容使用Base64编码。
encode.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <bits/stdc++.h>
using namespace std;
const int BUFSIZE = 200 << 20;
const string CONVERT_TABLE =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
char buf[BUFSIZE];
string base64Encode(char sourcebuf[], int buflen)
{
string ret;
char split3[3];
unsigned int split4[4], len = buflen, i = 0;
while (len--)
{
split3[i++] = *(sourcebuf++);
if (i == 3)
{
split4[0] = (split3[0] & 0xfc) >> 2; //第一个字节取前6位并将其右移2位
split4[1] = ((split3[0] & 0x03) << 4) + ((split3[1] & 0xf0) >> 4); //第一个字节取后2位并左移4位 + 第二个字节取前4位并右移4位
split4[2] = ((split3[1] & 0x0f) << 2) + ((split3[2] & 0xc0) >> 6); //第二个字节取后4位并左移2位 + 第三个字节取前2位并右移6位
split4[3] = (split3[2] & 0x3f); //第三个字节取后六位并且无需移位
for (int j = 0; j < 4; ++j) //从Base64编码表中查询split对应元素索引的Base64编码
ret += CONVERT_TABLE[split4[j]];
i = 0;
}
}
if (i)
{
if (i == 1)
{
split4[0] = (split3[0] & 0xfc) >> 2;
split4[1] = (split3[0] & 0x03 << 4);
for (int j = 0; j < 2; ++j)
ret += CONVERT_TABLE[split4[j]];
ret += "==";
}
if (i == 2)
{
split4[0] = (split3[0] & 0xfc) >> 2;
split4[1] = ((split3[0] & 0x03) << 4) + ((split3[1] & 0xf0) >> 4);
split4[2] = ((split3[1] & 0x0f) << 2);
for (int j = 0; j < 3; j++)
ret += CONVERT_TABLE[split4[j]];
ret += "=";
}
}
return ret;
}
int main()
{
ofstream fout("FileSet.pak");
fout << "{\"data\": [\n";
int first = 1;
for (string name; cout << "Input file name:", getline(cin, name);)
{
ifstream fin(name, ios::binary);
if (!fin.is_open())
{
cout << "Can not open " << name << "\n";
continue;
}
fin.read(buf, BUFSIZE);
if (first)
first = 0;
else
fout << " ,\n";
fout << " {\n"
<< " \"name\": \"" << name << "\",\n"
<< " \"size\": \"" << to_string(fin.gcount()) << "\",\n"
<< " \"content\": \"" << base64Encode(buf, fin.gcount()) << "\"\n"
<< " }";
}
fout << "\n]}";
}
decode.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <bits/stdc++.h>
using namespace std;
const string CONVERT_TABLE =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
string base64Decode(const string &s)
{
string::const_iterator sourcebuf = s.begin();
const int buflen = s.size();
string ret;
unsigned char split3[3];
unsigned char split4[4];
int len = buflen, ec = 0, seg = buflen / 4; //计算共可以切成几组
while (sourcebuf[--len] == '=') //判断末尾有几个等号,由此可知最后四个字节该如何处理
++ec;
for (int k = 0; k < seg; ++k)
{
for (int j = 0; j < 4; ++j) //每四字节为一组进行处理
{
int index = 0;
char c = *(sourcebuf++);
for (int i = 0; i < CONVERT_TABLE.size(); ++i) //由码索引数
{
if (CONVERT_TABLE[i] == c)
{
index = i;
break;
}
}
split4[j] = index;
}
if (k + 1 == seg)
{
if (ec == 2) //当末尾有两个等号时的处理
{
split3[0] = (split4[0] << 2) + ((split4[1] & 0x30) >> 4);
ret += split3[0];
}
else if (ec == 1) //当末尾有一个等号时的处理
{
split3[0] = (split4[0] << 2) + ((split4[1] & 0x30) >> 4);
split3[1] = ((split4[1] & 0x0f) << 4) + ((split4[2] & 0x3c) >> 2);
ret += split3[0];
ret += split3[1];
}
else
{
split3[0] = ((split4[0] & 0x3f) << 2) + ((split4[1] & 0x30) >> 4);
split3[1] = ((split4[1] & 0x0f) << 4) + ((split4[2] & 0x3c) >> 2);
split3[2] = ((split4[2] & 0x03) << 6) + split4[3];
for (int j = 0; j < 3; ++j)
ret += split3[j];
}
}
else
{
split3[0] = ((split4[0] & 0x3f) << 2) + ((split4[1] & 0x30) >> 4);
split3[1] = ((split4[1] & 0x0f) << 4) + ((split4[2] & 0x3c) >> 2);
split3[2] = ((split4[2] & 0x03) << 6) + split4[3];
for (int j = 0; j < 3; ++j)
ret += split3[j];
}
}
return ret;
}
int main()
{
ifstream fin("FileSet.pak");
unordered_map<string, string> mp;
int cnt = 0, tmp;
for (string s; getline(fin, s);)
{
if (s.find("[") != s.npos)
continue;
if (s.find("]") != s.npos)
break;
if (tmp = s.rfind(","), tmp != s.npos)
s.erase(tmp);
if (s.find("}") != s.npos)
{
ofstream fout(to_string(++cnt) + "." + mp["name"]);
fout << base64Decode(mp["content"]);
mp.clear();
}
else if (tmp = s.find(":"), tmp != s.npos)
{
string t = s.substr(tmp + 1);
s.erase(tmp);
s.erase(s.find_last_not_of("\" ") + 1);
t.erase(t.find_last_not_of("\" ") + 1);
mp.insert({s.substr(s.find_first_not_of("\" ")), t.substr(t.find_first_not_of("\" "))});
}
}
}
实验体会
在结构数据保存和读出实验中,使用了CSV表格的格式来存储数据,然而这个格式操作有些繁琐,因此单独设计了一个struct对其进行操作,使得代码更加清晰易读。
在多文件的合并保存和读出实验中,使用了流行的JSON格式来保存数据。然而,使用JSON保存文件的正文时,很难区分JSON本身格式上的内容和文本文档的内容;并且很多文件是不可以按照ascii文本文档的方式打开的。解决方式是使用二进制方式打开文件,并对其内容使用Base64形式进行编码,最终得以解决问题。