七、输入/输出流

流类简介

C++中凡是数据从一个地方传输到另一个地方的操作都是的操作。因此,一般意义下的读操作在流数据抽象中被称为(从流中)“提取”,写操作被称为(向流中)“插入”。

iostream流类库的类关系图如下:

流类库关系图

图中箭头代表派生关系。ios是抽象基类,提供输入/输出所需的公共操作,它派生出两个类istreamostream。为了避免多重继承的二义性,从ios派生istreamostream时,均使用了virtual关键字(虚继承)

stream类提供了流的大部分输入操作,对系统预定义的所有输入流重载提取运算符>>ostream类对系统预定义的所有输出流重载插入运算符<<

C++的iostream类库提供了数百种I/O功能,iostream类库的接口部分包含在几个头文件中。常见的头文件有以下3个:

  1. iostream:包含操作所有输入/输出流所需要的基本信息,因此大多数C++程序都应包含这个头文件。该文件含有4个标准流对象,提供了无格式化和格式化的I/O功能。
  2. iomanip:包含格式化I/O的带参数流操纵符,可用于指定数据输入/输出的格式。
  3. fstream:包含处理文件的有关信息,提供建立文件、读/写文件的各种操作接口。

标准流对象

C++在头文件iostream中为了用户预定义了4个标准流对象,分别是:

  • cin(标准输入流),cin与标准输入设备(键盘)相关联,用于读取数据,可以被重定向为从文件中读取数据。
  • cout(标准输出流),cout与标准输出设备(显示器)相关联,用于输出数据,可以被重定向为向文件里写入数据。
  • cerr(非缓冲错误输出流),cerr与标准错误信息输出设备(显示器)相关联(非缓冲),用于输出出错信息,不能被重定向。
  • clog(缓冲错误输出流),clog与标准错误信息输出设备相关联(缓冲),用于输出出错信息,不能被重定向。

在实际中,cin常用于从键盘输入数据,是流类istream的对象。cout常用于向屏幕输出数据,是流类ostream的对象。

#include <iostream>
using namespace std;

int main() {
    int x, y;
    cin >> x >> y;
    //函数`freopen()`的功能是将`stream`按`mode`指定的模式重定向到路径`path`指向的文件。
    //将标准输出定向到文件test.txt
    freopen("test.txt", "w", stdout);
    
    if (y == 0) {
        cerr << "error" << endl;
    } else {
        cout << x << " / " << y << " = " << x / y << endl;
    }

    return 0;
};

控制I/O格式

C++进行I/O格式控制的方式一般有使用流操纵符、设置标志字和调用成员函数。

流操纵符

流操纵符 作用
endl 输出一个新行符,并清空流
ends 输出字符串结束,并清空流
flush 清空流缓冲区
dec*(默认) 以十进制形式输入/输出整数
hex 以十六进制形式输入或输出整数
cot 以八进制形式输入或输出整数
ws 提取空白字符
fixed 以普通小数形式输出浮点数
scientific 以科学计数法形式输出浮点数
left 左对齐,即在宽度不足时将填充字符添加到右边
right* 右对齐,即在宽度不足时将填充字符添加到左边
setbase(int b) 设置输出整数时的进制,b为8、10、16
setw(int w) 指定输出宽度为w个字符,或输入字符串时读入w个字符。
一次有效。
setfill(int c) 在指定输出宽度的情况下,
输出的宽度不足时用ASCⅡ码为c的字符填充(默认情况是用空格填充)
setprecision(int n) 设置输出浮点数的精度为n
在使用非fixed且非scientific方式输出的情况下,
n即为有效数字最多的位数。
如果有效数字位数超过n
则小数部分四舍五入,
或自动变为科学计数法输出并保留一共n位有效数字;
在使用fixed方式和scientific方式输出的情况下,
n是小数点后面应保留的位数。
setiosflags(fmtflags f) 通用操纵符。将格式标志f所对应的格式标志位置为1
resetiosflags(fmtflags f) 通用操纵符。将格式标志f所对应的格式标志位置为0(清除)
boolapha turefalse输出为字符串
noboolapha* turefalse输出为10
showbase 输出表示数值进制的前缀
noshowbase* 不输出表示数值进制的前缀
showpoint 总是输出小数点
noshowpoint* 只有当小数部分存在时才显示小数点
showpos 在非负数值中显示+
noshowpos* 在非负数值中不显示+
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    int n = 65535, m = 20;
    
    //1.分别输出一个整数的十进制、十六进制和八进制表示
    cout << "1)" << n << " = " << hex << n << " = " << oct << n << endl;
    //1)65535 = ffff = 177777
    
    //2.使用setbase分别输出一个整数的十进制、十六进制和八进制表示
    cout << "2)" << setbase(10) << m << " = " << setbase(16) << m << " = " << setbase(8) << m << endl;
    //2)20 = 14 = 24
    
    //3.使用showbase和setbase分别输出一个整数的十进制、十六进制和八进制表示
    cout << "3)" << showbase;//输出表示数值进制的前缀
    cout << setbase(10) << m << " = " << setbase(16) << m << " = " << setbase(8) << m << endl;
    //3)20 = 0x14 = 024

    return 0;
};

标志字

标志常量名 含义 输入/输出
ios::skipws 0x0001 跳过输入中的空白 I
ios::left 0x0002 按输出域左对齐,用填充字符填充右边 O
ios::right* 0x0004 按输入域右对齐,用填充字符填充做左边 O
ios::internal 0x0008 在符号位或基数指示符后填入字符 O
ios::dec* 0x0010 转换为十进制基数形式 I/O
ios::oct 0x0020 转换为八进制基数形式 I/O
ios::hex 0x0040 转换为十六进制基数形式 I/O
ios::showbase 0x0080 在输出中显示基数指示符 O
ios::showpoint 0x0100 在输出浮点数时必须带小数点和尾部的0 O
ios::uppercase 0x0200 以大写字母表示十六进制数,科学计数法使用大写字母 O
ios::showpos 0x0400 正数前面加+ O
ios::scientific 0x0800 科学计数法显示浮点数 O
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    double x = 12.34;
    
    cout << "1)" << setiosflags(ios::scientific | ios::showpos) << x << endl;
    cout << "2)" << setiosflags(ios::fixed) << x << endl;
    cout << "3)" << resetiosflags(ios::fixed) << setiosflags(ios::scientific | ios::showpos) << x << end;
    cout << "4)" << resetiosflags(ios::showpos) << x << endl;
    //1)+1.234000e+001
    //2)+12.34
    //3)+1.234000e+001
    //4)1.234000e+001

    return 0;
};

调用cout的成员函数

成员函数 作用相同的流操作符
precision(int np) setprecision(np)
width(int nw) setw(nw)
fill(char cFill) setfill(cFill)
setf(long iFlags) setiosflags(iFlags)
unsetf(long iFlags) resetiosflags(iFlags)
#include <iostream>
using namespace std;

int main(){
    double values[] = {
        1.23,
        20.3456,
        300.4567,
        4000.45678,
        50000.1234567
    };
    
    cout.fill('*');//设置填充字符为`*`
    for (int i = 0; i < sizeof(values) / sizeof(double); i++) {
        cout << "values[" << i << "] = (";
        cout.width(10);//设置输出宽度
        cout << values[i] << ")" << endl;
    }
    /*
    values[0] = (******1.23)
    values[1] = (***20.3456)
    values[2] = (***300.457)
    values[3] = (***4000.46)
    values[4] = (***50000.1)
    */
    
    cout.fill(' ');//设置填充字符为空格
    for (int j = 0; j < sizefo(values) / sizeof(double); j++) {
        cout << "values[" << j << "] = (" 
        cout.width(10);//设置输出宽度
        cout.precision(j + 3);//设置保留有效数字
        cout << values[j] << ")" << endl;
    }
    /*
    values[0] = (     1.23)
    values[1] = (    20.35)
    values[2] = (   300.46)
    values[3] = (  4000.46)
    values[4] = ( 50000.12)
    */
    
    return 0;
};

调用cin的成员函数

istream类提供了一些公有成员函数,它们可以以不同的方式提取输入流中的数据。

get()函数

#include <iostream>
using namespace std;
 
int main() {
    int n = 0;
    char ch;
    //当文件没有结束时继续进行循环
    while ((ch = cin.get()) != EOF) {
        cout.put(ch);
        n++;
    }
    cout << "输入字符共计:" << n << endl;
};

Windows环境下,当进行键盘输入时,在单独的一行按ctrl + z组合键后再按enter键就代表文件输入结束。

getline()函数

getline()成员函数的原型如下:

istream & getline(char *buf, int bufSize);

其功能是从输入流中的当前字符开始读取bufSize - 1个字符到缓冲区buf,或读到\n为止(哪个条件先满足即按哪个执行)。函数会在buf中读入数据的结尾自动添加串结束标记\0

istream & getline(char *buf, int bufSize, char delim);

其功能是从输入流中的当前字符开始读取bufSize - 1个字符到缓冲区buf,或读到字符delim为止(哪个条件先满足即按哪个执行)。函数会在buf中读入数据的结尾自动添加\0

两者的区别在于,前者是读到\n为止,后者是读到指定字符delim为止。字符\ndelim都不会被存入buf中,但会从输入流中取走。

函数getline()的返回值是函数所作用的对象的引用。如果输入流中\ndelim之前的字符个数达到或超过bufSize,则会导致读入操作出错,其结果是:虽然本次读入已经完成,但是之后的读入都会失败。

#include <iostream>
using namespace std;

int main() {
    char buf[10];
    int i = 0;
    //若输入流的一行超过9个字符,则会出错
    while (cin.getline(buf, 10)) {
        cout << ++i << ":" << buf << endl;
    }
    cout << "last:" << buf << endl;
    
    return 0;
};

eof()函数

eof()成员函数的原型如下:

bool eof();

eof()函数用于判断输入流是否已经结束。返回值为true表示输入结束。

在应用程序中可以用eof()函数测试是否到达文件尾,当文件操作结束遇到文件尾时,函数返回1;否则返回0

ignore()函数

ignore()成员函数的原型如下:

istream & ignore(int n = 1, int delim = EOF);

此函数的作用是跳过输入流中的n个字符,或跳过delim及其之前的所有字符(哪个条件先满足就按哪个执行)。两个参数都有默认值。因此cin.ignore()等效于cin.ignore(1, EOF),即跳过一个字符。该函数常用于跳过输入中的无用部分,以便提取有用的部分。

#include <iostream>
using namespace std;

int main() {
    char str[30];
    while (!cin.eof()) {
        cin.ignore(10, ':');
        if (!cin.eof()) {
            cin >> str;
            cout << str << endl;
        }
    }

    return 0;
};

peek()函数

peek()成员函数的原型如下:

int peek();

函数peek()返回输入流中的当前字符,但是并不将该字符从输入流中取走,相当于只是“看了一眼”将要读入的下一个字符,因此叫“窥视”。

cin.peek()不会跳过输入流中的空格和回车符。在输入流已经结束的情况下,cin.peek()返回EOF

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,082评论 5 464
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,231评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,047评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,977评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,893评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,976评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,605评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,888评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,906评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,732评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,513评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,980评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,132评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,447评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,027评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,232评论 2 339