茴字的N種寫法
在Visual C++世界裏,對於每一種需求,幾乎都存在着多種技術實現方式,這些細枝末節層層交錯,從而造成了壯觀的Visual C++技術脈絡圖。本節將實現一個目標,即將孔乙己的「茴」字輸出至某個文件中,並再次讀取出來。其中主要的技術點就是針對文件作一些操做,而Visual C++關於文件的操做方式就很是之多,有Windows API方式、C++標準庫方式等,這些方式如圖2-48所示。
如下詳細介紹使用在Windows系統中以各類方式(優雅的或者鄙俗的)寫出「茴」字。
圖2-48 茴字的N種寫法
一、 使用Windows API
API的英文全稱爲Application Programming Interface,Win32 API也就是Microsoft Windows 32位平臺的應用程序編程接口。想想,若是沒有Win32 API,咱們該如何操做文件?本身去驅動磁盤驅動器?本身去編寫硬件驅動程序?不可能。Windows API介於Windows操做系統與應用程序之間,所以它是離Windows操做系統最近的函數接口,它們之間的關係如圖2-49所示。
圖2-49 Windows API的位置
針對文件讀寫這一最基礎的操做,很顯然,Windows會義不容辭地提供操做函數:
HANDLE CreateFile( //建立、打開一個文件
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
BOOL WriteFile( //寫文件
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);
BOOL ReadFile( //讀文件
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);
讀者根據如上函數原型,不難理解如何使用Win32 API。在Visual C++中調用Windows API,大部分狀況下須要包含一個windows.h。
如今動手
接下來咱們體驗一下如何調用文件操做API,來完成文件的讀寫。
【程序 2‑11】使用Windows API輸出茴字
01 #include "stdafx.h"
02 #include <windows.h>
03 #include <cstdio>
04
05 int main()
06 {
07 HANDLE hFile;
08 DWORD nBytes;
09
10 //寫入文件
11 hFile = CreateFile( _T("test.out"),
12 GENERIC_WRITE,
13 FILE_SHARE_WRITE,
14 NULL,
15 CREATE_ALWAYS,
16 0,
17 NULL );
18 char msg[] = "茴香豆的茴";
19 if( hFile != INVALID_HANDLE_VALUE )
20 {
21 WriteFile( hFile, msg, sizeof(msg) - 1, &nBytes, NULL );
22 CloseHandle(hFile);
23 }
24
25 //讀取文件
26 hFile = CreateFile( _T("test.out"),
27 GENERIC_READ,
28 FILE_SHARE_READ,
29 NULL,
30 OPEN_ALWAYS,
31 0,
32 NULL );
33 if( hFile != INVALID_HANDLE_VALUE )
34 {
35 char line[256] = {0};
36 BOOL bResult;
37 bResult = ReadFile(hFile,
38 line,
39 sizeof(line),
40 &nBytes,
41 NULL) ;
42
43 if (nBytes != 0 )
44 {
45 printf("%s\r\n", line);
46 }
47
48 CloseHandle(hFile);
49 }
50 }
運行該程序,讀者將會發現項目目錄下會生成一個test.out文件,同時控制檯輸出結果如圖2-50所示。
圖2-50 運行結果
能夠看出,使用Windows API進行編程,代碼的可讀性並非很高。所以通常狀況下,咱們推薦使用標準庫或者MFC類來進行操做。
光盤導讀
該項目對應於光盤中的目錄「\ch02\ WinApiWriter」。
2 、使用C++標準庫(stdcpp)
標準C++提供了常見的操做類和操做函數,如:針對文件處理,標準C++在<fstream>中就提供了fstream類。
通常咱們說起「C++標準庫(C++ standard library)」,它實際上包含一堆頭文件(.h)、實現文件(.cpp)及目標庫文件(.lib)等,其中包含的內容以下所示。
l 函數:函數的定義,如rand()函數用以獲取隨機數。
l 常量:一些常量的定義。
l 宏:一些宏的定義,如RAND_MAX。
l 類:公用類的定義,如string。
l 對象:公用對象的定義,如用以控制檯輸出的cout。
l 模板:C++標準庫中最多的就是類模板和函數模板的定義。
不一樣的C++庫完成對不一樣操做的封裝,爲C++程序員提供基本的操做能力。通常認爲C++標準庫可進行以下分類,如圖2-51所示。
圖2-51 C++標準庫的組成
l 字符串:用以完成字符串的封裝和操做。
l 輸入/輸出流:用以操做輸入、輸出流。
l 複數:用來進行復數類型的運算。
l 異常診斷:用來定義異常類和提供診斷的方法。
l C語言庫:舊版的C標準庫。
l 標準模板庫:STL容器、泛型算法庫。
l 其餘工具庫:包括函數對象類、內存操做類等。
C++標準庫中定義的成員都包括在std(標準standard的縮寫)名字空間裏。因此調用庫函數時別忘了對std名字空間的使用聲明:
using namespace std;
如今動手
標準C++提倡使用流(stream)來操做文件,接下來咱們體驗如何使用文件流fstream來操做文件輸入/輸出。
【程序 2‑12】使用fstream輸出茴字
01 #include "stdafx.h"
02 #include <iostream>
03 #include <fstream>
04
05 using namespace std;
06
07 int main()
08 {
09 //寫入文件
10 ofstream out("test.out");
11 out << "茴香豆的茴";
12 out.close();
13
14 //讀取文件
15 ifstream in("test.out");
16 char line[256];
17 in.getline(line, 256);
18 cout << line << endl;
19
20 return 0;
21 }
光盤導讀
該項目對應於光盤中的目錄「\ch02\ FstreamWriter」。
三、 使用CRT(C運行時期庫)
在遙遠的C語言時代,C語言就提供了豐富的函數庫,如<stdio.h>,它即對應於標準輸入/輸出的功能庫。
在C++中,這些C標準庫得以保留,但C++再也不同意以下方式使用傳統的C頭文件:
#include <stdlib.h>
int i = rand();
C++鼓勵使用<cstdlib>這樣的形式替換「.h」的寫法:
#include <cstdlib>
前綴c的含義在於這是一個C標準庫。C標準庫的內容會被C++同時置於全局名字空間和std名字空間,因此對隨機函數rand()的調用也能夠寫成:
std::rand()
C標準庫主要包括在表2-5所示的頭文件中。
頭文件
|
含義
|
<cassert>
|
診斷庫
|
<cctype>
|
字符處理函數庫
|
<cerrno>
|
錯誤定義
|
<cfloat>
|
浮點類型
|
<climits>
|
整型數值的尺寸定義
|
<clocale>
|
國際化庫
|
<cmath>
|
數學庫
|
<csetjmp>
|
跳轉函數庫
|
<csignal>
|
信號處理庫
|
<cstdarg>
|
可變參數處理
|
<cstddef>
|
標準定義庫
|
<cstdio>
|
標準輸入、輸出庫
|
<cstdlib>
|
標準工具庫
|
<cstring>
|
字符串函數庫
|
<ctime>
|
時間庫
|
在<cstdlib>中其實就有操做文件的函數:
FILE *fopen( //建立、打開一個文件
const char* filename,
const char* mode
);
int fclose( //關閉文件
FILE* stream
);
int fprintf( //向文件輸出指定格式的文本
FILE* stream,
const char* format [, argument ]...
);
int fscanf( //從文件讀取指定格式的文本
FILE* stream,
const char* format [, argument ]...
);
如今動手
接下來,咱們體驗如何採用CRT庫函數來操做文件。
【程序 2-13】使用CRT輸出茴字
01 #include "stdafx.h"
02 #include <cstdio>
03
04 int main()
05 {
06 //寫入文件
07 FILE * fp = fopen("test.out", "w");
08 fprintf(fp, "茴香豆的茴");
09 fclose(fp);
10
11 //讀取文件
12 fp = fopen("test.out", "r");
13 char line[256];
14 fscanf(fp, "%s", line);
15 printf("%s\r\n", line);
16 fclose(fp);
17
18 return 0;
19 }
輸出結果如圖2-50所示。
好奇的讀者能夠在項目的當前目錄找到輸出文件test.out,它的內容如圖2-52所示。
圖2-52 使用CRT庫函數生成的文件
光盤導讀
該項目對應於光盤中的目錄「\ch02\ CrtWriter」。
四、 使用CRT庫的寬字符版本
標準C++引入了寬字符wchar_t,用來表達像中文這樣的寬文本,對應地,與字符(char)相關的CRT庫函數基本上都有其寬字符(wchar_t)版本。
FILE *_wfopen(
const wchar_t* filename,
const wchar_t* mode
);
int fwprintf(
FILE* stream,
const wchar_t* format [, argument ]...
);
int fwscanf(
FILE* stream,
const wchar_t* format [, argument ]...
);
如今動手
接下來,咱們體驗如何採用CRT庫函數的寬字符版原本操做文件,並輸出茴字。
【程序 2-14】使用CRT的寬字符版本輸出茴字
01 #include "stdafx.h"
02 #include <cstdio>
03 #include <clocale>
04
05 int main()
06 {
07 setlocale(LC_ALL, "chs");
08
09 //寫入文件
10 FILE * fp = _wfopen(L"test.out", L"w,ccs=UNICODE");
11 fwprintf(fp, L"%s", L"茴香豆的茴");
12 fclose(fp);
13
14 //讀取文件
15 fp = _wfopen(L"test.out", L"r,ccs=UNICODE");
16 wchar_t line[256];
17 fwscanf(fp, L"%s", line);
18 wprintf(L"%s\r\n", line);
19 fclose(fp);
20 return 0;
21 }
光盤導讀
該項目對應於光盤中的目錄「\ch02\ CrtWcharWriter」。
提示
注意以上代碼中setlocale()的用法,它將當前的時區設定爲「chs」,即採用簡體中文編碼方式。若是讀者忘記了該行,那麼像wprintf()這樣的函數頗有可能沒法正確輸出寬字符。
5 、 使用CRT庫的安全版本
Visual C++爲一些CRT函數提供了安全版本,即secure version,這些安全版本的函數加強了參數校驗、緩衝區大小檢測、格式化參數校驗等功能。原先那些舊的函數在Visual C++中會被提示成「deprecated(不建議使用)」,如圖2-53所示。
圖2-53 不安全的CRT調用
負責任的程序員應該依照這些警告,改用安全的版本。好比,前面用到的函數都具備相應的的安全版本:
errno_t fopen_s(
FILE** pFile,
const char *filename,
const char *mode
);
int fprintf_s(
FILE *stream,
const char *format [,
argument ]...
);
int fwprintf_s(
FILE *stream,
const wchar_t *format [,
argument ]...
);
int fscanf_s(
FILE *stream,
const char *format [,
argument ]...
);
int fwscanf_s(
FILE *stream,
const wchar_t *format [,
argument ]...
);
後綴_s代表該函數是一個安全版本(secure version)。
如今動手
接下來,咱們體驗如何採用CRT庫函數的secure版原本操做文件。
【程序 2‑15】使用CRT庫的安全版本輸出茴字
01 #include "stdafx.h"
02 #include <cstdio>
03
04 int main()
05 {
06 //寫入文件
07 FILE * fp;
08 fopen_s(&fp, "test.out", "w");
09 fprintf_s(fp, "茴香豆的茴");
10 fclose(fp);
11
12 //讀取文件
13 fopen_s(&fp, "test.out", "r");
14 char line[256];
15 fscanf_s(fp, "%s", line, 256);
16 printf_s("%s\r\n", line);
17 fclose(fp);
18
19 return 0;
20 }
光盤導讀
該項目對應於光盤中的目錄「\ch02\ CrtSafeWriter」。
六、 使用MFC/ATL
MFC更多的工做在於:它們將Widnows API函數包裝成對象類及其成員函數。MFC的這種中間位置與標準C++很相似,只不過它僅用於Windows操做系統,MFC的位置如圖2-54所示。
好比,針對文件的操做,MFC就封裝了CFile類,CFile的UML類圖簡略如圖2-55所示。
圖2-54 MFC/ATL的位置 圖2-55 MFC封裝的CFile類
若是咱們再較真一點地話,就能夠經過調試等手段進入到CFile::Remove()函數的定義,來觀察CFile的廬山真面目:
void PASCAL CFile::Remove(LPCTSTR lpszFileName)
{
if (!::DeleteFile((LPTSTR)lpszFileName))
CFileException::ThrowOsError((LONG)::GetLastError(),
lpszFileName);
}
原來如此!MFC提供的CFile,其Remove()函數實際上就是簡單的調用一下Windows API「DeleteFile()」而已!
如今動手
使用MFC進行Windows編程,再也不是一種痛苦,以下即爲使用CFile操做文件的例子,爲了讓咱們的控制檯程序支持MFC,請參考2.4.2小節「讓控制檯程序支持MFC/ATL」。
【程序 2-16】使用CFile輸出茴字
01 #include "stdafx.h"
02
03 #include <afx.h>
04
05 int main()
06 {
07 //寫入文件
08 CFile file;
09 if(file.Open(_T("test.out"), CFile::modeCreate | CFile::modeWrite))
10 {
11 char line[256] = "茴香豆的茴";
12 file.Write(line, sizeof(line));
13 file.Close();
14 }
15
16 //讀取文件
17 if(file.Open(_T("test.out"), CFile::modeRead))
18 {
19 char line[256];
20 if(file.Read(line, 256) != 0)
21 {
22 printf("%s\r\n", line);
23 }
24
25 file.Close();
26 }
27
28 return 0;
29 }
使用MFC類,傳統的面向函數的編程接口即轉換成MFC類對象的接口,這樣一來,代碼的安全性和可讀性得以大大提升。
光盤導讀
該項目對應於光盤中的目錄「\ch02\ MfcFileWriter」。
7 、使用C++/CLI
前面說起,C++/CLI的目標是把C++帶到CLI平臺上,使C++可以在CLI平臺上發揮最大的能力。經過C++/CLI中的標準擴展,C++具備了原來沒有的動態編程能力及一系列的first class(一等公民)的.NET特性。
讀者有時會發現術語CLI和CLR可交換使用,實際上這二者之間的區別在於:CLI是一種標準規範,而CLR倒是微軟對CLI的實現。當咱們使用C++/CLI時,就能夠經過CLI接口與CLR通訊,而CLR至關於創建在操做系統之上的一個虛擬層。它們之間的調用關係如圖2-56所示。
如今動手
接下來,咱們使用C++/CLI來操做文件並輸出茴字。
【程序 2-17】使用C++/CLI輸出茴字
01 #include "stdafx.h"
02
03 using namespace System;
04 using namespace System::IO;
05
06 int main(array<System::String ^> ^args)
07 {
08 String^ path = "test.out";
09
10 //寫文件
11 StreamWriter^ sw = File::CreateText(path);
12 sw->WriteLine("茴香豆的茴");
13 sw->Close();
14
15 //讀文件
16 StreamReader^ sr = File::OpenText(path);
17 String^ s = "";
18 if (s = sr->ReadLine())
19 {
20 Console::WriteLine(s);
21 }
22 }
簡直太簡潔了,不是嗎?與本例子相關的.NET Framework 類包括以下。
Console:對應於控制檯輸出。
File:文件類,與CFile類似。
StreamWriter:流的寫入器。
StreamReader:流的讀取器。
光盤導讀
該項目對應於光盤中的目錄「\ch02\ ClrWriter」。
8 、 該採用哪種寫法
太多的選擇比沒有選擇更讓人痛苦。綜上所述,C++/CLI的使用彷佛更顯得時髦一些,可是因爲不少緣由,國內傳統Visual C++的用戶仍是不少,咱們大部分的程序員仍是在使用着純的Visual C++。所以,本書中咱們儘可能只介紹本地C++的用法,在本地C++語言中,若是同時又存在着幾種技術實現方法,那麼咱們給讀者的建議是:優先採用MFC和Windows API,其次再使用標準C++的類和CRT函數庫。
本書在談到C++/CLI編程時老是儘可能輕描淡寫,由於C++/CLI畢竟不是C++的標準語法,並且它必須和.NET搭配使用。使用C++/CLI編程,代碼老是顯得極其簡單,可是C++/CLI不是. NET環境下最簡易的編程語言,若是讀者願意在CLR編程投入更多的精力,那麼筆者建議你不妨去嘗試使用其餘語言,如C#。在Visual C++圖書中推薦C#,這彷佛有失厚道,可是程序員就應該這樣,什麼合適就用什麼,語言是工具,咱們不要讓偏心蛻變成不可變通的執拗。