目录
二. C标准IO + C++标准IO 刨析及其解决字符串输入空格结束问题刨析 (以及常用IO函数刨析)
五. 从C语言的 fprintf 进化到 sstream 实现序列化和反序列化
一. IO缓冲区存在的意义 (屏蔽低级IO)
- 缓冲区一般是行设计…. 也就是缓冲区大小一般就叫做一行, 一般就是1024字节 char buff[1024]
- 有了缓冲区, 我们就可以按照缓冲区大小进行读写操作, 基于行, 缓冲区 进一步向上封装成了C语言的 scanf 和 printf 这样的 可以跨平台使用的函数
缓冲区好处 :
- 可以屏蔽掉低级I/O的实现 ( 低级IO就是基于操作系统内核实现的系统调用), 可以减少系统调用次数 (用户空间向内核空间切换次数) 提高效率 方便写出可移植的程序。
- 有了行的概念, 可以实现行读取, 解析整个缓冲区中的内容返回一个行
二. C标准IO + C++标准IO 刨析及其解决字符串输入空格结束问题刨析 (以及常用IO函数刨析)
- FILE 的本质就是一个句柄 : 句柄思想, 使用简单的一个FILE* 就可以操作背后对应的文件, 做个比喻就是有点像是使用 遥控器 控制一个机器人, 通过对一个把手开关的简单操作, 将对应操作落实到一个比较复杂的东西上去, 操作 FILE就是操作文件 。。。 底层落实到 内核上的files_struct结构体上面去了
- fflush : 将缓冲区中的内容实时刷新写入到文件中去. (文件不一定是 stdout 其他也可)
- 其实可以简单做一个小小的测试…. 如下使用sleep + printf() + fflush(stdout) 测试 fflush功能 stdin + stdout + stderr 是在一开始就打开的三个文件流… 操作系统给我们打开的 在 Linux 环境下测试才可获取结果: windows环境下好像不行应该是做了优化
[tangyujie@VM - 4 - 9 - centos ~]$ cat test.c #include <stdio.h> int main() { printf("haha"); //fflush(stdout); while (1); return 0; }
- 希望读者自行测试, 上述存在 fflush 和 屏蔽fflush 效果是完全不一样的, 屏蔽掉发现显示器上没有输出, 说明 缓冲区中的数据没有刷新到显示器上 (没有写入标准输出设备文件中去) 解除屏蔽则可以瞬间刷新
- 然后接下来针对scanf提出一个小小要求 : 很多时候初学C语言的时候都在烦恼的问题. 想要输入一个完成的以行结束的字符串, 可以经常遇到 空格 就被截断的尴尬情况????? 咋办
此处给出格式化输入的办法: 专门应对 C语言想要输入字符串 但是不想被中间空格截断情况
#include <stdio.h>
int main() {
char buff[256] = { 0 };
scanf("%[^\n]%*c", buff);
printf("buff: %s\n", buff);
return 0;
}
- C++ 中 为了充分体现面向对象编程的思想, 其中的 输入输出都是定义的类 然后重载 operator <<运算符 和 operator >> 运算符来实现的.
- 而且将 ios 类叫做流类的基类: 为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功能…..
- C++ 将其形象的比作数据流, 使得 C++中对于数据的输入输出操作更加直观 。。。。
- C++标准库提供了4个全局流对象cin、cout、cerr、clog,使用cout进行标准输出,即数据从内存流向控制台(显示器)。使用cin进行标准输入即数据通过键盘输入到程序中,同时C++标准库还提供了cerr用来进行标 准错误的输出,以及clog进行日志的输出,从上图可以看出,cout、cerr、clog是ostream类的三个不同的 对象,因此这三个对象现在基本没有区别,只是应用场景不同。 在使用时候必须要包含文件并引入std标准命名空间。
注意点 :
- 空格和回车都可以作为数据之间的分格符 : 如何解决空格不能输入问题????
- cin和cout可以直接输入和输出内置类型数据, 但是自定义类型必须自己重载operator << 和 operator >>才可以为什么???
使用 getline 全局函数 解决 cin 空格结束问题
int main() {
string s;
//cin >> s; //空格就结束了, 达不到输入一行的效果
getline(cin, s); //成功读取一行,以回车键结束
cout << s << endl;
return 0;
}
内置类型, 标准内库已经帮助我们做好了重载了, 所以自定义类型可以直接进行输入
最后再解决一个C++ 中循环输入的疑惑问题, 为何C++可以支持 while (cin >> 对象 ) {操作;} 这样的奇葩操作??? cin 的返回值 就是 istream流对象
- 这个操作的意思是什么??? 意思就是说 返回值 istream 对象可以做判断, 对象如何做判断???
- 其实是因为 C++在 ios 类中重载了 operator bool
三. C 文件IO (从常用函数刨析到具体案例实现)
- 先是文本文件读写操作函数分析, 以及实际案例实操
- 上述注意点, 以及所有文件读取操作的时候一定注意的就是 是不是文件末尾我们只有读取之后才知道, 所以一般为了避免对于文件末尾的多余操作, 很多时候会先读取后操作, 还有很多是在循环外面先读取一次文件, 其实都是这个道理
代码写起来, 学以致用
int main() {
FILE* fp;
if ((fp = fopen("./test.txt", "r+")) == NULL) {
perror("Error open file");
exit(EXIT_FAILURE);
}
char ch; //一定先读取之后才判断是否是结尾
/*
或者这样写
ch = fgetc();
while (ch != EOF) {
操作;
ch = fgetc(fp);
}
*/
//又或者是这样写
/*
ch = fgetc(fp);
while (!feof(fp)) {
fputc(ch, stdout);//操作
ch = fgetc(fp);
}
*/
/*
while ((ch = fgetc(fp)) != EOF) {
fputc(ch, stdout); //操作
}
*/
fclose(fp); //最后一定关闭文件, 养成好习惯
return 0;
}
继续文本文件操作: 接下来介绍格式化输入输出函数….
测试使用:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main() {
srand((unsigned int)time(NULL));
FILE* fp;
if ((fp = fopen("./test.txt", "r+")) == NULL) {
perror("Error open file");
exit(EXIT_FAILURE);
}
int a, b;
for (int i = 0; i < 3; ++i) {
fprintf(fp, "%d\t%d\n", a = rand() % 1024, b = rand() % 1024);
printf("a: %d, b: %d\n", a, b);
}
/* //先写入后读取
int aa, bb;
for (int i = 0; i < 3; ++i) {
fscanf(fp, "%d\t%d\n", &aa, &bb);
printf("aa: %d, bb: %d\n", aa, bb);
}
*/
fclose(fp);
return 0;
}
- 然后进行二进制读写操作, 同时这个也叫块读写操作, 每一次都是读取连续的内存块….
- 二进制读写操作缺陷 : 会产生乱码 (对于非字符串会乱码)
- 先函数图解刨析:
学以致用, 常规用法: 一般来说二进制内存块文件读写操作都是针对数组 这种连续内存比较合适, 不论什么数组, 或者是结构体也是可以的…… 核心关键 : 内存连续 多个内存小块(一个内存大块)
typedef struct student {
int age;
char name[20];
}STU;
int main() {
//针对结构体数组其实用二进制块读写操作简直不要太爽 (小缺陷: 乱码)
STU s[3] = { { 18, "张三" }, { 20, "李四" }, { 24, "王五" } };
FILE* fp;
if ((fp = fopen("myfile.bin", "rb+")) == NULL) {
perror("Error open file");
exit(EXIT_FAILURE);
}
//fwrite(s, sizeof(STU), 3, fp);
/*
//先写入之后屏蔽写入放开读取测试
int n = fread(s, sizeof(STU), 3, fp);//从里面读取
//n : 成功读取的小块数目
for (int i = 0; i < n; ++i) {
printf("name: %s, age: %d\n", s[i].name, s[i].age);
}
*/
fclose(fp);
return 0;
}
- 定位函数 常用图解刨析 (定的什么位, 文件指针所指位)
- 活学活用, 上述 其实 rewind 完全是鸡肋了, 因为 fseek 完全可以代替其功能, 位异设置为0 直接起点文件指针设置为 SEEK_SET 可以达到一致效果: 如下代码端可以验证:
int main() { FILE* fp; if ((fp = fopen("test.txt", "r+")) == NULL) { perror("Error open file"); exit(EXIT_FAILURE); } //首先是演示常用功能1:fseek + ftell 可获取文件大小 { fseek(fp, 0, SEEK_END);//定位文件末尾 int file_size = ftell(fp);//获取文件开头到当前位置字节数 printf("file_size: %d\n", file_size); } //演示功能 2: fseek 如何达到和rewind一致效果 { //rewind(fp); fseek(fp, 0, SEEK_SET); printf("cur position is %d\n", ftell(fp)); } return 0; }
四. C++ 文件IO (从常见方法刨析到 案例实现)
基本面向对象式的实现文件读写操作, 核心 C++ 相对于C 函数式 —–> 过度到 面向对象实现文件IO操作.. (fstream 基类 : 既可以完成文件读也可以完成文件写操作, ifstream 仅文件读操作, ofstream, 仅文件写操作, C++文件读写操作 都是基于上面三个类的实例化对象完成的……)
图解刨析一下上述类的构造函数:
C++文件操作流程
- 定义一个 fstream\ifstream\ofstream 对象,调用默认构造函数
- 调用open 成员函数 (打开文件, 建立 对象和磁盘文件的关联)
- 其实上述两个步骤可以合二为一(合成调用有参构造,在构造函数中传入文件参数)
- 调用成员函数进行文件读写操作 或者是 使用C++专属的 流式 操作, 其实就是对于 operator << 和 operator >> 运算符的重载, 利用 << 和 >> 运算符 进行直观的文件读写操作
- ( 其实底层的 << 和 >> 本质 还是在重载函数中 进行的调用成员函数处理方式,只不过不需要我们写了. 直接用<< 和 >> 非常便捷)
文本读写操作
- 第一个文本读get方法:
代码测试:
int main() {
{
ifstream ifs("test.txt"); //mode默认 ios::in
if (!ifs) {
cerr << "destruct ifs error" << endl;
exit(EXIT_FAILURE);
}
char ch;
/*方式1:
while ((ch = ifs.get()) != EOF) {
cout << ch;
}
*/
/*方式2:
while (ifs.get(ch)) {
cout << ch;
}
*/
//方式3:
while (ifs) {
cout << (char)ifs.get();
}
}
ifs.close(); //也可以不写, 析构中会清理
return 0;
}
- 文本写put方法….
实例测试:
int main() {
//文件写 默认是 ios::out, 不存在会create a file
ofstream ofs("writefile.txt");
char ch;
//提示一个 \n回车换行键 作为结束
cout << "Type some text (type a \\n to finish):" << endl;
do {
ch = cin.get();
ofs.put(ch);
} while (ch != '\n');
return 0;
}
二进制文件读写操作 + << >> 文件读写操作: ( 使用 << 和 >> 替代了 C 中的格式化文件读写操作 ) << 和 >> 插入提取运算符 重载 代替了 C 语言中的格式化输入输出方式
- 首先 文件打开方式需要重新设置, 需要加入 ios::binary 进去, 然后是介绍两个函数, 几乎和C用法差不多, 只不过变成了类的方法, read() 和 write() 方法 (缺陷, 还是乱码)
//基础案例:
int main() {
//先写点东西进去, 然后才能够读取
/*
ofstream ofs("test.txt", ios::out | ios::binary);
ofs.write("Hello write\n", strlen("Hello write\n"));
ofs.close();
*/
ifstream ifs("test.txt", ios::in | ios::binary);
if (!ifs) {
cerr << "Error open file" << endl;
exit(EXIT_FAILURE);
}
//第一步先获取文件大小, 开缓冲区.....
ifs.seekg(0, ifs.end);
int length = ifs.tellg();
ifs.seekg(0, ifs.beg);
char *buff = new char[length];
ifs.read(buff, length);
//此处体现的C++ 判断读取的方式是直接 判断 ifs(对于bool的重载)
//C语言则是通过返回值判断
if (ifs) {
cout << "Read all bytes success and read " << length << " bytes" << endl;
buff[length] = 0;
cout << buff << endl;
}
else {
cerr << "Read error" << endl;
exit(EXIT_FAILURE);
}
ifs.close(); //有始有终, 虽然最后ifstream的析构中会处理
return 0;
}
流式用法, 替换C语言的格式化, 简易测试一下:
int main() {
int age = 10;
char name[20] = "张三";
//先向文件中流入数据
/*
ofstream ofs("test.txt");
ofs << age << "\t" << name; //直接进行数据流入
ofs.close(); //好习惯
//(类比 cout << 向标准输出设备文件显示器上输出)
*/
ifstream ifs("test.txt");
if (!ifs) {
cerr << "Error open file" << endl;
exit(EXIT_FAILURE);
}
ifs >> age >> name; //数据流出操作
//(类比 cin >> 从标准输入设备文件键盘上读取数据存储到内存)
cout << "age: " << age << ", name: " << name << endl;
ifs.close(); //好习惯
return 0;
}
来一个总的类 测试案例 (这个是比较常用的, ip + port管理类):
- 两个(常用)函数刨析:string <—–> int (C++的)
char* <—–> int C语言版本函数刨析 :
struct ServerInfo
{
char _ip[20];
int _port;
};
struct ConfigManager
{
public:
ConfigManager(const char* filename)
:_filename(filename)
{}
void ReadBin(ServerInfo& info)
{
ifstream ifs(_filename);
ifs.read(char*)&info, sizeof(info));
}
void WriteBin(const ServerInfo& info)
{
ofstream ofs(_filename);
ofs.write((char*)&info, sizeof(info));
}
void WriteTxt(const ServerInfo& info)
{
/*ofstream ofs(_filename);
ofs.write(info._ip, strlen(info._ip));
ofs.put('\n');
string portstr = to_string(info._port);
ofs.write(portstr.c_str(), portstr.size());*/
// C++流多提供的,其他的c一样都可以实现
ofstream ofs(_filename);
ofs << info._ip <<"\n"<< info._port;
}
void ReadTxt(ServerInfo& info)
{
//ifstream ifs(_filename);
//ifs.getline(info._ip, 20);
//char portbuff[20];
//ifs.getline(portbuff, 20);
//info._port = stoi(portbuff);
// C++流多提供的,其他的c一样都可以实现
ifstream ifs(_filename);
ifs >> info._ip >> info._port;
}
private:
string _filename;
};
//int main()
//{
// ServerInfo rinfo;
// ServerInfo winfo = {"192.0.0.1", 8000};
// // 读写 -- 二进制 -- 读写简单、高效快捷。 缺点:除了字符和字符串,内存中写到文件,是乱码
// /*ConfigManager cfbin("config.bin");
// cfbin.WriteBin(winfo);*/
// //ConfigManager cfbin("config.bin");
// //cfbin.ReadBin(rinfo);
//
// // 读写 -- 文本
// //ConfigManager cftxt("config.txt");
// //cftxt.WriteTxt(winfo);
//
// ConfigManager cftxt("config.txt");
// cftxt.ReadTxt(rinfo);
//
//
// return 0;
//}
文件定位操作
- 函数刨析:
测试代码:
int main() {
ifstream ifs("test.txt");
if (!ifs) {
cerr << "Destruct ifstream obj error" << endl;
exit(EXIT_FAILURE);
}
int length;
ifs.seekg(0, ifs.end);
length = ifs.tellg();
ifs.seekg(0, ifs.beg); //回到开头了
cout << "file_size: " << length << endl;
ifs.close();
while (1);
return 0;
}
int main() {
ifstream ifs("test.txt");
if (!ifs) {
cerr << "Destruct ifstream obj error" << endl;
exit(EXIT_FAILURE);
}
int length;
ifs.seekg(0, ifs.end);
length = ifs.tellg();
ifs.seekg(0, ifs.beg); //回到开头了
cout << "file_size: " << length << endl;
ifs.close();
return 0;
}
五. 从C语言的 fprintf 进化到 sstream 实现序列化和反序列化
- 首先刨析一下 sprintf + sscanf
代码测试:
//案例1
int main() {
int a = 10;
char s[5] = "abc";
char buff[20] = { 0 };
sprintf(buff, "%d %s", a, s); //序列化
a = 100;
s[0] = 'c';
sscanf(buff, "%d %s", &a, s); //反序列化还原
cout << a << " " << s << endl;
while (1);
return 0;
}
//案例2:
/*
int main ()
{
char sentence []="Rudolph is 12 years old";
char str [20];
int i;
sscanf (sentence,"%s %*s %d",str,&i);
printf ("%s -> %d\n",str,i);
return 0;
}
*/
- C++ 中的 序列化 和 反序列化 (针对这个:专门设置了一个字符串流类) string streams 字符串流
既然是流所以还是支持流式的数据流入和流出的….. 测试代码如下:
int main() {
stringstream ss;
int age = 20;
char name[20] = "张三";
ss << age <<"\t"<< name;//先流入到 ss 中去
age = 100;
strcpy(name, "12345"); //先改一下 这样后面才知道是不是流出了数据的
ss >> age >> name;
cout << "age: " << age << " " << "name: " << name << endl;
return 0;
}
从一道实例题目上手: 序列化和反序列化二叉树
剑指 Offer II 048. 序列化与反序列化二叉树
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string ans = "";
if (root == NULL) return ans; //直接返回就是了
stringstream in_out;
in_out << root->val; //流入根部
in_out >> ans; //每次将ss流入到ans中
/*
1.后面只要右孩子 就需要加上 ()
2.如果有右孩子加上 ,
3. 先 ( + 左孩子序列 + , + 右孩子序列 + )
*/
if (root->left || root->right) {
ans += "("; //只要有一个孩子加上(
}
ans += serialize(root->left); // + 左孩子
if (root->right) ans += ","; //存在右孩子 + ,
ans += serialize(root->right);
if (root->left || root->right) {
ans +=")";
}
return ans;
}
int toi(const string& s, int& i) {
int val = 0;
bool flag = 0; //标记负数
if (s[i] == '-') {
flag = 1;
i += 1; //跳过负号
}
for (; s[i] >= '0' && s[i] <= '9'; ++i) {
val = val * 10 + (s[i] - '0');
}
if (flag) val *= -1;
return val;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
//广义表转二叉树
TreeNode* root = NULL, *pTemp = NULL; //保存 根部 + 临时节点
stack<TreeNode*> st;
bool flag = 0; //标记是否存在右孩子
for (int i = 0; i < data.size(); ++i) {
switch(data[i]) {
case '(' : {
st.push(pTemp);// ( : 节点入栈
flag = 0; // 重置 flag
} break;
case ',' : {
flag = 1;//说明存在右孩子
} break;
case ')' : {
root = st.top();//记录可能的根部
st.pop(); //) pop节点
} break;
default : { //说明是一个实际节点
int val = toi(data, i); //提取数据
pTemp = new TreeNode(val);
if (!st.empty() && flag == 0) {
st.top()->left = pTemp; //栈顶节点的左孩子
} else if (!st.empty() && flag == 1) {
st.top()->right = pTemp;//栈顶节点的右孩子
}
i -= 1; //(需要体会) i -= 1(因为在 toi中 i走到哪里去了?)
} break;
}
}
if (root == NULL && pTemp) root = pTemp; //仅有一个孩子
return root;
}
};
- 解决上述题目 首先需要运用序列化 利用 stringstream 将 所有的 int数据转换为 string 并且实现字符串的广义表形式表示
- 解码 就是广义表转二叉树的过程, 如下博客可以解决这个问题:C语言, C++ IO 总结. 一篇文章帮你透析缓冲区存在的意义, C, C++ IO的常见用法
六. 总结本章
- 首先我们从为何需要缓冲区 入手解析 C 和 C++ 这些对系统调用进一步封装实现的 更高层的IO方法的好处 (减少系统调用次数) 提高效率 便利写出可移植性更好的代码
- 然后解析 C 语言 IO 和 C++ IO 的 基本 函数 和 C++流式IO的引入, C++流式IO 核心关键在于operator << 和 operator >> 运算符的重载实现. 以及 C 语言 利用 %[^\n]%*c 实现输入字符串仅仅以回车结束, getline(cin, s); 实现 C++的字符串输入仅回车结束
- C语言常见的文件IO 文本 + 二进制 + 格式化 + 定位
- C++ 常见的文件IO 文本 + 二进制 + 流式(代替格式化) + 定位
- sscanf + sprintf 的字符串序列化 + 反序列化 实现 + 整形转字符串 + 字符串拼接 转换为 C++中的 利用 stringstream实现的 序列化和反序列化
上述仅个人的学习C和 C++的一个笔记小结, 很多是个人看法和用法, 如有不足或者错误之处欢迎大家改正, 祝看本文的人一切顺利,共同进步 (内容简单, 但书写不易, 如果觉得有帮助, 希望点赞支持, 三连缘分)
今天的文章C语言, C++ IO 总结. 一篇文章帮你透析缓冲区存在的意义, C, C++ IO的常见用法分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/66035.html