Skip to content

C++标准输入与输出

标准I/O对象:cincoutcerrclog

cout流对象

cout是console output的缩写,意为在控制台(终端显示器)的输出。强调几点。

  1. cout不是C++预定义的关键字,它是ostream流类的对象,在iostream中定义。 顾名思义,流是流动的数据,cout流是流向显示器的数据。cout流中的数据是用流插入运算符 << 顺序加入的。

    如果有:     cout << "I " << "study C++ " << "very hard." << “hello world !";

    按顺序将字符串"I ", "study C++ ", "very hard." 插人到cout流中,cout就将它们送到显示器,在显示器上输出字符串"I study C++ very hard."

    cout流是容纳数据的载体,它并不是一个运算符。人们关心的是cout流中的内容,也就是向显示器输出什么。

  2. 用“cout <<”输出基本类型的数据时,可以不必考虑数据是什么类型,系统会判断数 据的类型,并根据其类型选择调用与之匹配的运算符重载函数。这个过程都是自动的, 用户不必干预。如果在C语言中用printf函数输出不同类型的数据,必须分别指定相应的输出格式符,十分麻烦,而且容易出错。C++的I/O机制对用户来说,显然是方便而安全的。

  1. cout流在内存中对应开辟了一个缓冲区,用来存放流中的数据,当向cout流插人一个endl时,不论缓冲区是否已满,都立即输出流中所有数据,然后插入一个换行符, 并刷新流(清空缓冲区)。注意如果插人一个换行符”\n“(如cout << a << "\n";),则只输出和换行,而不刷新cout 流(但并不是所有编译系统都体现出这一区别)。

  2. iostream中只对 <<>> 运算符用于标准类型数据的输入输出进行了重载,但未对用户声明的类型数据的输入输出进行重载。如果用户声明了新的类型,并希望用 <<>> 运算符对其进行输入输出,按照重运算符重载来做。

cerr流对象

cerr流对象是标准错误流,cerr流已被指定为与显示器关联cerr的作用是向标准错误设备(standard error device)输出有关出错信息。cerr与标准输出流cout的作用和用法差不多。但有一点不同:cout流通常是传送到显示器输出,但也可以被重定向输出到磁盘文件,而cerr流中的信息只能在显示器输出。当调试程序时,往往不希望程序运行时的出错信息被送到其他文件,而要求在显示器上及时输出,这时应该用cerrcerr流中的信息是用户根据需要指定的。

clog流对象

clog流对象也是标准错误流,它是console log的缩写。它的作用和cerr相同,都是在终端显示器上显示出错信息。两者的区别是:cerr是不经过缓冲区,直接向显示器上输出有关信息,而clog中的信息存放在缓冲区中,缓冲区满后或遇endl时向显示器输出。

缓冲区的概念:

标准输入流

标准输入流对象cin,重点掌握的函数

CPP
cin.get() //一次只能读取一个字符

cin.get(一个参数) //读一个字符

cin.get(两个参数) //可以读字符串
char buf[1024] = {0};
cin.get(buf, 1024);	//不会读取换行符,换行符会留在缓冲区

cin.getline() //获取一行字符串,换行符被丢弃不会遗留在缓冲区

cin.ignore() //默认忽略一个字符,如果填入参数X,则会忽略X个字符

cin.peek() //偷窥,只是看一下。不会影响缓冲区中的数据

cin.putback() //放回 放回原位置
cpp
//cin.get
void test01() {
#if 0
	char ch = cin.get();
	cout << ch << endl;

	cin.get(ch);
	cout << ch << endl;


	//链式编程
	char char1, char2, char3, char4;
	cin.get(char1).get(char2).get(char3).get(char4);

	cout << char1 << " " << char2 << "" << char3 <<  " " << char4 << " ";
#endif

    char buf[1024] = {0};
    //cin.get(buf.1024);
    cin.getline(buf, 1024);
    cout << buf;
}

//cin.ignore
void test02() {

    char buf[1024] = {0};
    cin.ignore(2);//忽略缓冲区当前字符
    cin.get(buf, 1024);
    cout << buf << endl;
}

//cin.putback 将数据放回缓冲区
void test03() {

    //从缓冲区取走一个字符
    char ch = cin.get();
    cout << "从缓冲区取走的字符:" << ch << endl;
    //将数据再放回缓冲区
    cin.putback(ch);
    char buf[1024] = {0};
    cin.get(buf, 1024);
    cout << buf << endl;
}

//cin.peek 偷窥
void test04() {

    //偷窥下缓冲区的数据
    char ch = cin.peek();
    cout << "偷窥缓冲区数据:" << ch << endl;
    char buf[1024] = {0};
    cin.get(buf, 1024);
    cout << buf << endl;
}

//练习  作业 使用cin.get和putback完成类似功能
void test05() {

    cout << "请输入一个数字或者字符串:" << endl;
    char ch = cin.peek();
    if (ch >= '0' && ch <= '9') {
        int number;
        cin >> number;
        cout << "数字:" << number << endl;
    } else {
        char buf[64] = {0};
        cin.getline(buf, 64);
        cout << "字符串:" << buf << endl;
    }
}

标志位:cin.fail()0为正常,为1为异常。

当标志位异常的时间会导致程序进入循环,此时需要重制标志位、清空缓冲区以保证程序的健壮性(在Visual Studio2013以上的版本还需要进行一个cin.ignore()的操作)

cpp
cin.clear();
cin.sync();
//cin.ignore(); //VS2013以上需要进行此步

标准输出流

字符输出

cpp
cout.flush() //刷新缓冲区 Linux下有效
cout.put() //向缓冲区写字符
cout.write() //从buffer中写num个字节到当前输出流中。
cpp
//cout.flush 刷新缓冲区,linux下有效
void test01() {
    cout << "hello world";
    //刷新缓冲区
    cout.flush();
}

//cout.put 输出一个字符
void test02() {

    cout.put('a');
    //链式编程
    cout.put('h').put('e').put('l');
}

//cout.write 输出字符串 buf,输出多少个
void test03() {

    //char* str = "hello world!";
    //cout.write(str, strlen(str));
    char *str = "*************";
    for (int i = 1; i <= strlen(str); i++) {
        cout.write(str, i);
        cout << endl;
    }

    for (int i = strlen(str); i > 0; i--) {
        cout.write(str, i);
        cout << endl;
    }
}

格式化输出

在输出数据时,为简便起见,往往不指定输出的格式,由系统根据数据的类型采取默认的格式,但有时希望数据按指定的格式输出,如要求以十六进制或八进制形式输出一个整数,对输出的小数只保留两位小数等。有两种方法可以达到此目的。

  • 使用控制符的方法;

  • 使用流对象的有关成员函数。

使用流对象的有关成员函数

通过调用流对象cout中用于控制输出格式的成员函数来控制输出格式。用于控制输出格式的常用的成员函数如下:

流成员函数setf和控制符setiosflags括号中的参数表示格式状态,它是通过格式标志来指定的。格式标志在类ios中被定义为枚举值。因此在引用这些格式标志时要在前面加上类名ios和域运算符“::”。格式标志见表13.5。

控制符格式化输出

C++提供了在输入输出流中使用的控制符(有的书中称为操纵符)。

cpp
//通过流成员函数
void test01() {

    int number = 99;
    cout.width(20);	//设置宽度为20
    cout.fill('*');	//使用*进行填充
    cout.setf(ios::left);	//设置左对齐
    cout.unsetf(ios::dec);	//卸载十进制
    cout.setf(ios::hex);	//设置十六进制
    cout.setf(ios::showbase);	//设置显示基数,十六进制或者八进制前面的标识符
    cout.unsetf(ios::hex);	//卸载十六进制
    cout.setf(ios::oct);	//设置八进制
    cout << number << endl;
}

//使用控制符
void test02() {

    int number = 99;	
    cout << setw(20)	//设置宽度为20
         << setfill('~')	//使用~进行填充
         << setiosflags(ios::showbase)	//设置显示基数,十六进制或者八进制前面的标识符
         << setiosflags(ios::left)	//设置左对齐
         << hex	//设置十六进制
         << number
         << endl;
}

对程序的几点说明

  1. 成员函数width(n)和控制符setw(n)只对其后的第一个输出项有效。如:

    cpp
    cout. width(6);  
    cout << 20 << 3.14 << endl;

    输出结果为 203.14

    在输出第一个输出项20时,域宽为6,因此在20前面有4个空格,在输出3.14时,width(6)已不起作用,此时按系统默认的域宽输出(按数据实际长度输出)。如果要求在输出数据时都按指定的同一域宽n输出,不能只调用一次width(n), 而必须在输出每一项前都调用一次width(n),上面的程序中就是这样做的。

  2. 在表13.5中的输出格式状态分为5组,每一组中同时只能选用一种(例如dec、hex和oct中只能选一,它们是互相排斥的)。在用成员函数setf和控制符setiosflags设置输出格式状态后,如果想改设置为同组的另一状态,应当调用成员函数unsetf(对应于成员函数self)或 resetiosflags(对应于控制符setiosflags),先终止原来设置的状态。然后再设置其他状态,大家可以从本程序中看到这点。程序在开始虽然没有用成员函数self和控制符setiosflags设置用dec输出格式状态,但系统默认指定为dec,因此要改变为hexoct,也应当先用unsetf 函数终止原来设置。如果删去程序中的第7行和第10行,虽然在第8行和第11行中用成员函数setf设置了hexoct格式,由于未终止dec格式,因此hexoct的设置均不起作用,系统依然以十进制形式输出。同理,程序倒数第8行的unsetf 函数的调用也是不可缺少的。

  3. setf 函数设置格式状态时,可以包含两个或多个格式标志,由于这些格式标志在ios类中被定义为枚举值,每一个格式标志以一个二进位代表,因此可以用位或运算符“|”组合多个格式标志。如倒数第5、第6行可以用下面一行代替:

    cpp
    cout.setf(ios::internal | ios::showpos);  //包含两个状态标志,用"|"组合
  4. 可以看到:对输出格式的控制,既可以用控制符(如例13.2),也可以用cout流的有关成员函数(如例13.3),二者的作用是相同的。==控制符是在头文件iomanip中定义的,因此用控制符时,必须包含iomanip头文件==。cout流的成员函数是在头文件iostream 中定义的,因此只需包含头文件iostream,不必包含iomanip。许多程序人员感到使用控制符方便简单,可以在一个cout输出语句中连续使用多种控制符。