茴字的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 <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”。
3、  使用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”。
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 <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”。
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 <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++

你可能感兴趣的:(职场,休闲)