在Windows中使用printf
==============
一、可變參函數
在Windows程序設計中, 由於Windows不存在標准輸入輸出的概念, 這就意味這以前我們學習的printf函數將不再適用, 我們知道, C語言標准輸入輸出函數printf一個常用的功能就是輸出格式化后的文字, 例如:
int iAge = 18 ; printf( "Hello, I am %d years old.\n", iAge );
是的, printf函數的使用確實十分方便, 今天上午的學習我們要做的就是完成一個Windows版的printf。
關於sprintf:
看一個C語言的函數, sprintf函數, 這個函數在stdio.h頭文件中有聲明, sprintf函數的主要功能是把格式化的數據寫入某個字符串中, 函數的原型如下:
int sprintf( char *buffer, const char *format, [ argument] … );
sprintf的第一個參數為字符緩沖區, 后面的參數就像printf一樣, 是一個格式化字符串, 函數的返回值為緩沖區buffer內的有效字符串長度, 我們嘗試使用一下:
#include <stdio.h> int main() { char szBuffer[50] ; //定義緩沖區大小為50字節 sprintf( szBuffer, "today: %d-%d-%d", 2012, 10, 8 ) ; //將今天的日期格式化輸出到緩沖區 puts( szBuffer ) ; //輸出緩沖區szBuffer中的字符 return 0 ; }
先在我們已經能看到Windows版的printf的影子了, 在Windows版的printf中, 我們可以使用MessageBox函數來替代C語言的標准輸入輸出函數puts, 但是在這之前我們還有一個重要的問題要解決。
sprintf的安全版
當我們使用sprintf時, 我們需要首先定義一個緩沖區, 這個緩沖區我們必須定義的足夠大以至於能容納我們格式化輸出的字符串, 如果超過了這個長度, 程序將會出錯, 這樣是不安全的, 這里, 使用_snprintf()函數來解決這個問題, _snprintf的原型為:
int _snprintf(char *szBuffer, size_t size, const char *format, ...);
_snprintf相對於sprintf函數多出了個參數, size_t size, 這個參數的功能是:
當格式化后的字符串長度 < size,則將此字符串全部復制到szBuffer中,並給其后添加一個字符串結束符('\0');當格式化后的字符串長度 => size,則僅復制( size - 1 )個字符到緩沖區szBuffer內, 並在其后添加一個字符串結束符('\0') 。
我們自己的可變參函數
sprintf有個變形函數, 叫vsprintf, 首先看下這個函數的原型:
int vsprintf(char *szBuffer, const char *format, va_list param);
vsprintf的前兩個參數與sprintf相同, 第三個參數為一個va_list宏, va_list宏就是解決C語言變參問題的。
va_list宏的用法:
我們將用到valist的以下幾個成員:
va_list、va_start、va_end;
在些宏在STDARG.H頭文件中有定義, 打開STDARG.H可以看到, 其中有句:
typedef char * va_list;
從這句我們可以看出, va_list其實就是char*型類型, 一個字符型指針;
再來看看va_start宏:
va_start(va_list ap, char*format);
format為函數的參數, 可以用來指明參數的類型
可以看出, ap為va_list類型, 也就是可變參數列表; 通過va_start后, 就能使得變參列表與format中的類型一一對應起來;
va_end的作用是銷毀釋放一個va_list型的參數列表, 例如:
va_list ap
va_end( ap );
下面我們就可以實現我們自己的變參了, 代碼如下:
#include <stdio.h> #include <stdarg.h> int mySprintf( char *szBuffer, const char *szFormat, ... ) { int iLength ; //存儲字符串的長度信息 va_list pArgs ; //聲明一個va_list型變量 va_start( pArgs, szFormat ) ; //讓pArgs指向變參 iLength = vsprintf( szBuffer, szFormat, pArgs ) ; //輸出到緩沖區szBuffer中 va_end(pArgs); //釋放pArgs return iLength ; //返回字符串長度 } int main() { int i; char szBuffer[128] ; i = mySprintf( szBuffer, "today: %d-%d-%d", 2012, 10, 8 ) ; puts( szBuffer ) ; return 0 ; }
運行后的結果和sprintf是相同的, 說明我們已經實現了自己的可變參函數。
二、實現Windows版的printf
在上面的介紹中, 我們所使用的字符串處理函數都是針對於char型的, 下面我們將這些轉換為wchar_t型來實現Windwos的格式化消息框。
vsprintf和sprintf一樣, 也有其相對應的安全版(最大長度版), 為_vsnprintf, 為了程序的安全性, 這里我們使用vsprintf的安全版, _vsnprintf。
myMessageBox的實現, 代碼如下:
#include <stdio.h> #include <windows.h> int CDECL myMessageBox( TCHAR * szCaption, size_t iStyle, TCHAR * szFormat, ... ) { //myMessageBox函數參數: 標題, 樣式, 格式化輸出內容
//CDECL為調用規則, 在WINDEF.H定義為: #define CDECL _cdecl
TCHAR szBuffer [1024] ; va_list pArgs ; va_start (pArgs, szFormat) ; _vsnprintf( szBuffer, sizeof(szBuffer) / sizeof (TCHAR), szFormat, pArgs ) ; //sizeof(szBuffer) / sizeof (TCHAR)得到最大能容下的字符個數 va_end (pArgs) ;
return MessageBox(NULL, szBuffer, szCaption, iStyle) ; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { int cxScreen, cyScreen ;
cxScreen = GetSystemMetrics (SM_CXSCREEN) ; //獲取顯示器x方向像素 cyScreen = GetSystemMetrics (SM_CYSCREEN) ; //獲取顯示器y方向像素 myMessageBox( TEXT ("顯示器分辨率"), MB_OKCANCEL, TEXT ("顯示器當前分辨率為:%dx%d。"), TEXT(cxScreen), TEXT(cyScreen) ) ; return 0 ; }

--------------------
