# C++基础5-输入输出

本文是《C++ Primer Plus》的笔记,本文中的案例均自己实践过,如需转发请在转发开头贴上原文地址,谢谢!

# 流与缓冲区

输入输出的字节可以想象成水流,我们浇灌庄稼,水流顺着沟渠灌溉到每块地里,而这些字节则是顺着抽象的管道流。在c++中IO有三种流:

  1. 标准输入输出流<iostream>。
    istream(cin),ostream(cout,cerr,clog),wistream(wcin),wostream(wcout,wcerr,wclog)。
  2. 文件流<fstream>。
    ifstream,ofstream,fstream,wifstream,wofstream,wfstream。
  3. 字符串流<sstream>。
    istringstream,ostringstream,stringstream,wistringstream,wostringstream,wstringstream。

流的特性决定了输入和输出需要相同的速度,试想一下如果输入速度大于输出速度的话会是怎样?如果是沟渠里的水那么会外溢,如果是一条管道的话会崩掉,假如我们不想实时输入输出,我们想输入后,等一会输出,那么我们需要一个缓冲区,将输入的数据先放入这个缓冲区,稍后输出从这个缓冲区里取数据。

buffer

从上图可以看出来,有个缓冲区的话,可以先进水,再放水,而如果只是一个水管的话只能边进边出。

# C++IO类之间的关系

引用http://www.cplusplus.com (opens new window)中的一张图:

buffer

引用http://www3.ntu.edu.sg (opens new window)中的两张图:

buffer buffer

# 标准输入输出流

C++的iostream类库管理标准输入输出。如果在程序中包含iostream将自动创建8个流对象。(4个用于窄字节流,4个用于宽字节流)。这些对象代表着字节流的管道,当声明一个cout对象时,将包含存储了与输出有关的相关信息,比如字段宽度,小数位数等等以及相应的缓冲区。

在linux终端或者windows cmd 和 powershell中都可以重定向程序的标准输入输出。以windows为例用记事本建立一个input.txt,其中内容为:

hello world你好
你好世界hello
1
2
    char * tempPtr;
    wchar_t *tempPtr2 ;
    const char *tempPtr3="测试窄字符常量";
    const wchar_t *tempPtr4=L"测试宽字符常量";
    tempPtr = new char[30];
    tempPtr2 = new wchar_t[30];
    cin >> tempPtr ;
    cout << tempPtr << endl;
    cout << tempPtr3 << endl;
    clog << "log:hello world" << endl;
    cerr << "err:this is a error" << endl;

    //setlocale(LC_ALL, "chs");
    setlocale(LC_ALL, "");
    wcout << tempPtr4 << endl;
    //wcout.imbue(locale("chs"));
    wcin >> tempPtr2;
    wcout << tempPtr2 << endl;
    wclog << L"log:你好世界" << endl;
    wcerr << L"err:这是一个错误" << endl;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

下面是cmd中的重定向:

重定向输入输出:
BasicPrimer.exe < input.txt > output.txt
//> 变成 >> 则变为追加到文件中
将标准错误和标准输出重定向到文件中:
BasicPrimer.exe < input.txt > output.txt 2>&1
1
2
3
4
5

下面是powershell中的重定向:

重定向输入输出:
Get-Content .\input.txt | .\BasicPrimer.exe > output.txt
将标准输入和标准输出重定向到文件中:
Get-Content .\input.txt | .\BasicPrimer.exe > output.txt 2>&1

输出当前的字符编码情况:
$OutputEncoding
获取文件时设置编码:
Get-Content -encoding utf8  .\input.txt
使用Out-File重定向:
Get-Content .\input.txt | ./BasicPrimer.exe | Out-File output.txt 2>&1
1
2
3
4
5
6
7
8
9
10
11

使用powershell管道传输汉字时,在c++中得到只是?,暂时没想到解决办法。

在PowerShell中有两种重定向方式:

  1. 使用重定向运算符>。
  2. 使用Out-File。

关于两种方式输出编码问题:

  1. 当>右侧是文件时,使用Unicode编码。这个编码也是Out-File的默认编码。
  2. 当>右侧是程序时,输出什么编码取决于$OutputEncoding的值,而它的值默认是ASCII码。

# 标准输出

C++将输出看做是字节流,但是在程序中有很多比字节更大的单位,比如double,float,我们要输出他们就要把字面值转换下,比如3.14,需要4个字符,这时候cout就把这件事情完成了,在ostream类中重新定义了<<运算符,使它被重载能识别:unsigned char,signed char,char,short,unsigned short,int,unsigned int,long,unsigned long,long long,unsigned long long,float,double,long double,const signed char *,const unsigned char *,const char *,void *.

对于指针类型,如果使用(void *)转换输出的话,会输出对应的内存地址。

<<运算符的的原型如下:

ostream & operator<<(type);

注意它返回一个自身的对象,这就可以使用链式调用。

除了cout外,还有put,和write方法,前者用于显示字符,后者用于显示字符串。

    char * str1;
    str1 = new char[30];
    cout.put(66);
    cout.put('h') << endl;
    cin >> str1;
    cout.write(str1,2);//要显示几个字符,write不会遇到空字符停止,而是打印指定位数的字符。
    int seevalue = 256;
    cout.write((char *)&seevalue, sizeof(int));
    //(char *)不会将数字转换为相应的字符,而是传输内存中存储的位表示。但是cout会将每个字节作为ASCII码进行解释,这就会导致乱码。
1
2
3
4
5
6
7
8
9

刷新输出缓存区

cout << "hello world"<<flush;
flush(cout);
cout << "hello world"<<endl;
//刷新缓存区并添加一个换行符
1
2
3
4

# cout输出各种格式的默认形式

    char char1 = 'A';
    int int1 = 1230120;
    double f1 = 1.0 / 3.0,f2=0.34,f3=1.6E2;
    cout << char1 << endl << int1 << endl << f1 << endl << f2 << endl << f3 * 1.0e5 << endl << f3 / 1.0e8 << endl;
1
2
3
4

输出为

A
1230120
0.333333
0.34
1.6e+07
1.6e-06
1
2
3
4
5
6

通过观察发现系统中每种类型的默认输出格式:

  1. char和数值型打印其字符自身。
  2. 浮点型其指数大于6或者小于等于5时用科学计数法,其他显示6位,末尾0不显示。

# 修改cout的计数系统

    int a = 256;
    cout << hex << a << endl;
    dec(cout);
    cout << a << endl << oct << a << endl;
1
2
3
4

# 调整字段宽度和默认填充字符

    float f1 = 1.341232323;
    cout.width(6);
    cout << f1 << endl;
    cout << "#";
    cout.width(6);
    cout << "#" << 5 << "#" << 6 << "#" << cout.width() << endl;
    //width()函数只影响接下来显示的一个项目,后面的字段宽度将恢复为默认值。只有第一个是"#"是6位宽度,其他都是默认宽度。默认是右对齐。
    cout.fill('*');
    cout.width(6);
    cout <<	"h" << endl;
1
2
3
4
5
6
7
8
9
10

输出为

1.34123
#     #5#6#6
*****h
1
2
3

# setf(constData)


    //显示末尾的0
    cout.setf(ios_base::showpoint);
    cout << 3.1 << endl << 3.2 << endl;

    //以true或者false来输出布尔变量的值,否则为整数或者0。
    cout << true<<endl;
    cout.setf(ios_base::boolalpha);
    cout << true << endl;

    //使用c++基数前缀(0,0x)
    cout.setf(ios_base::showbase);
    hex(cout);
    cout << 0xe1 << endl << 010 << endl;

    //对于16进制数,使用大写字母输出
    cout.setf(ios_base::uppercase);
    cout << 0xe1 << endl;

    //整数前面加上+
    dec(cout);
    cout.setf(ios_base::showpos);
    cout << 10 << endl;;

    //如果想取消某个效果可以调用unsetf()比如:
    cout.unsetf(ios_base::showpos);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

输出

3.10000
3.20000
1
true
0xe1
0x8
0XE1
+10
1
2
3
4
5
6
7
8

# setf(setflag,clearflag)

//有二个参数时:第二个参数用来指出清除哪些位,第一个参数指出要设置哪些位。
    cout.setf(ios_base::hex, ios_base::basefield);
    cout << 10 << endl;
    cout.setf(ios_base::oct, ios_base::basefield);
    cout << 10 << endl;
    cout.setf(ios_base::dec, ios_base::basefield);
    cout << 10 << endl;

    cout.unsetf(ios_base::dec);

    //设置浮点数的精度
    cout << "-----------------------" << endl;
    cout.precision(3);
    cout << 3.1 << endl << 3.1454 << endl;
    cout << "-----------------------" << endl;
    cout.setf(ios_base::scientific, ios_base::floatfield);
    cout << 3.1 << endl << 3.1454 << endl;
    cout << "-----------------------" << endl;
    cout.setf(ios_base::fixed, ios_base::floatfield);
    cout << 3.1 << endl << 3.1454 << endl;
    //在定点表示法和科学表示法中,精度指的是小数位数,而不是总位数,它们会显示末尾的0

    cout.unsetf(ios_base::fixed);

    cout << "-----------------------" << endl;
    cout.width(10);
    cout.setf(ios_base::adjustfield, ios_base::left);
    cout << 10 << endl;
    cout << "-----------------------" << endl;
    cout.width(10);
    cout.setf(ios_base::adjustfield, ios_base::right);
    cout << 10 << endl;
    cout << "-----------------------" << endl;

    cout.unsetf(ios_base::adjustfield);

    cout.setf(ios_base::showpos);
    cout.setf(ios_base::showbase);
    cout.setf(ios_base::adjustfield, ios_base::internal);
    cout.width(10);
    cout << 10 <<endl;
    cout.setf(ios_base::hex, ios_base::basefield);
    cout.width(10);
    cout << 0x16 <<endl;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

输出为:

a
12
10
-----------------------
3.1
3.15
-----------------------
3.100e+00
3.145e+00
-----------------------
3.100
3.145
-----------------------
10
-----------------------
        10
-----------------------
+       10
0x      16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 头文件iomanip

像前面的设置一些格式不太方便,C++在头文件iomanip中提供了其他一些控制符,它们能够提供前面讨论的服务。最常用的控制符有三个:setprecision(),setfill(),setw().

cout << fixed << right;
//setprecision(3);
//cout << 3.1423 << endl << 3.1<<endl;
cout << setfill('*') << setw(10) << 6 << endl << 3.1<<endl;
cout << setprecision(3) << 3.1323 << endl;
1
2
3
4
5

输出:

*********6
3.100000
3.132
1
2
3

# 标准输入

>>的原型为:

istream & operator >>(int &);

cin对象根据接收变量的类型使用对应的方法将字符序列转换成相应的类型,在istream类中重新定义了>>运算符,使它被重载能识别:unsigned char &,signed char &,char,short &,unsigned short &,int &,unsigned int &,long &,unsigned long &,long long &,unsigned long long &,float &,double &,long double &,signed char *,unsigned char *, char *.

# cin >>检查输入

跳过空格,换行符和制符表,直到遇到非空白字符,将这些空白字符读入到变量中。

    int a;
    char b;
    cin >> a >> b;
    cout << a << endl << b <<endl;
    //输入 112z,则a是112,b是z

    int sum = 0;
    while (cin >> a) {
        sum += a;
    }
    cout << sum << endl;
    //如果输入的是数字则一直执行,如果输入的不是数字,则cin >> a变为false

    //打印输入的字符,如果碰到回车则结束
       char ch;
    ch = cin.get();
    while (ch != '\n') {
        cout << ch << endl;
        //cin >> ch;
        //因为cin会过滤掉空格,回车,制表符。所以会死循环
        //cin.get(ch);
        ch = cin.get();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 流状态

cin或cout包含一个描述流状态(stream state)的数据成员,从ios_base继承,它是一种bitmask类型,由3个位组成。相关的变量和方法如下:

成员 作用
eofbit cin操作到达文件末尾,设置该位
badbit 流被破坏,则设置为1,比如文件读取错误
failbit 未能读取预期字符或IO失败(读取不可访问文件或试图写入写保护的磁盘),则置为1。
goodbit 全为0
good() 流可以使用,则返回true
eof() 如果eofbit被设置,返回true
bad() 如果badbit被设置,返回true
fail() 如果badbit或failbit被设置,返回true

# 设置状态

void printIOState() {
    cout << "---------------" << endl;
    cout << "eof:" << cin.eof() << endl;
    cout << "fail:" << cin.fail() << endl;
    cout << "bad:" << cin.bad() << endl;
    cout << "good:" << cin.good() << endl;
    cout << "---------------" << endl;
}

void testSetAndClear() {
    //cin.exceptions(ios_base::eofbit);
    //设置badbit位的同时也会设置failbit
    //设置failbit只会设置failbit
    cin.setstate(ios_base::eofbit);
    cin.setstate(ios_base::failbit);
    printIOState();
    cin.clear(ios_base::badbit);//不会报出异常。
    printIOState();
    //setstate 只会设置对应的位。
    //clear 则是先把其他位清除再设置该位。

}


int main()
{
    cout <<"eofbit:"<< ios_base::eofbit << ";failbit:" << ios_base::failbit<< ";badbit:" << ios_base::badbit << endl;

    //testSetAndClear();

    cin.exceptions(ios_base::failbit | ios_base::eofbit);

    try {
        int a, sum = 0;

        //cin.setstate(ios_base::failbit);
        while (!cin.eof()) {
            cin >> a;
            sum += a;
        }
        cout << sum << endl;
    }
    catch(ios_base::failure & bf){
        printIOState();
        cout <<"stream status:" << cin.rdstate() << "    exceptions:"<<cin.exceptions() <<endl;
        cout << bf.what()<<endl;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

setstate 只会设置对应的位。
clear 则是先把其他位清除再设置该位。

# 流状态对流输入的影响

实例1:输入一个整数,如果不是整数类型一直提示直到输入整数为止:


    //version1
    int a;
    while (!(cin >> a)) {
        cin.clear();    //重置流的状态
        while (cin.get() != '\n') {
            continue;
        }
        //上面清除输入缓存中的本行数据,不然会一直在缓冲区中存放不出来
        cout << "please enter a number:" << endl;
    }
    cout << a <<endl;

    //version2
    int a;
    while (!(cin >> a)) {
        cin.clear();    //重置流的状态
        //system("pause");
        //cin.sync(); 这个方法不能可靠地被使用,对于一些编译器可用,另一些编译器不可用。它的效果是implementation-defined
        //上面清除输入缓存中的本行数据,不然会一直在缓冲区中存放不出来
        //cin.ignore(100, EOF);
        cin.ignore(100, '\n');
        cout << "please enter a number:" << endl;
    }
    cout << a << endl;

    //version3
    int a;
    do
    {
        cin.clear();
        while (cin.get() != '\n') {
            continue;
        }
        cout << "input a int:" << endl;
        cin >> a;
    } while (cin.fail() && !cin.eof());
    cout << a << endl;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

实例2:一直输入字符,如果碰到eof就结束


//在常见系统上ctrl+z将会模拟EOF,在检测到EOF后,cin将(eofbit和failbit)都设置为1。可以分别使用cin.eof()和cin.fail()来检测.
//cin.fail()比cin.eof()更加通用,因为能检测IO问题比如硬盘损坏。

//version1
void InputString1(char * inputPtr) {
    char ch;
    cin.get(ch);
    int inputLength = 0;
    while (!cin.fail()) {
        *(inputPtr + inputLength) = ch;
        inputLength++;
        cin.get(ch);
    }
    *(inputPtr + inputLength) = '\0';

    cout << inputPtr << endl;
    for (int i = 0; i < inputLength; i++) {
        cout << *(inputPtr + i);
    }
    cout << endl << "------------------" << endl;
}

//version2
void InputString2(char * inputPtr) {
    char ch;
    int inputLength = 0;
    while ((ch=cin.get())!=EOF) {
        *(inputPtr + inputLength++) = ch;
    }
    *(inputPtr + inputLength) = '\0';

    cout << inputPtr << endl;
    for (int i = 0; i < inputLength; i++) {
        cout << *(inputPtr + i);
    }
    cout << endl << "------------------" << endl;
}

//version3
void InputString3(char * inputPtr) {
    char ch;
    int inputLength = 0;
    do{
        ch=cin.get();
        //如果将上面换成下面这行将不对
        //cin.get(ch);
        *(inputPtr + inputLength++) = ch;
    }while (ch!=EOF);

    *(inputPtr + inputLength) = '\0';

    cout << inputPtr << endl;
    for (int i = 0; i < inputLength; i++) {
        cout << *(inputPtr + i);
    }
    cout << endl << "------------------" << endl;
}

main(){
    char * inputPtr = new char[100];
    InputString4(inputPtr);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# cin的其他方法

# 实例1-get(char &)和get(void)区别


    char ch;

    if (!cin.get(ch)) cout << "cin.get false!" << endl;
    cout << (int)ch<<endl;

    if ((ch =cin.get())== EOF) {
        cout << "EOF detected!" << endl;
        cout << ch << endl;
    }
1
2
3
4
5
6
7
8
9
10
  1. 在读到EOF时,get(void)会返回EOF,而get(char &)会返回false.
  2. get(char &)会返回istream对象引用,而get(void)会返回字符编码int值.
  3. get(void)可以替换C语言中的getchar(),cout.put(ch)可以替换putchar(ch).

# 实例2-字符串的输入getline(),get(),ignore()

他们的原型

istream & get(char *,int,char);
istream & get(char *,int);

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

istream & ignore(int =1,int =EOF);
1
2
3
4
5
6
7
    char ch[10];
    cout << "get :" << endl;
    cin.get(ch,5);
    cout << ch <<endl;

    cout << "getline 1:" << endl;
    cin.getline(ch, 5);
    cout << ch << endl;

    cout << "getline 2:" << endl;
    cin.getline(ch, 5);
    cout << ch << endl;

    cout << "get 2:" << endl;
    cin.get(ch, 5);
    cout << ch << endl;

    cout << "get with ; :" << endl;
    cin.get(ch,5,';');
    cout << ch <<endl;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

输入输出:

get :
123
123
getline 1:

getline 2:
123
123
get 2:
123
123
get with ; :
1231

123
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

结论:

  1. get会将换行符留在输入流中,接下来的输入会先把换行符取出。get能够读取换行符。
  2. getline抽取并丢弃输入流中的换行符。getline无法读取换行符。
  3. ignore两个参数,第一个是抽取输入流中的最大字符数,第二个字符参数,是在最大字符数内遇到该字符则结束。

# 实例3-意外字符串的输入

    char ch1[5];
    char ch2[5];
    while (cin.get(ch1, 5)) {
        cout << ch1 << endl;
    };
    if (cin.peek() == EOF) {
        cout << "正常结束!" << endl;
    }
    //cin.getline(ch1,5);
    printIOState();
    cout << "InputEnd!" << endl;
1
2
3
4
5
6
7
8
9
10
11
1. 如果直接输入回车会输出:

正常结束!
---------------
eof:0
fail:1
bad:0
good:0
---------------
InputEnd!

2. 如果输入helloworld则输出:
helloworld
hell
owor
ld
正常结束!
---------------
eof:0
fail:1
bad:0
good:0
---------------
InputEnd!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

将上面的get改为getline

1. 如果直接输入回车会另起一行不会报错。
2. 如果输入helloworld则输出:
helloworld
正常结束!
---------------
eof:0
fail:1
bad:0
good:0
---------------
InputEnd!
1
2
3
4
5
6
7
8
9
10
11

# 实例4-其他函数

char ch1[5];
cin.read(ch1,5);
并不在字符串中自动添加空值字符
cin.peek();
返回输入缓冲中的下一个字符,但是并不抽取该字符。
cin.gcount();
返回最后一个由get(),getline(),ignore(),read()读取的字符数。
cin.putback('A');
将字符放入输入缓冲区中,下一个读取的第一个字符便是'A'
1
2
3
4
5
6
7
8
9