控制C++中 cout及print輸出的評論和回答
阿新 • • 發佈:2019-01-07
論壇的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語言標準庫的關於變參函式的方法和技巧,如果不太明白,請參考相關書籍。
有朋友給了一個最好的辦法,就是命令輸出重定位。假如,應用程式的名稱為: 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語言標準庫的關於變參函式的方法和技巧,如果不太明白,請參考相關書籍。