1. 程式人生 > >控制C++中 cout及print輸出的評論和回答

控制C++中 cout及print輸出的評論和回答

論壇的lhslktg朋友發了一個貼,大意是說在他的程式裡面呼叫了很多的cout的輸出,是否能夠使用最快速的方法,使得程式的輸出能夠定向到一個檔案內。我理解這個所謂的快速的方法,就是儘量不要改動原有的程式,至少不要改動程式的內部,而達到這個功能。

有朋友給了一個最好的辦法,就是命令輸出重定位。假如,應用程式的名稱為: testcmd,則可以使用下面的命令:
testcmd >test.log

就把命令中的cout的輸出寫到檔案中去了。
還有的朋友給出了在程式開始增加一句標準輸出重開啟的語句:

freopen("test.log", "w", stdout)

這樣也會把這個語句下面的所有輸出都寫到test.log檔案中去了。

但接著這位朋友又有了新的要求,想既保留原有螢幕的輸出,又能寫到檔案中去。
我給出了下面的方案。

testcmd | tee test.log

這裡,tee是UNIX系統中的命令,它就像它的名字一樣,充當管道T型接頭。將輸出分成兩個流,一個到test.log中去了,另一個仍然輸出到螢幕上去。接著,我給

出了tee的簡單的原始碼實現,是為了在那些沒有tee命令的系統上使用的。原始碼如下:

# include  <stdio.h>

int
main(int argc, char **argv)
{
  FILE *fp;
  int c;

  if ( argc > 1 )  {
    fp = fopen(argv[1], "w");
    if ( fp == NULL )  {
      fprintf(stderr, "%s: can not open <%s>/n", argv[0], argv[1]);
      return -1;
    }
  } else
    fp = stdout;

  while ( (c = fgetc(fp)) != EOF )
    fputc(fp);

  return 0;
}

不料,不知道是不是樓主沒有仔細看我的說明,竟然問我怎樣使用這段程式碼。我感覺,上面應該說明的很清楚了,就是把這段程式編譯成可執行檔案,命令名字叫做tee,或者tee.exe, 然後像上面介紹的那樣使用:

testcmd | tee test.log

就把testcmd程式的輸出既輸出到檔案裡了,又輸出到螢幕上去了。不知道這樣說,是否明白了。

到此為止,事情還沒完,又有了新的需求。就是說,在原來的程式裡,既有cout的輸出,又有printf的輸出。能不能單獨控制這兩輸出分別到螢幕和不同的檔案中去。比如將cout的輸出到cout.txt,而將printf輸出到printf.out中去。我在跟貼裡,給出了控制cout的方法,就是在程式的開始,增加下面的程式碼:

 1 # include      <iostream>
 2 # include      <fstream>
 3
 4 class dostream : public std::ostream {
 5  public:
 6    dostream() : ofs("cout.txt") {}
 7
 8    template <typename _T>
 9    dostream& operator << (_T& data)  {
10      std::cout << data;
11      ofs << data;
12
13      return *this;
14    }
15
16  private:
17    std::ofstream ofs;
18 };
19
20 dostream dout;
21
22 # define    cout        dout

樓主繼續發問:
這個是如何控制cout的輸出的?
直接在程式最開始新增會有什麼效果呢?

好了,我現在開始解釋這段程式:

這裡,從第4行開始,設計了dostream類,並繼承了ostream類,也就是說它繼承了ostream的所有函式功能,別忘了,cout就是ostream的子類物件啊!這個類很簡單,就是在建構函式裡,打開了一個輸出檔案cout.txt。既然是要控制cout的輸出,也就是要控制cout的<<操作符。所以,從第8行開始定義了一個過載<<的模版函式,因為要對各種型別進行輸出過載,所以這裡的輸出資料型別成為了模版引數。在這個模版函式裡面,第10行是按照正常的cout的輸出,輸出到螢幕上,而第11行則是將輸出輸出到cout.txt檔案中去了。

第20行,定義了dostream的一個物件 dout。
第22行,將cout定義為dout,這樣當預處理的時候,會將程式中所有呼叫cout的地方都替換為dout,也就是都呼叫了我們新定義的這個dostream類的功能。而這個

主要動能就表現在<<操作符的過載上,從而呼叫了我們實現的過載的函式,把輸出寫到螢幕上,也輸出到檔案中去了。

如果還不明白,我們就從實際的例子來說明。
假如,原來的程式裡有一句:

cout << "Hello/n";

當把上面的那段程式包含在程式的開始的時候,就出現下面的情況:
在預處理的時候,會根據上面程式的22行進行巨集替換,這樣,cout >> "Hello/n" 便被替換為:
dout << "Hello/n";

當編譯的時候,這個語句便會使用上面第8行開始定義的模版函式,模版的引數為string,也就是會呼叫下面的函式:
dostream& operator << (string& data);
這裡 data = "Hello/n", 也就是說 dostream& operator << ("Hello");
從而呼叫10行和11行,就把"Hello"分別輸出到螢幕上和檔案中了。

另外,下面再給出控制printf輸出的方法。類似的給出下面的程式碼:


# include       <stdio.h>
# include       <stdarg.h>

FILE *ofp;

void
init_printf(void)
{
  ofp = fopen("printf.txt", "w");
  if ( ofp == NULL )   {
    fputs("can not open <printf.txt>/n", stderr);
    exit(-1);
  }
}


int
printf(const char *format, ...)
{
  va_list ap;
  int  rc;

  va_start(ap, format);
  vprintf(format, ap);
  rc = vfprintf(ofp, ap, format);
  va_end(ap);

  return rc;
}

這段程式碼可以單獨編輯為一個源程式檔案,比如,printf.c。在原來的程式的開始,增加一句:
init_printf()
然後,使用下面的命令進行編譯連結:

cc -o testcmd testcmd.cpp printf.c

testcmd.cpp假設為原來的程式。這樣,程式會優先呼叫printf.c中的函式,也就是我們在上面編寫的那個printf,而不會呼叫標準庫裡的printf了,也就實現了既輸出到螢幕上也輸出到檔案裡的功能了。上面的程式呼叫了C語言標準庫的關於變參函式的方法和技巧,如果不太明白,請參考相關書籍。