茴字的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++
相关文章
相关标签/搜索