目录
一、IO流简介
IO流包括:标准IO流,字符串流,文件流。C++流是指信息从外部输入设备向计算机内部输入和从计算机内部向外部输出设备输出的过程。这种输入输出的过程被形象的比喻为“流”。
流的特性: 有序连续、具有方向性。为了实现这种流动,C++定义了I/O标准类库,当中的每个类都称为流/流类,用以完成某方面的功能。
基于流的输入/输出库围绕抽象的输入/输出设备组织。这些抽象设备允许相同代码处理对文件、内存流或随即进行任意操作的自定义适配器设备的输入/输出。
大多数已经被类模板化,故它们能被适配到任何标准字符类型。为最常用的基本字符类型( char 和 wchar_t )提供分离的 typedef 。以下列层次将类组织:
C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类。
C++预定义的类和函数参考手册:C++ 参考手册 - cppreference.com
二、标准IO流
C++标准库提供了4个全局流对象(cin、cout、cerr、clog)
#include <iostream> //包括istream和ostream
cin>> : 标准输入
cout<< : 标准输出
clog<< :带缓冲区的标准错误
cerr<< : 不带缓冲区的标准错误
从前面的图可以看出,cout、cerr、clog都是由ostream类实例化出的三个不同的对象,这三个对象基本没什么区别,只是应用场景不同。
cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中提取。如果一次输入过多,则多余的数据会留在缓冲区以供之后提取,如果输入错了,必须在回车之前进行修改,回车键按下就无法进行修改了,只有把输入缓冲区中的数据取完后,才会要求输入新的数据。
#include <iostream>
using namespace std;
int main()
{
int a = 0, b = 0;
cin >> a; //输入:10 20
cout << a << endl;
cin >> b; //直接从输入缓冲区提取
cout << b << endl;
return 0;
}
若在第一次输入时便以空格为分隔输入了两个数据,则在下一次需要提取数据的时候就直接从缓冲区进行提取。
注意:输入数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state中对应位置(置1),程序继续。
成员函数:
cin.get :读取一个字符
cout.put :输出一个字符
cin.getline : 读一行
cin.ignore :清空缓冲区
cin.clear: 清除错误信息
空格和回车都可以作为数据之间的分隔符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格无法用cin输入,字符串中也不能有空格,回车符也无法读入。这时就需要使用成员函数来完成。
使用cin无法将含空格的字符串"hello world"输入到string对象中。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cin >> s; //输入:"hello world"
cout << s << endl; //输出:"hello"
return 0;
}
对于含有空格的字符串,需要使用getline函数进行读取,因为getline函数只有遇到’\n’才会停止读取。
#include <iostream>
#include <string>
using namespace std;
int main()
{
char buf[100] = {};
//可以读空格
cin.getline(buf,sizeof(buf)); //输入:"hello world"
cout << buf<< endl; //输出:"hello world"
return 0;
}
所以,在使用cin读取字符串的时候一定要注意是否含有空格。
三、字符串流
1.C字符串处理函数
使用C语言的库函数需要加头文件
#include <string.h> 或者 #include <cstring>
这要与c++中的string类区分,使用string类的头文件
#include <string>
C语言常用字符串处理函数
strcpy:拷贝
strcmp:比较
strcat: 拼接
strstr:查找子串
strlen:长度
strtok:分割 //hello,bybye,new ===>按,分割===>hello byebye new
strchr:查找字符
sprintf:格式化字符串
这些函数在C++中同样适用。
2.string类
C++预定义了string类来表示字符串。
#include <string>
①构造函数
string类实现了多个构造函数的重载,常用的构造函数如下:
string(); //构造一个空字符串
string(const char* s); //复制s所指的字符序列
string(const char* s, size_t n); //复制s所指字符序列的前n个字符
string(size_t n, char c); //生成n个c字符的字符串
string(const string& str); //生成str的复制品
string(const string& str, size_t pos, size_t len = npos); //复制str中从字符位置pos开始并跨越len个字符的部分
常用构造初始化
string s1; //构造空字符串
string s2("hello string"); //复制"hello string"
string s3("hello string", 3); //复制"hello string"的前3个字符
string s4(10, 's'); //生成10个's'字符的字符串
string s5(s2); //生成s2的复制品
string s6(s2, 0, 4); //复制s2中从字符位置0开始并跨越4个字符的部分
②重载运算符
= :赋值
+ / += :拼接
<< >>:输入输出
== != > >= < <= :比较(ASCII码)
[] :根据下标获取字符(不检查越界,不推荐使用,推荐使用检查越界的成员函数at)
1.operator=
string类中对=运算符进行了重载,重载后的=运算符支持string类的赋值、字符串的赋值以及字符的赋值。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1;
string s2("CSDN");
//支持string类的赋值
s1 = s2;
cout << s1 << endl; //CSDN
//支持字符串的赋值
s1 = "hello";
cout << s1 << endl; //hello
//支持字符的赋值
s1 = 'x';
cout << s1 << endl; //x
return 0;
}
2.operator+
string类中对+运算符进行了重载,重载后的+运算符支持以下几种类型的操作:
string类 + string类
string类 + 字符串
字符串 + string类
string类 + 字符
字符 + string类
它们相加后均返回一个string类对象。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
string s1("super");
string s2("man");
char str[] = "woman";
char ch = '!';
//string类 + string类
s = s1 + s2;
cout << s << endl; //superman
//string类 + 字符串
s = s1 + str;
cout << s << endl; //superwoman
//字符串 + string类
s = str + s1;
cout << s << endl; //womansuper
//string类 + 字符
s = s1 + ch;
cout << s << endl; //super!
//字符 + string类
s = ch + s1;
cout << s << endl; //!super
return 0;
}
3.operator+=
string类中对+=运算符进行了重载,重载后的+=运算符支持string类的复合赋值、字符串的复合赋值以及字符复合的赋值。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1;
string s2("hello");
//支持string类的复合赋值
s1 += s2;
cout << s1 << endl; //hello
//支持字符串的复合赋值
s1 += " CSDN";
cout << s1 << endl; //hello CSDN
//支持字符的复合赋值
s1 += '!';
cout << s1 << endl; //hello CSDN!
return 0;
}
③成员函数
at :获取指定位置的字符
c_str :转换成C风格的字符串(const char *)
size/length :字符串的大小/长度
capacity : 容量
max_size : 最大支持的大小
empty : 判空
copy(char *s, int n, int pos=0): 从pos位置开始拷贝n个字符到s
insert(int pos,const char *s/string s):从pos位置插入字符串s
insert(int pos,const char *s,int n):从pos位置插入字符串s的前n个字符
swap(string &s):交换本字符串和s的内容
find(char ch,int pos=0):从pos位置开始查找ch字符
find(const char *s/string s,int pos=0):从pos位置开始查找字符串s
substr(int pos,int n=npos):获取从pos开始长度为n的子字符串
int main()
{
string str1("hello");
const char *s = str1.c_str(); //转换成C风格字符串
cout<<s<<endl;
return 0;
}
3.stringstream
在C语言中,我们若是想要将一个整型变量的数据转化为字符串格式,有以下两种方法:
①使用itoa函数进行转化
int a = 10;
char arr[10];
itoa(a, arr, 10); //将整型的a转化为十进制字符数字存储在字符串arr当中
②使用sprintf函数进行格式化转化
int a = 10;
char arr[10];
sprintf(arr, "%d", a); //将整型的a转化为字符串格式存储在字符串arr当中
itoa函数和sprintf函数都能完成转化,但是在两个函数在转化时,都需要先给出保存结果的空间,而空间的大小是不太好界定的,除此之外,转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃。
在C++中,可以使用stringstream字符串流类对象来避开此问题。在程序当中如果想要使用stringstream,必须要包含头文件sstream。在该头文件下,有三个类:
#include <sstream>
istringstream:数据来源于字符串(读取字符串)
ostringstream:将数据写入字符串
stringstream:即可以读又可以写
字符串流和标准IO流的关系类似于 sprintf/sscanf和printf/scanf的关系
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
int a = 10;
string sa;
stringstream s;
s << a; //将int类型的a放入输入流
s >> sa; //从s中抽取前面插入的int类型的值,赋值给string类型(方式一)
cout << sa << endl;
s.str(""); //将stringstream底层管理的string对象设置为""。
s.clear(); //将上次转换状态清空掉
//进行下一次转换
double b = 3.14;
s << b;
sa = s.str(); //获取stringstream中管理的string类型(方式二)
cout << sa << endl;
return 0;
}
①stringstream是在底层维护了一个string类型的对象用来保存结果
②stringstream在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit,因此在下一次转换前必须调用clear将状态重置为goodbit才可以转换,但clear不会将stringstream底层的string对象清空。
③可以使用s.str("")的方式将stringstream底层的string对象设置为空字符串,否则多次转换时,会将结果全部累积在底层string对象中。④获取stringstream转换后的结果有两个方法,一是使用>>运算符之间从流当中提取,二是使用s.str( )获取stringstream底层的string对象。
⑤stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会存在格式化失败的风险,因此使用更方便,更安全。
四、文件流
C++中预定义了fstream类来访问文件,分为ifstream(输入类)和ofstream(输出类),分别支持>>和<< 的操作,可以使用文件路径来构造文件流,构造会自动打开文件,使用其需要包含头文件
#include <fstream>
ofstream:只写
ifstream:只读
fstream:读+写
常用成员函数
1.打开文件 :open open(路径,打开方式);
2.关闭文件:close 无参
3.读写文件:read/write
read(读空间的首地址,读的长度);
write(写空间的首地址,写的长度);
4.gcount():上次读取(read)的字节数
5.>>运算符重载 :将数据形象地以“流”的形式进行输入
<<运算符重载:将数据形象地以“流”的形式进行输出
6.插入/提前一个字符到文件:put/get
7.获取当前字符在文件当中的位置:tellg
8.设置对文件进行操作的位置:seekg
使用文件流对象的成员函数打开一个文件,使得文件流对象和磁盘文件之间建立联系。文件常见的打开方式如下:
ios::in | 以读的方式打开文件 |
ios::out | 以写的方式打开文件 |
ios::app | 以追加的方式对文件进行写入 |
ios::trunc | 先将文件内容清空再打开文件 |
ios::binary | 以二进制方式对文件进行操作 |
ofstream ofs("a.txt");// 默认打开方式out+trunc
ifstream ifs("a.txt"); //默认ios::in打开
可以在定义文件流对象的同时指定将要打开的文件名,以及文件的打开方式。也可以在使用成员函数open时在指定要打开的文件名,以及文件的打开方式。
//写文件
ofstream ofile; //定义文件流对象
ofile.open("test.txt"); //以写入的方式打开test.txt文件
char data[] = "2021dragon";
ofile.write(data, strlen(data)); //将data字符串写入文件
ofile.put('!'); //将字符'!'写入文件
ofile.close(); //关闭文件
使用>>和<<运算符对文件进行读写操作
//对文件进行写
ofstream ofs("data.txt"); //定义文件流对象,并打开文件
ofs << "2021dragon!"; //字符串“流入”文件
ofs.close(); //关闭文件
//对文件进行读
ifstream ifs("data.txt"); //定义文件流对象,并打开文件
char data[100];
ifs >> data; //文件数据“流入”字符串data
ifs.close(); //关闭文件
简单的文件加密:异或运算:相同为0,不同为1 (异或0不变,异或1取反)
1111 1111 ^ 1100 0011 = 0011 1100
0011 1100 ^ 1100 0011 = 1111 1111
xxxx xxxx ^ 1111 0011 = yyyy xxyy
yyyy xxyy ^ 1111 0011 = xxxx xxxx