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中的輸出是沒有緩衝區的),而且流風格的寫法也更加自然和簡潔。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章