终端读写操作

作者:jicanmeng

时间:2017年06月07日


  1. setw控制符
  2. extraction operator(>>)的缺陷和解决方法: get()
  3. get()函数的缺陷和解决方法: getline()

c语言中,向monitor写数据使用printf()函数,从keyboard读取数据使用scanf()函数。

c++中,cout代表monitor,cin代表keyboard。向monitor写数据使用cout <<这种方式。从keyboard读取数据使用cin >> 这种方式。注意,cout和cin不是函数,而是对象。

首先看一个最简单的例子:

#include <iostream>
using namespace std;

int main()
{
    char buf[10];

    cout << "Enter your name: " << endl;
    cin >> buf;
    cout << "Your name is " << nAge << endl;

    return 0;
}

运行结果如下:

[jicanmeng@andy tmp]$ ./a.out
                Enter your name:
                andy
                Your name is andy
            [jicanmeng@andy tmp]$

但是上面的程序存在一个bug,我们定义的buf数组大小为10个字节,如果我们输入的字符大于9个,那么程序就会提示错误。应该如何解决这个问题呢?

1. setw控制符

解决方法是使用manipulator。A manipulator is an object that is used to modify a stream when applied with the extraction (>>) or insertion (<<) operators. C++ provides a manipulator known as setw(in the iomapip.h header) that can be used to limit the number of characters read in from a stream. 所以上面的例子修改如下:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    char buf[10];

    cout << "Enter your name: " << endl;
    cin >> setw(10) >> buf;
    cout << "Your name is " << nAge << endl;

    return 0;
}

This program will now only read the first 9 characters out of the stream (leaving room for a terminator). Any remaining characters will be left in the stream until the next extraction.

2. extraction operator(>>)的缺陷和解决方法: get()

使用>>将stream中的数据传递出来时,默认情况下会忽略空白字符(blanks, tabs, and newlines)。如下面的程序:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    char ch;
    while (cin >> ch) {
        cout << ch;
    }

    return 0;
}

运行结果如下:

[jicanmeng@andy tmp]$ ./a.out
                hello my name is canmeng
                hellomynameiscanmeng
            [jicanmeng@andy tmp]$

我们期望是,从cin这个stream object中传递出来的数据能够保持原样。One of the most useful method is the get() function, which simply gets a character from the input stream. 修改后的程序如下:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    char ch;
    while (cin.get(ch)) {
        cout << ch;
    }

    return 0;
}

运行结果如下:

[jicanmeng@andy tmp]$ ./a.out
                hello my name is canmeng
                hello my name is canmeng
            [jicanmeng@andy tmp]$

get() also has a string version that takes a maximum number of characters to read:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    char strBuf[11];
    cin.get(strBuf, 11);
    cout << strBuf << endl;

    return 0;
}

3. get()函数的缺陷和解决方法: getline()

get(strBuf, 11)函数有一个缺陷,它不能从stream中读出newline character。例如下面的例子:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    char strBuf[11];
    // Read up to 10 characters
    cin.get(strBuf, 11);
    cout << strBuf << endl;

    // Read up to 10 more characters
    cin.get(strBuf, 11);
    cout << strBuf << endl;
    return 0;
}

如果我们只输入一个"hello"字符串,那么程序就会直接结束了,而不会等待我们继续输入字符。 learncpp.com上面是这样说的:

The answer is because the first get() read up to the newline and then stopped. The second get() saw there was still input in the cin stream and tried to read it. But the first character was the newline, so it stopped immediately.

但是经过我的测试,发现上面的说法并不对。正确的说法应该是:第一次get()函数遇到了newline字符的时候,会停止读取,并将newline character转换为0x00保存在input stream中,但是返回的字符不包括0x00这个字符。然后第二次调用get()的时候,会首先读取0x00字符,就认为读取到字符串末尾,所以程序就结束了。(cin.get()函数读取到newline character不会进行转换,cin.get(str,number)才会转换)。

不管怎么样,上面的程序是存在问题的。解决方法是使用cin对象的getline()方法。和get()方法不同的是,这个方法可以在遇到newline character的时候,会停止读取,并将newline character转换为0x00保存在input stream中,但是返回的字符是包括0x00这个字符的。我们使用cin.gcount()函数可以看出从input stream中读取了多少个字符,。将上面的程序稍微修改一下:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    char strBuf[11];
    // Read up to 10 characters
    cin.getline(strBuf, 11);
    cout << strBuf << endl;
    cout << cin.gcount() << " characters were read" << endl;

    // Read up to 10 more characters
    cin.getline(strBuf, 11);
    cout << strBuf << endl;
    cout << cin.gcount() << " characters were read" << endl;

    return 0;
}

运行结果如下:

[jicanmeng@andy tmp]$ ./a.out
                hello
                hello
                6 characters were read
                dodo
                dodo
                5 characters were read
                请按任意键继续. . .
            [jicanmeng@andy tmp]$

但是上面的程序仍然是有问题的。如果我们输入"hello"字符串,是没有问题的。但是我们输入"helloworldmeng"就会直接退出了。为什么呢?我们把程序扩充一下看一看就知道了:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    char strBuf[11];

    cout << "At first: " << cin.rdstate() << endl;
    cout << ios_base::goodbit << endl;
    cout << ios_base::eofbit << endl;
    cout << ios_base::failbit << endl;
    cout << ios_base::badbit << endl;

    // Read up to 10 characters
    cin.getline(strBuf, 11);
    cout << strBuf << endl;
    cout << cin.gcount() << " characters were read" << endl;
    printf("count is %d\n", number);
    for (int i = 0; i < number; i++) {
        printf("%x ", strBuf[i]);
    }
    printf("\n");

    cout << "At half: " << cin.rdstate() << endl;

    // Read up to 10 more characters
    cin.getline(strBuf, 11);
    cout << strBuf << endl;
    number = cin.gcount();
    printf("count is %d\n", number);
    for (int i = 0; i < number; i++) {
        printf("%x ", strBuf[i]);
    }
    printf("\n");

    return 0;
}

运行结果如下:

[jicanmeng@andy tmp]$ ./a.out
                At first: 0
                0
                1
                2
                4
                helloworldmeng
                helloworld
                count is 10
                68 65 6c 6c 6f 77 6f 72 6c 64
                At half: 2

                count is 0

                请按任意键继续. . .
            [jicanmeng@andy tmp]$

可以看出,在25行打印stream的状态时出现了错误,错误为failbit。在stream的状态错误时调用getline(),就直接退出了(这是为什么,我也不知道)。我们可以通过cin.clear()将stream的状态重置为正常,通过cin.sync()来清除stream中的数据。这样,在25行添加两行代码,这个问题就解决了。

参考资料

  1. <<C++实用教程>> 电子工业出版社 郑阿奇 主编 丁有和 编著
  2. The C++ Tutorial:
    http://www.cnblogs.com/tonglingliangyong/p/3908463.html