茴字的N种写法

茴字的N种写法
在Visual C++世界里,对于每一种需求,几乎都存在着多种技术实现方式,这些细枝末节层层交错,从而形成了壮观的Visual C++技术脉络图。本节将实现一个目标,即将孔乙己的“茴”字输出至某个文件中,并再次读取出来。其中主要的技术点就是针对文件做一些操作,而Visual C++关于文件的操作方式就非常之多,有Windows API方式、C++标准库方式等,这些方式如图2-48所示。

以下详细介绍使用在Windows系统中以各种方式(优雅的或者鄙俗的)写出“茴”字。

茴字的N种写法

图2-48  茴字的N种写法

1、  使用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

03            #include

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类。

一般我们提及“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

03            #include

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”。

3、  使用CRT(C运行时期库)
在遥远的C语言时代,C语言就提供了丰富的函数库,如,它即对应于标准输入/输出的功能库。

在C++中,这些C标准库得以保留,但C++不再赞成如下方式使用传统的C头文件:

#include

int i = rand();

C++鼓励使用这样的形式替换“.h”的写法:

#include

前缀c的含义在于这是一个C标准库。C标准库的内容会被C++同时置于全局名字空间和std名字空间,所以对随机函数rand()的调用也可以写成:

std::rand()

C标准库主要包括在表2-5所示的头文件中。

头文件

含义

诊断库

字符处理函数库

错误定义

浮点类型

整型数值的尺寸定义

国际化库

数学库

跳转函数库

信号处理库

可变参数处理

标准定义库

标准输入、输出库

标准工具库

字符串函数库

时间库

中其实就有操作文件的函数:

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

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”。

4、 使用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

03            #include

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

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”。

6、  使用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

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 ^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++

请使用浏览器的分享功能分享到微信等