在C++中,标准输出流通常指的是与标准输出设备(通常是终端或控制台)相关联的流对象。这个流对象在C++标准库中被定义为std::cout、std::err、std::clog,它们是std::ostream类的一个实例。
一、cout,cerr和clog流
ostream类定义了3个输出流对象,即cout,cerr,clog。
1.1cout流对象
(1) cout是console output的缩写,意为控制台(终端显示器)的输出。它不是C++预定义的关键字,而是ostream流类的对象,在iostream中定义。顾名思义,流是流动的数据,cout流是流向输出(显示设备)的数据,cout流中的数据是用流插入运算符“<<“顺序加入的。
示例如下:
cout <<"Hello World!" <<"I am learning C++.";
cout流是容纳数据的载体,而并不是运算符。cout将它们输送到输出设备上显示,在显示设备上输出”Hello Wordl! I am learning C++.“。
(2) 在使用"cout <<"输出基本类型的数据时,不必考虑数据是什么类型,系统会自动判断数据的类型,并根据类型选择调用与之匹配的运算符重载函数。示例如下:
cout <<10.0f <<100.58 <<'=' <<"operator";
(3) cout流在内存中对应开辟了一个缓冲区,用来存储流中的数据,当向cout流插入一个endl时,不论缓冲区是否已满,都立即输出流中所有数据,然后插入一个换行符,并刷新流(清空缓冲区)。示例如下:
cout <<"Hello World" <<endl;
cout <<"I am learning C++" <<endl;
注意的是如果插入换行符”\n“,则只是换行而已,并不是刷新cout流,与endl刷新流是清空缓冲区不是一回事。
(4) 在iostream中只对”<<“和">>"运算符用于标准类型数据的输入输出进行了重载,但未对用户声明的类型数据的输入输出进行重载。所以用户可以声明新的类型,并用”<<“和">>"运算符对其进行输入输出另作重载。
1.2 cerr流对象
cerr流对象是标准出错流,cerr流已被指定为与显示器关联,cerr作用是向标准出错设备(standard error device)输出有关的出错信息。cerr是console error的缩写,意为在控制台(显示器)显示出错的信息。
注意的是cerr和cout的作用和用法虽然差不多,但是有一点不同的是,cout流通常是传送到显示设备输出,也可以被重定向输出到磁盘文件,而cerr流中的信息只能显示输出。
通过解一元二次方程,其一般解为,但若a=0,或时,用公式出错。编写程序,从键盘输入a、b、c的值,求和。如果a=0或,输出错误信息。代码示例如下:
#include <iostream>
#include <cmath>
using namespace std;
int main(){
float a, b, c, disc;
cout <<"Please enter the values of a, b, c:";
cin >>a >>b >>c;
// 如果a等于0输出错误信息
if(a == 0){
cerr <<"a is equal to zero, error!" <<endl;
} else{
// 如果b * b - 4 * a * c)结果小于0 ,输出错误信息
if( (disc = b * b - 4 * a * c) < 0 ){
cerr <<"Erro: dist=b*b-4*a*c<0" <<endl;
}
// 满足条件,则正常输出结果
else{
cout <<"x1 = " <<(-b + sqrt(disc) / (2*a)) <<endl;
cout <<"x2 = " <<(-b - sqrt(disc) / (2*a)) <<endl;
}
}
return 0;
}
1)如输入a=0,运行后结果如下图:
2)如输入,运行后结果如下图:
3)如满足条件,运行后结果如下图:
1.3 clog流对象
clog流对象也是标准出错流,它是console log的缩写,作用与cerr相同,都是在终端显示器上显示出错信息。它们之间只是有微小区别。cerr是不经过缓冲区,直接向显示器上输出有关信息,而clog中的信息存放在缓冲区中,缓冲区满后或遇endl时向显示器输出。
示例如下:
#include <iostream>
using namespace std;
int main(){
clog <<"This is a message to the C library's error stream." <<endl;
clog.flush(); //确保消息被立即刷新到设备
return 0;
}
运行结果如上图:
实际编程中,cerr通常用于输出需要立即显示的错误消息或调试信息,而clog用于记录那些稍后再处理的更详细的日志信息。这只是常见的约定,并不是强制的。
二、格式输出
在输出数据时,为简便起见,往往不指定输出的格式,由系统根据数据的类型采取默认的格式,但有时希望数据按指定的格式输出,如要求以十六进制或八进制形式输出一个整数,对输出的小数只保留两位小数等。
2.1 使用控制符控制输出格式
以下为输出数据的控制符,如下表:
控制符 | 作用 |
---|---|
dec | 设置整数的基数为10 |
hex | 设置整数的基数为16 |
oct | 设置整数的基数为8 |
setbae(n) | 设置整数的基数为n(n只能是8,10,16三者之一) |
setfill(c) | 设置填充字符c,c可以是字符常量或字符变量 |
setprecision(n) | 设置实数的精度为n位,在以一般十进制小数形式输出时n代表有效数字。在以fixed(固定小数位数)形式和scientific(指数)形式输出时n为小数位数。 |
setw(n) | 设置字段宽度为n位 |
setiosflags(ios::fixed) | 设置浮点数以固定的小数位数显示 |
setiosflags(ios::scientific) | 设置浮点数以科技记数法(即指数形式)显示 |
setiosflags(ios::left) | 输出数据左对齐 |
setiosflags(ios::right) | 输出数据右对齐 |
setiosflags(ios::skipws) | 忽略前导的空格 |
setiosflags(ios::uppercase) | 在以科学记数法输出E和以十六进制输出字母X时以大写表示 |
setiosflags(ios::showpos) | 输出正数时给出”+“号 |
resetioflags() | 终止已设置的输出格式状态,在括号中应指定内容 |
注意的是,这些控制符是在头文件iomanip中定义的,因而程序中应当包含头文件ipmanip。以下通过案例了解以上的方法,代码如下:
#include <iostream>
#include <iomanip>
using namespace std;
int main(){
int a = 20; //定义整数a值为20
// 输出信息
cout <<"Dec:" <<dec <<a <<endl; //以十进制形式输出整数
cout <<"Hex:" <<hex <<a <<endl; //以十六进制形式输出整数a
cout <<"Oct:" <<oct <<a <<endl; //以八进制形式输出整数a
cout <<endl;
// 通过setbase输出
cout <<"Use setbase to set the base:" <<endl;
cout <<"Dec:" <<setbase(10) <<a <<endl; //以十进制形式输出整数
cout <<"Hex:" <<setbase(16) <<a <<endl; //以十六进制形式输出整数a
cout <<"Oct:" <<setbase(18) <<a <<endl; //以八进制形式输出整数a
cout <<endl;
// 设置精度,宽度以及填充字符
double PI = 3.1415926;
cout <<"set precision 2: " <<setprecision(2) <<PI <<endl; //改为2位小数
cout <<setw(10) <<setprecision(2) <<PI <<endl; //设置宽度为10
cout <<setfill('*') <<setw(10) <<setprecision(2) <<PI <<endl; //设置填充字符
cout <<endl;
// setiosfloags
cout <<setiosflags(ios::scientific) <<setprecision(8); // 按指数形式输出8位小数
cout <<"PI = " <<PI <<endl; // 指数形式输出PI值
cout <<"precision 4 PI = " <<setprecision(4) <<PI <<endl; // 指数形式 输出PI为4位小数
cout <<"Left aligned, PI = " <<left <<setw(20) <<PI <<endl; // 左侧齐
cout <<"Right aligned, PI = " <<right <<setw(20) <<PI <<endl; // 右对齐
// 使用大写字母
cout <<"Uppercase: " <<uppercase <<hex <<255 <<endl; //以16进制显示并使用大写字母
cout <<"Show positive sign:" <<showpos <<PI <<endl;
// 注意此时
cout <<"Fixed, PI = " <<setiosflags(ios::fixed) <<PI <<endl; // 改为小数形式输出
return 0;
}
运行结果如下图:
最后代码试图将科学记数法设置为小数位,但是(cout <<"Fixed, PI = " <<setiosflags(ios::fixed) <<PI <<endl;)输出结果为+0XC.90FDA6896C25P-2,它可能是由于被setiosflags错误的解析导致的,通过resetiosflags重置清理掉前面的科学记数法格式即可。在上述代码后面追加以下代码即可:
// 重置指数形式
cout <<"reset ios flags:" <<endl;
cout <<resetiosflags(ios::scientific | ios::showpos);
cout <<"PI = " <<PI <<endl;
resetiosflags中ios::scientific是清除科学记数法,ios::showpos是清除数值前面”+“号,输出结果如下图:
2.2 用流对象的成员函数控制输出格式
除了可以用控制符来控制输出格式,还可以通过调用流对象cout中用于控制输出格式的成员函数来控制输出格式。用于控制输出格式的常用的成员函数如下表:
流成员函数 | 与之作用相同的控制符 | 作用 |
---|---|---|
precision(n) | setprecision(n) | 设置实数的精度为n位 |
width(n) | setw(n) | 设置字段宽度为n位 |
fill(c) | setfill(c) | 设置填充字符c |
setf() | setiosflags() | 设置输出格式状态,括号中应给出格式状态,内容与控制符setiosflags括号中的内容相同。 |
unsetf() | resetiosflag() | 终止已设置的输出格式状态,在括号中应该指定内容 |
格式标志在类ios中被定义为枚举值,因此在引用这些格式标志时要在前面加上类名ios和域运算符"::"。格式标志如下表:
格式标志 | 作用 |
---|---|
ios::left | 输出数据在本域范围内向左对齐 |
ios::right | 输出数据在本域范围内向右对齐 |
ios::internal | 数值的符号位在域宽度内左对齐,数值右对齐,中间由填充字符填充 |
ios::dec | 设置整数的基数为10 |
ios::oct | 设置整数的基数为8 |
ios::hex | 设置整数的基数为16 |
ios::showbase | 强制输出整数的基数(八进制数以0打头,十六进制数以0x打头) |
ios::showpoint | 强制输出浮点数的小点和尾数0 |
ios::uppercase | 在以科学记数法格式E和以十六进制输出字母时以大写表示 |
ios::showpos | 对正数显示”+“号 |
ios::scientific | 浮点数以科学记数法格式输出 |
ios::fixed | 浮点数以定点格式(小数形式)输出 |
ios::unitbuf | 每次输出之后刷新所有的流 |
ios::stdio | 每次输出之后清除stdout, stderr |
下面使用流控制成员输出之前案例数据,代码如下:
#include <iostream>
#include <iomanip>
using namespace std;
int main(){
int a = 20; //定义整数a值为20
// 输出信息
cout.setf(ios::dec); //设置以十进制形式输出整数
cout <<"Dec:" <<a <<endl;
cout.unsetf(ios::dec);
cout.setf(ios::hex); //设置以十六进制形式输出整数a
cout <<"Hex:" <<a <<endl;
cout.unsetf(ios::hex);
cout.setf(ios::oct); //设置以八进制形式输出整数a
cout <<"Oct:" <<a <<endl;
cout.unsetf(ios::oct);
cout <<endl;
// 设置精度,宽度以及填充字符
double PI = 3.1415926;
cout <<"set precision 2, PI = " <<PI <<endl; //显示PI值
cout.width(30); //设置宽度为30
cout <<"set width, PI =" <<PI <<endl;
cout.width(30); //设置宽度为30
cout.fill('*'); // 置填充字符*
cout <<"set fill, PI =" <<PI <<endl;
cout <<endl;
cout <<"set internal:" <<endl;
cout.width(30); //设置宽度为30
cout.fill(' '); // 置填充字符*
cout.setf(ios::internal | ios::showpos);
cout <<PI <<endl;
cout.unsetf(ios::internal | ios::showpos);
cout <<endl;
// 标记为科学记数法
cout.setf(ios::scientific); // 设置为科学记数法
cout <<"set scientific, PI = " <<PI <<endl;
cout.precision(4); //保留4位小数
cout <<"precision 4, PI = " <<PI <<endl;
cout <<endl;
// 指定用定点形式输出
cout.setf(ios::fixed);
cout <<"Fixed, PI = " <<PI <<endl;
cout.unsetf(ios::fixed);
return 0;
}
运行后结果如下图:
这里同样,在最后输出时PI未得到想要结果,这是因为在设置科学记数法格式cout.setf(ios::scientific)未及时清除设置的格式标志导致的,所以添加cout.unsetf(ios::scientific)及时清除以避免影响后续输出。
完整代码如下:
#include <iostream>
#include <iomanip>
using namespace std;
int main(){
int a = 20; //定义整数a值为20
// 输出信息
cout.setf(ios::dec); //设置以十进制形式输出整数
cout <<"Dec:" <<a <<endl;
cout.unsetf(ios::dec);
cout.setf(ios::hex); //设置以十六进制形式输出整数a
cout <<"Hex:" <<a <<endl;
cout.unsetf(ios::hex);
cout.setf(ios::oct); //设置以八进制形式输出整数a
cout <<"Oct:" <<a <<endl;
cout.unsetf(ios::oct);
cout <<endl;
// 设置精度,宽度以及填充字符
double PI = 3.1415926;
cout <<"set precision 2, PI = " <<PI <<endl; //显示PI值
cout.width(30); //设置宽度为30
cout <<"set width, PI =" <<PI <<endl;
cout.width(30); //设置宽度为30
cout.fill('*'); // 置填充字符*
cout <<"set fill, PI =" <<PI <<endl;
cout <<endl;
cout <<"set internal:" <<endl;
cout.width(30); //设置宽度为30
cout.fill(' '); // 置填充字符*
cout.setf(ios::internal | ios::showpos);
cout <<PI <<endl;
cout.unsetf(ios::internal | ios::showpos);
cout <<endl;
// 标记为科学记数法
cout.setf(ios::scientific); // 设置为科学记数法
cout <<"set scientific, PI = " <<PI <<endl;
cout.precision(4); //保留4位小数
cout <<"precision 4, PI = " <<PI <<endl;
cout.unsetf(ios::scientific);
cout <<endl;
// 指定用定点形式输出
cout.setf(ios::fixed);
cout <<"Fixed, PI = " <<PI <<endl;
cout.unsetf(ios::fixed);
return 0;
}
输出结果如下图:
三、用流成员函数put输出字符
在程序中一般用cout和插入运算符”<<“实现输出,cout流在内存中有相应的缓冲区。有时有些特殊输出要求,ostream类还提供了专用于输出单个字符的成员函数put。
3.1 ASCII码输出
如下示例代码:
#include <iostream>
using namespace std;
int main(){
// ASCII字符码输出
int arr[] = {71, 79, 79, 68}; // 字母GOOD分别对应ASCII码71,79,68
// 循环输出单个字符
for(int i = 0; i<4; i++) cout.put(arr[i]);
cout.put('\n');
return 0;
}
运行结果如下图:
3.2 字符串反转
也可使用cout.put()对字符串进行反转输出,示例代码如下:
#include <iostream>
#include <string>
using namespace std;
int main(){
string message = "Hello World!"; //定义字符串信息
const char *str = message.c_str(); //将字符串转换为char字符数组
// 循环输出字符信息,倒序输出
for(int i = message.length() - 1; i >= 0; i--) cout.put(*(str + i));
// 换行
cout.put('\n');
return 0;
}
运行后结果如下图:
字符指针变量str指向第1个字符'H', a+1则是第2个字符'e'的地址,*(a+1)的值就是’e'。指针的相关知识前面已讲解过,想了解朋友可以翻看下,地址:C++面向对象程序设计 - 对象指针和this指针_面向对象版本的指针-CSDN博客
3.3 putchar函数
除了可以用cout.put函数输出一个字符外,还可以用putchar函数输出一个字符。putchar函数是C语言中使用的,在stdio.h头文件中定义的。所以3.2中示例可以修改为putchar函数,代码如下:
#include <iostream>
#include <string>
using namespace std;
int main(){
string message = "Hello World!"; //定义字符串信息
const char *str = message.c_str(); //将字符串转换为char字符数组
// 循环输出字符信息,倒序输出
for(int i = message.length() - 1; i >= 0; i--) putchar(*(str + i));
// 换行
cout.put('\n');
return 0;
}
运行后输出结果与3.2相同。