• 【C++进阶之路】IO流


    一、C语言的IO

    1.键盘与显示屏

    //键盘与显示屏的交互
    int main()
    {
    	//从键盘读取格式化字符串。
    	int i = 0;
    	scanf("%d", &i);
    	//在显示屏上打印获取到的信息。
    	printf("%d", i);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2. 文件与内存

    int main()
    {
    	FILE* fptr = fopen("test.txt", "w+");
    	//w只写、w+可读可写。
    	//向文件里输入格式化字符串
    	fprintf(fptr, "%d hello", 123);
    	
    	//调整文件指针的位置为开头
    	rewind(fptr);
    	//从文件里面读取字符串与数字。
    	char arr[15] = { 0 };
    	int n = 0;
    	//只能以某种形式对字符串进行解读
    	fscanf(fptr, "%d%s", &n,arr);
    	printf("%s %d", arr, n);
    	fclose(fptr);//关闭文件并刷新缓存区。
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.字符串与内存

    //字符串的输入输出,对字符串按指定方式进行解读。
    int main()
    {
    	//从内存向字符串输出可控制的格式化字符串
    	char str[100];
    	sprintf(str, "hello %d", 11);//这里的11可变换为变量。
    	cout << "输出的字符串为:" << str << endl;
    	//从字符串向变量中读入可控制的格式化字符串
    	int i = 0;
    	char buf[256];
    	sscanf(str, "%s %d",buf,&i);
    	cout<<"输入的字符为:" << i << " " << buf << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 总结:

    • printf 与 scanf 是显示屏与键盘的IO函数

    • sprintf 与 sscanf 是字符串与内存的IO函数

    • fprintf 与 fscanf 是内存与文件的IO函数

    • 唯一不足的就是读取只能一个一个按照顺序的进行读取,无法按照自己想法

    对输入输出即 printf 与 scanf 的英文理解:

    在这里插入图片描述
    说明:这是基于内存的理解,方便理解printf即从内存往外输出,scanf即从外面向内存读写。

    补充:程序在运行时默认打开三个流——stdin,stdout,stderror。

    至于更多C语言的文件操作内容详见【进阶C语言】文件操作

    二、C++IO

    • c++以继承的方式,梳理了所有的流。

    在这里插入图片描述
     其中是我们经常包的头文件,现在看来竟然还是一个菱形虚拟继承,不愧是实现库的大佬,没有困难创造困难迎着困难上,不过也为我们提供一点的便利。

    1.iostream

    1.1基本使用

    • cin和cout是我们经常使用的。
    • cerr是面向标准错误流的,clog是应用于日志的,不过日常我们使用前两个即可。

    先来看最简便的一段C++代码:

    int main()
    {
    	int x;
    	cin >> x;//从键盘对x进行输入数据
    	cout << "hello world" << endl;//向显示器上打印数据
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

     不知各位刚学这一段代码的时候,是什么感觉,我是感觉看起来比较便捷形象易懂,只是最近再看其实就是一大堆的运算符重载形成的集合,而且当我们想要用char*打印地址时,它其实给我们呈现的是一段字符串,而不是地址, 不过用起来是真香。

    对自定义类型输入和输出比如之前文章中出现的日期类:

    class Date
    {
    	friend ostream& operator << (ostream& out, const Date& d);
    	friend istream& operator >> (istream& in, Date& d);
    public:
    	Date(int year = 1, int month = 1, int day = 1)
    		:_year(year)
    		, _month(month)
    		, _day(day)
    	{}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    istream& operator >> (istream& in, Date& d)
    {
    	in >> d._year >> d._month >> d._day;
    	return in;
    }
    ostream& operator << (ostream& out, const Date& d)
    {
    	out << d._year << " " << d._month << " " << d._day;
    	return out;
    }
    int main()
    {
    	Date d1;
    	cin >> d1;//其原理就是调用自定义的运算符重载函数。
    	cout << d1 << endl;//同理。
    	return 0;
    }
    
    • 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

    稍微总结一下:

    1. 使用运算符重载(流插入和流提取)进行实现,更加形象易懂。
    2. 自定义类型也可重载自己的流插入和流提取,也可使用cin和cout。
    3. 自动类型识别,无需在进行格式化控制。

    说明:

    运算符重载vs函数重载

    1. 有更严格的要求,要尽可能在多种状况下都能正确运行。
    2. 运算符重载则需要更多的了解系统中对表达式处理的各种默认形式,那些是你无法去改变的,只能努力适应。
    • 实用小知识

    同时回想在进行刷题时,如果面临读入一整行的字符串数据,有什么方法吗?

    1. gets
    char* gets(char* str);
    
    • 1
    • 缺陷:这个函数比较危险,是因为从缓存区往str读入数据时,并不知道str所指向空间的大小,因此可能会导致使str指向的空间进行越界访问,因此是危险的。
    • 说明:C++(vs2019下)此函数已删除。
    1. fgets
    char * fgets ( char * str, int num, FILE * stream );
    
    • 1

    基本使用:

    int main()
    {
    	char buf[256];
    	//从标准输入流(键盘)里读取最大不超过buf大小的一行数量,到buf数组中
    	fgets(buf, sizeof(buf), stdin);
    	cout << buf << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. scanf
    int main()
    {
    	char buf[256];
    	//从标准输入流(键盘)里读取一行数量,到buf数组中
    	scanf("%[^\n]", buf);//直到遇到换行才停止。
    	cout << buf << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 缺陷是跟gets一样的,不过因为比较好用编译器保留了下来,可以用宏/预处理指令进行屏蔽错误。

    4.getline(C++)

    • c++里面的string里面提供了一个接口getline,可以获取一行的字符串。
    #include
    #include
    int main()
    {
    	string buf;
    	//从标准输入流(键盘)里读取一行数量,到buf中
    	getline(cin, buf);
    	cout << buf << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 这是最便捷的也最安全的输入一行字符串的方法,因为string内部会自动进行扩容,也是C++刷题最经常用的方法。

    1.2operator bool

     除此之外,我们可能对多组输入的cin,会产生疑惑 , 比如如下一段代码:

    int main()
    {
    	int x;
    	while (cin >> x)
    	{
    		cout << x << endl;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    istream& operator>> (int& val);
    //返回的是cin,也就是类型为istream的对象,
    //而while()里面进行判断的是bool值,两者能进行转换吗?答案是肯定能的,
    //那是如何进行转换的呢?
    
    • 1
    • 2
    • 3
    • 4

    就比如:

    class A
    {
    public:
    private:
    	int _a = 0;
    };
    int main()
    {
    	A a;
    	if(a)//此处会报错:表达式必须包含 bool 类型(或可转换为 bool)	
    	{}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 那如何转换为bool呢?其实运算符重载就出现了。
    class A
    {
    public:
    	operator bool()
    	{
    		return _a == 0 ? false : true;
    	}
    private:
    	int _a = 0;
    };
    int main()
    {
    	A a;
    	if(a){}
    	//这也间接说明,iostream是实现了operator bool的。
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    说明:这里的实现的运算符重载没有返回值是有点奇怪的,这里当做特殊情况对待即可,不必深究。

    • cin转bool底层原理:设置标记

    在这里插入图片描述

    补充:在对循环终止时,有两种方式,ctrl z(正常终止循环) / Ctrl c(退出当前程序)。

    2. fstream

    在进行输入/输出时可有几种模式:

    在这里插入图片描述

    1. 对于fostream的对象默认打开了out,对于fistream的对象默认打开了in。
    2. 操作可通过 | 进行结合起来。
    3. truncate——对文件之前的内容进行覆盖。
    4. 对于有些对象比如fstream,可以支持输入也可以支持输出。

    2.1二进制的文件读写

    struct PeopleInfor
    {
    	PeopleInfor()
    	{}
    	PeopleInfor(const char* id,const char* name,int age)
    		//:_id(id)
    	{
    		strcpy(_id, id);
    		strcpy(_name, name);
    		_age = age;
    	}
    	char _id[256];//家庭住址
    	char _name[256] = {0};//名字
    	int _age = 0;//年龄
    };
    struct ManagePeoInf
    {
    	//初始化
    	ManagePeoInf(const char* file)
    		:_FileName(file)
    	{}
    	//向文件以二进制写入个人信息
    	void WriteBianry(const PeopleInfor& pe)
    	{
    		ofstream of(_FileName, ios_base::out | ios_base::binary);
    								//这里的out默认已经有了,可以不写。
    		of.write((char*)&pe, sizeof(pe));
    	}
    	void ReadBinary(PeopleInfor& pe)
    	{
    		ifstream in(_FileName, ios_base::in | ios_base::binary);
    								//这里的in默认已经有了,可以不写。
    		in.read((char*)&pe, sizeof(pe));
    	}
    	//向文件以二进制读取个人信息
    	string _FileName;
    };
    
    void BinaryWrite()
    {
    	PeopleInfor pe1 = { "河南省","舜华",19 };
    	ManagePeoInf m1("text.txt");
    	//向文件中输出字符串x
    	m1.WriteBianry(pe1);
    }
    void BinaryRead()
    {
    	PeopleInfor pe2;
    	ManagePeoInf m2("text.txt");
    	m2.ReadBinary(pe2);
    	cout << pe2._id << " " << pe2._name << " " << pe2._age << endl;
    }
    int main()
    {
    	//向文件里以二进制的形式写入数据
    	BinaryWrite();
    	//从文件中以二进制的形式读取数据
    	BinaryRead();
    	return 0;
    }
    
    • 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

    说明:这里的_id与_name并没有采用string的形式进行使用,而是char类型的数组进行的使用,其原因在于拷贝的是对象的内存,而string里面存的是指向字符串的地址,string释放时,读取在恢复时,其实是一段野指针,即使访问成功了也是幸运。

    2.2字符串的文件读写

    struct PeopleInfor
    {
    	PeopleInfor()
    	{}
    	PeopleInfor(const char* id, const char* name, int age)
    		:_id(id)
    	{
    		//strcpy(_id, id);
    		strcpy(_name, name);
    		_age = age;
    	}
    	string _id;//家庭住址
    	char _name[256] = { 0 };//名字
    	int _age = 0;//年龄
    };
    struct ManagePeoInf
    {
    	//初始化
    	ManagePeoInf(const char* file)
    		:_FileName(file)
    	{}
    	//向文件以二进制写入个人信息
    	void WriteText(const PeopleInfor& pe)
    	{
    		ofstream of(_FileName, ios_base::binary);
    		of << pe._id << " " << pe._name << " " << pe._age;
    		//这里的id是调用了string的流插入。
    	}
    	void ReadText(PeopleInfor& pe)
    	{
    		ifstream in(_FileName, ios_base::binary);
    		in >> pe._id >> pe._name >> pe._age;
    		//这里的_id是调用了string的流提取。
    		//读取自动识别空格与换行与分割符。
    	}
    	//向文件以二进制读取个人信息
    	string _FileName;
    };
    void WriteText()
    {
    	PeopleInfor pe1 = { "河南省","舜华",19 };
    	ManagePeoInf m1("text.txt");
    	m1.WriteText(pe1);
    }
    void ReadText()
    {
    	PeopleInfor p2;
    	ManagePeoInf m2("text.txt");
    	m2.ReadText(p2);
    	cout << p2._id << " " << p2._name << " "  << p2._age << endl;
    }
    int main()
    {
    	WriteText();
    	ReadText();
    	return 0;
    }
    
    • 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

    3. sstream

    3.1序列化与反序列化

    • 此处说明:这里存在序列化与反序列化,因为在存储的形式中,要么以二进制的形式进行存储(人看不懂),要么以字符串的形式进行存储(序列化),总之在计算机用于计算的类型比如int,float,double等形式,在文件中不复存在,因此计算机要想用存储的数据进行计算,就需要将字符串转换为相应的格式再参与计算(反序列化)。
    #include
    #include
    #include
    using namespace  std;
    class Date
    {
    	friend ostream& operator << (ostream& out, const Date& d);
    	friend istream& operator >> (istream& in, Date& d);
    public:
    	Date(int year = 1, int month = 1, int day = 1)
    		:_year(year)
    		, _month(month)
    		, _day(day)
    	{}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    istream& operator >> (istream& in, Date& d)
    {
    	in >> d._year >> d._month >> d._day;
    	return in;
    }
    ostream& operator << (ostream& out, const Date& d)
    {
    	out << d._year << " " << d._month << " " << d._day;
    	return out;
    }
    int main()
    {
    	string str;
    	Date d = { 2023,10,21 };
    	ostringstream os;
    	os << d;
    	str = os.str();
    	cout<<"字符串:" << str << endl;
    
    	//再将字符串转换为日期类
    	Date d1;
    	istringstream is(str);
    	is >> d1;
    	cout<<"日期类:" << d1 << endl;
    	return 0;
    }
    
    • 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

    这里附上一道序列化与反序列化的题:二叉树的序列化与反序列化

    3.2拼接字符串

    #include
    #include
    #include
    using namespace  std;
    int main()
    {
    	ostringstream os;
    	os << "hello";//当然也可是一些变量,比如int
    	os << " world";//同理
    	cout << os.str() << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    说明:

    1. stringstream实际是在其底层维护了一个string类型的对象用来保存结果。
    2. 多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将stringstream底层的string对象清空。
    3. 可以使用s. str(“”)方法将底层string对象设置为""空字符串。
    4. 可以使用s.str()将让stringstream返回其底层的string对象。
    5. stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更安全。

    3.3将数据类型转换为字符串

    int main()
    {
    	int a = 12345678;
    	string sa;
    	// 将一个整形变量转化为字符串,存储到string类对象中
    	stringstream s;
    	s << a;
    	s >> sa;
    
    	s.str("");//将s维护的字符串置为空
    	s.clear();
    	// 说明:
    	// stringstreams在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit
    	// 因此下一次转换是必须调用clear()将状态重置为goodbit才可以转换
    
    	double d = 12.34;
    	s << d;
    	s >> sa;
    	string sValue;
    	sValue = s.str();
    	cout << sValue << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    总结

     今天的分享就到此结束了,我是舜华,期待与你的下次相遇!

  • 相关阅读:
    uni-app微信小程序上拉加载,下拉刷新
    红米Note12Turbo解锁BL刷入PixelExperience原生ROM系统详细教程
    Linux 进程层次分析
    vue3 封装 naive input 组件
    单元测试的重要性
    linux驱动之内核定时器
    基于SSM技术的oa办公管理系统的设计与实现毕业设计源码100934
    「小邓观点」SIEM解决方案的数据聚合组件
    element ui el-table表格复选框,弹框关闭取消打勾选择
    教你手机变流畅的方法
  • 原文地址:https://blog.csdn.net/Shun_Hua/article/details/133943497