printf和cout的区别详述

        首先看C++中常见的输出格式:

#include<iostream>
using namespace std;
int main()
{
    cout<<"Hello,World!";
    return 0;
}

       我们在C中学习的标准输入输出的方法是借助输出函数printf和scanf,但是在C++中我们经常用cout和cin来进行输出和输入。cout看上去并不像一个函数,在C++的大趋势下,用printf和scanf显得格格不入,而且似乎也并不能理解为什么要用位计算符来进行输入输出字符的分隔?而且我们在使用cin和cout的时候发现发现它们在进行标准输入输出的时候<<和>>的用法有点类似于运算符,因为本身<<和>>是双目运算符,在进行标准输出和输出的时候我们如果当做运算符理解的话,的确就像一个双目运算符一样,左右量变的参数一个都不能少,实际上,在后续对于运算符重载的学习中,发现实际上cout和cin以及cerr其实都是运算符重载的结果。

  因为实际上是运算符重载的结果,所以我们不妨看下面的符合运算符重载逻辑的代码,如下:

#include<iostream>
using namespace std;
intmain()
{
    cout.operator<<("Hello,World!");
    cout.operator<<(endl);
    return0;
}

        再进行编译运行,结果与标准的cout没有任何区别,实际上对于上面的代码就更好理解了,cout实际上是一个iostream类的对象,每次调用的时候就会输出对应的字符串,调用的实际上就是成员运算符函数operator<<,当然这里还有一个问题就是:为什么实际上我们的cout可以接受不同类型的数据并进行输出呢?

  原因也很简单,就是因为我们在重载运算符的时候,也重载了多个该函数,因为标准库的作者们早就为使用者定制了iostream::operator<<对于各种C++基本数据类型的重载版本,这才使得我们在使用的时候变得如此地方便。

  那么既然我们已经看出了cout的实质,不妨写一个cout的简化版,这里就抛弃了iostream,而用stdio来实现,因为是简化版,所以就仅仅写两个函数,一个是针对int,一个是针对于字符串。

#include<stdio.h>
class MyOutstream   
{
public:
    const MyOutstream& operator<<(int value)const;//对整型变量的重载
    const MyOutstream& operator<<(char*str)const;//对字符串型的重载
};
const MyOutstream&MyOutstream::operator<<(int value)const
{
    printf("%d",value);
    return*this;//注意这个返回
}
const MyOutstream&MyOutstream::operator<<(char*str)const
{
    printf("%s",str);
    return*this;//同样
}
int main(){
    MyOutstream myout;//定义一个myoutstream的对象myout
    int a=2003;    
    char *myStr="Hello,World!";    
    myout<<a<<myStr;
    return 0;
}

       我们的mycout已经能完成一些工作了,但是我们观察上面的重载函数不难发现,重载函数最后都返回到了this,为什么?而且我们发现我们的mycout和标准的cout一样居然都可以实现连续地输出,其实这就是this指针的作用,在完成一次输出之后返回this指针,因为在这里方法是藏在已经实例化的对象中,所以返回this指针,后面就可以连续输出。

myout< <

  我们都知道,在printf中的\n可以实现一个换行,但是在C++中教程总是有意无意地让我们使用endl,两者看上去似乎一样,但是真的一样吗?

  实际上是不同的,endl实际上是一个操纵符,不仅仅实现了换行的操作,而且还对输出缓冲区进行刷新(使用缓冲区的原因是为了减少硬盘灯存储设备的读写次数),实际上,对于每一个输出流,都管理一个缓冲区(也就是说在系统中是存在多个缓冲区的),比如说下面这一行代码:

os<<"please enter a value:";
//文本串可能被打印出来,也有可能被操作系统保存在缓冲区中,随后再打印。

       有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作,(在系统中,很多读写操作并不是一定都会输出,而是存储在缓冲区上的,由于设备的写操作可能很耗时,因此允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。)

  那这里有一个问题,那我们很多的cout实际上是没有用endl或者flush等操纵符来主动清空缓冲区的,那么为什么还可以输出呢?

  其实,不仅仅是这些操纵符可以控制cout来清空缓冲区,实际上,有多种方法可以清空缓冲区:

  因为cout是行缓冲的,所以其实有以下几种方式(我们需要知道的是,下面任何会清空缓冲区的条件中都的确会导致输出,但是仅仅表明是在该条语句要清空缓冲区之间的某一时间点会导致输出,但是并没有说是具体什么时间点,具体时间点可能依据操作系统和具体编译环境而定):

   1、缓冲区满;

   2、用户手动刷新,即显示地清空,比如像上面的使用操纵符的方式;

   3、程序结束(这种情况非常常见),见下面例1代码;

   4、程序的下一步将要从标准输入流读入数据,则会将之前的缓冲区清空,见下面例2代码;

  实际上,endl是作为一个操纵符存在的,它不仅仅实现了换行操作,而且还对输出缓冲区进行了刷新,也就是说实际上,在本来的输出操作之后,endl在实现了换行的同时将缓冲区显式地清空了。

//例1
include<iostream>
using namespace std;
int main()
{
    int ch;
    while ((ch=getchar()) != '\n' )
    {
        putchar(ch);
        cout<<ch;
    }
    printf("\nbefore exit\n");
    return 0;
}
/*如果主函数并没有结束
int main()
{
int ch;
while ((ch=getchar()) != '\n' )
{
putchar(ch);
cout<<ch;
}
while (1){}
return 0;
}
//例2
#include<iostream>
using namespace std;
int main()
{
    int ch;
    while ((ch=getchar()) != '\n' )
    {
        putchar(ch);
        cout<<ch;
    }
    int a;
    cin>>a;
    while (1){}
    return 0;
}

       我们已经明白缓冲区的存在本身就是为了减少硬盘等存储设备的读写次数的,但是在使用cout在屏幕上进行输出的时候,有时候似乎看起来是否清空对于程序本身区别也不是很大,但是实际上真的是这样吗?实际上,在对于文件的操作中,懂得如何去高效地利用缓冲区来处理数据是相当重要的,比如我们看下面的代码:

#include <fstream>
using namespace std;
int main () {
ofstream outfile ("test.txt");
for (int n=0; n<100; n++)
{
    outfile << n;
    outfile.flush();//每次均清空缓冲区
}
outfile.close();
return 0;
}

      我们可以看到很明显很简单的读写操作由于持续的缓冲区清空操作则会导致速度大部分下降,所以在大容量的文件进行读写的时,以我们通常是写入一定的字节数之后再进行刷新,一般如何操作呢?靠的就是这些操作符。

  最后总结,C++的iostream库和C中的stdio库中分别的cout/cin和printf/scanf相比有哪些优势呢?首先是类型处理更加安全,更加智能,我们无须应对int、float中的%d、%f,而且扩展性极强,对于新定义的类,printf想要输入输出一个自定义的类的成员是天方夜谭的,而iostream中使用的位运算符都是可重载的,并且可以将清空缓冲区的自由交给了用户(在printf中的输出是没有缓冲区的),而且流风格的写法也更加自然和简洁。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值