C++ IO

文章目录

  • [C++ IO](#C++ IO)
    • [C 语言的输入与输出](#C 语言的输入与输出)
    • 缓冲区
    • [C++ 流的概念](#C++ 流的概念)
    • [C++IO 流](#C++IO 流)
    • [stringstream 的介绍](#stringstream 的介绍)

C++ IO

C 语言的输入与输出

🍉在 C 语言当中,我们使用最频繁的输入输出方式就是 scanf() 与 printf()

  1. scanf: 从标准输入设备(键盘)读取数据,并将读取到的值存放到某一指定变量当中

  2. printf: 将指定的数据输出到标准输出设备(屏幕),使用时需要注意宽度输出和精度输出的控制

c 复制代码
#include <stdio.h>
int main() {
	int a;
	double f;
	scanf("%d %f", &a, &f);
	printf("%d %.3f", a, f);	//打印三位小数
	return 0;
}

缓冲区

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

🍉缓冲区的作用:

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

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


C++ 流的概念

  1. C++ 流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入或从计算机内部向外部输出设备(如显示器)输出的过程

  2. 流的特性: 有序连续、具有方向性

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


C++IO 流

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


C++ 标准 IO 流

🍉C++ 标准库提供了4个全局流对象

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

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

  3. cerr:使用 cerr 进行标准错误的输出

  4. clog:使用 clog 进行日志的输出

🍉从上图可以看出,cout、cerr、clog 都是由 ostream 类实例化出的三个不同的对象,因此这三个对象基本没什么区别,只是应用场景不同

⭕️注意:

  1. 使用 cin、cout、cerr、clog 是需要包含 iostream 头文件,并指定或引入标准命名空间 std
cpp 复制代码
#include <iostream>
using namespace std;
int main() {
	int a;
	cin >> a;
	cout << a;
	return 0;
}
  1. cin 为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中提取(如果一次输入过多,则多余的数据会留在缓冲区以供之后提取。如果输入错了,必须在回车之前进行修改,回车键按下就无法进行修改了,只有把输入缓冲区中的数据取完后,才会要求输入新的数据)
cpp 复制代码
#include <iostream>
using namespace std;
int main() {
	int a, b;
	cin >> a;	//输入:10 20
	cout << a;	//10
	cin >> b;	//不用输入,直接从输入缓冲区中取
	cout << b;	//20
	return 0;
}
  1. 输入数据类型必须与要提取的数据类型一致,否则出错(出错只是在流的状态字state中对应位置设置为 1,程序继续)

  2. 空格和回车都可以作为数据之间的分隔符,所以多个数据可以在一行输入,也可以分行输入。如果是字符型和字符串,则空格无法用 cin 输入,字符串中也不能有空格,回车符也无法读入(可以使用 getline 函数解决)

cpp 复制代码
#include <iostream>
using namespace std;
int main() {
	string str;
	cin >> str;		//输入:hello world
	cout << str;	//hello
}
  1. 某些 OJ 题是要求我们接收输入的数据进行测试的,所以这里提供两种写法
cpp 复制代码
//C 语言
while(scanf("%d", &a) != EOF) {
	//else...
}

//C++ 语言
while(cin >> a) {
	//else...
}

C++ 文件 IO 流

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

🍉定义一个文件流对象。操作文件的类有以下三个:

对应操作场景
ofstream 只写
ifstream 只读
fstream 读 + 写

🍉使用文件流对象的成员函数 open() 打开一个磁盘文件,建立文件流对象和磁盘文件之间的联系。文件常见的打开方式如下:

打开方式 功能
in 以读的方式打开文件
out 以写的方式打开文件
binary 以二进制方式对文件进行操作
ate 输出位置从文件的末尾开始
app 以追加的方式对文件进行写入
trunc 先将文件内容清空再打开文件

🍉ate 和 app 的区别:

  1. 文件的初始位置指针:每个文件都有一个位置指针,指向将要读写的下一个字节,打开文件时,默认指向文件的第一个字节

  2. 打开方式 ate( at-end ) 和 app 能将位置指针定位到文件尾

  3. 如果以 app 方式打开文件,所有向文件的输出从当前文件的尾部开始进行,不必重新定位。即使你修改文件位置指针不指向文件尾部,也不会将输出写到那里

  4. 以 ate 方式打开文件时,文件起始位置指向文件尾部,但你可以修改位置指针指向其它位置,输出则写到指针指向的位置

⭕️注意:在操作系统中,文件的读指针和写指针通常是同一个指针(也就是文件位置指针)。‌这是因为一个进程通常只对一个文件进行读或写操作,因此当前操作位置可以用作每个进程当前文件位置的指针

🍉使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写。对文件进行提取和插入操作的常用成员函数:

成员函数 功能
is_open 判断文件是否打开
put 插入一个字符到文件
write 插入一段字符到文件
get 从文件提取一个字符
read 从文件提取多个字符
tellg 获取当前字符在文件当中的位置
seekg / seekp 设置对文件进行操作的位置
operator<<() 将数据形象的以 "流" 的方式输入到文件
operator>>() 将数据形象的以 "流" 的方式从文件输出

🍉seekg() 和 seekp() 的区别:

  1. seekg() 是对于读文件而言,设置文件的读指针

  2. seekp() 是对于写文件而言,设置文件的写指针

  3. 因为文件的读写指针是同一个,所以对于读写文件,二者没有区别

🍉关闭文件,使用成员函数 close()


以二进制的形式操作文件

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

cpp 复制代码
#include <fstream>	//使用文件流对象需要引入头文件 fstream
void write_binary() {
	//定义文件对象
	ofstream ofile;
	//以二进制写入的方式打开文件 file.bin
	ofile.open("./file.bin", ofstream::out | ofstream::binary);
	//写文件
	char buf[] = "hello world";
	ofile.write(buf, strlen(buf));
	ofile.put('!');
	//关闭文件
	ofile.close();
}

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

cpp 复制代码
#include <fstream>
void read_binary() {
	//定义文件对象
	ifstream ifile;
	//以二进制读的方式打开文件 file.bin
	ifile.open("file.bin", ifstream::in | ifstream::binary);
	//将读指针设置到文件末尾
	ifile.seekg(0, ifile.end);
	//获取文件总字符数量
	long len = iflie.tellg();
	//将读指针设置到文件起始位置
	ifile.seekg(0, ifile.beg);
	//读出文件所有数据
	char data[100];
	ifile.read(data, len);
	data[len] = '\0';
	//关闭文件
	ifile.close();
}

//将文件指针设置为 start + offset
seekg(int offset, fstream::iterator start);

以文本的形式操作文件

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

cpp 复制代码
#include <fistream>
void write_tex() {
	//定义文件对象
	ofstream ofile;
	//以文本写入的方式打开文件 file.txt
	ofile.open("./file.txt", ofstream::out);
	//写文件
	char buf = "hello world";
	ofile.write(buf, strlen(buf));
	ofile.put('!');
	//关闭文件
	ofile.close();
}

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

cpp 复制代码
#include <fstream>
void read_txt() {
	//定义文件对象
	ifstream ifile;
	//以二进制读的方式打开文件 file.txt
	ifile.open("file.txt", ifstream::in);
	//将读指针设置到文件末尾
	ifile.seekg(0, ifile.end);
	//获取文件总字符数量
	long len = iflie.tellg();
	//将读指针设置到文件起始位置
	ifile.seekg(0, ifile.beg);
	//读出文件所有数据
	char data[100];
	ifile.read(data, len);
	data[len] = '\0';
	//关闭文件
	ifile.close();
}

⭕️注意:

  1. 使用 ofstream 类对象的 open() 成员函数时,若不指定打开方式,则默认以写的方式打开文件

  2. 使用 ifstream 类对象的 open() 成员函数时,若不指定打开方式,则默认以读的方式打开文件

  3. 使用 fstream 类对象的 open() 成员函数时,若不指定打开方式,则默认以写 + 读的方式打开文件


使用 operator>>() 和 operator<<() 对文件进行操作

🍉对文件进行写操作:

cpp 复制代码
#include <fstream>
void write_file() {
	//定义文件对象并打开
	ofstream ofile("file.txt");
	//字符串流入文件
	ofile << "hello";
	//关闭文件
	ofile.close();
}

🍉对文件进行读操作:

cpp 复制代码
#include <fstream>
void read_file() {
	//定义文件对象并打开
	ifstream ifile("file.txt");
	//文件数据流入字符串 data
	char data[100];
	ifile >> data;
	//关闭文件
	ifile.close();
}

⭕️注意: 可以在定义文件流对象的同时指定将要打开的文件名,以及文件的打开方式


stringstream 的介绍

🍉在 C 语言中,我们若是想要将一个整型变量的数据转化为字符串格式,有以下两种方法:

  1. 使用 itoa() 函数进行转化,需要包含 stdlib.h 头文件
cpp 复制代码
//将整型 num 转化为 n 进制的字符数存储在字符串 str 中,返回 str
char* itoa(int num, char* str, size_t n);

int num = 101;
char str[10];
itoa(num, str, 10);
printf("%s", str);	//101

//将字符串 str 转换成整型
int atoi(const char* str);

const char* str = "111";
int num = atoi(str);
printf("%d", num);		//111
  1. 使用 sprintf() 函数进行转化,需要包含 stdio.h 头文件
cpp 复制代码
int num = 101;
char str[10];
sprintf(str, "%d", num);
printf("%s", str);		//101

//还可以使用 snprinf() 指明转换成最大字符串的个数
int num = 101;
char str[10];
sprintf(str, 2, "%d", num);
printf("%s", str);		//01

//使用 sscanf() 可以将字符串变整型
const char* str = "111";
int num;
sscanf(str, "%d", &num);
printf("%d", num);		//111

🍉缺点:

  1. 虽然 itoa() 函数和 sprintf() 函数都能完成转化,但是在两个函数在转化时,都需要先给出保存结果的空间,而空间的大小是不太好界定的

  2. 除此之外,转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃

🍉在 C++ 中,我们可以使用 stringstream 类对象来避开此问题,需要包含头文件 sstream。在该头文件下,有三个类:

类名 对应操作场景
ostringstream 输出操作
istringstream 输入操作
stringstream 输入 + 输出

🍉这里我们只介绍 stringstream 类,其作用如下:

  1. 将数值类型数据格式化为字符串
cpp 复制代码
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
	int num = 101;
	string str;
	stringstream ss;
	//将整型 num 放入输入流
	ss << num;
	//从 ss 中取出前面放入的整型 num,赋值给 str(方法 1)
	ss >> str;
	cout << str;	//101
	//将 stringstream 底层管理的 string 设置为空
	ss.str("");
	//清空上次转换状态
	ss.clear();

	double pi = 3.14;
	ss << pi;
	//获取 stringstream 底层管理的 string 对象(方法 2)
	str = s.str();
	cout << str;	//3.14
	return 0;
}
  1. 字符串拼接
cpp 复制代码
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
	string str;
	stringstream ss;
	//将多个字符串放入 stringstream 中
	ss << "hello" << "world";
	//方法 1 获取
	ss >> str;
	cout << str;	//helloworld
	//将 stringstream 底层管理的 string 设置为空
	ss.str("");
	//清空上次转换状态
	ss.clear();

	ss << "R" << "E" << "D";
	//方法 2 获取
	str = s.str();
	cout << str;	//RED
	return 0;
}

🍉说明:

  1. stringstream 实际是在底层维护了一个 string 类型的对象用来保存结果

  2. stringstream 在转换结尾时(即最后一个转换后),会将其内部状态设置为 badbit,因此在下一次转换前必须调用 clear() 将状态重置为 goodbit 才可以转换,但 clear() 不会将 stringstream 底层的 string 对象清空

  3. 可以使用 s.str( "" ) 的方式将 stringstream 底层的 string 对象设置为空字符串,否则多次转换时,会将结果全部累积在底层 string 对象中

  4. 获取 stringstream 转换后的结果有两个方法:一是使用 >> 运算符之间从流当中提取,二是使用 s.str() 获取 stringstream 底层的string对象

  5. stringstream 使用 string 类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会存在格式化失败的风险,因此使用更方便,更安全


本篇文章到这里就结束了,欢迎批评指正!

相关推荐
Y.O.U..2 小时前
STL学习-容器适配器
开发语言·c++·学习·stl·1024程序员节
lihao lihao2 小时前
C++stack和queue的模拟实现
开发语言·c++
姆路3 小时前
QT中使用图表之QChart概述
c++·qt
西几3 小时前
代码训练营 day48|LeetCode 300,LeetCode 674,LeetCode 718
c++·算法·leetcode
风清扬_jd3 小时前
Chromium HTML5 新的 Input 类型week对应c++
前端·c++·html5
南东山人4 小时前
C++静态成员变量需要在类外进行定义和初始化-error LNK2001:无法解析的外部符号
c++
lqqjuly4 小时前
C++ 中回调函数的实现方式-函数指针
开发语言·c++
2401_871120354 小时前
数组与指针基础
c++
程序猿阿伟4 小时前
《C++中的魔法:实现类似 Python 的装饰器模式》
java·c++·装饰器模式
Ethan Wilson4 小时前
C++/QT可用的websocket库
开发语言·c++·websocket