90.cout和printf有什么区别?

发布时间 2023-07-11 15:37:50作者: CodeMagicianT

90.cout和printf有什么区别?

coutprintf 是 C++ 和 C语言中用于输出信息的函数,它们有以下区别:

  1. 语法和用法:
    • cout 是 C++ 中的输出流对象,可以使用流操作符 << 来输出各种类型的数据。例如:cout << "Hello, World!"
    • printf 是 C语言中的函数,它使用格式化字符串来输出数据。例如:printf("Hello, World!")
  2. 处理数据类型的方式:
    • cout 能够自动推断输出对象的数据类型,并自动调用适当的输出运算符来进行输出。因此,它可以直接输出各种类型的数据,包括整数、浮点数、字符、字符串等。
    • printf 需要使用格式化字符串来指定输出的数据类型和格式,如 %d 表示整数,%f 表示浮点数,%c 表示字符等。
  3. 错误处理和安全:
    • cout 具有更好的错误处理机制,它提供了许多重载的输出运算符,可以检查输出操作是否成功,并能够自动刷新输出缓冲区。
    • printf 通常直接将数据写入输出缓冲区,如果需要手动刷新缓冲区,或者在输出过程中进行错误处理,可以使用相关的库函数来实现。
  4. 标准库支持:
    • cout 是 C++ 标准库的一部分,它提供了丰富的功能和灵活性,可以与其他标准库函数和类一起使用。
    • printf 是 C语言的标准库函数,它提供了一定的灵活性和功能,但相对于 cout 而言,它的功能相对较简单,且通常不与其他标准库函数和类一起使用。

总的来说,coutprintf 都是用于输出信息的函数,但它们在语法、处理数据类型的方式、错误处理和标准库支持等方面存在一些差异。在选择使用哪个函数时,需要根据具体的需求和编程语言的特点来决定。

1.cout用法

1.1C++输入输出综述

C++ IO首先建立在为Unix环境开发的原始库函数上;ANSI C正式承认这个库时,将其称为标准输入/输出包;
IO相关类定义在头文件iostream和fstream,这些类不是正式语言定义的组成部分,cin,istream都不是关键字。

1.1.1 流和缓冲

(1)流简介:

●C++程序将输入和输出看作字符流;对于输入来说,程序从输入流中抽取字符,对于输出来说,程序向输出流中插入字符;
●输入流可以来自键盘、存储设备或者其他程序;输出流可以输出至显示器、打印机、存储设备或者其他程序。
●流是程序与流源或流目的之间的中介,这样C++就可以对来源不同的字符做相同处理。
(2)管理输入:

●两个阶段:将流与程序绑定在一起,将流与源绑定在一起
(3)管理输出:

●两个阶段:将流与目的绑定在一起,将流与程序绑定在一起
(4)缓冲区简介

缓冲区就是一块存储空间,它是为了匹配程序处理速度和外设处理速度;比如程序一次处理1byte,但是磁盘一次读取512bytes;又或者程序一次处理1byte,可以1byte地从磁盘读取,但是由于硬件读取一次数据复杂且操作慢,因此使用缓冲区可以加快程序处理速度。
flushing the buffer:刷新缓冲区就是清空缓冲区地内容以备下次使用。

1.1.2 输入输出中比较重要的类

●streambuf:提供缓冲区,有成员方法填满缓冲区、获取缓冲区内容、刷新缓冲区、管理缓冲区
●ios_base:表示流的一般属性比如文件是否打开、是二进制流还是文本流等等
●ios:基于ios_base,并且它包含了一个指针成员指向一个streambuf对象
●ostream:继承自ios类并提供了输出方法
●istream:继承自ios类并提供了输入方法
●iostream:继承自ostream类和istream类

1.1.3C++11 I/O新特性

ostream.h转换为ostream,将ostream类放置到std命名空间中I/O类被重写,开发了I/O类模板包括basic_istream<charT,traits>和basic_ostream<charT, traits>。实现了char,wchar_t具体化;istream和ostream是char的具体化,cout输出字符流,wistream和wstream是wchar_t的具体化,wcout用于输出宽字符流。
ios基类中的一些独立与类型的信息被移动到ios_base类中,比如格式化常量ios::fixed变为ios_base::fixed,还新增了一些常量协议,转载请附上原文出处链接及本声明。

1.1.4 包含iostream头文件时会自动创建八个流对象(4个用于窄字符流,4个用于宽字符流)

cin对象:对应标准输入流,默认情况下这个流与标准输入设备匹配(键盘);wcin对象用于wchar_t类型;
cout对象:对应标准输出流,默认情况下这个流与标准输出设备匹配(显示器),借助streambuf管理流;wcout对象用于wchar_t类型;
cerr对象:对应于标准错误流(可以用于显示错误信息),默认情况下这个流与标准输出设备匹配(显示器),这个流是不缓冲的;wcerr对象用于wchar_t类型;不受重定向的影响,即使重定向了输入输出流,错误信息还是打印到显示器上
clog对象:对应于标准错误流,默认情况下这个流与标准输出设备匹配(显示器),这个流是缓冲的;wclog对象用于wchar_t类型。不受重定向的影响,即使重定向了输入输出流,错误信息还是打印到显示器上

1.1.5 重定向

修改标准输入和标准输出关联的工具。(比如输出到文件,而不是显示器)

1.2 cout的使用

当创建cout类时会自动打开一个流,创建一个缓冲区,并将流和缓冲区联系起来
C++在输出时将数据看作字符流。

1.2.1 显示各种类型的数据

各种char字符可以直接显示,但是对于各种数据包括int,long,float,double就无法直接解析为字符了
C++中将使用cout的<<称为插入操作符,意味着将右侧的信息流传递到左侧(因为<<是左移运算符,相当于给了个方便记忆的解释)
因此cout类重载了<<操作符,重载了适合多种数据类型的<<操作符成员函数,将数据类型转换为文本形式的字符流;以下数据类型皆被重载:unsigned char、signed char、char、short、unsigned short、int、unsigned int、long、unsigned long、long long (C++11)、unsigned long long (C++11)、float、double、long double
重载原型为:ostream & operator<<(type);

1.2.2 输出与指针

ostream为const signed char *、const unsigned char *、const char *、void *重载了<<操作符,因此,可以使用cout<<输出显示字符串;这个方法使用\0来判断是否停止显示字符。

如果要显示字符串的地址,由于传递指针输出了整个字符串,因此将其强制转换为void *类型可以显示字符串的地址。

举例:

cout << "输出与指针*********************************************************************" << endl;
int eggs = 12;
const char* amount = "dozen";
cout << &eggs; // prints address of eggs variable
cout << amount; // prints the string "dozen"
cout << (void*)amount<<endl; // prints the address of the "dozen" string

运行结果:

输出与指针*********************************************************************
008FFD9Cdozen0041BC80

1.2.3 输出连接

ostream & operator<<(type);
根据上述函数原型可以知道,返回值是ostream类型的引用,这就可以使得cout<<实现输出连接

cout << “We have " << count << " unhatched chickens.\n”;

举例:

cout << "输出连接***********************************************************************" << endl;
cout << "We have " << eggs << " unhatched chickens.\n";

运行结果:

输出连接***********************************************************************
We have 12 unhatched chickens.

1.2.4 输出字符cout.put()

原型:

template <class type>//type为char或wchar_t
ostream & put(type);

当传递的实参不是char或wchar_t时,会执行自动转换将其转换为char或wchar_t类型,这个很适合早期发行版本2.0,在那些版本中,使用int值表示字符。

cout << 'W';//在早期发行版本2.0,这个会输出W的ASCII码,当前版本会输出字符W
cout.put('W');//在早期发行版本2.0,这个会输出W字符,当前版本会输出W字符

一些编译器为char, unsigned char, and signed char重载了put(),但是这会给自动转换int实参带来歧义,因为int可以转换为其中任何一个。

举例:

cout << "cout.put()*********************************************************************" << endl;
cout.put('W'); // display the W character
cout.put('I').put('t'); // displaying It with two put() calls
cout.put(65); // display the A character
cout.put(66.3); // display the B character
cout.put(65); // display the A character
cout.put(66.3) << endl; // display the B character

运行结果:

cout.put()*********************************************************************
WItABAB

1.2.5 输出字符串cout.write()

原型:

basic_ostream<charT,traits>& write(const char_type* s, streamsize n);

第一个参数提供了需要显示的字符串地址,第二个参数指示了显示几个字符;cout.write()成员函数不会判断字符串是否结尾,指定显示几个字符就显示几个,即使它过界了。返回值为cout,说明支持输出连接
针对write非char类型的数据,程序根据8bit存储空间的内容来决定显示为哪个字符,超过8bit的数据类型,则分为几个字符显示。

举例:

cout << "cout.write()*******************************************************************" << endl;
const char* state1 = "Florida";
const char* state2 = "Kansas";
const char* state3 = "Euphoria";
int len = std::strlen(state2);
cout << "Increasing loop index:\n";
int i;
for (i = 1; i <= len; i++)
{
cout.write(state2, i);
cout << endl;
}
// concatenate output
cout << "Decreasing loop index:\n";
for (i = len; i > 0; i--)
cout.write(state2, i) << endl;
// exceed string length
cout << "Exceeding string length:\n";
cout.write(state2, len + 5) << endl;

long val = 560031841;
cout.write((char*)&val, sizeof(long))<<endl;

运行结果:

cout.write()*******************************************************************
Increasing loop index:
K
Ka
Kan
Kans
Kansa
Kansas
Decreasing loop index:
Kansas
Kansa
Kans
Kan
Ka
K
Exceeding string length:
KansasEup
aha!

1.2.6 清空输出缓存

缓存一般是512bytes或者其倍数。
在显示器输出时,三种情况可清空缓存
1.发送一个新行给缓冲器,endl
2.当输入挂起时
3.使用工具 flush()函数
使用方法:flush(cout);或者cout<<flush;(这个重载了<<操作符)

举例:

cout << "清空输出缓存*******************************************************************" << endl;
cout << "Enter a number: ";
float num;
cin >> num;
cout << "Hello, good-looking! " << flush;
cout << "Wait just a moment, please." << endl;

运行结果:

清空输出缓存*******************************************************************
Enter a number: 99
Hello, good-looking! Wait just a moment, please.

1.2.7 cout针对不同数据类型的输出格式

(1)char:占用一个字符的位置
(2)整形:占用足够显示它的字符位置数(十位数占用两个字符位置,个位数占用一个字符位置),如果是负数要在前面加一个-号
(3)字符串:占用字符串长度的字符位置数
(4)浮点型:显示的有效位数为6位,尾部的0不显示
注意事项:不同地区可能有不同的格式:比如欧洲的小数点用,表示而不是.表示;数据之间需要手动添加间隔符将两个数字分开,否则会连续显示。

举例:

cout << "cout针对不同数据类型的输出格式***************************************************" << endl;
cout << "12345678901234567890\n";
char ch = 'K';
int t = 273;
cout << ch << ":\n";
cout << t << ":\n";
cout << -t << ":\n";
double f1 = 1.200;
cout << f1 << ":\n";
cout << (f1 + 1.0 / 9.0) << ":\n";
double f2 = 1.67E2;
cout << f2 << ":\n";
f2 += 1.0 / 9.0;
cout << f2 << ":\n";
cout << (f2 * 1.0e4) << ":\n";
double f3 = 2.3e-4;
cout << f3 << ":\n";
cout << f3 / 10 << ":\n";

运行结果:

cout针对不同数据类型的输出格式***************************************************
12345678901234567890
K:
273:
-273:
1.2:
1.31111:
167:
167.111:
1.67111e+06:
0.00023:
2.3e-05:

1.2.8 cout以不同进制显示整数

ios_base是ios的基类,ios是一个模板类包含了char和wchar_t的专门化,ios_base包含了非模板的特征
流操纵符dec,hex,oct:ios_base中有工具dec,hex,oct使得数据以不同进制显示;dec(),hex(),oct()是函数,并且重载了<<操作符;这几个工具在std命名空间中
注意事项:
1.无法使用二进制显示;
2.在更改进制显示方式之后,系统默认后面使用该方式显示数据,如果有显示为其他进制形式的需要重新设置进制显示方式
三种格式:

hex(cout);
cout<<hex;
cout.setf(ios_base::dec);

显示bool类型:

cout.setf(ios_base::boolalpha);//true显示位true,false显示位false

举例:

cout << "cout以不同进制显示整数***********************************************************" << endl;
/*cout以不同进制显示整数*/
i = 90;
cout << "以十进制显示i:" << dec << i << endl;//以十进制显示
cout << "以八进制显示i:" << oct << i << endl;//以八进制显示
cout << "以16进制显示i:" << hex << i << endl;//以16进制显示
//如需更改为十进制显示方式,则可以使用以下方式
cout.setf(ios_base::dec);
cout << "以十进制显示i:" << i << endl << endl;
/*bool数据类型显示*/
cout << "bool数据类型显示" << endl;
bool is_true = true;
cout.setf(ios_base::boolalpha);//可以显示为true或false
cout << "is_true = " << is_true << endl;
is_true = false;
cout << "is_true = " << is_true << endl << endl;

1.2.9 设置当前字段宽度cout.width()

两种原型:

int width();//返回当前字段宽度
int width(int i);//将字段宽度设置为i,并且返回之前的字符宽度,以备返回上一步的字段宽度。

使用格式:

cout<<width(长度);

注意事项:
1.width()只影响下一次的cout,然后字段宽度将恢复默认值
2.显示为右对齐,多余的字段位置将使用空格填充
3.C++不会截断数据,因此如果视图在宽度为2个字段中打印一个7位数,C++将增宽字段,以容纳该数据。

举例:

cout << "width()***************************************************************************" << endl;
int w = cout.width(30);
cout << "default field width = " << w << ":\n";
cout.width(5);
cout << "N" << ':';
cout.width(8);
cout << "N * N" << ":\n";
for (long i = 1; i <= 100; i *= 10)
{
    cout.width(5);
    cout << i << ':';
    cout.width(8);
    cout << i * i << ":\n";
}
cout.width(5);
cout << 9.888889999 << endl;//将不会截断

运行结果:

width()***************************************************************************
        default field width = 0:
    N:   N * N:
    1:       1:
   10:     100:
  100:   10000:
9.88889

1.2.10 更改填充字符cout.fill()

默认情况下,将空格字符作为填充字符,可以使用fill()函数更改填充字符。

1 cout.fill('*');

这对于打印支票很有用,因为这样就不能随意在填充部分随意加数字了。
注意事项:在更改填充字符之后,系统默认后面使用该填充字符,如果有显示为其他填充字符的需要重新设置填充字符

举例:

cout << "更改填充字符****************************************************************************" << endl;
cout.fill('*');
const char* staff[2] = { "Waldo Whipsnade", "Wilmarie Wooper" };
long bonus[2] = { 900, 1350 };
for (int i = 0; i < 2; i++)
{
    cout << staff[i] << ": $";
    cout.width(7);
    cout << bonus[i] << "\n";
}

运行结果:

更改填充字符****************************************************************************
Waldo Whipsnade: $****900
Wilmarie Wooper: $***1350

1.2.11 更改浮点数显示精度cout.precision()

默认情况下是显示六位有效数字;可以使用precision()设置精度;

cout.precision(2);

注意事项:
1.在设置精度后,后面会一直使用这个精度直到下一次更改精度
2.当有效数字位数小于整数有效数字位数时,使用科学计数法显示

举例:

cout << "设置小数显示精度:precision()***********************************************************" << endl;
double j = 3333.1415926;
/*设置显示有效数字位数*/
cout << "默认情况显示(6位):" << j << endl;//输出浮点数时,默认情况下保留六位有效数字
cout.precision(9);//可以使用cout.precision(n)设置输出浮点数时保留n位有效数字
cout << "设置有效数字位数为9位时:" << j << endl;
cout.precision(3);//当有效数字位数小于整数有效数字位数时,使用科学计数法显示
cout << "设置有效数字位数为3位时:" << j << endl << endl;

运行结果:

设置小数显示精度:precision()***********************************************************
默认情况显示(6位):3333.14
设置有效数字位数为9位时:3333.14159
设置有效数字位数为3位时:3.33e+03

1.2.12 ios_base成员函数setf()的使用

ios_base类中有一个受保护的数据成员,这个数据成员的各位控制着格式化的各个方面(这些位置1则打开该模式);
ios_base类中定义了各种常量指向该受保护数据成员的各个类,称为bitmask类型,每个bitmask位都是可单独访问的并且有自己的含义,iostream软件包使用bitmask来存储状态信息

ios_base::boolalpha true显示为true,false显示为false
ios_base::showbase 输出时使用前缀 OX,ox(进制缩写)等等
ios_base::showpoint 输出小数点和无效0(就尾巴上的0)
ios_base::uppercase 输出16进制时使用大写,输出使用科学计数法时使用大写E
ios_base::showpos 在正数前面加+号,只适用于十进制,其他进制不用显示+号

setf原型1:形参为1个

fmtflags setf(fmtflags);//输入参数为bitmask类型,指出要设置哪一位;返回值为bitmask类型,指出以前的设置

self原型2:形参为两个

fmtflags setf(fmtflags , fmtflags );//第一个参数为bitmask类型,指出要设置哪一位;第二个参数为bitmask类型,指出要清除第一个参数中的哪些位;返回值为bitmask类型,指出以前的设置

应用:

cout.setf(ios_base::hex, ios_base::basefield);//取消ios_base::basefield原来的设置,将其设置为hex

可以传递的参数为以下表格:

Second Argument First Argument Meaning
ios_base::basefield ios_base::dec 使用十进制
ios_base::oct 使用八进制
ios_base::hex 使用十六进制
ios_base::floatfield ios_base::fixed 使用混合显示模式
ios_base::scientific 使用科学计数法显示模式
ios_base::adjustfield ios_base::left 左对齐
ios_base::right 右对齐
ios_base::internal 符号左对齐,数字右对齐

注意事项:在C++标准中,混合模式和科学计数法模式都有两个特征:
1.精度是指小数点后面的数据位数
2.尾部的0自动显示
恢复旧格式:

ios_base::fmtflags old = cout.setf(ios::left, ios::adjustfield);
cout.setf(old, ios::adjustfield);

取消设置:
方法1:保存旧设置,然后重置设置
方法2:unsetf()

函数原型为:

void unsetf(fmtflags mask);//mask是一个位,如果setf()将mask位设置为1,则unsetf()将mask位设置为0

举例:

cout << "ios_base成员函数setf()的使用***********************************************************"<< endl;
cout << "一个参数的setf():********************************" << endl;
int temperature = 63;
cout << "Today's water temperature: ";
cout.setf(ios_base::showpos); // show plus sign
cout << temperature << endl;
cout << "For our programming friends, that's\n";
cout << std::hex << temperature << endl; // use hex
cout.setf(ios_base::uppercase); // use uppercase in hex
cout.setf(ios_base::showbase); // use 0X prefix for hex
cout << "or\n";
cout << temperature << endl;
cout << "How " << true << "! oops -- How ";
cout.setf(ios_base::boolalpha);
cout << true << "!\n";
cout << "两个参数的setf():*********************************" << endl;
cout << dec;
// use left justification, show the plus sign, show trailing
// zeros, with a precision of 3
cout.fill(' ');
cout.setf(ios_base::left, ios_base::adjustfield);
cout.setf(ios_base::showpos);
cout.setf(ios_base::showpoint);
cout.precision(3);
// use e-notation and save old format setting
ios_base::fmtflags old = cout.setf(ios_base::scientific,
                                   ios_base::floatfield);
cout << "Left Justification:\n";
long n;
for (n = 1; n <= 41; n += 10)
{
    cout.width(4);
    cout << n << "|";
    cout.width(12);
    cout << sqrt(double(n)) << "|\n";
}
// change to internal justification
cout.setf(ios_base::internal, ios_base::adjustfield);
// restore default floating-point display style
cout.setf(old, ios_base::floatfield);
cout << "Internal Justification:\n";
for (n = 1; n <= 41; n += 10)
{
    cout.width(4);
    cout << n << "|";
    cout.width(12);
    cout << sqrt(double(n)) << "|\n";
}
// use right justification, fixed notation
cout.setf(ios_base::right, ios_base::adjustfield);
cout.setf(ios_base::fixed, ios_base::floatfield);
cout << "Right Justification:\n";
for (n = 1; n <= 41; n += 10)
{
    cout.width(4);
    cout << n << "|";
    cout.width(12);
    cout << sqrt(double(n)) << "|\n";
}
cout << "unsetf():*********************************" << endl;
cout << boolalpha;
cout << true << endl;
cout.unsetf(ios_base::boolalpha); // display 1, 0
cout << true << endl;
//将小数显示模式设置为默认状态,默认状态即为混合模式和科学计数法模式都未设置
cout.setf(0, ios_base::floatfield); // go to default mode

1.2.13 标准流操纵符

实质上就是各种函数,可以直接使用cout<<流操纵符;即可设置相应的位置。

Manipulator Calls
boolalpha setf(ios_base::boolalpha)
noboolalpha unset(ios_base::noboolalpha)
showbase setf(ios_base::showbase)
noshowbase unsetf(ios_base::showbase)
showpoint setf(ios_base::showpoint)
noshowpoint unsetf(ios_base::showpoint)
showpos setf(ios_base::showpos)
noshowpos unsetf(ios_base::showpos)
uppercase setf(ios_base::uppercase)
nouppercase unsetf(ios_base::uppercase)
internal setf(ios_base::internal,ios_base::adjustfield)
left setf(ios_base::left,ios_base::adjustfield)
right setf(ios_base::right,ios_base::adjustfield)
dec setf(ios_base::dec, ios_base::basefield)
hex setf(ios_base::hex, ios_base::basefield)
oct setf(ios_base::oct, ios_base::basefield)
fixed setf(ios_base::fixed,ios_base::floatfield)
scientific setf(ios_base::scientific,ios_base::floatfield)

举例:

cout<<otc;

1.2.14 iomanip头文件

iomanip头文件为precision() fill() width()准备的流操纵符(更方便使用)

ostream & setprecision(int);//设置精度
ostream & setfill(char);//设置填充符
ostream & setw(int);//设置宽度
//返回值使得可以实现输出连接

举例:

cout << "iomanip头文件************************************************************************" << endl;
cout << noshowpos;
// use new standard manipulators
cout << fixed << right;
// use iomanip manipulators
cout << setw(6) << "N" << setw(14) << "square root"
    << setw(15) << "fourth root\n";
double root;
for (int n = 10; n <= 100; n += 10)
{
    root = sqrt(double(n));
    cout << setw(6) << setfill('.') << n << setfill(' ')
        << setw(12) << setprecision(3) << root
        << setw(14) << setprecision(4) << sqrt(root)
        << endl;
}

运行结果:

iomanip头文件************************************************************************
     N   square root   fourth root
....10       3.162        1.7783
....20       4.472        2.1147
....30       5.477        2.3403
....40       6.325        2.5149
....50       7.071        2.6591
....60       7.746        2.7832
....70       8.367        2.8925
....80       8.944        2.9907
....90       9.487        3.0801
...100      10.000        3.1623

1.3完整测试代码

/*
Project name :			_1cout_usage
Last modified Date:		2022年3月12日11点52分
Last Version:			V1.0
Descriptions:			cout的常用用法
*/
#include <iostream>
#include <iomanip>
#include <cmath>

int main()
{
	using namespace std;//将命名空间std的所有名字都引用了
	cout << "输出与指针*********************************************************************" << endl;
	int eggs = 12;
	const char* amount = "dozen";
	cout << &eggs; // prints address of eggs variable
	cout << amount; // prints the string "dozen"
	cout << (void*)amount; // prints the address of the "dozen" string
	cout << "输出连接***********************************************************************" << endl;
	cout << "We have " << eggs << " unhatched chickens.\n";
	cout << "cout.put()*********************************************************************" << endl;
	cout.put('W'); // display the W character
	cout.put('I').put('t'); // displaying It with two put() calls
	cout.put(65); // display the A character
	cout.put(66.3); // display the B character
	cout.put(65); // display the A character
	cout.put(66.3) << endl; // display the B character
	cout << "cout.write()*******************************************************************" << endl;
	const char* state1 = "Florida";
	const char* state2 = "Kansas";
	const char* state3 = "Euphoria";
	int len = std::strlen(state2);
	cout << "Increasing loop index:\n";
	int i;
	for (i = 1; i <= len; i++)
	{
		cout.write(state2, i);
		cout << endl;
	}
	// concatenate output
	cout << "Decreasing loop index:\n";
	for (i = len; i > 0; i--)
		cout.write(state2, i) << endl;
	// exceed string length
	cout << "Exceeding string length:\n";
	cout.write(state2, len + 5) << endl;

	long val = 560031841;
	cout.write((char*)&val, sizeof(long))<<endl;
	cout << "清空输出缓存*******************************************************************" << endl;
	cout << "Enter a number: ";
	float num;
	cin >> num;
	cout << "Hello, good-looking! " << flush;
	cout << "Wait just a moment, please." << endl;
	cout << "cout针对不同数据类型的输出格式***************************************************" << endl;
	cout << "12345678901234567890\n";
	char ch = 'K';
	int t = 273;
	cout << ch << ":\n";
	cout << t << ":\n";
	cout << -t << ":\n";
	double f1 = 1.200;
	cout << f1 << ":\n";
	cout << (f1 + 1.0 / 9.0) << ":\n";
	double f2 = 1.67E2;
	cout << f2 << ":\n";
	f2 += 1.0 / 9.0;
	cout << f2 << ":\n";
	cout << (f2 * 1.0e4) << ":\n";
	double f3 = 2.3e-4;
	cout << f3 << ":\n";
	cout << f3 / 10 << ":\n";
	cout << "cout以不同进制显示整数***********************************************************" << endl;
	/*cout以不同进制显示整数*/
	i = 90;
	cout << "以十进制显示i:" << dec << i << endl;//以十进制显示
	cout << "以八进制显示i:" << oct << i << endl;//以八进制显示
	cout << "以16进制显示i:" << hex << i << endl;//以16进制显示
	//如需更改为十进制显示方式,则可以使用以下方式
	cout.setf(ios_base::dec);
	cout << "以十进制显示i:" << i << endl << endl;
	/*bool数据类型显示*/
	cout << "bool数据类型显示" << endl;
	bool is_true = true;
	cout.setf(ios_base::boolalpha);//可以显示为true或false
	cout << "is_true = " << is_true << endl;
	is_true = false;
	cout << "is_true = " << is_true << endl << endl;
	cout << "width()***************************************************************************" << endl;
	int w = cout.width(30);
	cout << "default field width = " << w << ":\n";
	cout.width(5);
	cout << "N" << ':';
	cout.width(8);
	cout << "N * N" << ":\n";
	for (long i = 1; i <= 100; i *= 10)
	{
		cout.width(5);
		cout << i << ':';
		cout.width(8);
		cout << i * i << ":\n";
	}
	cout.width(5);
	cout << 9.888889999 << endl;//将不会截断
	cout << "更改填充字符****************************************************************************" << endl;
	cout.fill('*');
	const char* staff[2] = { "Waldo Whipsnade", "Wilmarie Wooper" };
	long bonus[2] = { 900, 1350 };
	for (int i = 0; i < 2; i++)
	{
		cout << staff[i] << ": $";
		cout.width(7);
		cout << bonus[i] << "\n";
	}
	cout << "设置小数显示精度:precision()***********************************************************" << endl;
	double j = 3333.1415926;
	/*设置显示有效数字位数*/
	cout << "默认情况显示(6位):" << j << endl;//输出浮点数时,默认情况下保留六位有效数字
	cout.precision(9);//可以使用cout.precision(n)设置输出浮点数时保留n位有效数字
	cout << "设置有效数字位数为9位时:" << j << endl;
	cout.precision(3);//当有效数字位数小于整数有效数字位数时,使用科学计数法显示
	cout << "设置有效数字位数为3位时:" << j << endl << endl;
	cout << "ios_base成员函数setf()的使用***********************************************************"<< endl;
	cout << "一个参数的setf():********************************" << endl;
	int temperature = 63;
	cout << "Today's water temperature: ";
	cout.setf(ios_base::showpos); // show plus sign
	cout << temperature << endl;
	cout << "For our programming friends, that's\n";
	cout << std::hex << temperature << endl; // use hex
	cout.setf(ios_base::uppercase); // use uppercase in hex
	cout.setf(ios_base::showbase); // use 0X prefix for hex
	cout << "or\n";
	cout << temperature << endl;
	cout << "How " << true << "! oops -- How ";
	cout.setf(ios_base::boolalpha);
	cout << true << "!\n";
	cout << "两个参数的setf():*********************************" << endl;
	cout << dec;
	// use left justification, show the plus sign, show trailing
	// zeros, with a precision of 3
	cout.fill(' ');
	cout.setf(ios_base::left, ios_base::adjustfield);
	cout.setf(ios_base::showpos);
	cout.setf(ios_base::showpoint);
	cout.precision(3);
	// use e-notation and save old format setting
	ios_base::fmtflags old = cout.setf(ios_base::scientific,
		ios_base::floatfield);
	cout << "Left Justification:\n";
	long n;
	for (n = 1; n <= 41; n += 10)
	{
		cout.width(4);
		cout << n << "|";
		cout.width(12);
		cout << sqrt(double(n)) << "|\n";
	}
	// change to internal justification
	cout.setf(ios_base::internal, ios_base::adjustfield);
	// restore default floating-point display style
	cout.setf(old, ios_base::floatfield);
	cout << "Internal Justification:\n";
	for (n = 1; n <= 41; n += 10)
	{
		cout.width(4);
		cout << n << "|";
		cout.width(12);
		cout << sqrt(double(n)) << "|\n";
	}
	// use right justification, fixed notation
	cout.setf(ios_base::right, ios_base::adjustfield);
	cout.setf(ios_base::fixed, ios_base::floatfield);
	cout << "Right Justification:\n";
	for (n = 1; n <= 41; n += 10)
	{
		cout.width(4);
		cout << n << "|";
		cout.width(12);
		cout << sqrt(double(n)) << "|\n";
	}
	cout << "unsetf():*********************************" << endl;
	cout << boolalpha;
	cout << true << endl;
	cout.unsetf(ios_base::boolalpha); // display 1, 0
	cout << true << endl;
	//将小数显示模式设置为默认状态,默认状态即为混合模式和科学计数法模式都未设置
	cout.setf(0, ios_base::floatfield); // go to default mode
	cout << "iomanip头文件************************************************************************" << endl;
	cout << noshowpos;
	// use new standard manipulators
	cout << fixed << right;
	// use iomanip manipulators
	cout << setw(6) << "N" << setw(14) << "square root"
		<< setw(15) << "fourth root\n";
	double root;
	for (int n = 10; n <= 100; n += 10)
	{
		root = sqrt(double(n));
		cout << setw(6) << setfill('.') << n << setfill(' ')
			<< setw(12) << setprecision(3) << root
			<< setw(14) << setprecision(4) << sqrt(root)
			<< endl;
	}
	return 0;
}

————————————————
版权声明:本文为CSDN博主「Jasmine-Lily」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44410704/article/details/124398700

2.printf函数详细解析

1.printf() 简介

printf() 是 C 语言标准库函数,用于将格式化后的字符串输出到标准输出。标准输出,即标准输出文件,对应终端的屏幕。printf() 申明于头文件 stdio.h。

函数原型:

int printf ( const char * format, ... );

返回值:
正确返回输出的字符总数,错误返回负值。与此同时,输入输出流错误标志将被置值,可由指示器函数 ferror(FILE *stream) 来检查输入输出流的错误标志,如果 ferror() 返回一个非零值,表示出错。

调用格式:

printf("格式化字符串", 输出表列)

格式化字符串包含三种对象,分别为:
(1)字符串常量;
(2)格式控制字符串;
(3)转义字符。

字符串常量原样输出,在显示中起提示作用。输出表列中给出了各个输出项,要求格式控制字符串和各输出项在数量和类型上应该一一对应。其中格式控制字符串是以 % 开头的字符串,在 % 后面跟有各种格式控制符,以说明输出数据的类型、宽度、精度等。

2.格式控制字符串详解

printf() 的格式控制字符串组成如下:

%[flags][width][.prec][length]type

分别为:

%[标志][最小宽度][.精度][类型长度]类型。

2.1 类型(type)

首先说明类型,因为类型是格式控制字符串的重中之重,是必不可少的组成部分,其它的选项都是可选的。type 用于规定输出数据的类型,含义如下:

字符 对应数据类型 含义 示例
d/i int 输出十进制有符号 32bits 整数,i 是老式写法 printf("%i",123);输出123
o unsigned int 无符号8进制(octal)整数(不输出前缀0) printf("0%o",123);输出0173
u unsigned int 无符号10进制整数 printf("%u",123);输出123
x/X unsigned int 无符号16进制整数,x对应的是abcdef,X对应的是ABCDEF(不输出前缀0x) printf("0x%x 0x%X",123,123);输出0x7b 0x7B
f/lf float(double) 单精度浮点数用f,双精度浮点数用lf(printf可混用,但scanf不能混用) printf("%.9f %.9lf",0.000000123,0.000000123);输出0.000000123 0.000000123。注意指定精度,否则printf默认精确到小数点后六位
F float(double) 与f格式相同,只不过 infinity 和 nan 输出为大写形式。 例如printf("%f %F %f %F\n",INFINITY,INFINITY,NAN,NAN);输出结果为inf INF nan NAN
e/E float(double) 科学计数法,使用指数(Exponent)表示浮点数,此处"e"的大小写代表在输出时“e”的大小写 printf("%e %E",0.000000123,0.000000123);输出1.230000e-07 1.230000E-07
g float(double) 根据数值的长度,选择以最短的方式输出,%f或%e printf("%g %g",0.000000123,0.123);输出1.23e-07 0.123
G float(double) 根据数值的长度,选择以最短的方式输出,%f或%E printf("%G %G",0.000000123,0.123);输出1.23E-07 0.123
c char 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符 printf("%c\n",64)输出A
s char* 字符串。输出字符串中的字符直至字符串中的空字符(字符串以空字符’\0‘结尾) printf("%s","测试test");输出:测试test
S wchar_t* 宽字符串。输出字符串中的字符直至字符串中的空字符(宽字符串以两个空字符’\0‘结尾) setlocale(LC_ALL,"zh_CN.UTF-8");
wchar_t wtest[]=L"测试Test";
printf("%S\n",wtest);
输出:测试test
p void* 以16进制形式输出指针 printf("0x%p","lvlv");输出:0x000000013FF73350
n int* 什么也不输出。%n对应的参数是一个指向signed int的指针,在此之前输出的字符数将存储到指针所指的位置 int num=0;
printf("lvlv%n",&num);
printf("num:%d",num);
输出:lvlvnum:4
% 字符% 输出字符‘%’(百分号)本身 printf("%%");输出:%
m 打印errno值对应的出错内容 printf("%m\n");
a/A float(double) 十六进制p计数法输出浮点数,a为小写,A为大写 printf("%a %A",15.15,15.15);输出:0x1.e4ccccccccccdp+3 0X1.E4CCCCCCCCCCDP+3

注意:
(1)使用 printf() 输出宽字符时,需要使用 setlocale 指定本地化信息并同时指明当前代码的编码方式。除了使用 %S,还可以使用 %ls。

(2)printf() 输出 bool 类型无专用类型标识符,实际输出时按照整型 0 或 1 输出布尔值。

(3)%a 和 %A 是 C99 引入的格式化类型,采用十六进制 p 计数法输出浮点数。p 计数法类似 E 科学计数法,但有所不同。数以 0x 开头,然后是 16 进制浮点数部分,接着是 p 后面是以 2 为底的阶码。以上面输出的 15.15 为例,推算输出结果。15.15 转换成二进制为1111.00 1001 1001 1001 1001 ...,因为二进制表示数值的离散特点,计算机对于小数有时是不能精确表示的,比如 0.5 可以精确表示为0.12,而 0.15 却不能精确表示。将15.15 对应的二进制右移三位,为1.1110 0100 1100 1100 1100 ...转换对应的十六进制就是0x1.e4ccccccccccd,注意舍入时向高位进了1位。由于右移三位,所以二进制阶码是 3。最后的结果就是0x1.e4ccccccccccdp+3。
(4)格式控制字符串除了指明输出的数据类型,还可以包含一些其它的可选的格式说明,依序有 flags, width, .precision and length。下面一一讲解。

2.2 标志(flags)

flags 规定输出样式,取值和含义如下:

字符 名称 说明
- 减号 结果左对齐,右边填空格。默认是右对齐,左边填空格。
+ 加号 输出符号(正号或负号)
space 空格 输出值为正时加上空格,为负时加上负号
# 井号 type是o、x、X时,增加前缀0、0x、0X。
type是a、A、e、E、f、g、G时,一定使用小数点。默认的,如果使用.0控制不输出小数部分,则不输出小数点。
type是g、G时,尾部的0保留。
0 数字零 将输出的前面补上0,直到占满指定列宽为止(不可以搭配使用“-”)

示例:

printf("%5d\n",1000); 				//默认右对齐,左边补空格
printf("%-5d\n",1000); 				//左对齐,右边补空格

printf("%+d %+d\n",1000,-1000);		//输出正负号

printf("% d % d\n",1000,-1000);		//正号用空格替代,负号输出

printf("%x %#x\n",1000,1000);		//输出0x

printf("%.0f %#.0f\n",1000.0,1000.0)//当小数点后不输出值时依然输出小数点

printf("%g %#g\n",1000.0,1000.0);	//保留小数点后后的0

printf("%05d\n",1000);				//前面补0

输出结果为:

2.3 输出最小宽度(width)

用十进制整数来表示输出的最少位数。若实际位数多于指定的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。width的可能取值如下:

width 描述 示例
数值 十进制整数 printf("%06d",1000);输出:001000
* 星号。不显示指明输出最小宽度,而是以星号代替,在printf的输出参数列表中给出 printf("%0*d",6,1000);输出:001000

2.4 精度(.precision)

精度格式符以“.”开头,后跟十进制整数。可取值如下:

.precision 描述
.数值 十进制整数。
(1)对于整型(d,i,o,u,x,X),precision表示输出的最小的数字个数,不足补前导零,超过不截断。
(2)对于浮点型(a, A, e, E, f ),precision表示小数点后数值位数,默认为六位,不足补后置0,超过则截断。
(3)对于类型说明符g或G,表示可输出的最大有效数字。
(4)对于字符串(s),precision表示最大可输出字符数,不足正常输出,超过则截断。
precision不显示指定,则默认为0
* 以星号代替数值,类似于width中的*,在输出参数列表中指定精度。

示例:

printf("%.8d\n",1000);			//不足指定宽度补前导0,效果等同于%08d
printf("%.8f\n",1000.123456789);//超过精度,截断
printf("%.8f\n",1000.123456);	//不足精度,补后置0
printf("%.8g\n",1000.123456);	//最大有效数字为8位
printf("%.8s\n",“abcdefghij”);	//超过指定长度截断

输出结果:

00001000
1000.12345679
1000.12345600
1000.1235
abcdefgh

注意: 在对浮点数和整数截断时,存在四舍五入。

2.5 类型长度(length)

类型长度指明待输出数据的长度。因为相同类型可以有不同的长度,比如整型有 char(8bits)、short int(16bits),int(32bits)和 long int(64bits),浮点型有 32bits 的单精度 float 和 64bits 的双精度 double。为了指明同一类型的不同长度,于是乎,类型长度(length)应运而生,成为格式控制字符串的一部分。

因为 Markdown 表格不支持单元格合并,背景颜色等样式,所以直接引用C++ reference.printf的表格。

注意: 黄色背景行标识的类型长度说明符和相应的数据类型是C99引入的。

示例代码:

printf("%hhd\n",'A');				//输出有符号char
printf("%hhu\n",'A'+128);			//输出无符号char
printf("%hd\n",32767);				//输出有符号短整型short int
printf("%hu\n",65535);				//输出无符号短整型unsigned short int
printf("%ld\n",0x7fffffffffffffff);	//输出有符号长整型long int
printf("%lu\n",0xffffffffffffffff);	//输出有符号长整型unsigned long int

输出结果:

65
193
32767
65535
9223372036854775807
18446744073709551615

注意:
long int 到底是 32bits 还是 64bits 跟生成的程序是 32bits 还是 64bits 一一对应,如果使用 g++ 编译程序的话,可通过-m32或-m64选项分别生成 32bits 和 64bits 的程序。因本人测试代码编译生成的是 64bits 的程序,所以 long int 也就是 64btis。

3.转义字符

转义字符在字符串中会被自动转换为相应操作命令。printf() 使用的常见转义字符如下:

转义字符 意义
\a 警报(响铃)符
\b 回退符
\f 换页符
\n 换行符
\r 回车符
\t 横向制表符
\v 纵向制表符
\\ 反斜杠
\" 双引号

4.关于 printf 缓冲

在 printf 的实现中,在调用 write 之前先写入 IO 缓冲区,这是一个用户空间的缓冲。系统调用是软中断,频繁调用,需要频繁陷入内核态,这样的效率不是很高,而 printf 实际是向用户空间的 IO 缓冲写,在满足条件的情况下才会调用 write 系统调用,减少 IO 次数,提高效率。

printf(...) 在 glibc 中默认为行缓冲,遇到以下几种情况会刷新缓冲区,输出内容:
(1)缓冲区填满;
(2)写入的字符中有换行符\n或回车符\r;
(3)调用 fflush(...) 手动刷新缓冲区;
(4)调用 scanf(...) 从输入缓冲区中读取数据时,也会将输出缓冲区内的数据刷新。

可使用setbuf(stdout,NULL)关闭行缓冲,或者setbuf(stdout,uBuff)设置新的缓冲区,uBuff 为自己指定的缓冲区。也可以使用setvbuf(stdout,NULL,_IOFBF,0);来改变标准输出为全缓冲。全缓冲与行缓冲的区别在于遇到换行符不刷新缓冲区。
printf(…) 在 VC++ 中默认关闭缓冲区,输出时会及时输到屏幕 。如果显示开启缓冲区,只能设置全缓冲。因为微软闭源,所以无法研究 printf(…) 的实现源码。

Linux 和 Windows 下的缓冲区管理可见:C的全缓冲、行缓冲和无缓冲。

5.printf 与 wprintf 不能同时使用

该小结写在 2018 年 1 月15 日。两年后的今日,在网上苦苦搜索寻求答案,终于解决了之前的疑惑。

在输出宽字符串时,发现将 printf 和 wprintf 同时使用时,则后使用的函数没有输出。这里建议不要同时使用 printf 和 wprintf,以免发生错误。

printf 和 wprintf 不能同时输出宽字符串的示例代码如下:

#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main(int argc, char* argv[])
{
	char test[] = "测试Test";
	setlocale(LC_ALL, "zh_CN.UTF-8");
	wchar_t wtest[] = L"0m~K0m~UTest";
	printf("printf:%S\n", wtest);     //语句1:可正常输出"测试Test"
	wprintf(L"wprintf:%S\n", wtest);  //语句2:无任何内容输出
}

上面的代码中语句 1 和语句 2 不能同时存在,否则只能正常输出第一个。也不知道在 Windows 平台是否也存在这种问题,有兴趣的读者可以尝试一下。关于原因,GNU 官方文档中有明确说明不能同时使用 printf 与 wprintf,参见The GNU C Library Section 12.6 Streams in Internationalized Applications,内容如下:

It is important to never mix the use of wide and not wide operations on a stream. There are no diagnostics issued. The application behavior will simply be strange or the application will simply crash. 

这里是因为输出流在被创建时,不存在流定向,一旦使用了 printf(多字节字符串)wprintf(宽字符串) 后,就被设置为对应的流定向,且无法更改。可以使用如下函数获取当前输出流的流定向。

//
//@param: stream:文件流;mode:取值可以 >0、=0 或 <0
//@ret: <0:流已被设置为多字节流定向;=0:流尚未被设置;>0:流已被设置为宽字符流定向
//
int fwide (FILE* stream, int mode);

//获取当前标准输出流定向
int ret=fwide(stdout,0);

通过 fwide 可以设置当前流定向,前提是未有任何的 I/O 操作,也就是当前流尚未被设置任何流定向。顺带吐槽一下,不知为何标准库函数 fwide 实现的如此受限。具体操作如下:

// 设置标准输出流定向为多字节流定向
fwide(stdout, -1);

// 设置标准输出流定向为宽字符流定向
fwide(stdout, 1);

既然 GNU C 存在这个问题,那该如何解决呢?这里有两种办法:
(1)统一使用一种函数。

wprintf(L"%s","a\n");
wprintf(L"b\n");

//或
printf("a\n");
printf("%ls\n", L"b");

(2)使用 C 标准库函数 freopen(…) 清空流定向,可以让printf(...) 与 wprintf(...)同时使用。

// 重新打开标准输出流,清空流定向
FILE* pFile=freopen("/dev/tty", "w", stdout);
wprintf(L"wide freopen succeeded\n");

// 重新打开标准输出流,清空流定向
pFile=freopen("/dev/tty", "w", stdout);
printf("narrow freopen succeeded\n");

6.小结

耗时将近两年,终于完成了此篇看似基础却纷繁复杂的printf()用法。由于时间和个人水平有限,文章不足之处在所难免,也请读者批评指正,不甚感激。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/K346K346/article/details/52252626