文章目录
- [C++ IO](#C++ IO)
-
- [C 语言的输入与输出](#C 语言的输入与输出)
- 缓冲区
- [C++ 流的概念](#C++ 流的概念)
- [C++IO 流](#C++IO 流)
-
- [C++ 标准 IO 流](#C++ 标准 IO 流)
- [C++ 文件 IO 流](#C++ 文件 IO 流)
-
- 以二进制的形式操作文件
- 以文本的形式操作文件
- [使用 operator>>() 和 operator<<() 对文件进行操作](#使用 operator>>() 和 operator<<() 对文件进行操作)
- [stringstream 的介绍](#stringstream 的介绍)
C++ IO
C 语言的输入与输出
🍉在 C 语言当中,我们使用最频繁的输入输出方式就是 scanf() 与 printf()
-
scanf: 从标准输入设备(键盘)读取数据,并将读取到的值存放到某一指定变量当中
-
printf: 将指定的数据输出到标准输出设备(屏幕),使用时需要注意宽度输出和精度输出的控制
c
#include <stdio.h>
int main() {
int a;
double f;
scanf("%d %f", &a, &f);
printf("%d %.3f", a, f); //打印三位小数
return 0;
}
缓冲区
🍉C 语言借助了相应的缓冲区来进行输入与输出,如下图所示:
🍉缓冲区的作用:
-
可以屏蔽低级 I/O 的实现。 低级I/O的实现依赖操作系统本身内核的实现(read、write 等),所以如果能够屏蔽这部分的差异,可以很容易写出可移植的程序
-
可以使用这部分的内容实现 " 行 " 读取的行为。 对于计算机而言是没有 " 行 " 这个概念的,有了这部分,就可以定义 " 行 " 的概念,然后解析缓冲区的内容,返回一个 " 行 "( \n )
C++ 流的概念
-
C++ 流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入或从计算机内部向外部输出设备(如显示器)输出的过程
-
流的特性: 有序连续、具有方向性
-
为了实现这种流动,C++ 定义了 I/O 标准类库,当中的每个类都称为流 / 流类,用以完成某方面的功能
C++IO 流
🍉C++ 系统实现了一个庞大的类库,其中 ios 为基类,其他类都是直接或间接派生自 ios 类
C++ 标准 IO 流
🍉C++ 标准库提供了4个全局流对象
-
cout:使用 cout 进行标准输出,即数据从内存流向控制台(显示器)
-
cin:使用 cin 进行标准输入,即数据通过键盘输入到程序中
-
cerr:使用 cerr 进行标准错误的输出
-
clog:使用 clog 进行日志的输出
🍉从上图可以看出,cout、cerr、clog 都是由 ostream 类实例化出的三个不同的对象,因此这三个对象基本没什么区别,只是应用场景不同
⭕️注意:
- 使用 cin、cout、cerr、clog 是需要包含 iostream 头文件,并指定或引入标准命名空间 std
cpp
#include <iostream>
using namespace std;
int main() {
int a;
cin >> a;
cout << a;
return 0;
}
- 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;
}
-
输入数据类型必须与要提取的数据类型一致,否则出错(出错只是在流的状态字state中对应位置设置为 1,程序继续)
-
空格和回车都可以作为数据之间的分隔符,所以多个数据可以在一行输入,也可以分行输入。如果是字符型和字符串,则空格无法用 cin 输入,字符串中也不能有空格,回车符也无法读入(可以使用 getline 函数解决)
cpp
#include <iostream>
using namespace std;
int main() {
string str;
cin >> str; //输入:hello world
cout << str; //hello
}
- 某些 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 的区别:
-
文件的初始位置指针:每个文件都有一个位置指针,指向将要读写的下一个字节,打开文件时,默认指向文件的第一个字节
-
打开方式 ate( at-end ) 和 app 能将位置指针定位到文件尾
-
如果以 app 方式打开文件,所有向文件的输出从当前文件的尾部开始进行,不必重新定位。即使你修改文件位置指针不指向文件尾部,也不会将输出写到那里
-
以 ate 方式打开文件时,文件起始位置指向文件尾部,但你可以修改位置指针指向其它位置,输出则写到指针指向的位置
⭕️注意:在操作系统中,文件的读指针和写指针通常是同一个指针(也就是文件位置指针)。这是因为一个进程通常只对一个文件进行读或写操作,因此当前操作位置可以用作每个进程当前文件位置的指针
🍉使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写。对文件进行提取和插入操作的常用成员函数:
成员函数 | 功能 |
---|---|
is_open | 判断文件是否打开 |
put | 插入一个字符到文件 |
write | 插入一段字符到文件 |
get | 从文件提取一个字符 |
read | 从文件提取多个字符 |
tellg | 获取当前字符在文件当中的位置 |
seekg / seekp | 设置对文件进行操作的位置 |
operator<<() | 将数据形象的以 "流" 的方式输入到文件 |
operator>>() | 将数据形象的以 "流" 的方式从文件输出 |
🍉seekg() 和 seekp() 的区别:
-
seekg() 是对于读文件而言,设置文件的读指针
-
seekp() 是对于写文件而言,设置文件的写指针
-
因为文件的读写指针是同一个,所以对于读写文件,二者没有区别
🍉关闭文件,使用成员函数 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();
}
⭕️注意:
-
使用 ofstream 类对象的 open() 成员函数时,若不指定打开方式,则默认以写的方式打开文件
-
使用 ifstream 类对象的 open() 成员函数时,若不指定打开方式,则默认以读的方式打开文件
-
使用 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 语言中,我们若是想要将一个整型变量的数据转化为字符串格式,有以下两种方法:
- 使用 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
- 使用 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
🍉缺点:
-
虽然 itoa() 函数和 sprintf() 函数都能完成转化,但是在两个函数在转化时,都需要先给出保存结果的空间,而空间的大小是不太好界定的
-
除此之外,转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃
🍉在 C++ 中,我们可以使用 stringstream 类对象来避开此问题,需要包含头文件 sstream。在该头文件下,有三个类:
类名 | 对应操作场景 |
---|---|
ostringstream | 输出操作 |
istringstream | 输入操作 |
stringstream | 输入 + 输出 |
🍉这里我们只介绍 stringstream 类,其作用如下:
- 将数值类型数据格式化为字符串
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;
}
- 字符串拼接
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;
}
🍉说明:
-
stringstream 实际是在底层维护了一个 string 类型的对象用来保存结果
-
stringstream 在转换结尾时(即最后一个转换后),会将其内部状态设置为 badbit,因此在下一次转换前必须调用 clear() 将状态重置为 goodbit 才可以转换,但 clear() 不会将 stringstream 底层的 string 对象清空
-
可以使用 s.str( "" ) 的方式将 stringstream 底层的 string 对象设置为空字符串,否则多次转换时,会将结果全部累积在底层 string 对象中
-
获取 stringstream 转换后的结果有两个方法:一是使用 >> 运算符之间从流当中提取,二是使用 s.str() 获取 stringstream 底层的string对象
-
stringstream 使用 string 类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会存在格式化失败的风险,因此使用更方便,更安全
本篇文章到这里就结束了,欢迎批评指正!