茴字的N種寫法

茴字的N種寫法
在Visual C++世界裏,對於每一種需求,幾乎都存在着多種技術實現方式,這些細枝末節層層交錯,從而造成了壯觀的Visual C++技術脈絡圖。本節將實現一個目標,即將孔乙己的「茴」字輸出至某個文件中,並再次讀取出來。其中主要的技術點就是針對文件作一些操做,而Visual C++關於文件的操做方式就很是之多,有Windows API方式、C++標準庫方式等,這些方式如圖2-48所示。
如下詳細介紹使用在Windows系統中以各類方式(優雅的或者鄙俗的)寫出「茴」字。
茴字的N種寫法
圖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所示。
使用CRT庫函數生成的文件
圖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所示。
不安全的CRT調用
圖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#,這彷佛有失厚道,可是程序員就應該這樣,什麼合適就用什麼,語言是工具,咱們不要讓偏心蛻變成不可變通的執拗。
本文出自:《 把脈VC++
相關文章
相關標籤/搜索