当前位置: 首页 > news >正文

【C++】IO流

文章目录

  • 1.C语言的输入与输出
      • 对输入输出缓冲区的理解:
      • C++什么时候用printf,scanf 什么时候用cin和cout
  • 2.流是什么
  • 3. C++IO流
      • C++标准IO流
        • 注意事项:
      • C语言形式的读写
        • 以二进制形式读写
        • 以文本形式读写
      • C++文件IO流
      • C++形式的读写
        • 以二进制形式读写
        • 以文本形式读写
          • C++特供写法:使用>> << 进行读写
  • 4.stringstream的介绍
      • 注意事项:

1.C语言的输入与输出

C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()

scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中

printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)

注意宽度输出和精度输出控制


C语言借助了相应的缓冲区来进行输入与输出,如下图所示:

image-20220223214531522


对输入输出缓冲区的理解:

1.可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差异,可以很容易写出可移植的程序

2.可以使用这部分的内容实现“行”读取的行为,对于计算机而言是没有“行”这个概念,有了这部分,就可以定义“行”的概念,然后解析缓冲区的内容,返回一个“行”


C++什么时候用printf,scanf 什么时候用cin和cout

使用scanf和printf可能导致错误:

image-20220223222925297

如果真的要使用scanf就在使用前,先开好足够的空间 s._name.resize(100)


建议:

1.C++尽量取用cin和cout

2.如果要精准输出某个值的前几位,或者以什么方式打印,用cout和cin不方便的时候,再去用scanf和printf


2.流是什么

“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据( 其单位 可以是bit,byte,packet )的抽象描述

C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程 这种输入输出的过程被形象的比喻为“流”

特性:有序连续、具有方向性

为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功能


Student s;
cin>> s._name>>s._age;//数据从设备(终端)流向对象中(内存)
cout<<"名字":<<s._name<<"年龄"<<s._age<<endl;//数据对象(内存)流向设备(终端)

3. C++IO流

C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类

image-20220223214826570

cin 和 cout都是全局对象


C++标准IO流

C++标准库提供了4个全局流对象cin、cout、cerr、clog,

  • 使用cout进行标准输出,即数据从内存流向控制台(显示器)

  • 使用cin进行标准输入即数据通过键盘输入到程序中

  • 同时C++标准库还提供了cerr用来进行标准错误的输出,以及clog进行日志的输出,从上图可以看出,cout、cerr、clog是ostream类的三个不同的对象,因此这三个对象现在基本没有区别,只是应用场景不同

注意:在使用时候必须要包含文件并引入std标准命名空间


注意事项:

  1. cin为缓冲流,键盘输入的数据保存在缓冲区中
  • 当要提取时,是从缓冲区中拿 如果一次输入过多,则多余的数据会留在缓冲区以供后序提取
  • 如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了
  • 只有把输入缓冲区中的数据取完后,才要求输入新的数据
//使用cin和cout需要包含头文件iostream 以及引入命名空间std
#include<iostream>
using namespace std;
int main()
{
	int a = 0;
	cin >> a;
	cout << a;
}

//当然也可以在使用的时候指定cout和cin所属的命名空间
int main()
{
	int a = 0;
	std::cin >> a;
	std::cout << a;
}

//如果一次输入过多,则多余的数据会留在缓冲区以供后序提取
int main()
{
	int a = 0;
	int b = 0;
	cin >> a;//输入:20 30
	cout << a << endl;//20
	cin >> b;//直接从缓冲区提取,不需要我们输入
	cout << b << endl;//30
}
  1. 输入的数据类型必须与要提取的数据类型一致,否则出错

    • 出错只是在流的状态字state中对应位置位(置1),程序继续
  2. 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入

    • 但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有空格,回车符也无法读入,如果想要让字符串读取空格 ->使用getline()函数,第一个参数传cin
    • getline 遇到\n才停止
    int main()
    {
        string s;
        getline(cin,s);//获取字符串以换行符\n结束
        cout<<s<<endl;
    }
    
  3. cin和cout可以直接输入和输出内置类型数据,原因:标准库已经将所有内置类型的输入和输出全部重载了:

image-20220709124757082

所以内置类型我们可以直接使用cout<< 和cin >>进行输入和输出

int main()
{
    int i = 0;
    double d = 3.14;
    cout << i;//cout.operator<<(i)
    cout << d;//cout.operator<<(d)
    return 0;
}

  1. 对于自定义类型,如果要支持cin和cout的标准输入输出,需要对<<和>>进行重载 (建议在类外重载,在类里声明为友元函数)

例如:日期类

class Date
{
public:
	//声明为友元函数
	friend istream& operator>>(istream& in, Date& d);
	friend ostream& operator<<(ostream& out, Date& d);
	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, Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}
int main()
{
	Date d1;
	cin >> d1;//2022 1 1
	cout << d1;//2022/1/1
	return 0;
}

问:scanf/printf 和cin/cout的优势

scanf/printf 的缺点是只支持内置类型. cin/cout真正优势是,自定义类型重载了operator>> 和operator<<之后,自定义类型对象也可也直接输出


6.在线OJ中的输入和输出:

  • 对于IO类型的算法,一般都需要循环输入:

C语言的方式: 按ctrl+c终止程序

while(scanf("%d",&a) != EOF)  //EOF->-1
{}
//或者写成:
while(~scanf("%d",&a))  //~(-1) ->0
{}

C++的方式 按ctrl+z + enter结束程序

// 单个元素循环输入
while(cin>>a)  	
{}
// 多个元素循环输入
while(cin>>a>>b>>c)
{}
// 整行接收
while(cin>>str) //operator>>(cin,str)
    //istream& operator>>(istream& is,string& str) 返回流is对象 
    //流对象is调用operator bool函数,该函数没有返回值
{}
  • operator>>(cin,str) -> 返回cin对象,

    image-20220223224502229

  • operator bool函数没有返回值

    image-20220709125602844

小例子:

class A
{
public:
	operator bool()
	{
		return _a != 0;
	}
	int _a;
};
int main()
{
	A a;
	while (a)//相当于a.operator bool()
	{
		cin >> a._a;
	}
	return 0;
}

此时也支持连续输入, 当输入0时停止, 因为operator bool的条件时 _a != 0

  • 输出:严格按照题目的要求进行,多一个少一个空格都不行

C语言形式的读写

struct ServerInfo
{
	char _ip[20];//保存ip地址
	int _port;//端口号
};
//打开文件/关闭文件:fopen fclose
//文本读写:fprintf fscanf	二进制读写:fwrite fread

以二进制形式读写

以二进制形式写:

fwrite函数原型:

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
//ptr:指向写入的内容  size:每一个写入的内容是多少字节 count:写入多少个  stream:写到哪个流中

//以二进制形式写入
void TestC_Write_Bin()
{
	ServerInfo info = { "127.1.1.1",10 };
	FILE* fout = fopen("test.bin", "wb");
	//检查文件是否打开失败
	//assert(fout);
	if (NULL == fout)
	{
		perror("fopen");
		exit(-1);
	}
	//写文件
	fwrite(&info, sizeof(info), 1, fout);
	fclose(fout);
}

以二进制形式读

fread函数原型:

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
//ptr:从文件中读取放到ptr中  size:每一个读取的内容是多少字节 count:读取多少个 stream:从哪个流中读取

//以二进制形式读取
void TestC_Read_Bin()
{
	FILE* fin = fopen("test.bin", "rb");
	//检查文件是否打开失败
	//assert(fin);
	if (NULL == fin)
	{
		perror("fopen");
		exit(-1);
	}
	//读文件
	ServerInfo info;
	fread(&info, sizeof(info), 1, fin);
	fclose(fin);

	printf("%s:%d\n", info._ip, info._port);
}

测试:

int main()
{
	TestC_Write_Bin();
	TestC_Read_Bin(); //输出:127.1.1.1:10
	return 0;
}

以文本形式读写

以文本形式写入

fprintf形式:

int fprintf ( FILE * stream, const char * format, ... );

写入的时候注意事项:要用空格/换行符区分每一个项


//以文本形式写
void TestC_Write_Text()
{
	FILE* fout = fopen("test.txt", "w");
	//检查文件是否打开失败
	//assert(fout);
	if (NULL == fout)
	{
		perror("fopen");
		exit(-1);
	}
	ServerInfo info = { "127.1.1.1",10 };
	fprintf(fout, "%s %d", info._ip, info._port);//写入的时候,必须要有区分(空格/换行符)
	//fprintf(fout, "%s\n%d", info._ip, info._port);
	fclose(fout);
}

以文本形式读取:

fscanf形式:

int fscanf ( FILE * stream, const char * format, ... );

//以文本形式读
void TestC_Read_Text()
{
	FILE* fin = fopen("test.txt", "r");
	//检查文件是否打开失败
	//assert(fin);
	if (NULL == fin)
	{
		perror("fopen");
		exit(-1);
	}
	ServerInfo info;
	fscanf(fin,"%s%d", info._ip, &info._port);//读取,注意:_ip是数组名,所以不用加&,但是_port是变量,要加&
	fclose(fin);
	printf("%s:%d\n", info._ip, info._port);
}

测试:

int main()
{
	TestC_Write_Text();
	TestC_Read_Text();//127.1.1.1:10
	return 0;
}

C++文件IO流

C++根据文件内容的数据格式分为二进制文件和文本文件,采用文件流对象操作文件的一般步骤:

  1. 定义一个文件流对象
流对象(类)作用:
ofstream只写
ifstream只读
fstream读+写
  1. 使用文件流对象的成员函数打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系
打开方式功能
in以读的方式打开文件
out以写的方式打开文件
binary以二进制方式对文件进行操作
ate输出位置从文件的末尾开始
app以追加的方式对文件进行写入
trunc先将文件内容清空再打开文件

3.使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写

成员函数功能
put写入一个字符到文件中
write写入一段字符到文件
get从文件中读取字符
read从文件中读取多个字符
tellg获取当前字符在文件中的位置
seekg设置对文件进行操作的位置
>>运算符重载将数据能像“流”一样的形式进行输入
<<运算符重载将数据能像“流”一样的形式进行输出

4.关闭文件


C++形式的读写

class ConfigManager
{
public:
	ConfigManager(const char* filename)
		:_filename(filename)
	{}
private:
	string _filename;
};

下面的函数都可以写为类的成员函数

image-20220709151245520

image-20220709151304474

使用或的方式进行多种打开

例如:

以二进制形式写入数据模式为:ios_base::out | ios_base::binary
以二进制形式读取数据模式为:ios_base::in  | ios_base::binary

以二进制形式读写

以二进制的形式对文件进行写入:

void WriteBin(ServerInfo& info)
{
    ofstream ofs(_filename.c_str(), ios_base::out | ios_base::binary);//构造流对象
    ofs.write((const char*)&info, sizeof(ServerInfo));//把info的内容写入到ofs流对象
}

以二进制的形式对文件进行读取:

void ReadBin(ServerInfo& info)
{
    ifstream ifs(_filename.c_str(), ios_base::in | ios_base::binary);
    ifs.read((char*)&info, sizeof(ServerInfo));//将内容读取到info
    cout << info._ip << " " << info._port << endl;//验证是否读取了
}

测试 :

int main()
{
	ConfigManager con("hello");
	ServerInfo info = { "1.1.1",10 };
	con.WriteBin(info);
	con.ReadBin(info);//1.1.1 10
	return 0;
}

不需要检查文件是否打开成功,因为如果失败了会抛异常


以文本形式读写

可以使用>> 和<< 对文件进行操作

以文本的形式对文件进行写入:

void WriteTest()
{
	ofstream ofs("test.txt");//定义文件流对象,打开文件
	ofs << "helloMango\n";//将字符串(流)到文件中
	ofs.close();
}

以文本的形式对文件进行读取:

void ReadTest()
{
	ifstream ifs("test.txt");//定义文件流对象,打开文件
	string tmp;
	ifs >> tmp;//把文件流得到数据读取到tmp,以空格/换行作为结束
	ifs.close();

	cout << tmp << endl;
}
C++特供写法:使用>> << 进行读写

4.stringstream的介绍

在C语言中,如果想要将一个整形变量的数据转化为字符串格式,如何去做?

  1. 使用itoa()函数

image-20220223220227090

int a = 10;
char arr[10];
itoa(a,arr,10);//将整形a的转化为10进制字符数字存储在arr数组中

2.使用sprintf()函数

img

int a = 10;
char arr[10];
sprintf(arr,%d,a);//将整形a的转化为十进制基数格式存储在arr数组中

但是两个函数在转化时,都得需要先给出保存结果的空间,那空间要给多大呢,就不太好界定,而且转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃


在C++中

可以使用stringstream类对象来避开此问题, 在程序中如果想要使用stringstream,必须要包含头文件sstream

在该头文件下,有标准库三个类:

作用
istringstream流的输入操作
ostringstream流的输出操作
stringstream输入操作+输出操作

stringstream主要可以用来: (可以进行输入操作也可以进行输出操作)

  • 1.将数值类型数据格式化为字符串
#include<sstream>
int main()
{
    int a = 12345678;
    string sa;
    // 将一个整形变量转化为字符串,存储到string类对象中
    stringstream s;
    s << a;//将int类型的a变量的值放入输入流s中  (输入操作)
    s >> sa;//从流s中提取字符串赋给sa			 (输出操作)
    cout << sa<<endl;//输出sa的值:12345678
    // s.clear():注意多次转换时,必须使用clear将上次转换状态清空掉
    // stringstreams在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit
    // 因此下一次转换是必须调用clear()将状态重置为goodbit才可以转换.但是clear()不会将stringstreams底层字符串清空掉
    // s.str(""):将stringstream底层管理string对象设置成"",否则多次转换时,会将结果全部累积在底层string对象中
    s.str("");//清空s,
    s.clear(); // 重置状态.不重置会转化失败
    
    //进行下一次转化
    double d = 12.34;
    s << d;
    s >> sa;
    string sValue;
    sValue = s.str(); // str()方法:返回stringsteam中管理的string类型
    cout << sValue << endl;//12.34
    return 0;
}

  • 2.字符串拼接
int main()
{
    stringstream sstream;
    sstream << "first" << " " << "string,";    // 将多个字符串放入 sstream 中
    sstream << " second string";
    cout << "strResult is: " << sstream.str() << endl;//输出sstream流对象的内容:strResult is: first string, second string

    // 清空sstream流对象的内容
    sstream.str("");
    s.clear()//将上次转换状态清空
    sstream << "third string";
    cout << "strResult is: " << sstream.str() << endl;//strResult is: third string
    return 0;
}

注意事项:

注意:

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

相关文章:

  • Pytorch理解
  • 区块链中的隐私保护技术
  • postgres 源码解析 元组插入流程 Heap_Insert
  • 什么是数字化转型? 怎样算是转型?
  • js单行代码------日期和时间
  • 公众号网课查题接口
  • 科莱瑞迪IPO被终止:曾拟募资3.4亿 詹德仁夫妇控制63%股权
  • OpenCV入门(十三):常用图形绘制
  • 项目实战:QuickHit
  • 【听听iecne怎么说】C++技术的发展趋势, MFC过时了吗?QT呢?
  • C++1-C语言和C++的区别
  • iOS开发 - NSTimer极限使用
  • 【电商】电商后台设计—售后流程
  • 2022:【例4.7】最小n值
  • CDH 08Cloudera Manager freeIPAKerberos安装配置
  • 「译」Node.js Streams 基础
  • 08.Android之View事件问题
  • canvas绘制圆角头像
  • DOM的那些事
  • gops —— Go 程序诊断分析工具
  • java概述
  • jQuery(一)
  • js学习笔记
  • Promise初体验
  • QQ浏览器x5内核的兼容性问题
  • Redis在Web项目中的应用与实践
  • SQL 难点解决:记录的引用
  • Stream流与Lambda表达式(三) 静态工厂类Collectors
  • Web设计流程优化:网页效果图设计新思路
  • Zepto.js源码学习之二
  • 复杂数据处理
  • 工程优化暨babel升级小记
  • 力扣(LeetCode)965
  • 少走弯路,给Java 1~5 年程序员的建议
  • 突破自己的技术思维
  • 推荐一款sublime text 3 支持JSX和es201x 代码格式化的插件
  • 用jQuery怎么做到前后端分离
  • 追踪解析 FutureTask 源码
  • 走向全栈之MongoDB的使用
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (三) diretfbrc详解
  • (十八)用JAVA编写MP3解码器——迷你播放器
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (转)EXC_BREAKPOINT僵尸错误
  • (转)Unity3DUnity3D在android下调试
  • .net core 连接数据库,通过数据库生成Modell
  • .net core使用ef 6
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET 使用 XPath 来读写 XML 文件
  • .NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • .Net6使用WebSocket与前端进行通信
  • .NET开发不可不知、不可不用的辅助类(三)(报表导出---终结版)