http://courses.cs.vt.edu/cs2604/fall01/binio.html
用c++ 流读取文件发生错误,如果没有判断就会发生崩溃。
#include <iostream>
#include <fstream>
void mytest()
char buffer[100];
std::ifstream myFile ("d:\\test.bat", std::iostream::binary);
// 如果发生错误要判断,否则就是在try catch里也不能捕获到而发生崩溃。
if (!myFile.read (buffer, 100))
return false;
int a = 0;
c++流读写时的好文章http://courses.cs.vt.edu/cs2604/fall01/binio.html用c++流读取文件发生错误,如果没有判断就会发生崩溃。#include <iostream>#include <fstream>void mytest(){ char buffer[100]; std::ifstream myFile ("d:\\test.bat", std::iostream::binary); // 如果.
1、面向对象的特征有哪些方面
1.抽象:
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。
2.继承:
继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
3.封装:
封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
4. 多态性:
多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
2、String是最基本的数据类型吗?
基本数据类型包括byte、int、char、long、float、double、boolean和short。
java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类
3、int 和 Integer 有什么区别
Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。Java为每个原始类型提供了封装类。
原始类型封装类
booleanBoolean
charCharacter
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
引用类型和原始类型的行为完全不同,并且它们具有不同的语义。引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为 null,而原始类型实例变量的缺省值与它们的类型有关。
4、String 和StringBuffer的区别
JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。
5、运行时异常与一般异常有何异同?
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。
6、说出Servlet的生命周期,并说出Servlet和CGI的区别。
Servlet被服务器实例化后,容器运行其init方法,请求到达时运行其service方法,service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其destroy方法。
与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。
7、说出ArrayList,Vector, LinkedList的存储性能和特性
ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
8、EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean和StatelessBean的区别。
EJB包括Session Bean、Entity Bean、Message Driven Bea
在我们开始介绍Microsoft Windows应该提供的许多特性之前,我们首先必须了解Windows的各个函数是如何进行错误处理的。
当你调用一个Windows函数时,它首先要检验你传递给它的的各个参数的有效性,然后再设法执行它的任务。如果你传递了一个无效参数,或者由于某种原因它无法执行这项操作,那么该函数就会返回一个值,指明该函数在某种程度上运行失败了。表1-1列出了大多数Windows函数使用的数据类型的返回值。
表1-1 Windows函数常用的返回值类型
数据类型 表示失败的值
VOID 该函数的运行不可能失败。Windows函数的返回值类型很少
是VOID。
BOLL 如果函数运行失败,那么返回值是0,否则返回的是非0值。最
好对返回值进行测试,以确定它是0还是非0。如果它是TRUE
,则不要测试返回值。
HANDLE 如果函数运行失败,则返回值通常是NULL,否则返回值为
HANDLE,,用于标识你可以操作的一个对象。对于这个返回
值,你应该小心处理,因为有些函数会返回一个句柄
值INVALID_HANDLE_VALUE,它被定义为-1。该函数的
Platform SDK资料将会清楚地说明该函数是返回NULL还
是INVALID_HANDLE_VALID,以便指明函数运行已经失败。
PVOID 如果函数运行失败,则返回值是NULL,否则返回PVOID,以
标识数据块的内存地址。
LONG/DWORD 这是个难以处理的值。返回数量的函数通常返回LONG
或DWORD。如果由于某种原因,函数无法对你想要进行计数
的对象进行计数,那么该函数通常返回0或-1(根据该函数而定)
。如果你调用的函数返回了LONG/DWORD,那么请认真阅
读Platform SDK资料,以确保你能正确检查潜在的错误。
当一个Windows函数返回一个错误代码时,它常常可以用来了解函数为什么会运行失败。Microsoft公司编译了一个所有可能的错误代码的列表,并且为每个错误代码分配了一个32位的号码。
从系统内部来讲,当一个Windows函数检测到一个错误时,它会使用一个称为线程本地存储器的机制,将相应的错误代码号码与调用的线程关联起来。(“线程本地存储器”将在第21章中介绍)。这将使线程能够互相独立地运行,而不会影响各自的错误代码。当函数返回给你时,它的返回值就能指明一个错误已经发生。若要确定这是个什么错误,请调用GetLastError函数:
见原书P4的程序(1)
该函数只返回线程的32位错误代码。
当你拥有32位错误代码的号码时,你必须将该号码转换成更有用的某种对象。WinError.h头文件包含了Microsoft公司定义的错误代码的列表。下面我显示了该列表的某些内容,使你能够看到它的大概样子:
见原书P4的程序(2)和P5的程序
你可以看到,每个错误都有3种表示法:即一个消息ID(这是你可以在源代码中使用的一个宏,以便与GetLastError的返回值进行比较),消息文本(对错误的英文描述)和一个号码(你应该避免使用这个号码,而应该使用消息ID)。请记住,我只选择了WinError.h头文件中的很少一部分内容来向你进行展示,整个文件的长度超过21000行。
当Windows函数运行失败时,你应该立即调用GetLastError函数,否则,如果你调用另一个Windows函数,它的值很可能被改写。
说明 GetLastError能返回线程产生的最后一个错误。如果该线程调用的Windows
函数运行成功,那么最后一个错误代码就不被改写,并且不指明运行成功。有少
数Windows函数并不遵循这一规则,并且它会更改最后的错误代码,但是Platform
SDK资料通常指明,当函数运行成功时,该函数会更改最后的错误代码。
Windows
98 许多Windows 98的函数实际上是用Microsoft公司的16位Windows 3.1产
品产生的16位代码来实现的。这种比较老的代码并不通过GetLastError之类函
数来报告错误,而且Microsoft公司并没有在Windows 98中修改16位代码,以
支持这种错误处理方式。对于我们来说,这意味着Windows 98中的许多Win32
函数在运行失败时不能设置最后的错误代码。该函数将返回一个值,指明运行失
败,这样你就能够发现该函数确实已经运行失败。但是你无法确定运行失败的原
有些Windows函数之所以能够成功运行,那是若干个原因产生的结果。例如,创建指明的事件内核对象之所以能够取得成功,原因是你实际上创建了该对象,或者是因为已经存在带有相同名字的事件内核对象。你的应用程序必须知道成功的原因。为了将该信息返回给你,Microsoft公司选择使用最后错误代码机制。这样,当某些函数运行成功时,你就能够通过调用GetLadtError函数来确定其他的一些信息。对于具有这种行为特性的函数来说,Platform SDK资料清楚地说明了GetLastError函数可以这样来使用。请参见该资料,以便找出CreateEvent函数的例子。
当你进行调试的时候,我发现监控线程的最后错误代码是非常有用的。在Microsoft Visual studio 6.0中,Microsoft的调试程序支持一个非常有用的特性,即你可以配置Watch窗口,以便始终都能向你显示线程的最后错误代码的号码和该错误的英文描述。通过选定Watch窗口中的一行,并键入“@err,hr",你就能够做到这一点。观察图1-1,你会看到我已经调用了CreateFile函数。该函数返回INVALID_HANDLE_VALUE(-1)的HANDLE,表示它未能打开指定的文件。但是Watch窗口向我们显示最后错误代码(即如果我调用GetLastErro函数,该函数返回的错误代码)是0x00000002。该Watch窗口又进一步指明错误代码2是指“系统不能找到指定的文件。”你会发现它与WinError.h头文件中的错误代码2所指的字符串是相同的。
图1-1 在Visual Studio 6.0的Watch窗口中键入
“@err,hr",你就可以查看当前线程的最后错误代码。
Visual studio还配有一个小的实用程序,称为Error Lookup。你可以使用Error Lookup将错误代码的号码转换成它的文本描述。
见P7的Error Lookup插图
如果我在我编写的应用程序中发现一个错误,我可能想要向用户显示该错误的文本描述。Windows提供了一个函数,可以将错误代码转换成它的文本描述。该函数称为FormatMessage。请看下面的代码:
见原书P8的程序(1)
FormatMessage函数的功能实际上是非常丰富的,在创建向用户显示的字符串信息时,它是人们首选的函数。该函数之所以有这样大的作用,原因之一是它很容易用多种语言来进行操作。该函数能够检测出用户首选的语言(在Regional Settings Control Panel小应用程序中设定),并返回相应的文本。当然,你首先必须自己转换字符串,然后将已转换的消息表资源嵌入你的.exe文件或DLL模块,不过,这时该函数会选定正确的嵌入对象。ErrorShow示例应用程序(本章后面将加以介绍)展示了如何调用该函数,以便将Microsoft公司定义的错误代码转换成它的文本描述。
有些人常常问我,Microsoft公司是否建立了一个主控列表,以显示每个Windows函数可能返回的所有错误代码。可惜,答案是没有这样的列表,而且Microsoft公司将永远不会建立这样的一个列表。因为在创建系统的新版本时,建立和维护该列表实在太困难了。
建立这样一个列表时存在的问题是,你可以调用一个Windows函数,但是该函数能够在内部调用另一个函数,而这另一个函数又可以调用另一个函数,如此类推。由于各种不同的原因,这些函数中的任何一个函数都可能运行失败。有时,当一个函数运行失败时,较高级的函数对它进行恢复,并且仍然可以执行你想执行的操作。为了创建该主控列表,Microsoft公司必须跟踪每个函数的运行路径,并建立所有可能的错误代码的列表。这项工作很困难。当创建系统的新版本时,这些函数的运行路径就会改变。
1.1 你也能够定义自己的错误代码
好了,我已经说明Windows函数是如何向函数的调用者指明发生的错误。Microsoft公司也使你能够将该机制用于你自己的函数。比如说,你编写了一个你希望其他人调用的函数。你的函数可能因为这样或那样的原因而运行失败,你必须向函数的调用者说明它已经运行失败。
若要指明函数运行失败,你只需要设定线程的最后的错误代码,然后让你的函数返回FALSE,INVALID_HANDLE_VALUE,NULL,或者返回任何合适的信息。若要设定线程的最后错误代码,你只需要调用下面的代码:
见原书P8的程序(2)
请将你认为合适的任何32位号码传递给该函数。我设法使用WinError.h中已经存在的代码,只要该代码能够正确地指明我想要报告的错误即可。如果你认为WinError.h中的任何代码都不能正确地反映该错误的性质,那么你可以创建你自己的代码。错误代码是个32位的数字,它可以划分成下表所示的各个域。
位 31-30 29 28 27-16 15-0
内容 严重性 Microsoft/ 保留 设备代码 异常代码
含义 0=成功 0=Microsoft 必须是0 由Microsoft 由Microsoft/
1=供参考 公司定义的 公司定义 客户定义
2=警告 代码
3=错误 1=客户定义
这些域将在第24章中详细讲述。现在,你需要知道的重要域是第29位的信息。Microsoft公司规定,他们建立的所有错误代码的这个信息位均使用0。如果你创建自己的错误代码,你必须在这个信息位中输入1。这样,就可以确保你的错误代码与Microsoft公司目前或者将来定义的错误代码不会发生冲突,
1.2 ErrorShow示例应用程序
ErrorShow应用程序“01 ErrorShow.exe"(在图1-2中列出)展示了如何获取错误代码的文本描述的方法。该应用程序的源代码和资源文件位于本书所附光盘上的01-ErrorShow目录下。一般来说,该应用程序用于显示调试程序的Watch窗口和Error Lookup程序是如何运行的。当你启动该程序时,就会出现下面这个窗口。
见原书P9的插图
你可以将任何错误代码键入该编辑控件。当你单击Look Up按钮时,在底部的滚动窗口中就会显示该错误的文本描述。该应用程序唯一令人感兴趣的特性是如何调用FormatMessage函数。下面是我使用该函数的方法:
见原书P10的程序(1)
第一个代码行用于从编辑控件中检索错误代码的号码。然后,建立一个内存块的句柄并将它初始化为NULL。FormatMessage函数在内部对内存块进行分配,并将它的句柄返回给我们。
当调用FormatMessage函数时,我传递了FORMAT_MESSAGE_FROM_SYSTEM标志。该标志告诉FormatMessage函数,我们想要系统定义的错误代码的字符串。我还传递了FORMAT_MESSAGE_ALLOCATE_BUFFER标志,告诉该函数为错误代码的文本描述分配足够大的内存块。该内存块的句柄将在hlocal变量中返回。第三个参数指明我们想要查找的错误代码的号码,第四个参数指明我们想要文本描述使用什么语言。
如果FormatMessage函数运行成功,那么错误代码的文本描述就位于内存块中,我将它拷贝到对话框底部的滚动窗口中。如果FormatMesage函数运行失败,我设法查看NetMsg.dll模块中的消息代码,以了解该错误是否与网络有关。使用NetMsg.dll模块的句柄,我再次调用FormatMessage函数。你会看到,每个DLL(或.exe)都有它自己的一组错误代码,你可以使用Message Compiler(MC.exe)将这组错误代码添加给该模块,并将一个资源添加给该模块。这就是Visual Studio的Error Lookup工具允许你用Modules对话框进行的操作。
图1-2 ErrorShow示例应用程序
见原书P11—16
第2章 UNICODE
随着Microsoft公司的Windows操作系统在全世界日益广泛的流行,对于我们这些软件开发人员来说,将我们的目标瞄准国际上的各个不同市场,已经成为一个越来越重要的问题。美国的软件版本比国际版本提前6个月推向市场,这曾经是个司空见惯的现象。但是,由于各国对Windows操作系统提供了越来越多的支持,因此就更加容易为国际市场生产各种应用软件,从而缩短了软件的美国版本与国际版本推出的时间间隔。
Windows操作系统始终不逾地提供各种支持,以帮助软件开发人员进行应用程序的本地化工作。应用软件可以从各种不同的函数中获得特定国家的信息,并可观察控制面板的设置,以确定用户的首选项。Windows甚至支持不同的字体,以适应我们的应用的需要。
我之所以将这一章放在本书的开头,是因为我考虑到Unicode是开发任何应用程序时要采用的基本步骤。关于Unicode的问题,我在本书的每一章中几乎都要讲到,而且本书中给出的所有示例应用程序都是“用Unicode实现的”。如果你为Microsoft Windows 2000或Microsoft Windows CE开发应用程序,你应该使用Unicode进行开发。如果你为Microsoft Windows 98开发应用程序,你必须对某些问题作出决定。本章也要讲述Windows 98的有关问题。
2.1 字符集
软件的本地化要解决的真正问题,实际上就是如何来处理不同的字符集。多年来,我们许多人一直将文本串作为一系列单字节字符来进行编码,并在结尾处放上一个零。对于我们来说,这已经成了习惯。当我们调用strlen函数时,它在以0结尾的单字节字符数组中返回字符的数目。
问题是,有些文字和书写规则(比如日文中的汉字就是个典型的例子)的字符集中的符号太多了,因此单字节(它提供的符号最多不能超过256个)是根本不敷使用的。为此我们创建了双字节字符集(DBCS),以支持这些文字和书写规则。
2.1.1 单字节与双字节字符集
在双字节字符集中,字符串中的每个字符可以包含一个字节,也可以包含两个字节。例如,日文中的汉字,如果第一个字符在0x81与0x9F之间,或者在0xE0与0xFC之间,那么你就必须观察下一个字节,才能确定字符串中的这个完整的字符。如果要使用双字节字符集,对于程序员来说简直是个很大的难题,因为有些字符只有一个字节宽,而有些字符则是两个字节宽。
如果只是调用strlen函数,那么你无法真正了解字符串中究竟有多少字符,它只能告诉你到达结尾的0之前有多少个字节。ANSI的C运行期库中没有配备相应的函数,使你能够对双字节字符集进行操作。但是,Microsoft Visual C++的运行期库却包含许多函数,如_mbslen,它可以用来操作多字节(既包括单字节也包括双字节)字符串。
为了帮助你对DBCS字符串进行操作,Windows提供了下面的一组帮助函数。
函数 描述
PTSTR CharNext 返回字符串中的下一个字符的地址
(PCTSTR pszCurrentChar);
PTSTR CharPrev 返回字符串中的上一个字符的地址
(PCTSTR pszStart,
PCTSTR pszCurrentChar);
BOOL IsDBCSLendByte 如果该字节是DBCS字符的第一个字节,则返
(BYTE bTestChar); 回TRUE
2.1.2 Unicode:宽字节字符集
Unicode是Apple和Xerox公司于1988年建立的一个技术标准。1991年,成立了一个集团机构负责Unicode的开发和推广应用。该集团由Apple、Compaq、HP、IBM、Microsoft、Oracle、Silicon Graphics、Sybase、Unisys和Xerox等公司组成。(若要了解该集团的全部成员,请通过网址www.Unicode.org查找。)该集团公司负责维护Unicode标准。Unicode的完整描述可以参阅AddisonWesley出版的《Unicode Standard》一书。(该书可以通过网址www.Unicode.org订购。)
Unicode提供了一种简单而又一致的表示字符串的方法。Unicode字符串中的所有字符都是16位的字符(两个字节)。它没有专门的字节来指明下一个字节是属于同一个字符的组成部分,还是一个新字符。这意味着你只需要对指针进行递增或递减,就可以遍历字符串中的各个字符。你不再需要调用CharNext,CharPrev和IsDBCSLeadByte之类的函数。
由于Unicode用一个16位的值来表示每个字符,因此总共可以得到65000个字符,这样,它就能够对世界各国的书面文字中的所有字符进行编码。这远远超过了单字节字符集的256个字符的数目。
目前,已经为阿拉伯文、中文拼音、西里尔字母(俄文)、希腊文、西伯莱文、日文、韩文和拉丁文(英文)字母定义了Unicode代码点1。这些字符集中还包含了大量的标点符号、数学符号、技术符号、箭头、装饰标志、区分标志和其他许多字符。如果你将所有这些字母和符号加在一起,总计约达35000个不同的代码点,这样,总计的65000个代码点中,大约还有一半可供将来扩充时使用。
这65536个字符可以分成不同的区域。下面这个表显示了一部分这样的区域以及分配给这些区域的字符。
16位代码 字符 16位代码 字符
0000-007F ASCII 0300-036F 通用区分标志
0080-00FF 拉丁文1字符 0400-04FF 西里尔字母
0100-017F 欧洲拉丁文 0530-058F 亚美尼亚文
0180-01FF 扩充拉丁文 0590-05FF 西伯莱文
0250-02AF 标准拼音 0600-06FF 阿拉伯文
02B0-02FF 修改型字母 0900-097F 梵文
注1. 代码点是指字符集中的一个符号的位置
目前尚未分配的代码点大约还有29000个,不过它们是保留供将来使用的。另外,大约有6000个代码点是保留供你个人使用的。
2. 2 为何应该使用Unicode
当你开发应用程序时,你当然应该考虑利用Unicode的优点。即使现在你不打算对你的应用程序进行本地化,开发时将Unicode放在心上,肯定可以简化将来的代码转换工作。此外,Unicode还具备下列功能:
* 可以很容易地在不同语言之间进行数据交换
* 使你能够分配支持所有语言的单个二进制.exe文件或DLL文件
* 提高你的应用程序的运行效率(本章后面还要详细介绍)
2.3 Windows 2000与Unicode
Windows 2000是使用Unicode从头进行开发的,用于创建窗口、显示文本、进行字符串操作等的所有核心函数都需要Unicode字符串。如果你调用任何一个Windows函数并给它传递一个ANSI字符串,那么系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果你希望函数返回ANSI字符串,系统就会首先将Unicode字符串转换成ANSI字符串,然后将结果返回给你的应用程序。所有这些转换操作都是在你看不见的情况下发生的。当然,进行这些字符串的转换需要占用系统的时间和内存开销。
例如,如果你调用CreateWindowEx函数,并传递类名字和窗口标题文本的非Unicode字符串,那么CreateWindowEx必须分配内存块(在你的进程的默认堆中),将非Unicode字符串转换成Unicode字符串,并将结果存储在分配到的内存块中,然后调用Unicode版本的CreateWindowEx函数。
对于用字符串填入缓存的函数来说,系统必须首先将Unicode字符串转换成非Unicode字符串,然后你的应用程序才能处理该字符串。由于系统必须执行所有这些转换操作,因此你的应用程序需要更多的内存,并且运行的速度比较慢。通过从头开始用Unicode来开发应用程序,你就能够使你的应用程序更加有效地运行。
2. 4 Windows 98与Unicode
Windows 98不是一种全新的操作系统。它继承了16位Windows操作系统的特性,它不是用来处理Unicode。如果要增加对Unicode的支持,其工作量非常大,因此在该产品的特性列表中没有包括这个支持项目。由于这个原因,Windows 98象它的前任产品一样,几乎都是使用ANSI字符串来进行所有的内部操作的。
你仍然可以编写用于处理Unicode字符和字符串的Windows应用程序,不过,使用Windows函数要难得多。例如,如果你想要调用CreateWindowEx函数并将ANSI字符串传递给它,这个调用的速度非常快,不需要从你进程的默认堆栈中分配缓存,也不需要进行字符串转换。但是,如果你想要调用CreateWindowEx函数并将Unicode字符串传递给它,你就必须明确分配缓存,并调用函数,以便执行从Unicode到ANSI字符串的转换操作。然后你可以调用CreateWindowEx,传递ANSI字符串。当CreateWindowEx函数返回时,你就能释放临时缓存。这比使用Windows 2000上的Unicode要麻烦得多。在本章的后面部分中,我要介绍如何在Windows 98下进行这些转换。
虽然Unicode函数的大多数代码在Windows 98中不起任何作用,但是有少数Unicode函数确实拥有非常有用的实现代码。这些函数是:
见原书的P21
可惜的是,这些函数中有许多函数在Windows 98中会出现各种各样的错误。有些函数无法使用某些字体,有些函数会破坏内存堆栈,有些函数会使打印机驱动程序崩溃,如此等等。如果你要使用这些函数,你必须对它们进行大量的测试。即使这样,你可能仍然无法解决问题。因此你必须向用户说明这些情况。
2. 5 Windows CE与Unicode
Windows CE操作系统是为小型设备开发的,这些设备的内存很小,并且不带磁盘存储器。你可能认为,由于Microsoft公司的主要目标是建立一种尽可能小的操作系统,因此它会使用ANSI作为自己的字符集。但是Microsoft公司并不是鼠目寸光。他们懂得,采用Windows CE的设备要在世界各地销售,他们希望降低软件开发成本,这样就能更加容易地开发应用程序。为此,Windows CE本身就是使用Unicode的一种操作系统。
但是,为了使Windows CE尽量做得小一些,Microsoft公司决定完全不支持ANSI Windows函数。因此,如果你要为Windows CE开发应用程序,你必须懂得Unicode,并且在整个应用程序中使用Unicode。
2. 6 需要注意的问题
下面让我们进一步明确一下“Microsoft公司对Unicode支持的情况”:
* Windows 2000既支持Unicode,也支持ANSI,因此你可以为它们当中的任何一种开发应用程序
* Windows 98 只支持ANSI,你只能为ANSI开发应用程序
* Windows CE只支持Unicode,你只能为Unicode开发应用程序
虽然Microsoft公司试图让软件开发人员能够非常容易地开发在这3种平台上运行是软件,但是Unicode与ANSI之间的差异使得事情变得困难起来,并且这种差异通常是我遇到的最大的问题之一。请不要误解,Microsoft公司坚定地支持Unicode,并且我也坚决鼓励你使用它。不过你应该懂得,你可能遇到一些问题,需要一定的时间来解决这些问题。我建议你尽可能使用Unicode。如果你运行Windows 98,那么只有在必要时才要转换到ANSI。
不过,还有另一个小问题你应该了解,那就是COM。
2.7 对COM的简单说明
当Microsoft公司将COM从16位Windows转换成Win32时,公司作出了一个决定,即,需要字符串的所有COM接口方法都只能接受Unicode字符串。这是个了不起的决定,因为COM通常用于使不同的组件能够互相之间进行通信,而Unicode则是传递字符串的最佳手段。
如果你为Windows 2000或Windows CE开发应用程序,并且也使用COM,那么你将会如虎添翼。在你的整个源代码中使用Unicode,将使与操作系统进行通信和与COM对象进行通信的操作变成一件轻而易举的事情。
如果你为Windows 98开发应用程序,并且也使用COM,那么你将会遇到一些问题。COM要求你使用Unicode字符串。操作系统的大多数函数要求你使用ANSI字符串。那是多么难办的事情啊!我曾经从事过若干个项目的开发,在这些项目中,我编写了许多代码,仅仅是为了来回进行字符串的转换。
2. 8 如何编写Unicode源代码
Microsoft公司为Unicode设计了Windows API,这样,它可以尽量减少对你的代码的影响。实际上,你可以编写单个源代码文件,以便使用或者不使用Unicode来对它进行编译。你只需要定义两个宏(UNICODE和_UNICODE),就可以修改然后重新编译该源文件。
2. 8.1 C运行期库对Unicode的支持
为了利用Unicode字符串,因此定义了一些数据类型。标准的C头文件String.h已经作了修改,以便定义一个名字为wchar_t的数据类型,它是一个Unicode字符的数据类型:
见原书P23的程序(1)
例如,如果你想要创建一个缓存,用于存放最多为99个字符的Unicode字符串和一个结尾为零的字符,你可以使用下面这个语句:
见原书P23的程序(2)
该语句创建了一个由100个16位值组成的数组。当然,标准的C运行期字符串函数,如strcpy、strchr和strcat等,只能对ANSI字符串进行操作,它们不能正确地处理Unicode字符串。因此,ANSI C也拥有一组补充函数。图2-1显示了一些标准的ANSI C字符串函数,后面是它们的等价Unicode函数。
图2-1 标准的ANSI C字符串函数和它们的等价Unicode函数
见原书P23的程序(3)和P24的程序
请注意,所有的Unicode函数均以wcs开头,wcs是宽字符串的英文缩写。若要调用Unicode函数,只需用前缀wcs来取代任何ANSI字符串函数的前缀str即可。
说明 大多数软件开发人员可能已经不记得这样一个非常重要的问题了,那就
是Microsoft公司提供的C运行期库与ANSI的标准C运行期库是一致的。
ANSI C规定,C运行期库支持Unicode字符和字符串。这意味着你始终都可
以调用C运行期函数,以便对Unicode字符和字符串进行操作,即使你是在
Windows 98上运行,也可以调用这些函数。换句话说,wcscat,wcslen和wcstok
等函数都能够在Windows 98上很好地运行,这些都是你必须关心的操作系统函数。
对于包含了对str函数或wcs函数进行显式调用的代码来说,你无法非常容易地同时为ANSI和Unicode对这些代码进行编译。在本章前面部分的内容中,我说过可以创建同时为ANSI和Unicode进行编译的单个源代码文件。若要建立这种双重功能,你必须包含Tchar.h文件,而不是包含String.h文件。
Tchar.h文件的唯一作用是帮助你创建ANSI/Unicode通用源代码文件。它包含你应该用在源代码中的一组宏,而不应该直接调用str函数或者wcs函数。如果你在编译源代码文件时定义了_UNICODE,这些宏就会引用wcs这组函数。如果你没有定义_UNICODE,那么这些宏将引用str这组宏。
例如,在Tchar.h中有一个宏称为_tcscpy。如果在你包含该头文件时没有定义_UNICODE,那么_tcscpy就会扩展为ANSI的strcpy函数。但是如果定义了_UNICODE,_tcscpy将扩展为Unicode的wcscpy函数。拥有字符串参数的所有C运行期函数都在Tchar.h文件中定义了一个通用宏。如果你使用通用宏,而不是ANSI/Unicode的特定函数名,你就能够顺利地创建可以为ANSI或Unicode进行编译的源代码。
但是,除了使用这些宏之外,还有一些操作你是必须进行的。Tchar.h文件包含了一些其他的宏。
若要定义一个ANSI/Unicode通用的字符串数组,请使用下面的TCHAR数据类型。如果定义了_UNICODE,TCHAR将声明为下面的形式:
见原书P25的程序(1)
如果没有定义_UNICODE,则TCHAR将声明为下面的形式:
见原书P25的程序(2)
使用该数据类型,你可以象下面这样分配一个字符串:
见原书P25的程序(3)
你也可以创建对字符串的指针:
见原书P25的程序(4)
不过上面这行代码存在一个问题。按照默认设置,Microsoft公司的C++编译器能够编译所有的字符串,就象它们是ANSI字符串,而不是Unicode字符串。因此,如果没有定义_UNICODE,该编译器将能正确地编译这一行代码。但是,如果定义了_UNICODE,就会产生一个错误。若要生成一个Unicode字符串而不是ANSI字符串,你必须将该代码行改写为下面的样子:
见原书P25的程序(5)
原义字符串前面的大写字母L,用于告诉编译器该字符串应该作为Unicode字符串来编译。当编译器将字符串置于程序的数据部分中时,它在每个字符之间分散插入零字节。这种变更带来的问题是,现在只有当定义了_UNICODE时,程序才能成功地进行编译。我们需要另一个宏,以便有选择地在原义字符串的前面加上大写字母L。这项工作由_TEXT宏来完成,_TEXT宏也在Tchar.h文件中做了定义。如果定义了_UNICODE,那么_TEXT定义为下面的形式:
见原书P25的程序(6)
2000 年 6 月 22 日 不论对 Microsoft 还是对整个 IT 业界都将成为值得纪念的一天
这一天 微软公司正式推出了其下一代计算计划 Microsoft.NET(以下简称.NET)
这项计划将使微软现有的软件在 Web 时代不仅适用于传统的 PC 而且也能够满足目前
呈强劲增长势头的新设备 诸如蜂窝电话以及个人数字助理 Personal Digital Assistant,
PDA 等的需要 微软还计划通过创建新的工具来吸引软件开发人员和合作伙伴对
Microsoft.NET 的认同 并且开发出其他基于 Internet 的服务
那么 你是否想知道 究竟什么是.NET?
请听听微软官员的声音 因特网的革命 从微软的角度来讲 我们就是要
建设一个平台来创建并且支持新一代的应用 我们必须有一套通用系统服务来支
持这样的操作 这种观点就说明 我们还有下一个层次的发展 也就是说因特网下一
步的发展 它将使因特网的作用远远超越展现一个网站
.NET 首先是一个开发平台 它定义了一种公用语言子集 Common Language
Subset CLS ,这是一种为符合其规范的语言与类库之间提供无缝集成的混合语 .NET
统一了编程类库 提供了对下一代网络通信标准 可扩展标记语言 Extensible Markup
终端的支持能力 最为重要的 .NET 将改变因特网的行为方式 软件将变成为服务
与 Microsoft 的其它产品一样 .NET 与 Windows 平台紧密集成 并且与其它微软产品
相比它更进一步 由于其运行库已经与操作系统融合在了一起 从广义上把它称为一
个运行库也不为过
简而言之 .NET 是一种面向网络 支持各种用户终端的开发平台环境 微软的宏
伟目标是让 Microsoft.NET 彻底改变软件的开发方式 发行方式 使用方式等等 并且
不止是针对微软一家 而是面向所有开发商与运营商 .NET 的核心内容之一就是要搭
建第三代因特网平台 这个网络平台将解决网站之间的协同合作问题 从而最大限度
地获取信息 在 .NET 平台上 不同网站之间通过相关的协定联系在一起 网站之间
形成自动交流 协同工作 提供最全面的服务
1.1.2 我们为什么需要.NET
某一天 你出差到外地 在机场租借手机电话 在向该终端插入自己的 IC 卡后
自己的地址簿和计划簿被自动下载 随即它就变成了你个人专用的 PDA 这不是梦境
这是.NET 为我们描绘的一个未来生活的场景
人们的需要总是无法满足 我们不断地问自己 我们还应该有些什么 需求推
动着技术的进步 在二十一世纪 Internet 将成为商业活动的主要场所 B2B B2C 等
电子商务的运作方式 一对一营销的经营概念将网络的服务功能提高到了前所未有的
程度 微软公司在此时提出.NET 有其深远的战略考虑
改革商务模型 微软公司感觉到只靠销售软件包的商务模型没有什么前途 该公
司打算今后将中心转移到可以在网络上使用“服务”型商务 这样 首要的问题就是解
决网络上用来开发并执行“服务”的平台 这就是 Microsoft.NET
提高软件开发生产效率 并且试图使应用软件的发布更为容易 再也不想因为 DLL
版本不同而烦恼 希望不用重新启动电脑就能够安装应用软件
改进用户界面 并能支持多种用户终端 用户界面演进的结果包括两方面的内容
一是完成传统的 PC 界面与基于 XML 的浏览器界面间的过渡 二是对自然语言和语音
识别的支持 从而使用户与各种终端之间的沟通更加透明 真正达到网络互连的 3A
Anywhere Anytime Any device
今天 许多的人时常问 除了上网看新闻 我们究竟还能干什么 这是因为今
天的互联网与旧式的大型计算机的工作模式还有许多相似之处 信息被储存在中央服
务器内 而用户的所有操作都要依靠它们 让不同的网址之间相互传递有意义的信息
或者合作提供更广泛和更深层次的服务 还是一件十分困难的事
现代人时常有一种困惑 感觉到如今生活在技术与机器架构的丛林中 我们在努
力地去适应机器 适应技术 而不是机器和技术适应人类 科技以人为本还只是一个
美好的愿望 这是因为我们还不能将控制信息的权利交给那些需要信息的人们 .NET
开发开销 而.NET Framework 的出现使得一切问题都迎刃而解 实际上 在.NET
Framework 中 所有的编程语言 从相对简单的 JScript 到复杂的 C++语言 一律是等
Framework 框架 是开发人员对编程语言命令集的称呼 .Net 框架的意义就在
于只用统一的命令集支持任何的编程语言 正如微软 Web 服务中心的成组产品经理 John
Montgomery 所说 只需简单地一用 .NET 框架便可消除各种异类框架之间的差异
将它们合并为一个整体 .NET 的作用不仅仅是将开发人员从必须掌握多种框架的束缚
中解脱出来 通过创建跨编程语言的公共 API 集 .NET 框架可提供强大的跨语言继承
性 错误处理和调试功能 现在 开发人员可以自由地选择他们喜欢的编程语言 .NET
平台欢迎所有人的垂顾 ”.NET 将使编程人员梦想的语言互用性变成为近在眼前的现
实 想想看 一个在 Visual Basic VB 中定义的类能够在另一种与它完全不同的语言
环境中使用 调试 甚至继承 这是多么令人兴奋的事情
.NET 框架是.NET 平台的基础架构 其强大功能来自于公共语言运行时 Common
Language Runtime,CLR 将在第二章中进行详细的解释 环境和类库 CLR 和类库 包
括 Windows Forms ADO.NET 和 ASP.NET 紧密结合在一起 提供了不同系统之间
交叉与综合的解决方案和服务 .NET 框架创造了一个完全可操控的 安全的和特性丰
富的应用执行环境 这不但使得应用程序的开发与发布更加简单 并且成就了众多种
类语言间的无缝集成
1.2.2 面向.Net 的全新开发工具 C#
在最近的一段时间里 C 和 C++一直是最有生命力的程序设计语言 这两种语言
为程序员提供了丰富的功能 高度的灵活性和强大的底层控制能力 而这一切都不得
不在效率上作出不同程度的牺牲 如果你使用过包括 C 和 C++在内的多种程序设计语
言 相信你会深刻体会到它们之间的区别 比如与 Visual Basic 相比 Visual C++程序
员为实现同样的功能就要花费更长的开发周期 由于 C 和 C++即为我们带来了高度的
灵活性 又使我们必须要忍受学习的艰苦和开发的长期性 许多 C 和 C++程序员一直
在寻求一种新的语言 以图在开发能力和效率之间取得更好的平衡
今天 人们改进 开发出了许多语言以提高软件生产率 但这些或多或少都以牺
牲 C 和 C++程序员所需要的灵活性为代价 这样的解决方案在程序员身上套上了太多
的枷锁 限制了他们能力的发挥 它们不能很好地与原有的系统兼容 更为令人头痛
的是 它们并不总是与当前的 Web 应用结合得很好
理想的解决方案 是将快速的应用开发与对底层平台所有功能的访问紧密结合在
不但如此 C#还能为 C++程序员提供快捷的开发方式 又没有丢掉 C 和 C++的基
本特征 强大的控制能力 C#与 C 和 C++有着很大程度上的相似性 熟悉 C 和 C++
的开发人员很快就能精通 C#
现它是那样的熟悉 即使你是一位新手 C#也不会给你带来任何其它的麻烦 快速应
用程序开发 Rapid Application Development RAD 的思想与简洁的语法将会使你迅
速成为一名熟练的开发人员
正如前文所述 C#是专门为.NET 应用而开发出的语言 这从根本上保证了 C#
与.NET 框架的完美结合 在.NET 运行库的支持下 .NET 框架的各种优点在 C#中表现
得淋漓尽致 让我们先来看看 C#的一些突出的特点 相信在以后的学习过程中 你将
会深深体会到 # SHARP 的真正含义
简洁的语法
精心地面向对象设计
与 Web 的紧密结合
完整的安全性与错误处理
版本处理技术
灵活性与兼容性
1.3.1 简洁的语法
请原谅 虽然我们一再强调学习本书不需要任何的编程基础 但在这里还不得不
提到 C++
在缺省的情况下 C#的代码在.NET 框架提供的 可操控 环境下运行 不允许直
接地内存操作 它所带来的最大特色是没有了指针 与此相关的 那些在 C++中被疯
狂使用的操作符 例如 -> 和 ., 已经不再出现 C#只支持一个 . 对
C#用真正的关键字换掉了那些把活动模板库 Active Template Library ALT 和
COM 搞 得 乱 糟 糟 的 伪 关 键 字 , 如 OLE_COLOR BOOL VARIANT_BOOL
DISPID_XXXXX 等等 每种 C#类型在.NET 类库中都有了新名字
语法中的冗余是 C++中的常见的问题 比如 const”和 #define 各种各样的字
符类型等等 C#对此进行了简化 只保留了常见的形式 而别的冗余形式从它的语法
结构中被清除了出去
1.3.2 精心地面向对象设计
也许你会说 从 Smaltalk 开始 面向对象的话题就始终缠绕着任何一种现代程序
设计语言 的确 C#具有面向对象的语言所应有的一切特性 封装 继承与多态 这
并不出奇 然而 通过精心地面向对象设计 从高级商业对象到系统级应用 C#是建
造广泛组件的绝对选择
在 C#的类型系统中 每种类型都可以看作一个对象 C#提供了一个叫做装箱
boxing 与拆箱 unboxing 的机制来完成这种操作 而不给使用者带来麻烦 这在
以后的章节中将进行更为详细的介绍
C#只允许单继承 即一个类不会有多个基类 从而避免了类型定义的混乱 在后
面的学习中你很快会发现 C#中没有了全局函数 没有了全局变量 也没有了全局常
数 一切的一切 都必须封装在一个类之中 你的代码将具有更好的可读性 并且减
少了发生命名冲突的可能
整个 C#的类模型是建立在.NET 虚拟对象系统 Visual Object System VOS 的基
础之上 其对象模型是.NET 基础架构的一部分 而不再是其本身的组成成分 在下面
将会谈到 这样做的另一个好处是兼容性
借助于从 VB 中得来的丰富的 RAD 经验 C#具备了良好的开发环境 结合自身强
大的面向对象功能 C#使得开发人员的生产效率得到极大的提高 对于公司而言 软
件开发周期的缩短将能使它们更好地应付网络经济的竞争 在功能与效率的杠杆上人
们终于找到了支点
1.3.3 与 Web 的紧密结合
.NET 中新的应用程序开发模型意味着越来越多的解决方案需要与 Web 标准相统
一 例如超文本标记语言 Hypertext Markup Language HTML 和 XML 由于历史
的原因 现存的一些开发工具不能与 Web 紧密地结合 SOAP 的使用使得 C#克服了这
一缺陷 大规模深层次的分布式开发从此成为可能
由于有了 Web 服务框架的帮助 对程序员来说 网络服务看起来就像是 C#的本地
对象 程序员们能够利用他们已有的面向对象的知识与技巧开发 Web 服务 仅需要使
用简单的 C#语言结构 C#组件将能够方便地为 Web 服务 并允许它们通过 Internet 被
运行在任何操作系统上的任何语言所调用 举个例子 XML 已经成为网络中数据结构
传送的标准 为了提高效率 C#允许直接将 XML 数据映射成为结构 这样就可以有
.NET 运行库提供了代码访问安全特性 它允许管理员和用户根据代码的 ID 来配
置安全等级 在缺省情况下 从 Internet 和 Intranet 下载的代码都不允许访问任何本地
文件和资源 比方说 一个在网络上的共享目录中运行的程序 如果它要访问本地的
一些资源 那么异常将被触发 它将会无情地被异常扔出去 若拷贝到本地硬盘上运
行则一切正常 内存管理中的垃圾收集机制减轻了开发人员对内存管理的负担 .NET
平台提供的垃圾收集器 Garbage Colection GC 将负责资源的释放与对象撤销时的
内存清理工作
变量是类型安全的 C#中不能使用未初始化的变量 对象的成员变量由编译器负
责将其置为零 当局部变量未经初始化而被使用时 编译器将做出提醒 C#不支持不
安全的指向 不能将整数指向引用类型 例如对象 当进行下行指向时 C#将自动验
证指向的有效性 C#中提供了边界检查与溢出检查功能
1.3.5 版本处理技术
C#提供内置的版本支持来减少开发费用 使用 C#将会使开发人员更加轻易地开发
和维护各种商业应用
升级软件系统中的组件 模块 是一件容易产生错误的工作 在代码修改过程中
可能对现存的软件产生影响 很有可能导致程序的崩溃 为了帮助开发人员处理这些
问题 C#在语言中内置了版本控制功能 例如 函数重载必须被显式地声明 而不会
像在 C++或 Java 中经常发生的那样不经意地被进行 这可以防止代码级错误和保留版
本化的特性 另一个相关的特性是接口和接口继承的支持 这些特性可以保证复杂的
软件可以被方便地开发和升级
1.3.6 灵活性和兼容性
在简化语法的同时 C#并没有失去灵活性 尽管它不是一种无限制的语言 比如
它不能用来开发硬件驱动程序 在默认的状态下没有指针等等 但是 在学习过程中
你将发现 它仍然是那样的灵巧
如果需要 C#允许你将某些类或者类的某些方法声明为非安全的 这样一来 你
DLL 的任何入口点都可以在程序中进行访问 C#遵守.NET 公用语言规范 Common
Language Specification CLS 从而保证了 C#组件与其它语言组件间的互操作性 元
数据 Metadata 概念的引入既保证了兼容性 又实现了类型安全
公用语言规范 Common Language Specification CLS 是 CLR 定义的语言特性
集合 主要用来解决互操作问题 如果一个类库遵守 CLS 那么同样遵守 CLS 规范的
其它编程语言将能够使用它的外部可见项 详细的内容见本章第二节
2.1.4 虚拟执行系统
虚拟执行系统 Visual Execution System VES 是 VOS 的实现 它用来驱动运行
环境 元数据的生成与使用 公用语言规范的满足性检查以及应用程序执行过程中的
内存管理均由它来完成 具体说来 VES 主要完成以下功能
装入中间代码
使用 JIT 将中间代码转换为本地码
装入元数据
代码管理服务 包括垃圾收集器和异常处理
定制与调试服务
线程和环境管理
当你完成并编译你的代码时 编译器将它转换为微软中间语言 Microsoft
Intermediate Language MSIL 同时产生元数据 当你要执行你的代码时 这种中间
语言被即时 Just In Time JIT 编译器编译成为本地代码 如果安全策略需要的代码
是类型安全的 通常情况下都是如此 JIT 编译器将在编译进程中对中间语言进行
类型检查 一旦失败 在代码执行中将会触发异常
2.2.3 CLR 的突出特色
跨语言集成的能力
CLR 包含了一个丰富的语言特性集 保证了它与各种程序设计语言的兼容性 这
一特性集即公用语言规范 稍后将对其进行详细说明
内存管理自动化
在执行过程中管理应用程序的资源是一项单调而困难的工作 它会将你的注意力
从你本应解决的问题中引开 而垃圾收集机制完全解决了程序员在编程过程中头痛的
问题 跟踪内存的使用 并知道何时将它们释放
在面向对象的环境中 每种类型都标识了对你的应用有用的某种资源 为了使用
这些资源 你需要为类型分配内存 在应用中 访问一种资源要通过以下步骤
1 为类型分配内存
2 初始化内存 设置资源的初始状态并使其可用
3 通过访问该类型的实例成员来访问资源
4 卸下将被清除的资源状态
5 释放内存
这一看似简单的过程在实际的编程中是产生程序错误的主要来源之一 更可怕的
是 内存中的错误往往导致不可预见的结果 如果你有过编程的经验 想想看 有多
少次你的程序因为内存访问错误而崩溃
CLR 要求所有的资源从可操控的堆 注 在此指一种内存结构 中分配 当一个
进程被初始化后 CLR 保留了一个未被分配的地址空间 这一区域叫做可操控堆 在
堆中保持了指向下一个将被分配给对象的堆地址的指针 NEXT 初始状态下 该指
针是保留地址空间的基地址 一个应用使用新的操作产生对象 此操作首先检查新对
象需要字节的大小是否会超出保留空间 如果对象大小合适 指向下一个地址的指针
将指向堆中的这个对象 该对象的构造器被调用 新的操作返回对象的地址
当一个应用请求建立一个对象时 地址空间可能不够大 堆将发现这一点 通过
将新对象的大小与 NEXT 指针相加 并与堆的大小进行比较 这时垃圾收集器就将被
调用 在这里 CLR 引入了 代 的概念 代 指堆中对象产生的先后 这样 垃圾
收集器在将发生溢出时回收属于特定的 代 的对象 而不是回收堆中的所有对象
6 即时编译
在各种语言的编译器对源代码进行编译之后 在 CLR 环境中产生的是中间代码 出
于兼容性与跨语言集成的考虑 其内容虽然有效 但在转化为本地代码之前它本身是
不可执行的 这就是 JIT 编译器需要完成的工作
这里需要说明一个问题 为什么要即时编译 而不是一次性的将中间代码文件进
行编译 答案很简单 原因在于效率 在大型的应用中 你很少会用到程序的全部功
能 这种边执行边编译的措施比一次性的完全编译效率更高
在 Windows 平台中 CLR 带有三个不同的 JIT 编译器
7 缺省的编译器 主编译器 由它进行数据流分析并输出经过优化的本地代
码 所有的中间代码指令均可被它处理
8 PREJIT 它建立在主 JIT 编译器之上 其运行方式更像一个传统的编译器
每当一个.NET 组件被安装时它就运行
9 ECONOJIT 在并不充分优化的前提下 它能够快速完成 IL 代码到本地码的
转换 编译速度与运行速度都很快
为了配合编译器的工作 在.NET SDK 的安装路径下的/bin 目录中有一个负责管理
JIT 的应用程序 jitman.exe 具体的使用参见联机帮助
10 解决版本与发布问题
在当前以组件为基础的系统中 开发人员和用户对于软件版本和发布中存在的问
题已经十分熟悉了 当我们安装一个新的应用之后 我们很可能发现原本正常的某个
应用程序奇怪地停止了工作 绝大多数开发人员将时间花在了确保所有注册表入口的
一致性 以便激活 COM 类上 这就是所谓的 DLL 地狱
.NET 平台通过使用集合来解决这一问题 在这里 集合 是一个专有名词 指
类型与资源的发布单元 在很大程度上它等同于今天的 DLL 正像.NET 用元数据描述
类型一样 它也用元数据描述包含类型的集合 通常说来 集合由四个部分组成 集
合的元数据 集合的内部清单 元数据描述的类型 实现类型的中间语言代码和一组
资源 在一个集合中 以上四个部分并不是都必须存在 但是 集合中必须包含类型
或资源 这样集合才有意义
在.NET 中一个基本的设计方针是使用孤立的组件 一个孤立的集合的含义是指一
个集合只能被一个应用所访问 在一台机器上 它不被多个应用共享 也不会受其它
应用程序对系统的更改的影响 孤立 赋予了开发人员在自己的程序中对代码的完全
过程中涉及的数据类型和语言特性对所有的语言来说是公共的 为了这个目的 公用
运行时环境标识了一组语言特征的集合 称为公用语言规范 CLS 如果你的组件在
应用程序接口 Application Program Interface 中仅使用 CLS 的特征语言 包括子类
那么该组件能够被任何支持CLS的语言所编译的组件访问 所有支持CLS并仅使用CLS
中的语言特征的组件被称为符合 CLS 的组件
设计公用语言规范时遇到的一个最主要的挑战是选择适当的语言特性子集的大
小 它应具有完全的表达能力 又应足够小 使得所有的语言能够容纳它 由于 CLS
是关于语言互用性的规范 它的规则仅应用于外部可见的条目中 CLS 假设语言间的
互操作性仅在语言集合的边界发生交叉时才是重要的 也就是说 在单一的语言集中
对于编程技术的使用没有任何限制 CLS 的规则仅作用于在定义它们的语言集合之外
仍然可见的项上 这样就大大缩小了 CLS 的范围 减轻了系统的负担
在 CLS 中是用 System.CLSCompliantAtribute 类来标识一个集合或者类是否是符合
CLS 规范的 在 System.CLSCompliantAtribute 的构造器中有一个 Boolean 型的返回值
代表了与之相关联的项是否符合 CLS 规范
或 MSIL 格式的文件转换为带有内容清单的集合
3. Windows Forms ActiveX Control Importer (aximp.exe) 完成 COM 类库中类型定
义的转换 使 ActiveX 控件能够在 Windows 窗口控件上使用
4. Code Access Security Policy Utility (caspol.exe) 在用户与机器水平上修改安全策
<> page begin====================
5. Software Publisher Certificate Test Utility (Cert2spc.exe) 用于从 X.509 证书中生
成软件出版证明书 SPC
6. Certificate Manager Utility (certmgr.exe) 管理证书 证书信任列表和证书回收列
7. Certificate Verification Utility (chktrust.exe) 检查证书签名的合法性
8. Runtime Debugger (cordbg.exe) 运行时调试器 是一个命令行程序 帮助开发
人员发现和调试基于 CLR 的应用程序中的错误
9. Global Assembly Cache Utility (gacutil.exe) 允许你浏览与操纵全局集合缓存中
内容的命令行程序
10. MSIL Assembler (ilasm.exe) MSIL 汇编程序 协助设计与实现 MSIL 生成器的
11. MSIL Disassembler (ildasm.exe) MSIL 反汇编程序 与 ilasm.exe 共同使用 将
由 MSIL 代码产生的 Portable Executable 文件转换为文本文件
12. Instaler Utility (instalutil.exe) 用来安装与卸载服务资源
13. License Compiler (lc.exe) 产生可包含在可执行二进制文件中的二进制资源文
14. Certificate Creation Utility (makecert.exe) 生成 X.509 证书与用于数字签名的公
用与私有密钥
15. Permissions View Utility(permview.exe) 通过一个集合浏览许可集的工具
16. Peverify Utility(peverify.exe) 检查中间语言与元数据是否符合类型安全认证要
17. Assembly Registration Tool(RegAsm.exe) 读取集合中的元数据并加上必要注册
表入口信息 使得 COM 客户透明地建立 CLR 的类
18. Services Registration Tool (RegSvcs.exe) 服务注册工具 它完成执行以下功能
装载与注册一个集合 为现有的 COM+1.0 应用生成 注册与安装类库
19. Resource File Generator Utility(ResGen.exe) 资源文件生成器 用来将文本文件
和 XML 格式的资源文件转换为 CLR 的二进制文件
20. Secutil Utility(SecUtil.exe) 使得从集合中抽取的安全信息更加容易
21. Set Registry Utility(setreg.exe) 改变注册表中公开密钥密码系统的设置
22. Assembly Cache Viewer(shfusion.dl) 允许你使用 Windows 浏览器察看与操作
全局集合缓存中的内容
23. File Signing Utility(signcode.exe) 为 PE (portable executable)文件做标记 赋予
程序员在组件安全约束的基础上对安全性有更多的控制权
24. Shared Name Utility(Sn.exe) 帮助程序员以共享名称建立集合
25. Soapsuds Utility(SoapSuds.exe) 使用远程技术帮助你编译与 Web 服务相通信的
26. Isolated Storage Utility(storeadm.exe) 一种用来管理隔离存储区的命令行工具
27. Type Library Exporter(TlbExp.exe) 命令行程序 生成由集合名称指示的包含集
合中公共类型定义的类库
<> page begin====================
28. Type Library Importer (TlbImp.exe) 将 COM 类库中的类型定义转换为在 CLR
中与元数据格式一致的类型定义
29. Web Service Utility(WebServiceUtil.exe) 帮助建立 ASP.NET Web 服务与客户
30. Windows Forms Class Viewer(wincv.exe) 能够在某种查找模式下快速查找类或
者类序列的信息
31. Windows Forms Designer Test Container(windes.exe) 允许开发人员测试开发出
的视窗窗体控件在设计时的行为
32. XML Schema Definition Tool(xsd.exe) XML 计划定义工具
可以这么说 与用户没有任何交互的应用程序根本没有任何用处 病毒和黑客当
然除外 然而即使是病毒程序的作者 也常常喜欢在自己得逞之后炫耀一番 学习任
何一门语言 绝大多数情况下人们都是从输入输出开始的
第一个程序总是非常简单的 我们让用户通过键盘输入自己的名字 然后程序在
屏幕上打印一条欢迎信息 程序的代码是这样的
程序 序清 清单单 3-1
using System;
class Welcome
static void Main() {
Console.WriteLine("Please enter your name:");
Console.ReadLine();
Console.WriteLine("Welcome to you!");
您可以在任意一种编辑软件中完成上述代码的编写 然后把文件存盘 文件名叫
做 Welcome.cs 典型的 C#源文件通常都是以 .cs 作为文件的扩展名
using System 表示导入名字空间 高级语言总是依赖于许多系统预定义的元素 如果
您是 C 或 C++的程序员 那么您一定对使用#include 之类的语句来导入其它 C 或 C++
源文件再熟悉不过了 C#中的含义与此类似 用于导入预定义的元素 这样在自己的
程序中就可以自由地使用这些元素
如果没有导入名字空间的话 我们该怎么办呢 程序还能保持正确吗 答案是肯
定的 那样的话 我们就必须把代码改写成下面的样子
程序 序清 清单 单 3-2
class Welcome
static void Main() {
System.Console.WriteLine("Please enter your name:");
System.Console.ReadLine();
System.Console.WriteLine("Welcome to you!");
也就是说 在每个 Console 前加上一个前缀 System. 这个小原点 . 表示 Console
是作为 System 的成员而存在的 C#中抛弃了 C 和 C++中繁杂且极易出错的操作符像 :
和 -> 等 C#中的复合名字一律通过 . 来连接
System 是.Net 平台框架提供的最基本的名字空间之一 有关名字空间的详细使用
方法我们将放在第十七章中详细介绍 在这里 只要我们学会怎样导入名字空间就足
3.2.2 类和类的方法
让我们从写第一个程序时就记住 每个东西都必须属于一个类 如果您是 C 或 C++
的程序员 请暂时忘掉那些全局变量
在程序的第二行 class Welcome 声明了一个类 类的名字叫做 Welcome 这个程
序为我们所作的事情就是依靠它来完成的
和 C C++中一样 源代码块被包含在一对大括号 { 和 } 中 每一个右括号
} 总是和它前面离它最近的一个左括号 { 相配套 如果左括号 { 和右括号 }
没有全部配套 那程序就是一个错误的程序
static void Main()表示类 Welcome 中的一个方法 方法总是为我们完成某件工作的
注意 在 C#程序中 程序的执行总是从 Main()方法开始的 一个程序中不允许出
上面的代码中 类 Console 为我们展现了两个最基本的方法 WriteLine 和 ReadLine
Console.ReadLine 表示接受输入设备输入 Console. WriteLine 则用于在输出设备上输
我们再为读者介绍 Console 中用于输入输出的另两个方法 Read 和 Write 它们和
ReadLine 与 WriteLine 的不同之处在于 ReadLine 和 WriteLine 执行时相当在显示时多
加了一个回车键 而使用 Read 和 Write 时则光标不会自动转移到下一行
让我们再对例子程序进行扩展 使得用户的输入对输出产生作用
程序序清 清单 单 3-3
using System;
class Welcome
static void Main() {
Console.WriteLine("Please enter your name:");
string name = Console.ReadLine();
Console.WriteLine("Welcome to you,{0}!",name);
我们用到了 string name = Console.ReadLine()这条语句 其中 string name 表示声明一
个字符串类型的变量 name 系统定义的 Console 类提供的方法 ReadLine()的返回值类型
为 string 所以 这句话表示从输入设备读取一个字符串 并把读取的值赋予变量 name
再来看一下程序的最后一条输出语句
Console.WriteLine("Welcome to you,{0}!",name);
这条语句表示在屏幕上对输出的字符串进行格式化 其中表示用方法的第二个参
数来替代格式化后字符串相应的位置 对字符串进行格式化的参数可以是一个字符串
也可以是一个字符 或者是一个整数 等等 采用这种方式最多可以格式化三个变量
int x = 3;
string name1 = “Mike”;
提供的类十分类似 C#中的 string 类型是一个引用类型 引用类型在第四章中我们有
详细说明 为标准字符集 利用 string 可以方便地对字符串进行连接 截断等操作
string s = “Good” + “Morning”;
char x = s[3];
例子演示了字符串 s 由两个字符串 Good 和 Morning 相加得到 字符串还可
以通过下标进行索引 得到一个字符 上面的例子中字符 x 的值为 o
所以 源程序 3-4 和源程序 3-3 的作用没什么区别
程序 序清 清单 单 3-4
using System;
class Welcome
static void Main() {
Console.WriteLine("Please enter your name:");
string message = “Welcome to you “ + Console.ReadLine();
Console.WriteLine(mesage);
如果您的电脑上安装了 Visual Studio .Net 则可以在集成开发环境 Integrated
Developer Environment IDE 中直接选择快捷键或菜单命令 编译并执行源文件
如果您不具备这个条件 那么您至少需要安装 Microsoft .Net Framework SDK 这
样才能够不妨碍您在本书中继续学习 C#语言 实际上 .Net 平台内置了 C#的编译器
下面让我们使用这个微软提供的命令行编译器对我们的程序进行编译
启动一个命令行提示符 在屏幕上输入一行命令
csc welcome.cs
编译这个过程 而在 C 和 C++中要经过编译和链接两个阶段 换而言之 C#源文件并不
被编译为目标文件 .obj 而是直接生成可执行文件 .exe 或动态链接库 .dll
C#编译器中不需要包含链接器
我们可以灵活地使用 .Net 平台提供的命令行编译器的不同选项 选择不同的编译
方式 从而灵活地对编译进行控制
例如 如果我们希望对源文件 Welcome.cs 进行编译 生成名为 MyWelcome.exe 的
可执行文件 我们可以采用这样的命令
csc/out: MyWelcome.exe Welcome.cs
如果我们并不需要一个可执行文件 而仅仅是希望简单地检查源文件中是否存在
语法错误 则命令可以写成
csc/nooutput: Welcome.cs
如果不知道各个选项的具体含义 可以通过求助来获得
csc/?
为方便读者 我们在表 3-1 中按字母排序的顺序列出了命令行编译器 csc 常用的参
数及其用途 更详细的信息请参阅 C#联机帮助文档
表 3-1 命令行编译器选项
选项 作用
@ 指定响应文件
/? 列出编译命令选项
/addmodule 指定一个或多个模块作为装配的一部分
/baseaddress 指定载入动态链接库的首选地址
/bugreport 生成一个报告文件 其中包含程序 Bug 的详细信息
/checked 指定算术运算的溢出是否会导致程序在运行时抛出一个异常
/codepage 指定编译的所有源文件所使用的代码页
/debug 给出调试信息
/define 定义预处理程序的符号
/doc 由文件注释生成 XML 文件
/fulpaths 指定输出的绝对路径
/help 列出编译命令选项
/incremental 允许对源文件进行递增式编译
/linkresource 在装配时链接指定的 NET 资源
注释的方式和 ++没有区别 每一行中双斜杠 后面的内容 以及在
分割符 和 之间的内容都将被编译器忽略
这样 我们就可以采用 进行单行注释 采用分割符 和
进行多行注释 让我们对 Welcome 程序加上注释
程序序清 清单 单 3-5
源文件 welcome.cs
/ 说明 这里是我的第一个
上面的注释似乎有些小题大做 但它毕竟说明了 中注释的使用方法
下面是对 程序进行注释时要注意的两个问题
首先 避免在 之后的单行注解中使用反斜杠符号 \ 因为反斜杠符号 \
在 中是一个续行符 这样做往往会导致你所不希望的结果出现 例如 当你写了
类似于下面的代码
Console.WriteLine(“The result is:{0}” , / \
150 );
在编译这段代码时 表示逻辑上同一行剩余的所有文字被作为注释看待
而续行符 \ 则将这一行同下一行连接起来 那么第二行也被作为注释的一部分 这
时编译器找不到与第一行的左括号 相匹配的右括号 因此编译出错
其次 分割符 和 之间的注释不能有嵌套注释 这是因为
编译器从遇到第一个分割符 开始 将忽略下一个 直到遇上下一个与
之匹配的分割符 才认为注释结束 这样编译器就会对多余的 报告
1.1 我该如何决定使用哪种整数类型? 1
1.2 为什么不精确定义标准类型的大小? 2
1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16和int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 2
1.4 新的64位机上的64位类型是什么样的? 3
指针声明 3
1.5 这样的声明有什么问题?char *p1, p2; 我在使用p2的时候报错了。 3
1.6 我想声明一个指针,并为它分配一些空间,但却不行。这样的代码有什么问题?char *p; *p=malloc(10); 4
声明风格 4
1.7 怎样声明和定义全局变量和函数最好? 4
1.8 如何在C中实现不透明(抽象)数据类型? 5
1.9 如何生成“半全局变量”,就是那种只能被部分源文件中的部分函数访问的变量? 5
存储类型 6
1.10 同一个静态(static)函数或变量的所有声明都必须包含static存储类型吗? 6
1.11 extern在函数声明中是什么意思? 6
1.12 关键字auto到底有什么用途? 7
类型定义(typedef) 7
1.13 对于用户定义类型,typedef 和#define有什么区别? 7
1.14 我似乎不能成功定义一个链表。我试过typedef struct{char *item; NODEPTR next;}* NODEPTR; 但是编译器报了错误信息。难道在C语言中结构不能包含指向自己的指针吗? 7
1.15 如何定义一对相互引用的结构? 9
1.16 Struct{ } x1;和typedef struct{ } x2; 这两个声明有什么区别? 10
1.17 “typedef int(*funcptr)();”是什么意思? 10
const 限定词 10
1.18 我有这样一组声明:typedef char *charp; const charp p; 为什么是p而不是它指向的字符为const? 10
1.19 为什么不能像下面这样在初始式和数组维度值中使用const值?const int n=5; int a[n]; 10
1.20 const char *p、char const *p和char *const p有什么区别? 10
复杂的声明 11
1.21 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组? 11
1.22 如何声明返回指向同类型函数的指针的函数?我在设计一个状态机,用函数表示每种状态,每个函数都会返回一个指向下一个状态的函数的指针。可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数……,如此往复,以至无穷。 12
数组大小 13
1.23 能否声明和传入数组大小一致的局部数组,或者由其他参数指定大小的参数数组? 13
1.24 我在一个文件中定义了一个extern数组,然后在另一个文件中使用,为什么sizeof取不到数组的大小? 13
声明问题 14
1.25 函数只定义了一次,调用了一次,但编译器提示非法重声明了。 14
*1.26 main的正确定义是什么?void main正确吗? 15
1.27 我的编译器总在报函数原型不匹配的错误,可我觉得没什么问题。这是为什么? 15
1.28 文件中的第一个声明就报出奇怪的语法错误,可我看没什么问题。这是为什么? 15
1.29 为什么我的编译器不允许我定义大数组,如double array[256][256]? 15
命名空间 15
1.30 如何判断哪些标识符可以使用,哪些被保留了? 15
初始化 18
1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 18
1.32 下面的代码为什么不能编译? intf(){char a[]=Hello, world!;} 18
*1.33 下面的初始化有什么问题?编译器提示“invalid initializers ”或其他信息。char *p=malloc(10); 19
1.34 char a[]= string literal;和char *p=string literal; 初始化有什么区别?当我向p[i] 赋值的时候,我的程序崩溃了。 19
1.35 char a{[3]}= abc; 是否合法? 20
1.36 我总算弄清楚函数指针的声明方法了,但怎样才能初始化呢? 20
1.37 能够初始化联合吗? 20
第2章 结构、联合和枚举 21
结构声明 21
2.1 struct x1{ };和typedef struct{ }x2; 有什么不同? 21
2.2 这样的代码为什么不对?struct x{ }; x thestruct; 22
2.3 结构可以包含指向自己的指针吗? 22
2.4 在C语言中用什么方法实现抽象数据类型最好? 22
*2.5 在C语言中是否有模拟继承等面向对象程序设计特性的好方法? 22
2.6 为什么声明extern f(struct x *p); 给我报了一个晦涩难懂的警告信息? 23
2.7 我遇到这样声明结构的代码:struct name {int namelen; char namestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素,namelen记录了元素个数。它是怎样工作的?这样是合法的和可移植的吗? 23
2.8 我听说结构可以赋给变量也可以对函数传入和传出。为什么K&R1却明确说明不能这样做? 25
2.9 为什么不能用内建的==和!=操作符比较结构? 26
2.10 结构传递和返回是如何实现的? 26
2.11 如何向接受结构参数的函数传入常量值?怎样创建无名的中间的常量结构值? 26
2.12 怎样从/向数据文件读/写结构? 27
结构填充 27
2.13 为什么我的编译器在结构中留下了空洞?这导致空间浪费而且无法与外部数据文件进行“二进制”读写。能否关掉填充,或者控制结构域的对齐方式? 27
2.14 为什么sizeof返回的值大于结构大小的期望值,是不是尾部有填充? 28
2.15 如何确定域在结构中的字节偏移量? 28
2.16 怎样在运行时用名字访问结构中的域? 29
2.17 C语言中有和Pascal的with等价的语句吗? 29
2.18 既然数组名可以用作数组的基地址,为什么对结构不能这样? 29
2.19 程序运行正确,但退出时却“core dump ”(核心转储)了,怎么回事? 29
联合 30
2.20 结构和联合有什么区别? 30
2.21 有办法初始化联合吗? 30
2.22 有没有一种自动方法来跟踪联合的哪个域在使用? 30
枚举 31
2.23 枚举和一组预处理的#define有什么不同? 31
2.24 枚举可移植吗? 31
2.25 有什么显示枚举值符号的容易方法吗? 31
位域 31
2.26 一些结构声明中的这些冒号和数字是什么意思? 31
2.27 为什么人们那么喜欢用显式的掩码和位操作而不直接声明位域? 32
第3章 表达式 33
求值顺序 33
3.1 为什么这样的代码不行?a[i]= i++; 33
3.2 使用我的编译器,下面的代码int i= 7; printf(%d\n, i++ * i++); 打印出49。不管按什么顺序计算,难道不该是56吗? 33
3.3 对于代码int i=3; i=i++; 不同编译器给出不同的i值,有的为3,有的为4,哪个是正确的? 34
*3.4 有这样一个巧妙的表达式:a^= b^= a^= b; 它不需要临时变量就可以交换a和b的值。 34
3.5 可否用显式括号来强制执行我所需要的计算顺序并控制相关的副作用?就算括号不行,操作符优先级是否能够控制计算顺序呢? 35
3.6 可是&&和||操作符呢?我看到过类似while((c = getchar()) != EOF && c != '\n')的代码…… 35
3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个表达式的结果,则右边的表达式不会被求值? 36
3.8 为什么表达式printf(%d %d, f1(), f2()); 先调用了f2?我觉得逗号表达式应该确保从左到右的求值顺序。 36
3.9 怎样才能理解复杂表达式并避免写出未定义的表达式?“序列点”是什么? 36
3.10 在a[i] = i++;中,如果不关心a[]的哪一个分量会被写入,这段代码就没有问题,i也的确会增加1,对吗? 38
3.11 人们总是说i=i++的行为是未定义的。可我刚刚在一个ANSI编译器上尝试过,其结果正如我所期望的。 38
3.12 我不想学习那些复杂的规则,怎样才能避免这些未定义的求值顺序问题呢? 38
其他的表达式问题 39
*3.13 ++i和i++有什么区别? 39
3.14 如果我不使用表达式的值,那我应该用i++还是++i来做自增呢? 39
3.15 我要检查一个数是不是在另外两个数之间,为什么if(a b c)不行? 40
3.16 为什么如下的代码不对?int a=1000, b=1000; long int c=a * b; 40
3.17 为什么下面的代码总是给出0?double degC, degF; degC= 5.0 / 9 * (degF - 32); 40
3.18 需要根据条件把一个复杂的表达式赋给两个变量中的一个。可以用下面这样的代码吗?((condition) ? a : b)= complicated_expression; 41
3.19 我有些代码包含这样的表达式。a ? b=c : d 有些编译器可以接受,有些却不能。为什么? 41
保护规则 42
3.20 “semantics of‘’change in ANSI C”的警告是什么意思? 42
3.21 “无符号保护”和“值保护”规则的区别在哪里? 42
第4章 指针 45
基本的指针应用 45
4.1 指针到底有什么好处? 45
4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char *p; *p =malloc(10); 45
4.3 *p++自增p还是p所指向的变量? 46
指针操作 46
4.4 我用指针操作int数组的时候遇到了麻烦。 46
4.5 我有一个char *型指针碰巧指向一些int型变量,我想跳过它们。为什么((int *)p)++; 这样的代码不行? 47
4.6 为什么不能对void *指针进行算术操作? 47
4.7 我有些解析外部结构的代码,但是它却崩溃了,显示出了“unaligned access”(未对齐的访问)的信息。这是什么意思? 47
作为函数参数的指针 47
4.8 我有个函数,它应该接受并初始化一个指针:void f(int *ip){ static int dummy = 5; ip = &dummy;}但是当我如下调用时:int *ip; f(ip); 调用者的指针没有任何变化。 47
4.9 能否用void ** 通用指针作为参数,使函数模拟按引用传递参数? 48
4.10 我有一个函数extern intf(int *); ,它接受指向int型的指针。我怎样用引用方式传入一个常数?调用f(&5);似乎不行。 49
4.11 C语言可以“按引用传参”吗? 50
其他指针问题 50
4.12 我看到了用指针调用函数的不同语法形式。到底怎么回事? 50
4.13 通用指针类型是什么?当我把函数指针赋向void *类型的时候,编译通不过。 51
4.14 怎样在整型和指针之间进行转换?能否暂时把整数放入指针变量中,或者相反? 51
*4.15 我怎样把一个int变量转换为char *型?我试了类型转换,但是不行。 52
第5章 空指针 53
空指针和空指针常量 53
5.1 臭名昭著的空指针到底是什么? 53
5.2 怎样在程序里获得一个空指针? 54
5.3 用缩写的指针比较“if(p)”检查空指针是否有效?如果空指针的内部表达不是0会怎样? 55
NULL 宏 56
5.4 NULL是什么,它是怎么定义的? 56
5.5 在使用非零位模式作为空指针的内部表示的机器上,NULL 是如何定义的? 56
5.6 如果NULL定义成#define NULL((char *)0) ,不就可以向函数传入不加转换的NULL 了吗? 57
5.7 我的编译器提供的头文件中定义的NULL为0L。为什么? 57
5.8 NULL可以合法地用作函数指针吗? 57
5.9 如果NULL和0作为空指针常量是等价的,那我到底该用哪一个呢? 58
5.10 但是如果NULL的值改变了,比如在使用非零内部空指针的机器上,用NULL(而不是0)
不是更好吗? 58
5.11 我曾经使用过一个编译器,不使用NULL就不能编译。 58
5.12 我用预处理宏#define Nullptr(type)(type *)0帮助创建正确类型的空指针。 59
回顾 59
5.13 这有点奇怪:NULL可以确保是0,但空(null)指针却不一定? 59
5.14 为什么有那么多关于空指针的疑惑?为什么这些问题如此频繁地出现? 60
5.15 有没有什么简单点儿的办法理解所有这些与空指针有关的东西呢? 60
5.16 考虑到有关空指针的所有这些困惑,要求它们的内部表示都必须为0不是更简单吗? 60
5.17 说真的,真有机器用非零空指针吗,或者不同类型用不同的表示? 61
地址0 上到底有什么? 61
5.18 运行时的整数值0转换为指针以后一定是空指针吗? 61
5.19 如何访问位于机器地址0处的中断向量?如果我将指针值设为0,编译器可能会自动将它转换为非零的空指针内部表示。 62
5.20 运行时的“null pointer assignment”错误是什么意思?应该怎样捕捉它? 62
第6章 数组和指针 63
数组和指针的基本关系 63
6.1 我在一个源文件中定义了char a[6],在另一个源文件中声明了extern char *a。为什么不行? 63
6.2 可是我听说char a[]和char *a是等价的。是这样的吗? 63
6.3 那么,在C语言中“指针和数组等价”到底是什么意思? 64
6.4 既然它们这么不同,那为什么作为函数形参的数组和指针声明可以互换呢? 65
数组不能被赋值 66
6.5 为什么不能这样向数组赋值?extern char *getpass(); char str[10]; str=getpass(Enter password:); 66
6.6 既然不能向数组赋值,那这段代码为什么可以呢?int f(char str[]){ if(str[0] == '\0') str=none;…} 66
6.7 如果你不能给它赋值,那么数组如何能成为左值呢? 66
回顾 67
6.8 现实地讲,数组和指针的区别是什么? 67
6.9 有人跟我讲,数组不过是常指针。这样讲准确吗? 67
6.10 我还是很困惑。到底指针是一种数组,还是数组是一种指针? 67
6.11 我看到一些“搞笑”的代码,包含5[abcdef]这样的“表达式”。这为什么是合法的C语言表达式呢? 68
数组的指针 68
6.12 既然数组引用会退化为指针,如果array是数组,那么array和&array又有什么区别呢? 68
6.13 如何声明一个数组的指针? 69
动态数组分配 70
6.14 如何在运行时设定数组的大小?怎样才能避免固定大小的数组? 70
6.15 我如何声明大小和传入的数组一样的局部数组? 70
6.16 如何动态分配多维数组? 71
6.17 有个很好的窍门,如果我这样写:int realarray[10]; int *array = &realarray[-1]; 我就可以把“array”当作下标从1 开始的数组。 72
函数和多维数组 73
6.18 当我向一个接受指针的指针的函数传入二维数组的时候,编译器报错了。 73
6.19 我怎样编写接受编译时宽度未知的二维数组的函数? 74
6.20 我怎样在函数参数传递时混用静态和动态多维数组? 74
数组的大小 75
6.21 当数组是函数的参数时,为什么sizeof不能正确报告数组的大小? 76
6.22 如何在一个文件中判断声明为extern的数组的大小(例如,数组定义和大小在另一个文件中)?sizeof操作符似乎不行。 76
6.23 sizeof返回的大小是以字节计算的,怎样才能判断数组中有多少个元素呢? 76
第7 章 内存分配 77
基本的内存分配问题 77
7.1 为什么这段代码不行?char *answer; printf(Type something:\n); gets(answer); printf(You typed \%s\\n, answer); 77
7.2 我的strcat() 不行。我试了下面的代码:char *s1= Hello,; char *s2= world!; char *s3= strcat(s1, s2);但是我得到了奇怪的结果。 78
7.3 但是strcat的文档说它接受两个char *型参数。我怎么知道(空间)分配的事情呢? 78
*7.4 我刚才试了这样的代码:char *p; strcpy(p, abc);它运行正常。怎么回事?为什么它没有出错? 79
*7.5 一个指针变量分配多少内存? 79
7.6 我使用fgets将文件的所有行读入一个数组,为什么读入的每一行都是最后一行的内容呢? 79
7.7 我有个函数,本该返回一个字符串,但当它返回调用者的时候,返回的字符串却是垃圾信息。
为什么? 80
*7.8 那么返回字符串或其他聚集的正确方法是什么呢? 81
调用malloc 81
7.9 为什么在调用malloc()时报出了“waring: assignment of pointer from integer lacks a cast”? 81
7.10 为什么有些代码小心翼翼地把malloc返回的值转换为分配的指针类型? 81
*7.11 在调用malloc()的时候,错误“不能把void * 转换为int * ”是什么意思? 82
7.12 我看到下面这样的代码:char *p = malloc(strlen(s) + 1); strcpy(p,s); 难道不应该是malloc ((strlen(s) + 1) * sizeof(char)) 吗? 82
7.13 我为malloc写了一个小小的封装函数。它为什么不行? 82
7.14 我想声明一个指针并向它分配一些内存,但是不行。这样的代码有什么问题?char *p; *p = malloc(10); 82
7.15 我如何动态分配数组? 83
7.16 怎样判断还有多少内存? 83
7.17 malloc(0)是返回空指针还是指向0个字节的指针? 83
7.18 我听说有的操作系统在程序使用的时候才真正分配malloc申请的内存。这合法吗? 83
有关malloc 的问题 83
7.19 为什么malloc返回了离谱的指针值?我的确读过问题7.9,而且也在调用之前包含了extern void *malloc();声明。 83
7.20 我用一行这样的代码分配一个巨大的数组,用于数值运算:double *array = malloc (256 *256 *sizeof(double));malloc()并没有返回空指针,但是程序运行得有些奇怪,好像改写了某些内存,或者malloc()并没有分配我申请的那么多内存。为什么? 84
7.21 我的PC机有8兆内存。为什么我只能分配640K左右的内存? 84
7.22 我的应用程序非常依赖数据结构的节点的动态分配,而malloc/free的代价成了瓶颈。我该怎么做? 84
7.23 我的程序总是崩溃,显然发生在malloc内部的某个地方。但是我看不出哪里有问题。是malloc有bug吗? 84
释放内存 85
7.24 动态分配的内存一旦释放之后就不能再使用,是吧? 85
7.25 为什么在调用free()之后指针没有变空?使用(赋值、比较)释放之后的指针有多么不安全? 86
7.26 当我调用malloc()为一个函数的局部指针分配内存时,我还需要用free()显式地释放吗? 86
7.27 我在分配一些结构,它们包含指向其他动态分配的对象的指针。我在释放结构的时候,还需要释放每一个下级指针吗? 86
7.28 我必须在程序退出之前释放分配的所有内存吗? 86
7.29 我有个程序分配了大量的内存,然后又释放了。但是从操作系统看,内存的占用率却并没有变回去。 87
分配内存块的大小 87
7.30 free()怎么知道有多少字节需要释放? 87
7.31 那么我能否查询malloc包,以查明可分配的最大块是多大? 87
7.32 为什么sizeof不能告诉我它所指的内存块的大小? 87
其他分配函数 88
7.33 (像问题6.14中那样)动态分配数组之后,还能改变它的大小吗? 88
7.34 向realloc()的第一个参数传入空指针合法吗?你为什么要这样做? 89
7.35 calloc()和malloc()有什么区别?应该用哪一个?利用calloc 的零填充功能安全吗?free()可以释放calloc()分配的内存吗,还是需要一个cfree()? 90
7.36 alloca是什么?为什么不提倡使用它? 91
第8章 字符和字符串 92
8.1 为什么strcat(string, '!'); 不行? 92
8.2 我想检查一个字符串是否跟某个值匹配。为什么这样不行?if(string == value) 92
8.3 如果我可以写char a[] = Hello, world!; 那为什么不能写char a[14]; a = Hello, world!; 93
8.4 为什么我的strcat 不行?我试了char *s1=Hello,; char *s2=world!; char *s3 =strcat(s1, s2);可得到的结果很奇怪。 93
8.5 char a[]= string literal; 和char *p= string literal; 初始化有什么区别?当我对p[i]赋值的时候,程序崩溃了。 93
8.6 我怎么得到与字符相对应的数字(即ASCII 或其他字符集下的)值?反过来又该怎么做? 94
8.7 C语言有类似其他语言的substr(提取子串)这样的函数吗? 94
8.8 我将用户键入的字符串读入数组,然后再显示出来。当用户键入\n这样的序列时,为什么不能正确处理呢? 94
8.9 我注意到sizeof('a')是2而不是1(即不是sizeof(char)),是不是我的编译器有问题? 94
8.10 我正开始考虑多语言字符集的问题。是否有必要担心sizeof(char)会被定义为2,以便表达16位的字符集呢? 95
第9章 布尔表达式和变量 96
9.1 C语言中布尔值该用什么类型?为什么它不是一个标准类型?我应该用#define或enum定义真值和假值吗? 96
9.2 既然在C 语言中所有的非零值都被看作“真”,那是不是把TRUE 定义为1很危险?如果某个内建的函数或关系操作符“返回”不是1的其他值怎么办? 97
9.3 当p是指针时,if(p)是合法的条件表达式吗? 98
9.4 我该使用像TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 98
9.5 我准备使用的一个第三方头文件定义了自己的TRUE和FALSE,它们跟我已经开发的部分不兼容。我该怎么办? 98
第10章 C预处理器 99
宏定义 99
10.1 我想定义一些函数式的宏,例如:#define square(x)x * x但它们并不总是正确的。为什么? 99
10.2 这里有一些的预处理宏,使用它们,我可以写出更像Pascal的C代码。你觉得怎么样? 100
10.3 怎么写一个交换两个值的通用宏? 101
10.4 书写多语句宏的最好方法是什么? 101
10.5 用typdef和预处理宏生成用户定义类型有什么区别? 102
头文件 102
10.6 我第一次把一个程序分成多个源文件,我不知道该把什么放到.c文件,把什么放到.h文件。(“.h”到底是什么意思?) 102
10.7 可以在一个头文件中包含另一头文件吗? 103
10.8 完整的头文件搜索规则是怎样的? 104
10.9 我在文件的第一个声明就遇到奇怪的语法错误,但是看上去没什么问题。 104
10.10 我使用了来自两个不同的第三方库的头文件,它们都定义了相同的宏,如TRUE、FALSE、Min()和Max()等,但是它们的定义相互冲突,而且跟我在自己的头文件中的定义也有冲突。我该怎么办? 104
10.11 我在编译一个程序,看起来我好像缺少需要的一个或多个头文件。谁能发给我一份? 105
条件编译 105
10.12 怎样构造比较字符串的#if预处理表达式? 105
10.13 sizeof操作符可以用在#if预处理指令中吗? 106
10.14 我可以像这样在#define行里使用#ifdef来定义两个不同的东西吗? 106
10.15 对typedef的类型定义有没有类似#ifdef的东西? 106
10.16 我如何用#if表达式来判断机器是高字节在前还是低字节在前? 107
10.17 为什么在我用#ifdef关掉的代码行中报出了奇怪的语法错误? 107
10.18 我拿到了一些代码,里边有太多的#ifdef。我不想使用预处理器把所有的#include 和#ifdef都扩展开,有什么办法只保留一种条件的代码呢? 107
10.19 如何列出所有的预定义宏? 107
奇异的处理 108
10.20 我有些旧代码,试图用这样的宏来构造标识符:#define Paste(a, b) a/**/b 但是现在不行了。为什么? 108
10.21 我有一个旧宏:#define CTRL(c) ('c' & 037)现在不能用了。为什么? 108
10.22 为什么宏#define TRACE(n) printf(TRACE: \%d\n, n) 报出警告“macro replacement within a string literal ”?它似乎把TRACE(count);扩展成了printf(TRACE: \%d\count, count); 109
10.23 如何在宏扩展的字符串字面量中使用宏参数? 109
10.24 我想用ANSI 的“字符串化”预处理操作符#将符号常量的值放入消息中,但它总是对宏名称而不是它的值进行字符串化。这是什么原因? 109
10.25 我想用预处理器做某件事情,但却不知道如何下手。 110
可变参数列表的宏 110
10.26 怎样写可变参数宏?如何用预处理器“关掉”具有可变参数的函数调用? 110
10.27 如何在通用的调试宏中包含__FILE__和__LINE__宏? 111
第11章 ANSI/ISO标准C 113
标准 113
11.1 什么是“ANSI C标准”? 113
11.2 如何得到一份标准的副本? 114
*11.3 我在哪里可以找到标准的更新? 115
函数原型 115
11.4 为什么我的ANSI编译器对用float声明的参数会警告类型不匹配? 115
11.5 能否混用旧式的和新型的函数语法? 116
*11.6 为什么下述声明报出了一个奇怪的警告信息“Struct X declared inside parameter list”? extern int f(struct x *p); 116
11.7 有个问题一直困扰着我,它是由这一行printf (%d, n); 导致的,因为n是个long int型。难道 ANSI 的函数原型不能检查这种函数的参数不匹配问题吗? 116
11.8 我听说必须在调用printf之前包含stdio.h。为什么? 117
const 限定词 117
11.9 为什么不能在初始化和数组维度中使用const值?例如const int n = 5; int a[n]; 117
11.10 “const char *p”、“char const *p ”和“char * const p ”有何区别? 117
11.11 为什么不能向接受const char ** 的函数传入char **? 118
11.12 我这样声明:typedef char * charp; const charp p; 为什么是p而不是它所指向的字符为const? 118
main()函数的使用 119
11.13 能否通过将main声明为void来关掉“main没有返回值”的警告? 119
11.14 main()的第3个参数envp是怎么回事? 120
11.15 我觉得把main()声明为void也不会失败,因为我调用了exit()而不是return,况且我的操作系统也忽略了程序的退出/返回状态。 120
*11.16 那么到底会出什么问题?真的有什么系统不支持void main()吗? 120
11.17 为什么以前流行的那些C 语言书总是使用void main()? 120
11.18 在main()中调用exit(status)和返回同样的status真的等价吗? 121
预处理功能 121
11.19 我试图用ANSI“字符串化”预处理操作符'#'向信息中插入符号常量的值,但它字符串化的总是宏的名字而不是它的值。为什么? 121
11.20 警告信息“warning: macro replacement within a string literal”是什么意思? 121
11.21 为什么在我用#ifdef去掉的代码里出现了奇怪的语法错误? 122
11.22 #pragma是什么,有什么用? 122
11.23 “#pragma once”什么意思?我在一些头文件中看到了它。 122
其他的ANSI C 问题 123
11.24 char a[3] = abc;合法吗?它是什么意思? 123
11.25 既然对数组的引用会退化为指针,那么,如果array是数组,array和&array之间有什么区别呢? 123
11.26 为什么我不能对void *指针进行算术运算? 123
11.27 memcpy()和memmove() 有什么区别? 124
11.28 malloc(0)有什么用?返回一个空指针还是指向0字节的指针? 124
11.29 为什么ANSI 标准规定了外部标识符的长度和大小写限制? 125
11.30 noalias是怎么回事?在它身上发生了什么? 125
老的或非标准的编译器 125
11.31 为什么我的编译器对最简单的测试程序都报出了一大堆的语法错误?对这段代码的第一行就报错了:main(int argc. char **argv) { return0; } 125
11.32 为什么有些 ASNI/ISO 标准库函数未定义?我明明使用的就是ANSI 编译器。 126
11.33 谁有可以在旧的C 程序和ANSI C 之间相互转换的工具,或者自动生成原型的工具? 127
11.34 为什么声称兼容ANSI 的编译器不能编译这些代码?我知道这些代码是 ANSI 的,因为gcc 可以编译。 127
兼容性 127
11.35 人们好像有些在意实现定义的(implementation-defined)、不确定的(unspecified)和未定义的(undefined) 行为的区别。它们的区别到底在哪里? 128
*11.36 一个程序“合法(legal)”、“有效(valid)”或“符合标准的”(conforming )到底是什么意思? 128
11.37 我很吃惊,ANSI 标准竟然有那么多未定义的东西。标准的唯一任务不就是让这些东西标准化吗? 129
11.38 有人说i=i++的行为是未定义的,但是我刚在一个兼容ANSI 的编译器上测试,得到了我希望的结果。它真的是未定义的吗? 129
第12章 标准输入输出库 130
基本输入输出 130
12.1 这样的代码有什么问题?char c; while((c = getchar()) != EOF) 130
12.2 我有个读取直到EOF的简单程序,但是我如何才能在键盘上输入那个“\EOF”呢?我看stdio.h 中定义的EOF 是-1,是不是说我该输入-1? 131
12.3 为什么这些代码把最后一行复制了两遍?while(!feof(infp)){fgets(buf, MAXLINE, infp); fputs(buf, outfp);} 131
12.4 我用fgets将文件的每行内容读入指针数组。为什么结果所有的行都是最后一行的内容呢? 132
12.5 我的程序的屏幕提示和中间输出有时没有在屏幕上显示,尤其是当我用管道通过另一个程序输出的时候。为什么? 132
12.6 我怎样才能不等待回车键而一次输入一个字符? 132
printf格式 132
12.7 如何在printf 的格式串中输出一个'%'字符?我试过\%,但是不行。 132
12.8 为什么这么写不对?long int n = 123456; printf(%d\n, n); 133
12.9 有人告诉我不能在printf 中使用%lf。为什么printf() 用%f输出double 型,而scanf 却用%lf 呢? 133
*12.10 对于size_t 那样的类型定义,当我不知道它到底是long 还是其他类型的时候,我应该使用什么样的printf格式呢? 134
12.11 如何用printf 实现可变的域宽度?就是说,我想在运行时确定宽度而不是使用%8d? 134
12.12 如何输出在千位上用逗号隔开的数字?货币格式的数字呢? 135
12.13 为什么scanf(%d, i) 调用不行? 136
*12.14 为什么char s[30]; scamf(%s, s); 不用&也可以?我原以为传给scanf的每个变量都要带&。 136
12.15 为什么这些代码不行?double d; scanf(%f, &d); 136
12.16 为什么这段代码不行?short int s; scanf(%d, &s); 136
12.17 怎样在scanf 格式串中指定可变的宽度? 136
12.18 怎样从特定格式的数据文件中读取数据?怎样读入10个float 而不用使用包含10次%f的奇怪格式?如何将一行的任意多个域读入一个数组中? 137
scanf问题 138
12.19 我像这样用%d\n调用scanf 从键盘读取数字:int n; scanf(%d\n,&n); printf(you typed %d\ n, n);好像要多输入一行才返回。为什么? 138
12.20 我用scanf 和%d读取一个数字,然后再用gets() 读取字符串,但是编译器好像跳过了gets() 调用! 139
12.21 我发现如果坚持检查返回值以确保用户输入的是我期待的数值,则scanf 的使用会安全很多。但有的时候好像会陷入无限循环。为什么? 139
12.22 为什么大家都说不要使用scanf?那我该用什么来代替呢? 140
其他stdio 函数 141
12.23 我怎样才知道对于任意的sprintf 调用需要多大的目标缓冲区?怎样才能避免sprintf 目标缓冲区溢出? 141
12.24 sprintf的返回值是什么?是int 还是char *? 142
12.25 为什么大家都说不要使用gets? 142
12.26 我觉得我应该在一长串的printf 调用之后检查errno ,以确定是否有失败的调用。为什么当我将输出重定向到文件的时候会输出奇怪的“printf failed: Not a typewriter ”信息? 142
12.27 fgetops/fsetops和ftell/fseek之间有什么区别?fgetops和fsetops 到底有什么用处? 143
12.28 如何清除用户的多余输入,以防止在下一个提示符下读入?用fflush(stdin) 可以吗? 143
打开和操作文件 144
12.29 我写了一个函数用来打开文件:myfopen(char *filename, FILE *fp){fp = fopen(filename, r);}可我这样调用的时候:FILE *infp; myfopen(filename.dat, infp);,infp 指针并
没有正确设置。为什么? 144
12.30 连一个最简单的fopen调用都不成功!这个调用有什么问题?FILE *fp = fopen(filename, 'r'); 145
12.31 为什么我不能用完整路径名打开一个文件?这个调用总是失败:fopen(c:\newdir\ file. dat, r); 145
12.32 我想用fopen模式r+打开一个文件,读出一个字符串,修改之后再写入,从而就地更新一个文件。可是这样不行。为什么? 145
12.33 如何在文件中间插入或删除一行(一条记录)? 145
12.34 怎样从打开的流中恢复文件名? 145
重定向stdin 和stdout 146
12.35 怎样在程序里把stdin或stdout重定向到文件? 146
12.36 一旦使用freopen之后,怎样才能恢复原来的stdout (或stdin)? 146
12.37 如何判断标准输入或输出是否经过了重定向,即是否在命令行上使用了“”或“”? 147
12.38 我想写个像more那样的程序。怎样才能在stdin 被重定向之后再回到交互键盘? 147
*12.39 怎样同时向两个地方输出,如同时输出到屏幕和文件? 147
“二进制”输入输出 148
12.40 我希望按字节在内存和文件之间直接读写数字,而不像fprintf和fscanf进行格式化。我该怎么办? 148
12.41 怎样正确地读取二进制文件?有时看到0x0a和0x0d容易混淆,而且如果数据中包含0x1a的话,我好像会提前遇到EOF。 148
12.42 我在写一个二进制文件的“过滤器”,但是stdin和stdout却被作为文本流打开了。怎样才能把它们的模式改为二进制? 148
12.43 文本和二进制输入输出有什么区别? 149
12.44 如何在数据文件中读写结构? 149
12.45 怎样编写符合旧的二进制数据格式的代码? 149
第13章 库函数 151
字符串函数 151
13.1 怎样把数字转为字符串(与atoi相反)?有itoa函数吗? 151
13.2 为什么strncpy不能总在目标串放上终止符'\0'? 152
13.3 C 语言有类似于其他语言中的“substr ”(取出子串)的例程吗? 152
13.4 怎样把一个字符串中所有字符转换成大写或小写? 153
13.5 为什么有些版本的toupper对大写字符会有奇怪的反应?为什么有的代码在调用toupper 前先调用islower? 153
13.6 怎样将字符串分割成用空白分隔的字段?怎样实现类似main 处理argc和argv的过程? 153
13.7 哪里可以找到处理正则表达式或通配符匹配的代码? 155
排序 156
13.8 我想用strcmp作为比较函数,调用qsort对一个字符串数组排序,但是不行。为什么? 156
13.9 我想用qsort()对一个结构数组排序。我的比较函数接受结构指针,但是编译器认为这个函数不是qsort需要的类型。我要怎样转换这个函数指针才能避免这样的警告? 156
13.10 怎样对一个链表排序? 158
13.11 怎样对大于内存容量的数据排序? 158
日期和时间 159
13.12 怎样在C 程序中取得当前日期或时间? 159
13.13 我知道库函数localtime可以把time_t转换成结构struct tm,而ctime可以把time_t转换成为可打印的字符串。怎样才能进行反向操作,把struct tm或一个字符串转换成time_t? 159
13.14 怎样在日期上加n天?怎样取得两个日期的时间间隔? 160
随机数 162
13.15 怎么生成一个随机数? 162
13.16 怎样获得某一范围内的随机整数? 163
13.17 每次执行程序,rand都返回相同的数字序列。为什么? 164
13.18 我需要随机的真/假值,所以我就直接用rand()%2,可是我得到交替的0, 1, 0, 1, 0 …。为什么? 164
13.19 如何获取根本不重复的随机数? 165
13.20 怎样产生正态分布或高斯分布的随机数? 165
13.21 我在移植一个程序,里边调用了一个函数drand48 ,而我的库又没有这个。这是个什么函数? 167
其他库函数 168
13.22 exit(status)是否真的跟从main 函数返回status 等价? 168
13.23 memcpy和memmove 有什么区别? 168
13.24 我想移植这个旧程序。为什么报出这些“undefined external”错误:index? 、rindex?、bcopy?、bcmp?、bzero?? 168
13.25 我不断得到库函数未定义错误,但是我已经包含了所有用到的头文件了。 168
13.26 虽然我在连接时明确地指定了正确的函数库,我还是得到库函数未定义错误。 168
13.27 一个最简单的程序,不过在一个窗口里打印出“Hello,World”,为什么会编译出巨大的可执行代码(数百K)?我该少包含一些头文件吗? 169
13.28 连接器报告_end未定义代表什么意思? 169
*13.29 我的编译器提示printf未定义!这怎么可能? 169
第14章 浮点运算 170
14.1 一个float变量赋值为3.1时,为什么printf输出的值为3.0999999? 170
14.2 我想计算一些平方根,我把程序简化成这样:main(){printf (%f\h, sqrt(144.)); 可得到的结果却是疯狂的数字。为什么? 170
14.3 我想做一些简单的三角函数运算,也包含了math.h ,但连接器总是提示sin、cos这样的函数未定义。为什么? 171
14.4 我的浮点数计算程序表现得很奇怪,在不同的机器上给出了不同的结果。为什么? 171
14.5 有什么好的方法来检查浮点数在“足够接近”情况下的相等? 171
14.6 怎样取整? 172
14.7 为什么C语言不提供乘幂的操作符? 173
14.8 为什么我机器上的math.h没有预定义常量M_PI? 173
14.9 怎样将变量置为IEEE NaN(“Not a Number”)或检测变量是否为NaN及其他特殊值? 173
14.10 如何简洁地处理浮点异常? 174
14.11 在C语言中如何很好地实现复数? 174
14.12 我要寻找一些实现以下功能的程序源代码:快速傅立叶变换(FFT)、矩阵算术(乘法、求逆等函数)、复数算术。 175
14.13 Turbo C的程序崩溃,显示错误为“floating point formats not linked”(浮点格式未连接)。我还缺点儿什么呢? 175
第15章 可变参数列表 176
调用变参函数 176
15.1 为什么调用printf前必须要包含stdio.h? 176
15.2 为什么%f可以在printf参数中同时表示float和double?它们难道不是不同类型吗? 177
15.3 我遇到了一个令人十分受挫的问题,后来发现是这行代码造成的:printf(%d, n);原来n 是longint型。难道ANSI的函数原型不就是用来防止这类的参数类型不匹配吗? 177
15.4 怎样写一个接受可变参数的函数? 177
15.5 怎样写一个函数,像printf那样接受一个格式串和可变参数,然后再把参数传给printf去完成大部分工作? 180
15.6 怎样写类似scanf的函数,再把参数传给scanf去完成大部分工作? 180
15.7 我用的是ANSI前的编译器,没有stdarg.h文件。我该怎么办? 181
提取可变参数 182
15.8 怎样知道实际上有多少个参数传入函数? 182
15.9 为什么编译器不允许我定义一个没有固定参数项的可变参数函数? 182
15.10 我有个接受float型的变参函数,为什么va_arg(argp, float)却不行? 183
15.11 为什么va_arg不能得到类型为函数指针的参数? 183
困难的问题 184
15.12 怎样实现一个可变参数函数,它把参数再传给另一个可变参数函数? 184
15.13 怎样调用一个在运行时才构建参数列表的函数? 186
第16 章 奇怪的问题 187
16.1 为什么这个循环只执行了一次?for(i=start;i end ; i ++);{printf(%d\n,i);} 187
*16.2 遇到不可理解的不合理语法错误,似乎大段的程序没有编译。 187
*16.3 为什么过程调用不起作用?编译器似乎直接跳过去了。 187
16.4 程序在执行之前就崩溃了!(用调试器单步跟踪,在main函数的第一个语句之前就死了。)为什么? 188
16.5 程序执行正确,但退出时在main函数的最后一个语句之后崩溃了。为什么会这样? 188
16.6 程序在一台机器上运行完美,但在另一台上却得到怪异的结果。更奇怪的是,增加或去除调试的打印语句,就改变了症状…… 188
16.7 为什么下面的代码会崩溃?char *p = hello, world!; p[0] = 'H'; 189
16.8 我有些代码是用来解析外部结构的,但它却崩溃了,报了“unaligned access ”(未对齐的访问)错误。这是什么意思? 190
16.9 “Segmentation violation”、“Bus error”和“General protection fault”是什么意思? 191
第17章 风格 192
17.1 什么是C最好的代码布局风格? 192
17.2 如何在源文件中合理分配函数? 193
17.3 用if(!strcmp(s1, s2))比较两个字符串是否相等是个好风格吗? 193
17.4 为什么有的人用if(0== x)而不是if(x== 0)? 193
17.5 为什么有些代码在每次调用printf 前增加了类型转换(void)? 194
17.6 既然NULL和0都是空指针常量,我到底该用哪一个? 194
17.7 是该用TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 194
17.8 什么是“匈牙利表示法”(Hungarian Notation )?是否值得一试? 194
17.9 哪里可以找到“Indian Hill Style Guide ”及其他编码标准? 194
17.10 有人说goto是邪恶的,永远都不该用它。这是否太极端了? 195
17.11 人们总是说良好的风格很重要,但当他们使用良好的风格写出清晰易读的程序后,又发现程序的效率似乎降低了。既然效率那么重要,是否可以为了效率牺牲一些风格和可读性呢? 196
第18章 工具和资源 197
18.1 能否列一个常用工具列表? 197
18.2 怎样捕获棘手的malloc问题? 198
18.3 有什么免费或便宜的编译器可以使用? 198
lint 198
18.4 刚刚输入完一个程序,但它表现得很奇怪。你能发现有什么错误的地方吗? 199
18.5 如何关掉lint对每个malloc调用报出的“warning: possible pointer alignment problem”警告消息? 199
18.6 哪里可以找到兼容ANSI的lint? 199
18.7 难道ANSI函数原型说明没有使lint过时吗? 199
资源 200
18.8 网上有哪些C语言的教程或其他资源? 200
*18.9 哪里可以找到好的源代码实例,以供研究和学习? 201
18.10 有什么好的学习C语言的书?有哪些高级的书和参考? 201
18.11 哪里能找到K&R的练习答案? 201
18.12 哪里能找到Numerical Recipes in C 、Plauger的The Standard C Library或Kernighan和Pike的The UNIX Programming Enviroment等书里的源码? 201
18.13 哪里可以找到标准C函数库的源代码? 202
18.14 是否有一个在线的C参考指南? 202
18.15 我需要分析和评估表达式的代码。从哪里可以找到? 202
18.16 哪里可以找到C的BNF或YACC语法? 202
*18.17 谁有C编译器的测试套件? 203
*18.18 哪里有一些有用的源代码片段和例子的收集? 203
*18.19 我需要执行多精度算术的代码。 203
18.20 在哪里和怎样取得这些可自由发布的程序? 203
第19章 系统依赖 205
键盘和屏幕I/O 205
19.1 怎样从键盘直接读入字符而不用等回车键?怎样防止字符输入时的回显? 205
19.2 怎样知道有未读的字符(如果有,有多少)?另外,如何在没有字符的时候不阻塞读入? 209
19.3 怎样显示一个在原地更新自己的百分比或“旋转棒”的进度指示器? 209
19.4 怎样清屏?怎样反色输出?怎样把光标移动到指定的x, y位置? 210
19.5 怎样读入方向键、功能键? 210
其他I/O 211
19.6 怎样读入鼠标输入? 211
19.7 怎样做串口(“comm”)的输入输出? 211
19.8 怎样直接输出到打印机? 211
19.9 怎样发送转义字符序列控制终端或其他设备? 211
19.10 怎样做图形? 212
*19.11 怎样显示GIF和JPEG图像? 212
文件和目录 212
19.12 怎样检验一个文件是否存在?如果请求的输入文件不存在,我希望向用户提出警告。 212
19.13 怎样在读入文件前,知道文件大小? 213
*19.14 怎样得到文件的修改日期和时间? 213
19.15 怎样原地缩短一个文件而不用清除或重写? 213
19.16 怎样在文件中插入或删除一行(或一条记录)? 214
19.17 怎样从一个打开的流或文件描述符得到文件名? 214
19.18 怎样删除一个文件? 214
*19.19 怎样复制文件? 215
19.20 为什么用了详尽的路径还不能打开文件?下面的代码会返回错误。Fopen(c:\newdir\file.dat, r) 215
*19.21 fopen不让我打开文件$HOME/.profile和~~/.myrcfile。 215
*19.22 怎样制止MS-DOS下令人恐怖的“Abort,Retry,Ignore? ”信息? 215
19.23 遇到“Too many open files(打开文件太多)”的错误,怎样增加同时打开文件的允许数目? 215
19.24 如何得到磁盘的可用空间大小? 216
19.25 怎样在C语言中读入目录? 216
19.26 如何创建目录?如何删除目录(及其内容)? 217
访问原始内存 217
19.27 怎样找出系统还有多少内存可用? 217
19.28 怎样分配大于64K的数组或结构? 217
19.29 错误信息“DGROUP data allocation exceeds 64K(DGROUP 数据分配内存超过64K)”什么意思?我应该怎么做?我以为使用了大内存模型,就可以使用大于64K的数据! 217
19.30 怎样访问位于某特定地址的内存(内存映射的设备或图形显示内存)? 218
19.31 如何访问机器地址0处的中断向量?如果将指针设为0,编译器可能把它转成一个非零的内部空指针值。 218
“系统”命令 219
19.32 怎样在一个C程序中调用另一个程序(独立可执行的程序或系统命令)? 219
19.33 如果运行时才知道要执行的命令的参数(文件名等),应该如何调用system? 219
19.34 在MS-DOS上如何得到system返回的准确错误状态? 220
19.35 怎样调用另一个程序或命令,然后获取它的输出? 220
进程环境 220
19.36 怎样才能发现程序自己的执行文件的全路径? 220
19.37 怎样找出和执行文件在同一目录的配置文件? 221
19.38 进程如何改变它的调用者的环境变量? 221
19.39 如何打开命令行给出的文件并解析选项? 221
19.40 exit(status)是否真的和从main函数返回同样的status等价? 221
19.41 怎样读入一个对象文件并跳跃到其中的函数? 221
其他系统相关的操作 222
19.42 怎样以小于1秒的精度延时或计算用户响应时间? 222
19.43 怎样捕获或忽略control-C这样的键盘中断? 222
19.44 怎样简洁地处理浮点异常? 223
19.45 怎样使用socket?如何联网?如何写客户/服务器程序? 223
*19.46 怎样调用BIOS函数?如何写ISR?如何创建TSR? 224
*19.47 什么是“near”和“far”指针? 224
回顾 224
19.48 我不能使用这些非标准、依赖系统的函数,程序需要兼容ANSI! 224
19.49 为什么这些内容没有在C语言中进行标准化?任何现实程序都会用到这些东西。 224
第20章 杂项 226
20.1 怎样从函数返回多个值? 226
20.2 用什么数据结构存储文本行最好?我开始用固定大小的char型数组的数组,但是有很多局限。 227
20.3 怎样打开命令行提到的文件并处理参数? 229
20.4 如何正确地使用errno? 231
20.5 怎样写数据文件,使之可以在不同字大小、字节顺序或浮点格式的机器上读入? 232
20.6 怎样用char *指针指向的函数名调用函数? 232
位和字节 233
20.7 如何操作各个位? 233
20.8 怎样实现位数组或集合? 234
20.9 怎样判断机器的字节顺序是高字节在前还是低字节在前? 235
*20.10 怎样调换字节? 236
20.11 怎样将整数转换到二进制或十六进制? 237
20.12 可以使用二进制常数(类似0b101010这样的东西)吗?printf有二进制的格式说明符吗? 237
效率 238
20.13 用什么方法计算整数中为1的位的个数最高效? 238
20.14 怎样提高程序的效率? 238
20.15 指针真的比数组快吗?函数调用会拖慢程序多少?++i比i=i+1快吗? 240
20.16 用移位操作符替换乘法和除法是否有价值? 240
*20.17 人们说编译器优化得很好,我们不再需要为速度而写汇编了,但我的编译器连用移位代替i/=2都做不到。 240
*20.18 怎样不用临时变量而交换两个值? 241
switch 语句 241
20.19 switch语句和if/else链哪个更高效? 241
20.20 是否有根据字符串进行条件切换的方法? 241
20.21 是否有使用非常量case行标的方法(如范围或任意的表达式)? 242
各种语言功能 243
20.22 return语句外层的括号是否真的可选择? 243
20.23 为什么C语言的注释不能嵌套?怎样注释掉含有注释的代码?引号包含的字符串内的注释是否合法? 243
20.24 为什么C语言的操作符不设计得更全面一些?好像还缺了一些^^、&&=和-=这样的操作符。 244
*20.25 C语言有循环移位操作符吗? 244
*20.26 C是个伟大的语言还是别的什么东西?哪个其他语言可以写出像a+++++b这样的代码? 244
20.27 如果赋值操作符是:=,是不是就不容易意外地写出if(a=b)了? 245
20.28 C语言有和Pascal 的with等价的语句吗? 245
20.29 为什么C语言没有嵌套函数? 245
*20.30 assert是什么?如何使用? 246
其他语言 246
20.31 怎样从C中调用FORTRAN(C++、BASIC、Pascal、Ada、LISP)的函数?反之如何? 246
20.32 有什么程序可以将Pascal或FORTRAN(或LISP、Ada、awk、“老”C)程序转化为C程序? 246
20.33 C++是C的超集吗?可以用C++编译器来编译C代码吗? 247
20.34 我需要用到“近似”的strcmp例程,比较两个字符串的近似度,并不需要完全一样。有什么好办法? 247
20.35 什么是散列法? 248
20.36 如何生成正态或高斯分布的随机数? 248
20.37 如何知道某个日期是星期几? 249
20.38 (year % 4== 0)是否足以判断闰年?2000年是闰年吗? 250
20.39 为什么tm结构中的tm_sec的范围是0到61,暗示一分钟有62秒? 250
琐事 250
20.40 一个难题:怎样写一个输出自己源代码的程序? 250
20.41 什么是“达夫设备”(Duff’s Device)? 251
20.42 下届国际C语言混乱代码竞赛(International Obfuscated C Code Contest,IOCCC)什么时候进行?哪里可以找到当前和以前的获胜代码? 251
20.43 K&R1提到的关键字entry是什么? 252
20.44 C的名字从何而来? 252
20.45 “char”如何发音? 252
*20.46 “lvalue”和“rvalue”代表什么意思? 252
20.47 哪里可以获得本书的在线版? 252
术语表 253
参考文献 261~
|------ 1.2 理解Windows消息机制
|------ 1.3 利用Visual C++/MFC开发Windows程序的优势
|------ 1.4 利用MFC进行开发的通用方法介绍
|------ 1.5 MFC中常用类,宏,函数介绍
+-- 第二章 图形输出
|------ 2.1 和GUI有关的各种对象
|------ 2.2 在窗口中输出文字
|------ 2.3 使用点,刷子,笔进行绘图
|------ 2.4 在窗口中绘制设备相关位图,图标,设备无关位图
|------ 2.5 使用各种映射方式
|------ 2.6 多边形和剪贴区域
+-- 第三章 文档视结构
|------ 3.1 文档 视图 框架窗口间的关系和消息传送规律
|------ 3.2 接收用户输入
|------ 3.3 使用菜单
|------ 3.4 文档,视,框架之间相互作用
|------ 3.5 利用序列化进行文件读写
|------ 3.6 MFC中所提供的各种视类介绍
+-- 第四章 窗口控件
|------ 4.1 Button
|------ 4.2 Static Box
|------ 4.3 Edit Box
|------ 4.4 Scroll Bar
|------ 4.5 List Box/Check List Box
|------ 4.6 Combo Box/Combo Box Ex
|------ 4.7 Tree Ctrl
|------ 4.8 List Ctrl
|------ 4.9 Tab Ctrl
|------ 4.A Tool Bar
|------ 4.B Status Bar
|------ 4.C Dialog Bar
|------ 4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar
|------ 4.E General Window
|------ 4.F 关于WM_NOTIFY的使用方法
+-- 第五章 对话框
|------ 5.1 使用资源编辑器编辑对话框
|------ 5.2 创建有模式对话框
|------ 5.3 创建无模式对话框
|------ 5.4 在对话框中进行消息映射
|------ 5.5 在对话框中进行数据交换和数据检查
|------ 5.6 使用属性对话框
|------ 5.7 使用通用对话框
|------ 5.8 建立以对话框为基础的应用
|------ 5.9 使用对话框作为子窗口
+-- 第六章 网络通信开发
|------ 6.1 WinSock介绍
|------ 6.2 利用WinSock进行无连接的通信
+------ 6.3 利用WinSock建立有连接的通信
第一章 VC入门
1.1 如何学好VC
这个问题很多朋友都问过我,当然流汗是必须的,但同时如果按照某种思路进行有计划的学习就会起到更好的效果。万事开头难,为了帮助朋友们更快的掌握VC开发,下面我将自己的一点体会讲一下:
1、需要有好的C/C++基础。正所谓“磨刀不误砍柴工”,最开始接触VC时不要急于开始Windows程序开发,而是应该进行一些字符界面程序的编写。这样做的目的主要是增加对语言的熟悉程度,同时也训练自己的思维和熟悉一些在编程中常犯的错误。更重要的是理解并能运用C++的各种特性,这些在以后的开发中都会有很大的帮助,特别是利用MFC进行开发的朋友对C++一定要能熟练运用。
2、理解Windows的消息机制,窗口句柄和其他GUI句柄的含义和用途。了解和MFC各个类功能相近的API函数。
3、一定要理解MFC中消息映射的作用。
4、训练自己在编写代码时不使用参考书而是使用Help Online。
5、记住一些常用的消息名称和参数的意义。
6、学会看别人的代码。
7、多看书,少买书,买书前一定要慎重。
8、闲下来的时候就看参考书。
9、多来我的主页。^O^
后面几条是我个人的一点意见,你可以根据需要和自身的情况选用适用于自己的方法。
此外我将一些我在选择参考书时的原则:
对于初学者:应该选择一些内容比较全面的书籍,并且书籍中的内容应该以合理的方式安排,在使用该书时可以达到循序渐进的效果,书中的代码要有详细的讲解。尽量买翻译的书,因为这些书一般都比较易懂,而且语言比较轻松。买书前一定要慎重如果买到不好用的书可能会对自己的学习积极性产生击。
对于已经掌握了VC的朋友:这种程度的开发者应该加深自己对系统原理,技术要点的认识。需要选择一些对原理讲解的比较透彻的书籍,这样一来才会对新技术有更多的了解,最好书中对技术的应用有一定的阐述。尽量选择示范代码必较精简的书,可以节约银子。
此外最好涉猎一些辅助性的书籍。
1.2 理解Windows消息机制
Windows系统是一个消息驱动的OS,什么是消息呢?我很难说得清楚,也很难下一个定义(谁在嘘我),我下面从不同的几个方面讲解一下,希望大家看了后有一点了解。
1、消息的组成:一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM)。当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。例如当菜单转中之后会有WM_COMMAND消息发送,WPARAM的高字中(HIWORD(wParam))是命令的ID号,对菜单来讲就是菜单ID。当然用户也可以定义自己的消息名称,也可以利用自定义消息来发送通知和传送数据。
2、谁将收到消息:一个消息必须由一个窗口接收。在窗口的过程(WNDPROC)中可以对消息进行分析,对自己感兴趣的消息进行处理。例如你希望对菜单选择进行处理那么你可以定义对WM_COMMAND进行处理的代码,如果希望在窗口中进行图形输出就必须对WM_PAINT进行处理。
3、未处理的消息到那里去了:M$为窗口编写了默认的窗口过程,这个窗口过程将负责处理那些你不处理消息。正因为有了这个默认窗口过程我们才可以利用Windows的窗口进行开发而不必过多关注窗口各种消息的处理。例如窗口在被拖动时会有很多消息发送,而我们都可以不予理睬让系统自己去处理。
4、窗口句柄:说到消息就不能不说窗口句柄,系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二。
5、示例:下面有一段伪代码演示如何在窗口过程中处理消息
LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM)
switch(uMessageType)
{ //使用SWITCH语句将各种消息分开
case(WM_PAINT):
doYourWindow(...);//在窗口需要重新绘制时进行输出
break;
case(WM_LBUTTONDOWN):
doYourWork(...);//在鼠标左键被按下时进行处理
break;
default:
callDefaultWndProc(...);//对于其它情况就让系统自己处理
break;
接下来谈谈什么是消息机制:系统将会维护一个或多个消息队列,所有产生的消息都回被放入或是插入队列中。系统会在队列中取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统所以Windows可以同时进行多个任务。下面的伪代码演示了消息循环的用法:
while(1)
id=getMessage(...);
if(id == quit)
break;
translateMessage(...);
当该程序没有消息通知时getMessage就不会返回,也就不会占用系统的CPU时间。 下图为消息投递模式
在16位的系统中系统中只有一个消息队列,所以系统必须等待当前任务处理消息后才可以发送下一消息到相应程序,如果一个程序陷如死循环或是耗时操作时系统就会得不到控制权。这种多任务系统也就称为协同式的多任务系统。Windows3.X就是这种系统。而32位的系统中每一运行的程序都会有一个消息队列,所以系统可以在多个消息队列中转换而不必等待当前程序完成消息处理就可以得到控制权。这种多任务系统就称为抢先式的多任务系统。Windows95/NT就是这种系统。
1.3 利用Visual C++/MFC开发Windows程序的优势
MFC借助C++的优势为Windows开发开辟了一片新天地,同时也借助ApplicationWizzard使开发者摆脱离了那些每次都必写基本代码,借助ClassWizard和消息映射使开发者摆脱了定义消息处理时那种混乱和冗长的代码段。更令人兴奋的是利用C++的封装功能使开发者摆脱Windows中各种句柄的困扰,只需要面对C++中的对象,这样一来使开发更接近开发语言而远离系统。(但我个人认为了解系统原理对开发很有帮助)
正因为MFC是建立在C++的基础上,所以我强调C/C++语言基础对开发的重要性。利用C++的封装性开发者可以更容易理解和操作各种窗口对象;利用C++的派生性开发者可以减少开发自定义窗口的时间和创造出可重用的代码;利用虚拟性可以在必要时更好的控制窗口的活动。而且C++本身所具备的超越C语言的特性都可以使开发者编写出更易用,更灵活的代码。
在MFC中对消息的处理利用了消息映射的方法,该方法的基础是宏定义实现,通过宏定义将消息分派到不同的成员函数进行处理。下面简单讲述一下这种方法的实现方法:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
//}}AFX_MSG_MAP
ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)
END_MESSAGE_MAP()
经过编译后,代码被替换为如下形式(这只是作讲解,实际情况比这复杂得多):
//BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
CMainFrame::newWndProc(...)
switch(...)
//{{AFX_MSG_MAP(CMainFrame)
// ON_WM_CREATE()
case(WM_CREATE):
OnCreate(...);
break;
//}}AFX_MSG_MAP
// ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)
case(WM_COMMAND):
if(HIWORD(wP)==ID_FONT_DROPDOWN)
DoNothing(...);
break;
//END_MESSAGE_MAP()
newWndProc就是窗口过程只要是该类的实例生成的窗口都使用该窗口过程。
所以了解了Windows的消息机制在加上对消息映射的理解就很容易了解MFC开发的基本思路了。
1.4 利用MFC进行开发的通用方法介绍
以下是我在最初学习VC时所常用的开发思路和方法,希望能对初学VC的朋友有所帮助和启发。
1、开发需要读写文件的应用程序并且有简单的输入和输出可以利用单文档视结构。
2、开发注重交互的简单应用程序可以使用对话框为基础的窗口,如果文件读写简单这可利用CFile进行。
3、开发注重交互并且文件读写复杂的的简单应用程序可以利用以CFormView为基础视的单文档视结构。
4、利用对话框得到用户输入的数据,在等级提高后可使用就地输入。
5、在对多文档要求不强烈时尽量避免多文档视结构,可以利用分隔条产生单文档多视结构。
6、在要求在多个文档间传递数据时使用多文档视结构。
7、学会利用子窗口,并在自定义的子窗口包含多个控件达到封装功能的目的。
8、尽量避免使用多文档多视结构。
9、不要使用多重继承并尽量减少一个类中封装过多的功能。
1.5 MFC中常用类,宏,函数介绍
CRect:用来表示矩形的类,拥有四个成员变量:top left bottom right。分别表是左上角和右下角的坐标。可以通过以下的方法构造:
CRect( int l, int t, int r, int b ); 指明四个坐标
CRect( const RECT& srcRect ); 由RECT结构构造
CRect( LPCRECT lpSrcRect ); 由RECT结构构造
CRect( POINT point, SIZE size ); 有左上角坐标和尺寸构造
CRect( POINT topLeft, POINT bottomRight ); 有两点坐标构造
下面介绍几个成员函数:
int Width( ) const; 得到宽度
int Height( ) const; 得到高度
CSize Size( ) const; 得到尺寸
CPoint& TopLeft( ); 得到左上角坐标
CPoint& BottomRight( ); 得到右下角坐标
CPoint CenterPoint( ) const; 得当中心坐标
此外矩形可以和点(CPoint)相加进行位移,和另一个矩形相加得到“并”操作后的矩形。
CPoint:用来表示一个点的坐标,有两个成员变量:x y。 可以和另一个点相加。
CString:用来表示可变长度的字符串。使用CString可不指明内存大小,CString会根据需要自行分配。下面介绍几个成员函数:
GetLength 得到字符串长度
GetAt 得到指定位置处的字符
operator + 相当于strcat
void Format( LPCTSTR lpszFormat, ... ); 相当于sprintf
Find 查找指定字符,字符串
Compare 比较
CompareNoCase 不区分大小写比较
MakeUpper 改为小写
MakeLower 改为大写
CStringArray:用来表示可变长度的字符串数组。数组中每一个元素为CString对象的实例。下面介绍几个成员函数:
Add 增加CString
RemoveAt 删除指定位置CString对象
RemoveAll 删除数组中所有CString对象
GetAt 得到指定位置的CString对象
SetAt 修改指定位置的CString对象
InsertAt 在某一位置插入CString对象
TRACE
ASSERT
VERIFY
CWindApp* AfxGetApp();
HINSTANCE AfxGetInstanceHandle( );
HINSTANCE AfxGetResourceHandle( );
int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );用于弹出一个消息框
第二章 图形输出
2.1 和GUI有关的各种对象
在Windows中有各种GUI对象(不要和C++对象混淆),当你在进行绘图就需要利用这些对象。而各种对象都拥有各种属性,下面分别讲述各种GUI对象和拥有的属性。
字体对象CFont用于输出文字时选用不同风格和大小的字体。可选择的风格包括:是否为斜体,是否为粗体,字体名称,是否有下划线等。颜色和背景色不属于字体的属性。关于如何创建和使用字体在2.2 在窗口中输出文字中会详细讲解。
刷子CBrush对象决定填充区域时所采用的颜色或模板。对于一个固定色的刷子来讲它的属性为颜色,是否采用网格和网格的类型如水平的,垂直的,交叉的等。你也可以利用8*8的位图来创建一个自定义模板的刷子,在使用这种刷子填充时系统会利用位图逐步填充区域。关于如何创建和使用刷子在2.3 使用刷子,笔进行绘图中会详细讲解。
画笔CPen对象在画点和画线时有用。它的属性包括颜色,宽度,线的风格,如虚线,实线,点划线等。关于如何创建和使用画笔在2.3 使用刷子,笔进行绘图中会详细讲解。
位图CBitmap对象可以包含一幅图像,可以保存在资源中。关于如何使用位图在2.4 在窗口中绘制设备相关位图,图标,设备无关位图中会详细讲解。
还有一种特殊的GUI对象是多边形,利用多边形可以很好的限制作图区域或是改变窗口外型。关于如何创建和使用多边形在2.6 多边形和剪贴区域中会详细讲解。
在Windows中使用GUI对象必须遵守一定的规则。首先需要创建一个合法的对象,不同的对象创建方法不同。然后需要将该GUI对象选入DC中,同时保存DC中原来的GUI对象。如果选入一个非法的对象将会引起异常。在使用完后应该恢复原来的对象,这一点特别重要,如果保存一个临时对象在DC中,而在临时对象被销毁后可能引起异常。有一点必须注意,每一个对象在重新创建前必须销毁,下面的代码演示了这一种安全的使用方法:
OnDraw(CDC* pDC)
CPen pen1,pen2;
pen1.CreatePen(PS_SOLID,2,RGB(128,128,128));//创建对象
pen2.CreatePen(PS_SOLID,2,RGB(128,128,0));//创建对象
CPen* pPenOld=(CPen*)pDC->SelectObject(&pen1);//选择对象进DC
drawWithPen1...
(CPen*)pDC->SelectObject(&pen2);//选择对象进DC
drawWithPen2...
pen1.DeleteObject();//再次创建前先销毁
pen1.CreatePen(PS_SOLID,2,RGB(0,0,0));//再次创建对象
(CPen*)pDC->SelectObject(&pen1);//选择对象进DC
drawWithPen1...
pDC->SelectObject(pOldPen);//恢复
此外系统中还拥有一些库存GUI对象,你可以利用CDC::SelectStockObject(SelectStockObject( int nIndex )选入这些对象,它们包括一些固定颜色的刷子,画笔和一些基本字体。
• BLACK_BRUSH Black brush.
• DKGRAY_BRUSH Dark gray brush.
• GRAY_BRUSH Gray brush.
• HOLLOW_BRUSH Hollow brush.
• LTGRAY_BRUSH Light gray brush.
• NULL_BRUSH Null brush.
• WHITE_BRUSH White brush.
• BLACK_PEN Black pen.
• NULL_PEN Null pen.
• WHITE_PEN White pen.
• ANSI_FIXED_FONT ANSI fixed system font.
• ANSI_VAR_FONT ANSI variable system font.
• DEVICE_DEFAULT_FONT Device-dependent font.
• OEM_FIXED_FONT OEM-dependent fixed font.
• SYSTEM_FONT The system font. By default, Windows uses the system font to draw menus, dialog-box controls, and other text. In Windows versions 3.0 and later, the system font is proportional width; earlier versions of Windows use a fixed-width system font.
• SYSTEM_FIXED_FONT The fixed-width system font used in Windows prior to version 3.0. This object is available for compatibility with earlier versions of Windows.
• DEFAULT_PALETTE Default color palette. This palette consists of the 20 static colors in the system palette.
这些对象留在DC中是安全的,所以你可以利用选入库存对象来作为恢复DC中GUI对象。
大家可能都注意到了绘图时都需要一个DC对象,DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows耀眼的一点设备无关性。如同你将对一幅画使用照相机或复印机将会产生不同的输出,而不需要对画进行任何调整。DC的使用会穿插在本章中进行介绍。
2.2 在窗口中输出文字
在这里我假定读者已经利用ApplicationWizard生成了一个SDI界面的程序代码。接下来的你只需要在CView派生类的OnDraw成员函数中加入绘图代码就可以了。在这里我需要解释一下OnDraw函数的作用,OnDraw函数会在窗口需要重绘时自动被调用,传入的参数CDC* pDC对应的就是DC环境。使用OnDraw的优点就在于在你使用打印功能的时候传入OnDraw的DC环境将会是打印机绘图环境,使用打印预览时传入的是一个称为CPreviewDC的绘图环境,所以你只需要一份代码就可以完成窗口/打印预览/打印机绘图三重功能。利用Windows的设备无关性和M$为打印预览所编写的上千行代码你可以很容易的完成一个具有所见即所得的软件。
输出文字一般使用CDC::BOOL TextOut( int x, int y, const CString& str )和CDC::int DrawText( const CString& str, LPRECT lpRect, UINT nFormat )两个函数,对TextOut来讲只能输出单行的文字,而DrawText可以指定在一个矩形中输出单行或多行文字,并且可以规定对齐方式和使用何种风格。nFormat可以是多种以下标记的组合(利用位或操作)以达到选择输出风格的目的。
• DT_BOTTOM底部对齐 Specifies bottom-justified text. This value must be combined with DT_SINGLELINE.
• DT_CALCRECT计算指定文字时所需要矩形尺寸 Determines the width and height of the rectangle. If there are multiple lines of text, DrawText will use the width of the rectangle pointed to by lpRect and extend the base of the rectangle to bound the last line of text. If there is only one line of text, DrawText will modify the right side of the rectangle so that it bounds the last character in the line. In either case, DrawText returns the height of the formatted text, but does not draw the text.
• DT_CENTER中部对齐 Centers text horizontally.
• DT_END_ELLIPSIS or DT_PATH_ELLIPSIS Replaces part of the given string with ellipses, if necessary, so that the result fits in the specified rectangle. The given string is not modified unless the DT_MODIFYSTRING flag is specified.
You can specify DT_END_ELLIPSIS to replace characters at the end of the string, or DT_PATH_ELLIPSIS to replace characters in the middle of the string. If the string contains backslash (\) characters, DT_PATH_ELLIPSIS preserves as much as possible of the text after the last backslash.
• DT_EXPANDTABS Expands tab characters. The default number of characters per tab is eight.
• DT_EXTERNALLEADING Includes the font抯 external leading in the line height. Normally, external leading is not included in the height of a line of text.
• DT_LEFT左对齐 Aligns text flush-left.
• DT_MODIFYSTRING Modifies the given string to match the displayed text. This flag has no effect unless the DT_END_ELLIPSIS or DT_PATH_ELLIPSIS flag is specified.
Note Some uFormat flag combinations can cause the passed string to be modified. Using DT_MODIFYSTRING with either DT_END_ELLIPSIS or DT_PATH_ELLIPSIS may cause the string to be modified, causing an assertion in the CString override.
• DT_NOCLIP Draws without clipping. DrawText is somewhat faster when DT_NOCLIP is used.
• DT_NOPREFIX禁止使用&前缀 Turns off processing of prefix characters. Normally, DrawText interprets the ampersand (&) mnemonic-prefix character as a directive to underscore the character that follows, and the two-ampersand (&&) mnemonic-prefix characters as a directive to print a single ampersand. By specifying DT_NOPREFIX, this processing is turned off.
• DT_PATH_ELLIPSIS
• DT_RIGHT右对齐 Aligns text flush-right.
• DT_SINGLELINE单行输出 Specifies single line only. Carriage returns and linefeeds do not break the line.
• DT_TABSTOP设置TAB字符所占宽度 Sets tab stops. The high-order byte of nFormat is the number of characters for each tab. The default number of characters per tab is eight.
• DT_TOP定部对齐 Specifies top-justified text (single line only).
• DT_VCENTER中部对齐 Specifies vertically centered text (single line only).
• DT_WORDBREAK每行只在单词间被折行 Specifies word-breaking. Lines are automatically broken between words if a word would extend past the edge of the rectangle specified by lpRect. A carriage return杔inefeed sequence will also break the line.
在输出文字时如果希望改变文字的颜色,你可以利用CDC::SetTextColor( COLORREF crColor )进行设置,如果你希望改变背景色就利用CDC::SetBkColor( COLORREF crColor ),很多时候你可能需要透明的背景色你可以利用CDC::SetBkMode( int nBkMode )设置,可接受的参数有
• OPAQUE Background is filled with the current background color before the text, hatched brush, or pen is drawn. This is the default background mode.
• TRANSPARENT Background is not changed before drawing.
接下来讲讲如何创建字体,你可以创建的字体有两种:库存字体CDC::CreateStockObject( int nIndex )和自定义字体。
在创建非库存字体时需要填充一个LOGFONT结构并使用CFont::CreateFontIndirect(const LOGFONT* lpLogFont ),或使用CFont::CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename )其中的参数和LOGFONT中的分量有一定的对应关系。下面分别讲解参数的意义:
nHeight 字体高度(逻辑单位)等于零为缺省高度,否则取绝对值并和可用的字体高度进行匹配。
nWidth 宽度(逻辑单位)如果为零则使用可用的横纵比进行匹配。
nEscapement 出口矢量与X轴间的角度
nOrientation 字体基线与X轴间的角度
nWeight 字体粗细,可取以下值
Constant Value
FW_DONTCARE 0
FW_THIN 100
FW_EXTRALIGHT 200
FW_ULTRALIGHT 200
FW_LIGHT 300
FW_NORMAL 400
FW_REGULAR 400
FW_MEDIUM 500
FW_SEMIBOLD 600
FW_DEMIBOLD 600
FW_BOLD 700
FW_EXTRABOLD 800
FW_ULTRABOLD 800
FW_BLACK 900
FW_HEAVY 900
bItalic 是否为斜体
bUnderline 是否有下划线
cStrikeOut 是否带删除线
nCharSet 指定字符集合,可取以下值
Constant Value
ANSI_CHARSET 0
DEFAULT_CHARSET 1
SYMBOL_CHARSET 2
SHIFTJIS_CHARSET 128
OEM_CHARSET 255
nOutPrecision 输出精度
OUT_CHARACTER_PRECIS OUT_STRING_PRECIS
OUT_DEFAULT_PRECIS OUT_STROKE_PRECIS
OUT_DEVICE_PRECIS OUT_TT_PRECIS
OUT_RASTER_PRECIS
nClipPrecision 剪辑精度,可取以下值
CLIP_CHARACTER_PRECIS CLIP_MASK
CLIP_DEFAULT_PRECIS CLIP_STROKE_PRECIS
CLIP_ENCAPSULATE CLIP_TT_ALWAYS
CLIP_LH_ANGLES
nQuality 输出质量,可取以下值
• DEFAULT_QUALITY Appearance of the font does not matter.
• DRAFT_QUALITY Appearance of the font is less important than when PROOF_QUALITY is used. For GDI raster fonts, scaling is enabled. Bold, italic, underline, and strikeout fonts are synthesized if necessary.
• PROOF_QUALITY Character quality of the font is more important than exact matching of the logical-font attributes. For GDI raster fonts, scaling is disabled and the font closest in size is chosen. Bold, italic, underline, and strikeout fonts are synthesized if necessary.
nPitchAndFamily 字体间的间距
lpszFacename 指定字体名称,为了得到系统所拥有的字体可以利用EmunFontFamiliesEx。
此外可以利用CFontDialog来得到用户选择的字体的LOGFONT数据。
最后我讲一下文本坐标的计算,利用CDC::GetTextExtent( const CString& str )可以得到字符串的在输出时所占用的宽度和高度,这样就可以在手工输出多行文字时使用正确的行距。另外如果需要更精确的对字体高度和宽度进行计算就需要使用CDC::GetTextMetrics( LPTEXTMETRIC lpMetrics ) 该函数将会填充TEXTMETRIC结构,该结构中的分量可以非常精确的描述字体的各种属性。
2.3 使用点,刷子,笔进行绘图
在Windows中画点的方法很简单,只需要调用COLORREF CDC::SetPixel( int x, int y, COLORREF crColor )就可以在指定点画上指定颜色,同时返回原来的颜色。COLORREF CDC::GetPixel( int x, int y)可以得到指定点的颜色。在Windows中应该少使用画点的函数,因为这样做的执行效率比较低。
刷子和画笔在Windows作图中是使用最多的GUI对象,本节在讲解刷子和画笔使用方法的同时也讲述一写基本作图函数。
在画点或画线时系统使用当前DC中的画笔,所以在创建画笔后必须将其选入DC才会在绘图时产生效果。画笔可以通过CPen对象来产生,通过调用CPen::CreatePen( int nPenStyle, int nWidth, COLORREF crColor )来创建。其中nPenStyle指名画笔的风格,可取如下值:
• PS_SOLID 实线 Creates a solid pen.
• PS_DASH 虚线,宽度必须为一 Creates a dashed pen. Valid only when the pen width is 1 or less, in device units.
• PS_DOT 点线,宽度必须为一 Creates a dotted pen. Valid only when the pen width is 1 or less, in device units.
• PS_DASHDOT 点划线,宽度必须为一 Creates a pen with alternating dashes and dots. Valid only when the pen width is 1 or less, in device units.
• PS_DASHDOTDOT 双点划线,宽度必须为一 Creates a pen with alternating dashes and double dots. Valid only when the pen width is 1 or less, in device units.
• PS_NULL 空线,使用时什么也不会产生 Creates a null pen.
• PS_ENDCAP_ROUND 结束处为圆形 End caps are round.
• PS_ENDCAP_SQUARE 结束处为方形 End caps are square.
nWidth和crColor为线的宽度和颜色。
刷子是在画封闭曲线时用来填充的颜色,例如当你画圆形或方形时系统会用当前的刷子对内部进行填充。刷子可利用CBrush对象产生。通过以下几种函数创建刷子:
• BOOL CreateSolidBrush( COLORREF crColor ); 创建一种固定颜色的刷子
• BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); 创建指定颜色和网格的刷子,nIndex可取以下值:
• HS_BDIAGONAL Downward hatch (left to right) at 45 degrees
• HS_CROSS Horizontal and vertical crosshatch
• HS_DIAGCROSS Crosshatch at 45 degrees
• HS_FDIAGONAL Upward hatch (left to right) at 45 degrees
• HS_HORIZONTAL Horizontal hatch
• HS_VERTICAL Vertical hatch
• BOOL CreatePatternBrush( CBitmap* pBitmap ); 创建以8*8位图为模板的刷子
在选择了画笔和刷子后就可以利用Windows的作图函数进行作图了,基本的画线函数有以下几种
• CDC::MoveTo( int x, int y ); 改变当前点的位置
• CDC::LineTo( int x, int y ); 画一条由当前点到参数指定点的线
• CDC::BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 画弧线
• CDC::BOOL Polyline( LPPOINT lpPoints, int nCount ); 将多条线依次序连接
基本的作图函数有以下几种:
• CDC::BOOL Rectangle( LPCRECT lpRect ); 矩形
• CDC::RoundRect( LPCRECT lpRect, POINT point ); 圆角矩形
• CDC::Draw3dRect( int x, int y, int cx, int cy, COLORREF clrTopLeft, COLORREF clrBottomRight ); 3D边框
• CDC::Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 扇形
• CDC::Ellipse( LPCRECT lpRect ); 椭圆形
• CDC::Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
• CDC::Polygon( LPPOINT lpPoints, int nCount ); 多边形
对于矩形,圆形或类似的封闭曲线,系统会使用画笔绘制边缘,使用刷子填充内部。如果你不希望填充或是画出边缘,你可以选入空刷子(NULL_PEN)或是(NULL_BRUSH)空笔。
下面的代码创建一条两象素宽的实线并选入DC。并进行简单的作图:
CPen pen;
pen.CreatePen(PS_SOLID,2,RGB(128,128,128));
CPen* pOldPen=(CPen*)dc.SelectObject(&pen);
dc.SelectStockObject(NULL_BRUSH);//选入空刷子
dc.Rectangle(CRect(0,0,20,20));//画矩形
2.4 在窗口中绘制设备相关位图,图标,设备无关位图
在Windows中可以将预先准备好的图像复制到显示区域中,这种内存拷贝执行起来是非常快的。在Windows中提供了两种使用图形拷贝的方法:通过设备相关位图(DDB)和设备无关位图(DIB)。
DDB可以用MFC中的CBitmap来表示,而DDB一般是存储在资源文件中,在加载时只需要通过资源ID号就可以将图形装入。BOOL CBitmap::LoadBitmap( UINT nIDResource )可以装入指定DDB,但是在绘制时必须借助另一个和当前绘图DC兼容的内存DC来进行。通过CDC::BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )绘制图形,同时指定光栅操作的类型。BitBlt可以将源DC中位图复制到目的DC中,其中前四个参数为目的区域的坐标,接下来是源DC指针,然后是源DC中的起始坐标,由于BitBlt为等比例复制,所以不需要再次指定长宽,(StretchBlt可以进行缩放)最后一个参数为光栅操作的类型,可取以下值:
• BLACKNESS 输出区域为黑色 Turns all output black.
• DSTINVERT 反色输出区域 Inverts the destination bitmap.
• MERGECOPY 在源和目的间使用AND操作 Combines the pattern and the source bitmap using the Boolean AND operator.
• MERGEPAINT 在反色后的目的和源间使用OR操作 Combines the inverted source bitmap with the destination bitmap using the Boolean OR operator.
• NOTSRCCOPY 将反色后的源拷贝到目的区 Copies the inverted source bitmap to the destination.
• PATINVERT 源和目的间进行XOR操作 Combines the destination bitmap with the pattern using the Boolean XOR operator.
• SRCAND 源和目的间进行AND操作 Combines pixels of the destination and source bitmaps using the Boolean AND operator.
• SRCCOPY 复制源到目的区 Copies the source bitmap to the destination bitmap.
• SRCINVERT 源和目的间进行XOR操作 Combines pixels of the destination and source bitmaps using the Boolean XOR operator.
• SRCPAINT 源和目的间进行OR操作 Combines pixels of the destination and source bitmaps using the Boolean OR operator.
• WHITENESS 输出区域为白色 Turns all output white.
下面用代码演示这种方法:
CYourView::OnDraw(CDC* pDC)
CDC memDC;//定义一个兼容DC
memDC.CreateCompatibleDC(pDC);//创建DC
CBitmap bmpDraw;
bmpDraw.LoadBitmap(ID_BMP) ;//装入DDB
CBitmap* pbmpOld=memDC.SelectObject(&bmpDraw) ;
//保存原有DDB,并选入新DDB入DC
pDC->BitBlt(0,0,20,20,&memDC,0,0,SRCCOPY) ;
//将源DC中(0,0,20,20)复制到目的DC(0,0,20,20)
pDC->BitBlt(20,20,40,40,&memDC,0,0,SRCAND);
//将源DC中(0,0,20,20)和目的DC(20,20,40,40)中区域进行AND操作
memDC.SelectObject(pbmpOld) ;//选入原DDB
(图标并不是一个GDI对象,所以不需要选入DC)在MFC中没有一个专门的图标类,因为图标的操作比较简单,使用HICON CWinApp::LoadIcon( UINT nIDResource )或是HICON CWinApp::LoadStandardIcon( LPCTSTR lpszIconName ) 装入后就可以利用BOOL CDC::DrawIcon( int x, int y, HICON hIcon )绘制。由于在图标中可以指定透明区域,所以在某些需要使用非规则图形而且面积不大的时候使用图标会比较简单。下面给出简单的代码:
OnDraw(CDC* pDC)
HICON hIcon1=AfxGetApp()->LoadIcon(IDI_I1);
HICON hIcon2=AfxGetApp()->LoadIcon(IDI_I2);
pDC->DrawIcon(0,0,hIcon1);
pDC->DrawIcon(0,40,hIcon2);
DestroyIcon(hIcon1);
DestroyIcon(hIcon2);
同样在MFC也没有提供一个DIB的类,所以在使用DIB位图时我们需要自己读取位图文件中的头信息,并读入数据,并利用API函数StretchDIBits绘制。位图文件以BITMAPFILEHEADER结构开始,然后是BITMAPINFOHEADER结构和调色版信息和数据,其实位图格式是图形格式中最简单的一种,而且也是Windows可以理解的一种。我不详细讲解DIB位图的结构,提供一个CDib类供大家使用,这个类包含了基本的功能如:Load,Save,Draw。DownLoad CDib 4K
2.5 使用各种映射方式
所谓的映射方式简单点讲就是坐标的安排方式,系统默认的映射方式为MM_TEXT即X坐标向右增加,Y坐标向下增加,(0,0)在屏幕左上方,DC中的每一点就是屏幕上的一个象素。也许你会认为这种方式下是最好理解的,但是一个点和象素对应的关系在屏幕上看来是正常的,但到了打印机上就会很不正常。因为我们作图是以点为单位并且打印机的分辨率远远比显示器高(800DPI 800点每英寸)所以在打印机上图形看起来就会很小。这样就需要为打印另做一套代码而加大了工作量。如果每个点对应0.1毫米那么在屏幕上的图形就会和打印出来的图形一样大小。
通过int CDC::SetMapMode( int nMapMode )可以指定映射方式,可用的有以下几种:
• MM_HIENGLISH 每点对应0.001英寸 Each logical unit is converted to 0.001 inch. Positive x is to the right; positive y is up.
• MM_HIMETRIC 每点对应0.001毫米 Each logical unit is converted to 0.01 millimeter. Positive x is to the right; positive y is up.
• MM_LOENGLISH 每点对应0.01英寸 Each logical unit is converted to 0.01 inch. Positive x is to the right; positive y is up.
• MM_LOMETRIC 每点对应0.001毫米 Each logical unit is converted to 0.1 millimeter. Positive x is to the right; positive y is up.
• MM_TEXT 象素对应 Each logical unit is converted to 1 device pixel. Positive x is to the right; positive y is down.
以上几种映射默认的原点在屏幕左上方。除MM_TEXT外都为X坐标向右增加,Y坐标向上增加,和自然坐标是一致的。所以在作图是要注意什么时候应该使用负坐标。而且以上的映射都是X-Y等比例的,即相同的长度在X,Y轴上显示的长度都是相同的。
DownLoad Sample
另外的一种映射方式为MM_ANISOTROPIC,这种方式可以规定不同的长宽比例。在设置这中映射方式后必须调用CSize CDC::SetWindowExt( SIZE size )和CSize CDC::SetViewportExt( SIZE size )来设定长宽比例。系统会根据两次设定的长宽的比值来确定长宽比例。下面给出一段代码比较映射前后的长宽比例:
OnDraw(CDC* pDC)
CRect rcC1(200,0,400,200);
pDC->FillSolidRect(rcC1,RGB(0,0,255));
pDC->SetMapMode(MM_ANISOTROPIC );
CSize sizeO;
sizeO=pDC->SetWindowExt(5,5);
TRACE("winExt %d %d\n",sizeO.cx,sizeO.cy);
sizeO=pDC->SetViewportExt(5,10);
TRACE("ViewExt %d %d\n",sizeO.cx,sizeO.cy);
CRect rcC(0,0,200,200);
pDC->FillSolidRect(rcC,RGB(0,128,0));
上面代码在映射后画出的图形将是一个长方形。
DownLoad Sample
最后讲讲视原点(viewport origin),你可以通过调用CPoint CDC::SetViewportOrg( POINT point )重新设置原点的位置,这就相对于对坐标进行了位移。例如你将原点设置在(20,20)那么原来的(0,0)就变成了(-20,-20)。
2.6 多边形和剪贴区域
多边形也是一个GDI对象,同样遵守其他GDI对象的规则,只是通常都不将其选入DC中。在MFC中多边形有CRgn表示。多边形用来表示一个不同与矩形的区域,和矩形具有相似的操作。如:检测某点是否在内部,并操作等。此外还得到一个包含此多边形的最小矩形。下面介绍一下多边形类的成员函数:
• CreateRectRgn 由矩形创建一个多边形
• CreateEllipticRgn 由椭圆创建一个多边形
• CreatePolygonRgn 创建一个有多个点围成的多边形
• PtInRegion 某点是否在内部
• CombineRgn 两个多边形相并
• EqualRgn 两个多边形是否相等
在本节中讲演多边形的意义在于重新在窗口中作图时提高效率。因为引发窗口重绘的原因是某个区域失效,而失效的区域用多边形来表示。假设窗口大小为500*400当上方的另一个窗口从(0,0,10,10)移动到(20,20,30,30)这时(0,0,10,10)区域就失效了,而你只需要重绘这部分区域而不是所有区域,这样你程序的执行效率就会提高。
通过调用API函数int GetClipRgn( HDC hdc, HRGN hrgn)就可以得到失效区域,但是一般用不着那么精确而只需得到包含该区域的最小矩形就可以了,所以可以利用int CDC::GetClipBox( LPRECT lpRect )完成这一功能。
第三章 文档视结构
3.1 文档 视图 框架窗口间的关系和消息传送规律
在MFC中M$引入了文档-视结构的概念,文档相当于数据容器,视相当于查看数据的窗口或是和数据发生交互的窗口。(这一结构在MFC中的OLE,ODBC开发时又得到更多的拓展)因此一个完整的应用一般由四个类组成:CWinApp应用类,CFrameWnd窗口框架类,CDocument文档类,CView视类。(VC6中支持创建不带文档-视的应用)
在程序运行时CWinApp将创建一个CFrameWnd框架窗口实例,而框架窗口将创建文档模板,然后有文档模板创建文档实例和视实例,并将两者关联。一般来讲我们只需对文档和视进行操作,框架的各种行为已经被MFC安排好了而不需人为干预,这也是M$设计文档-视结构的本意,让我们将注意力放在完成任务上而从界面编写中解放出来。
在应用中一个视对应一个文档,但一个文档可以包含多个视。一个应用中只用一个框架窗口,对多文档界面来讲可能有多个MDI子窗口。每一个视都是一个子窗口,在单文档界面中父窗口即是框架窗口,在多文档界面中父窗口为MDI子窗口。一个多文档应用中可以包含多个文档模板,一个模板定义了一个文档和一个或多个视之间的对应关系。同一个文档可以属于多个模板,但一个模板中只允许定义一个文档。同样一个视也可以属于多个文档模板。(不知道我说清楚没有)
接下来看看如何在程序中得到各种对象的指针:
• 全局函数AfxGetApp可以得到CWinApp应用类指针
• AfxGetApp()->m_pMainWnd为框架窗口指针
• 在框架窗口中:CFrameWnd::GetActiveDocument得到当前活动文档指针
• 在框架窗口中:CFrameWnd::GetActiveView得到当前活动视指针
• 在视中:CView::GetDocument得到对应的文档指针
• 在文档中:CDocument::GetFirstViewPosition,CDocument::GetNextView用来遍历所有和文档关联的视。
• 在文档中:CDocument::GetDocTemplate得到文档模板指针
• 在多文档界面中:CMDIFrameWnd::MDIGetActive得到当前活动的MDI子窗口
一般来讲用户输入消息(如菜单选择,鼠标,键盘等)会先发往视,如果视未处理则会发往框架窗口。所以定义消息映射时定义在视中就可以了,如果一个应用同时拥有多个视而当前活动视没有对消息进行处理则消息会发往框架窗口。
3.2 接收用户输入
在视中接收鼠标输入:
鼠标消息是我们常需要处理的消息,消息分为:鼠标移动,按钮按下/松开,双击。利用ClassWizard可以轻松的添加这几种消息映射,下面分别讲解每种消息的处理。
WM_MOUSEMOVE对应的函数为OnMouseMove( UINT nFlags, CPoint point ),nFlags表明了当前一些按键的消息,你可以通过“位与”操作进行检测。
• MK_CONTROL Ctrl键是否被按下 Set if the CTRL key is down.
• MK_LBUTTON 鼠标左键是否被按下 Set if the left mouse button is down.
• MK_MBUTTON 鼠标中间键是否被按下 Set if the middle mouse button is down.
• MK_RBUTTON 鼠标右键是否被按下 Set if the right mouse button is down.
• MK_SHIFT Shift键是否被按下 Set if the SHIFT key is down.
point表示当前鼠标的设备坐标,坐标原点对应视左上角。
WM_LBUTTONDOWN/WM_RBUTTONDOWN(鼠标左/右键按下)对应的函数为OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。
WM_LBUTTONUP/WM_RBUTTONUP(鼠标左/右键松开)对应的函数为OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。
WM_LBUTTONDBLCLK/WM_RBUTTONDBLCLK(鼠标左/右键双击)对应的函数为OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。
下面我用一段伪代码来讲解一下这些消息的用法:
代码的作用是用鼠标拉出一个矩形
global BOOL fDowned;//是否在拉动
global CPoint ptDown;//按下位置
global CPoint ptUp;//松开位置
OnLButtonDown(UINT nFlags, CPoint point)
fDowned=TRUE;
ptUp=ptDown=point;
DrawRect();
OnMouseMove(UINT nFlags, CPoint point)
if(fDowned)
DrawRect();//恢复上次所画的矩形
ptUp=point;
DrawRect();//画新矩形
OnLButtonUp(UINT nFlags, CPoint point)
if(fDowned)
DrawRect();//恢复上次所画的矩形
ptUp=point;
DrawRect();//画新矩形
fDowned=FALSE;
DrawRect()
{//以反色屏幕的方法画出ptDown,ptUp标记的矩形
CClientDC dc(this);
MakeRect(ptDown,ptUp);
SetROP(NOT);
Rect();
坐标间转换:在以上的函数中point参数对应的都是窗口的设备坐标,我们应该将设备坐标和逻辑坐标相区别,在图32_g1由于窗口使用了滚动条,所以传入的设备坐标是对应于当前窗口左上角的坐标,没有考虑是否滚动,而逻辑坐标必须考虑滚动后对应的坐标,所以我以黄线虚拟的表达一个逻辑坐标的区域。可以看得出同一点在滚动后的坐标值是不同的,这一规则同样适用于改变了映射方式的窗口,假设你将映射方式设置为每点为0.01毫米,那么设备坐标所对应的逻辑坐标也需要重新计算。进行这种转换需要写一段代码,所幸的是系统提供了进行转换的功能DC的DPtoLP,LPtoDP,下面给出代码完成由设备坐标到逻辑坐标的转换。
图32_g1
CPoint CYourView::FromDP(CPoint point)
CClientDC dc(this);
CPoint ptRet=point;
dc.PrepareDC();//必须先准备DC,这在使用滚动时让DC重新计算坐标
//如果你作图设置了不同的映射方式,则在下面需要设置
dc.SetMapMode(...)
dc.DPtoLP(&ptRet);//DP->LP进行转换
return ptRet;
在图32_g1中以蓝线标记的是屏幕区域,红线标记的客户区域。利用ScreenToClient,ClientToScreen可以将坐标在这两个区域间转换。
在视中接收键盘输入:
键盘消息有三个:键盘被按下/松开,输入字符。其中输入字符相当于直接得到用户输入的字符这在不需要处理按键细节时使用,而键盘被按下/松开在按键状态改变时发送。
WM_CHAR对应的函数为OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ),其中nChar为被按下的字符,nRepCnt表明在长时间为松开时相当于的按键次数,nFlags中的不同位代表不同的含义,在这里一般不使用。
WM_KEYDOWN/WM_KEYUP所对应的函数为OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags )nChar代表按键的虚拟码值,如VK_ALT为ALT键,VK_CONTROL为Ctrl键。nFlags各位的含义如下:
Value Description
0? Scan code (OEM-dependent value).
8 Extended key, such as a function key or a key on the numeric keypad (1 if it is an extended key).
9?0 Not used.
11?2 Used internally by Windows.
13 Context code (1 if the ALT key is held down while the key is pressed; otherwise 0).
14 Previous key state (1 if the key is down before the call, 0 if the key is up).
15 Transition state (1 if the key is being released, 0 if the key is being pressed).
3.3 使用菜单
利用菜单接受用户命令是一中很简单的交互方法,同时也是一种很有效的方法。通常菜单作为一中资源存储在文件中,因此我们可以在设计时就利用资源编辑器设计好一个菜单。关于使用VC设计菜单我就不再多讲了,但你在编写菜单时应该尽量在属性对话框的底部提示(Prompt)处输入文字,这虽然不是必要的,但MFC在有状态栏和工具条的情况下会使用该文字,文字的格式为“状态栏出说明\n工具条提示”。
图33_g1
我们要面临的任务是如何知道用户何时选择了菜单,他选的是什么菜单项。当用户选择了一个有效的菜单项时系统会向应用发送一个WM_COMMAND消息,在消息的参数中表明来源。在MFC中我们只需要进行一次映射,将某一菜单ID映射到一处理函数,图33_g2。在这里我们在CView的派生类中处理菜单消息,同时我对同一ID设置两个消息映射,接下来将这两种映射的作用。
图33_g2
ON_COMMAND 映射的作用为在用户选择该菜单时调用指定的处理函数。如:ON_COMMAND(IDM_COMMAND1, OnCommand1)会使菜单被选择时调用OnCommand1成员函数。
ON_UPDATE_COMMAND_UI(IDM_COMMAND1, OnUpdateCommand1) 映射的作用是在菜单被显示时通过调用指定的函数来进行确定其状态。在这个处理函数中你可以设置菜单的允许/禁止状态,其显示字符串是什么,是否在前面打钩。函数的参数为CCmdUI* pCmdUI,CCmdUI是MFC专门为更新命令提供的一个类,你可以调用
• Enable 设置允许/禁止状态
• SetCheck 设置是否在前面打钩
• SetText 设置文字
下面我讲解一个例子:我在CView派生类中有一个变量m_fSelected,并且在视中处理两个菜单的消息,当IDM_COMMAND1被选时,对m_fSelected进行逻辑非操作,当IDM_COMMAND2被选中时出一提示;同时IDM_COMMAND1根据m_fSelected决定菜单显示的文字和是否在前面打上检查符号,IDM_COMMAND2根据m_fSelected的值决定菜单的允许/禁止状态。下面是代码和说明:下载示例代码 17K
void CMenuDView::OnCommand1()
m_fSelected=!m_fSelected;
TRACE("command1 selected\n");
void CMenuDView::OnUpdateCommand1(CCmdUI* pCmdUI)
pCmdUI->SetCheck(m_fSelected);//决定检查状态
pCmdUI->SetText(m_fSelected?"当前被选中":"当前未被选中");//决定所显示的文字
void CMenuDView::OnUpdateCommand2(CCmdUI* pCmdUI)
{//决定是否为允许
pCmdUI->Enable(m_fSelected);
void CMenuDView::OnCommand2()
{//选中时给出提示
AfxMessageBox("你选了command2");
接下来再讲一些通过代码操纵菜单的方法,在MFC中有一个类CMenu用来处理和菜单有关的功能。在生成一个CMenu对象时你需要从资源中装如菜单,通过调用BOOL CMenu::LoadMenu( UINT nIDResource )进行装入,然后你就可以对菜单进行动态的修改,所涉及到的函数有:
• CMenu* GetSubMenu( int nPos ) 一位置得到子菜单的指针,因为一个CMenu对象只能表示一个弹出菜单,如果菜单中的某一项也为弹出菜单,就需要通过该函数获取指针。
• BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ) 在末尾添加一项,nFlag为MF_SEPARATOR表示增加一个分隔条,这样其他两个参数将会被忽略;为MF_STRING表示添加一个菜单项uIDNewItem为该菜单的ID命令值;为MF_POPUP表示添加一个弹出菜单项,这时uIDNewItem为另一菜单的句柄HMENU。lpszNewItem为菜单文字说明。
• BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于在指定位置插入一菜单,位置由变量nPosition指明。如果nFlags包含MF_BYPOSITION则表明插入在nPosition位置,如果包含MF_BYCOMMAND表示插入在命令ID为nPosition的菜单处。
• BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于修改某一位置的菜单,如果nFlags包含MF_BYPOSITION则表明修改nPosition位置的菜单,如果包含MF_BYCOMMAND表示修改命令ID为nPosition处的菜单。
• BOOL RemoveMenu( UINT nPosition, UINT nFlags )用于删除某一位置的菜单。如果nFlags包含MF_BYPOSITION则表明删除nPosition位置的菜单,如果包含MF_BYCOMMAND表示删除命令ID为nPosition处的菜单。
• BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ) 和 BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp )可以添加一位图菜单,但这样的菜单在选中时只是反色显示,并不美观。
视图中是没有菜单的,在框架窗口中才有,所以只有用AfxGetApp()->m_pMainWnd->GetMenu()才能得到应用的菜单指针。
最后我讲一下如何在程序中弹出一个菜单,你必须先装入一个菜单资源,你必需得到一个弹出菜单的指针然后调用BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL )弹出菜单,你需要指定(x,y)为菜单弹出的位置,pWnd为接收命令消息的窗口指针。下面有一段代码说明方法,下载示例代码 17K。当然为了处理消息你应该在pWnd指明的窗口中对菜单命令消息进行映射。
CMenu menu;
menu.LoadMenu(IDR_POPUP);
CMenu* pM=menu.GetSubMenu(0);
CPoint pt;
GetCursorPos(&pt);
pM->TrackPopupMenu(TPM_LEFTALIGN,pt.x,pt.y,this);
另一种做法是通过CMenu::CreatePopupMenu()建立一个弹出菜单,然后使用TrackPopupMenu弹出菜单。使用CreatePopupMenu创建的菜单也可以将其作为一个弹出项添加另一个菜单中。下面的伪代码演示了如何创建一个弹出菜单并进行修改后弹出:
CMenu menu1,menu2;
menu1.CreatePopupMenu
menu1.InsertMenu(1)
menu1.InsertMenu(2)
menu1.InsertMenu(3)
menu2.CreatePopupMenu
menu2.AppendMenu(MF_POPUP,1,menu1.Detach()) 将弹出菜单加入 or InsertMenu...
menu2.InsertMenu("string desc");
menu.TrackPopupMenu(...)
3.4 文档,视,框架之间相互作用
一般来说用户的输入/输出基本都是通过视进行,但一些例外的情况下可能需要和框架直接发生作用,而在多视的情况下如何在视之间传递数据。
在使用菜单时大家会发现当一个菜单没有进行映射处理时为禁止状态,在多视的情况下菜单的状态和处理映射是和当前活动视相联系的,这样MFC可以保证视能正确的接收到各种消息,但有时候也会产生不便。有一个解决办法就是在框架中对消息进行处理,这样也可以保证当前文档可以通过框架得到当前消息。
在用户进行输入后如何使视的状态得到更新?这个问题在一个文档对应一个视图时是不存在的,但是现在有一个文档对应了两个视图,当在一个视上进行了输入时如何保证另一个视也得到通知呢?MFC的做法是利用文档来处理的,因为文档管理着当前和它联系的视,由它来通知各个视是最合适的。让我们同时看两个函数:
• void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
• void CDocument::UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL )
当文档的UpdateAllViews被调用时和此文档相关的所有视的OnUpdate都会被调用,而参数lHint和pHint都会被传递。这样一来发生改变视就可以通知其他的兄弟了。那么还有一个问题:如何在OnUpdate中知道是那个视图发生了改变呢,这就可以利用pHint参数,只要调用者将this指针赋值给参数就可以了,当然完全可以利用该参数传递更复杂的结构。
视的初始化,当一个文档被打开或是新建一个文档时视图的CView::OnInitialUpdate()会被调用,你可以通过重载该函数对视进行初始化,并在结束前调用父类的OnInitialUpdate,因为这样可以保证OnUpdate会被调用。
文档中内容的清除,当文档被关闭时(比如退出或是新建前上一个文档清除)void CDocument::DeleteContents ()会被调用,你可以通过重载该函数来进行清理工作。
在单文档结构中上面两点尤其重要,因为软件运行文档对象和视对象只会被产生并删除一次。所以应该将上面两点和C++对象构造和构析分清楚。
最后将一下文档模板(DocTemplate)的作用,文档模板分为两类单文档模板和多文档模板,分别由CSingleDocTemplate和CMultiDocTemplate表示,模板的作用在于记录文档,视,框架之间的对应关系。还有一点就是模板可以记录应用程序可以打开的文件的类型,当打开文件时会根据文档模板中的信息选择正确的文档和视。模板是一个比较抽想的概念,一般来说是不需要我们直接进行操作的。
当使用者通过视修改了数据时,应该调用GetDocument()->SetModifiedFlag(TRUE)通知文档数据已经被更新,这样在关闭文档时会自动询问用户是否保存数据。
好象这一节讲的有些乱,大家看后有什么想法和问题请在VCHelp论坛上留言,我会尽快回复并且会对本节内容重新整理和修改。
3.5 利用序列化进行文件读写
在很多应用中我们需要对数据进行保存,或是从介质上读取数据,这就涉及到文件的操作。我们可以利用各种文件存取方法完成这些工作,但MFC中也提供了一种读写文件的简单方法——“序列化”。序列化机制通过更高层次的接口功能向开发者提供了更利于使用和透明于字节流的文件操纵方法,举一个例来讲你可以将一个字串写入文件而不需要理会具体长度,读出时也是一样。你甚至可以对字符串数组进行操作。在MFC提供的可自动分配内存的类的支持下你可以更轻松的读/写数据。你也可以根据需要编写你自己的具有序列化功能的类。
序列化在最低的层次上应该被需要序列化的类支持,也就是说如果你需要对一个类进行序列化,那么这个类必须支持序列化。当通过序列化进行文件读写时你只需要该类的序列化函数就可以了。
怎样使类具有序列化功能呢?你需要以下的工作:
• 该类从CObject派生。
• 在类声明中包括DECLARE_SERIAL宏定义。
• 提供一个缺省的构造函数。
• 在类中实现Serialze函数
• 使用IMPLEMENT_SERIAL指明类名和版本号
下面的代码建立了一个简单身份证记录的类,同时也能够支持序列化。
struct strPID
char szName[10];
char szID[16];
struct strPID* pNext;
class CAllPID : public CObject
public:
DECLARE_SERIAL(CAllPID)
CAllPID();
~CAllPID();
public:// 序列化相关
struct strPID* pHead;
//其他的成员函数
void Serialize(CArchive& ar);
in CPP
IMPLEMENT_SERIAL(CAllPID,CObject,1) // version is 1,版本用于读数据时的检测
void CAllPID::Serialize(CArchive& ar)
int iTotal;
if(ar.IsStoring())
{//保存数据
iTotal=GetTotalID();//得到链表中的记录数量
arr<26;i++)
ar<>iTotal;
for(int i=0;i26;j++)
ar>>*(((BYTE*)pID)+j);//读一个strPID中所有的数据
//修改链表
当然上面的代码很不完整,但已经可以说明问题。这样CAllPID就是一个可以支持序列化的类,并且可以根据记录的数量动态分配内存。在序列化中我们使用了CArchive类,该类用于在序列化时提供读写支持,它重载了<>运算符号,并且提供Read和Write函数对数据进行读写。
下面看看如何在文档中使用序列化功能,你只需要修改文档类的Serialize(CArchive& ar)函数,并调用各个进行序列化的类的Serial进行数据读写就可以了。当然你也可以在文档类的内部进行数据读写,下面的代码利用序列化功能读写数据:
class CYourDoc : public CDocument
void Serialize(CArchive& ar);
CString m_szDesc;
CAllPID m_allPID;
......
void CYourDoc::Serialize(CArchive& ar)
if (ar.IsStoring())
{//由于CString对CArchive定义了<>操作符号,所以可以直接利用>>和<<
ar<>m_szDesc;
m_allPID.Serialize(ar);//调用数据类的序列化函数
3.6 MFC中所提供的各种视类介绍
MFC中提供了丰富的视类供开发者使用,下面对各个类进行介绍:
CView类是最基本的视类只支持最基本的操作。
CScrollView类提供了滚动的功能,你可以利用void CScrollView::SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault )设置滚动尺寸,和坐标映射模式。但是在绘图和接收用户输入时需要对坐标进行转换。请参见3.2 接收用户输入。
CFormView类提供用户在资源文件中定义界面的能力,并可以将子窗口和变量进行绑定。通过UpdateData函数让数据在变量和子窗口间交换。
CTreeView类利用TreeCtrl界面作为视界面,通过调用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CTreeCtrl的引用。
CListView类利用ListCtrl界面作为视界面,通过调用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CListCtrl的引用。
CEditView类利用Edit接收用户输入,它具有输入框的一切功能。通过调用CEdit& CEditView::GetEditCtrl( ) const得到Edit&的引用。void CEditView::SetPrinterFont( CFont* pFont )可以设置打印字体。
CRichEditView类作为Rich Text Edit(富文本输入)的视类,提供了可以按照格式显示文本的能力,在使用时需要CRichEditDoc的支持。
第四章 窗口控件
4.1 Button
按钮窗口(控件)在MFC中使用CButton表示,CButton包含了三种样式的按钮,Push Button,Check Box,Radio Box。所以在利用CButton对象生成按钮窗口时需要指明按钮的风格。
创建按钮:BOOL CButton::Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );其中lpszCaption是按钮上显示的文字,dwStyle为按钮风格,除了Windows风格可以使用外(如WS_CHILD|WS_VISUBLE|WS_BORDER)还有按钮专用的一些风格。
• BS_AUTOCHECKBOX 检查框,按钮的状态会自动改变 Same as a check box, except that a check mark appears in the check box when the user selects the box; the check mark disappears the next time the user selects the box.
• BS_AUTORADIOBUTTON 圆形选择按钮,按钮的状态会自动改变 Same as a radio button, except that when the user selects it, the button automatically highlights itself and removes the selection from any other radio buttons with the same style in the same group.
• BS_AUTO3STATE 允许按钮有三种状态即:选中,未选中,未定 Same as a three-state check box, except that the box changes its state when the user selects it.
• BS_CHECKBOX 检查框 Creates a small square that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style).
• BS_DEFPUSHBUTTON 默认普通按钮 Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option).
• BS_LEFTTEXT 左对齐文字 When combined with a radio-button or check-box style, the text appears on the left side of the radio button or check box.
• BS_OWNERDRAW 自绘按钮 Creates an owner-drawn button. The framework calls the DrawItem member function when a visual aspect of the button has changed. This style must be set when using the CBitmapButton class.
• BS_PUSHBUTTON 普通按钮 Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button.
• BS_RADIOBUTTON 圆形选择按钮 Creates a small circle that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). Radio buttons are usually used in groups of related but mutually exclusive choices.
• BS_3STATE 允许按钮有三种状态即:选中,未选中,未定 Same as a check box, except that the box can be dimmed as well as checked. The dimmed state typically is used to show that a check box has been disabled.
rect为窗口所占据的矩形区域,pParentWnd为父窗口指针,nID为该窗口的ID值。
获取/改变按钮状态:对于检查按钮和圆形按钮可能有两种状态,选中和未选中,如果设置了BS_3STATE或BS_AUTO3STATE风格就可能出现第三种状态:未定,这时按钮显示灰色。通过调用int CButton::GetCheck( ) 得到当前是否被选中,返回0:未选中,1:选中,2:未定。调用void CButton::SetCheck( int nCheck );设置当前选中状态。
处理按钮消息:要处理按钮消息需要在父窗口中进行消息映射,映射宏为ON_BN_CLICKED( id, memberFxn )id为按钮的ID值,就是创建时指定的nID值。处理函数原型为afx_msg void memberFxn( );
4.2 Static Box
静态文本控件的功能比较简单,可作为显示字符串,图标,位图用。创建一个窗口可以使用成员函数:
BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对静态控件指明专门的风格。
• SS_CENTER,SS_LEFT,SS_RIGHT 指明字符显示的对齐方式。
• SS_GRAYRECT 显示一个灰色的矩形
• SS_NOPREFIX 如果指明该风格,对于字符&将直接显示,否则&将作为转义符,&将不显示而在其后的字符将有下划线,如果需要直接显示&必须使用&&表示。
• SS_BITMAP 显示位图
• SS_ICON 显示图标
• SS_CENTERIMAGE 图象居中显示
控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。
控制显示的图标利用成员函数SetIcon/GetIcon用于设置/得到当前显示的图标。
控制显示的位图利用成员函数SetBitmap/GetBitmap用于设置/得到当前显示的位图。下面一段代码演示如何创建一个显示位图的静态窗口并设置位图
CStatic* pstaDis=new CStatic;
pstaDis->Create("",WS_CHILD|WS_VISIBLE|SS_BITMAP|SSCENTERIMAGE,
CRect(0,0,40,40),pWnd,1);
CBitmap bmpLoad;
bmpLoad.LoadBitmap(IDB_TEST);
pstaDis->SetBitmap(bmpLoad.Detach());
4.3 Edit Box
Edit窗口是用来接收用户输入最常用的一个控件。创建一个输入窗口可以使用成员函数:
BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对输入控件指明专门的风格。
• ES_AUTOHSCROLL,ES_AUTOVSCROLL 指明输入文字超出显示范围时自动滚动。
• ES_CENTER,ES_LEFT,ES_RIGHT 指定对齐方式
• ES_MULTILINE 是否允许多行输入
• ES_PASSWORD 是否为密码输入框,如果指明该风格则输入的文字显示为*
• ES_READONLY 是否为只读
• ES_UPPERCASE,ES_LOWERCASE 显示大写/小写字符
控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。
通过GetLimitText/SetLimitText可以得到/设置在输入框中输入的字符数量。
由于在输入时用户可能选择某一段文本,所以通过void CEdit::GetSel( int& nStartChar, in
FILE *fp=NULL; //创建文件指针
fp=fopen("/root/code/file_rw/data","r"); //linux系统下注意 /root
//判断文件是否成功打开
PERL语言编程Perl 是一种能完成任务的语言。 当然,如果你的工作就是写程序,那么从理论上来讲,你可以使用任何“完整”的计算机语言来完成任务。但是从我们的经验来看,计算机语言的区别很大程度上不在它能干什么,而是在它做事情的时候是否简单。从一个极端来说,那些所谓的第四代语言做某些事情的时候相当容易,但是做其它一些事情的时候几乎不可能。从另外一个极端来看,那些所谓的工业级的语言在做任何事情的时候都几乎一样困难。 Perl 是不同的语言。从一开始,Perl 就设计成可以把简单工作简单化,同时又不失去处理困难问题能力的语言。 那什么是“简单工作”呢?当然就是那些你每天都要干的。你需要一种可以很容易操作数字,文本,文件和目录,计算机和网络,特别是程序的语言。这种语言应该很容易运行外部的程序并且扫描这些程序的输出获取感兴趣的东西。而且它还应该很容易能把这些你感兴趣的东西交给其它程序做特殊的处理。当然,这种语言还应该很容易在任何现代的操作系统上可以移植地编译和运行。 Perl 做到了上述所有需求,甚至更多。 Perl 最初是当做一种 Unix 的胶水语言设计的,但是她早就移植到大多数其它操作系统里了。因为 Perl 几乎可以在任何地方运行,所以 Perl 可以说是当今最具有移植性的编程环境。要想写可移植的 C/C++ 程序,你得在程序里加上一大堆 #ifdef 标签来区分不同的系统。要想写可移植的 Java 程序,你必须理解每种新的 Java 实现的特质。要想写可移植的 shell,你可能要记住每条命令在每种操作系统上的语法,走运的时候你可能可以找到一些公共的东西。而要想写可移植的 Visual Basic 程序,那么你只需要对“移植”有个更灵活的定义就可以了。 我们很高兴的是 Perl 避免了所有这些问题,同时还保留了这些语言中的许多优点,同时还有一些自己的特色。Perl 的特色来自许多方面:它的特性集的工具,Perl 社区的创造性,以及开源运动的大环境。不过,许多这些特性都是混合的东西;Perl 的身世复杂,它总是把事物看成是优点的不同方面,而不是弱点。Perl 是“背黑锅我来”的语言。如果你觉得自己陷入一团乱麻之中,非常渴望自由,那么请使用 Perl。 Perl 是跨文化的。Perl 的爆炸性增长很大程度上是因为那些前 Unix 系统程序员的渴望,他们希望从他们的“老家”带着尽可能多的东西。对于他们而言,Perl 是可移植的 Unix 文化蒸馏器,是"此路不通"的沙漠中的绿洲。从另外一个角度来看,Perl 还可以从另外一个方向运转:在 Windows 上工作的 web 设计者通常会非常开心地发现他们的 Perl 程序可以不加修改地在 Unix 服务器上跑。 尽管 Perl 在系统程序员和 web 设计师中间非常流行,但这只是因为是他们最早发现 Perl 的,Perl 可以用于更广泛的用途。从 Perl 最早的文本处理语言开始,它已经发展成为一种非常复杂的,通用的编程语言,以及完整的开发环境,包括调试器,调节器,交叉引用,编译器,库,语法提示编辑器,以及所有其它“真正”的编程语言所具有的所有挂勾,只要你需要。当然这些东西都是让我们可能处理难的问题的东西,而且很多其它语言也可以做到这一点。Perl 之所以成为 Perl 是因为它从来不会因为保持简单事情简单化而丢失其他方面的特性。 因为 Perl 既强大又好用,所以它被广泛地用于日常生活的方方面面,从宇航工程到分子生物学,从数学到语言学,从图形处理到文档处理,从数据库操作到网络管理。很多人用 Perl 进行快速处理那些很难分析或转换的大批量数据,不管你是处理 DNA 序列,网页,还是猪肚皮的未来都无所谓。实际上,在 Perl 社区有一个笑话就是,下次股市大崩盘就很有可能是呢个家伙写的脚本里头有臭虫造成的。(不过,乐观点来看就是,任何还在失业的股票分析师仍然有可以利用的技巧。) Perl 的成功有许多原因。Perl 早在开源软件的名字出现之前就已经是一个成功的开源项目了。Perl 是自由的,并将永远自由下去。你可以在任何合适的场合使用 Perl,只需要遵守一个非常自由的版权就可以了。如果你在从事商业活动并且还想使用 Perl,那么用就是了。你可以把 Perl 嵌入到你写的商业软件中而不需要支付任何费用也没有任何限制。如果你碰上一个 Perl 社区解决不了的问题,那你也还有最后的一招:源程序本身。 Perl 社区不会在“升级”的伪装下租给你它们的商业秘密。而且 P
序自从1978年《C程序设计语言》出版以来,计算机界经历了一场革命。大型计算机变得更大;而个人计算机的能力可以和十年前的主流计算机相媲美。在这段时间中,C语言也在悄悄地改变,并且早已超出了仅仅作为UNIX操作系统的语言的范畴。 C的适用范围的扩大、在这些年中语言的改变和各个组织开发的超出其预定内容的编译器,所有这一切要求对C语言有一个比本书第1版更精确和更新的定义。在1983年,美国国家标准协会(ANSI)成立了一个委员会,它的目标是产生“一个无二义性的、独立于机器的C语言的定义”,同时仍保持其精髓。其结果就是C的ANSI标准。此标准规范了一些在本书第1版中提示过但没有描述的结构,特别是结构赋值和枚举。它提供了一种新的函数声明的形式,允许在使用中对函数的定义进行交叉检查。它说明了一个标准库和一个完成输入输出、内存管理和字符串操作等类似任务的函数集扩充。此标准明确地说明了原始定义没有指出的一些特性的行为。同时,此标准还明确地说明了语言中的哪些部分依然依赖于机器。《C程序设计语言》的第2版描述的是ANSI标准定义的C语言。尽管我们已经指出语言中的多种革新,但我们还是决定不用新的形式来写。对于大部分内容并没有多大的差别,最明显的改变是新形式的函数声明和定义。现代编译器已能支持此标准的大部分特性。我们尽力保持本书第1版的简洁性。C不是一个大型语言,也不需要一本很厚的书来描述。我们改进了对典型特性的阐述,如指针,它是C程序设计的中心。我们对以前的例子进行了提炼,并在几章中增加了新的例子。例如,我们用程序来处理复杂的声明,这些程序将声明转换为单词或反之。像以前一样,所有例子的文本都以机器可读的形式直接经过测试。附录A是参考手册,不是标准,但我们的目的是希望用较少的篇幅表述标准的要点。它是要对于程序员来说容易理解,而不是提供给编译器实现者的定义—这正是标准承担的角色。附录B是对标准库提供的功能的总结。附录C是对以前版本的变动的小结。就像我们在第1版序中所说的,“当对C的经验增加时它显得很好用”。经过十几年的实践,我们仍然这么认为。我们希望这本书能帮助你学习并使用好C语言。非常感谢那些帮助我们完成本书这一版的朋友们。Jon Bentley、Doug Gwyn、Doug McIlroy、Peter Nelson和Rob Pike几乎对本书手稿的每一页都提出了建议。我们非常感谢Al Aho、Dennis Allison、Joe Campbell、G.R.Emlin、Karen Fortgang、Allen Holub、Andrew Hume、Dave Kristol、John Linderman、Dave Prosser、Gene Spafford和Chris Van Wyk,他们仔细地阅读了本书。我们也收到了来自Bill Cheswick、Mark Kernighan、Andy Koenig、Robin Lake、Tom London、Jim Reeds、Clovis Tondo和Peter Weinberger的有益的建议。Dave Prosser回答了很多关于ANSI标准的细节问题。我们广泛地使用了Bjarne Stroustrup的C++的翻译程序来部分测试我们的程序。Dave Kristol为我们提供了一个ANSI C编译器进行最终测试。Rich Drechsler帮助我们进行了排版。诚挚地感谢每一个人。 Brian W. Kernighan Dennis M. Ritchie
《你必须知道的495个C语言问题》以问答的形式组织内容,讨论了学习或使用C语言的过程中经常遇到的一些问题。书中列出了C用户经常问的400多个经典问题,涵盖了初始化、数组、指针、字符串、内存分配、库函数、C预处理器等各个方面的主题,并分别给出了解答,而且结合代码示例阐明要点。
《你必须知道的495个C语言问题》结构清晰,讲解透彻,是各高校相关专业C语言课程很好的教学参考书,也是各层次C程序员的优秀实践指南。
--------------------------------------------------------------------------------
C是一门简洁精妙的语言,掌握基本语法容易,真正能够自如运用,就不那么简单了。你难免会遇到各种各样的问题,有些可能让你百思不得其解,甚至翻遍图书馆,也找不到问题的答案。
《你必须知道的495个C语言问题》的出版填补了这一空白。许多知识点的阐述都是其他资料中所没有的,弥足珍贵。
涵盖C99标准
~第1章 声明和初始化 1
基本类型 1
1.1 我该如何决定使用哪种整数类型? 1
1.2 为什么不精确定义标准类型的大小? 2
1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16和int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 2
1.4 新的64位机上的64位类型是什么样的? 3
指针声明 3
1.5 这样的声明有什么问题?char *p1, p2; 我在使用p2的时候报错了。 3
1.6 我想声明一个指针,并为它分配一些空间,但却不行。这样的代码有什么问题?char *p; *p=malloc(10); 4
声明风格 4
1.7 怎样声明和定义全局变量和函数最好? 4
1.8 如何在C中实现不透明(抽象)数据类型? 5
1.9 如何生成“半全局变量”,就是那种只能被部分源文件中的部分函数访问的变量? 5
存储类型 6
1.10 同一个静态(static)函数或变量的所有声明都必须包含static存储类型吗? 6
1.11 extern在函数声明中是什么意思? 6
1.12 关键字auto到底有什么用途? 7
类型定义(typedef) 7
1.13 对于用户定义类型,typedef 和#define有什么区别? 7
1.14 我似乎不能成功定义一个链表。我试过typedef struct{char *item; NODEPTR next;}* NODEPTR; 但是编译器报了错误信息。难道在C语言中结构不能包含指向自己的指针吗? 7
1.15 如何定义一对相互引用的结构? 9
1.16 Struct{ } x1;和typedef struct{ } x2; 这两个声明有什么区别? 10
1.17 “typedef int(*funcptr)();”是什么意思? 10
const 限定词 10
1.18 我有这样一组声明:typedef char *charp; const charp p; 为什么是p而不是它指向的字符为const? 10
1.19 为什么不能像下面这样在初始式和数组维度值中使用const值?const int n=5; int a[n]; 10
1.20 const char *p、char const *p和char *const p有什么区别? 10
复杂的声明 11
1.21 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组? 11
1.22 如何声明返回指向同类型函数的指针的函数?我在设计一个状态机,用函数表示每种状态,每个函数都会返回一个指向下一个状态的函数的指针。可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数……,如此往复,以至无穷。 12
数组大小 13
1.23 能否声明和传入数组大小一致的局部数组,或者由其他参数指定大小的参数数组? 13
1.24 我在一个文件中定义了一个extern数组,然后在另一个文件中使用,为什么sizeof取不到数组的大小? 13
声明问题 14
1.25 函数只定义了一次,调用了一次,但编译器提示非法重声明了。 14
*1.26 main的正确定义是什么?void main正确吗? 15
1.27 我的编译器总在报函数原型不匹配的错误,可我觉得没什么问题。这是为什么? 15
1.28 文件中的第一个声明就报出奇怪的语法错误,可我看没什么问题。这是为什么? 15
1.29 为什么我的编译器不允许我定义大数组,如double array[256][256]? 15
命名空间 15
1.30 如何判断哪些标识符可以使用,哪些被保留了? 15
初始化 18
1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 18
1.32 下面的代码为什么不能编译? intf(){char a[]="Hello, world!";} 18
*1.33 下面的初始化有什么问题?编译器提示“invalid initializers ”或其他信息。char *p=malloc(10); 19
1.34 char a[]= "string literal";和char *p="string literal"; 初始化有什么区别?当我向p[i] 赋值的时候,我的程序崩溃了。 19
1.35 char a{[3]}= "abc"; 是否合法? 20
1.36 我总算弄清楚函数指针的声明方法了,但怎样才能初始化呢? 20
1.37 能够初始化联合吗? 20
第2章 结构、联合和枚举 21
结构声明 21
2.1 struct x1{ };和typedef struct{ }x2; 有什么不同? 21
2.2 这样的代码为什么不对?struct x{ }; x thestruct; 22
2.3 结构可以包含指向自己的指针吗? 22
2.4 在C语言中用什么方法实现抽象数据类型最好? 22
*2.5 在C语言中是否有模拟继承等面向对象程序设计特性的好方法? 22
2.6 为什么声明extern f(struct x *p); 给我报了一个晦涩难懂的警告信息? 23
2.7 我遇到这样声明结构的代码:struct name {int namelen; char namestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素,namelen记录了元素个数。它是怎样工作的?这样是合法的和可移植的吗? 23
2.8 我听说结构可以赋给变量也可以对函数传入和传出。为什么K&R1却明确说明不能这样做? 25
2.9 为什么不能用内建的==和!=操作符比较结构? 26
2.10 结构传递和返回是如何实现的? 26
2.11 如何向接受结构参数的函数传入常量值?怎样创建无名的中间的常量结构值? 26
2.12 怎样从/向数据文件读/写结构? 27
结构填充 27
2.13 为什么我的编译器在结构中留下了空洞?这导致空间浪费而且无法与外部数据文件进行“二进制”读写。能否关掉填充,或者控制结构域的对齐方式? 27
2.14 为什么sizeof返回的值大于结构大小的期望值,是不是尾部有填充? 28
2.15 如何确定域在结构中的字节偏移量? 28
2.16 怎样在运行时用名字访问结构中的域? 29
2.17 C语言中有和Pascal的with等价的语句吗? 29
2.18 既然数组名可以用作数组的基地址,为什么对结构不能这样? 29
2.19 程序运行正确,但退出时却“core dump ”(核心转储)了,怎么回事? 29
联合 30
2.20 结构和联合有什么区别? 30
2.21 有办法初始化联合吗? 30
2.22 有没有一种自动方法来跟踪联合的哪个域在使用? 30
枚举 31
2.23 枚举和一组预处理的#define有什么不同? 31
2.24 枚举可移植吗? 31
2.25 有什么显示枚举值符号的容易方法吗? 31
位域 31
2.26 一些结构声明中的这些冒号和数字是什么意思? 31
2.27 为什么人们那么喜欢用显式的掩码和位操作而不直接声明位域? 32
第3章 表达式 33
求值顺序 33
3.1 为什么这样的代码不行?a[i]= i++; 33
3.2 使用我的编译器,下面的代码int i= 7; printf("%d\n", i++ * i++); 打印出49。不管按什么顺序计算,难道不该是56吗? 33
3.3 对于代码int i=3; i=i++; 不同编译器给出不同的i值,有的为3,有的为4,哪个是正确的? 34
*3.4 有这样一个巧妙的表达式:a^= b^= a^= b; 它不需要临时变量就可以交换a和b的值。 34
3.5 可否用显式括号来强制执行我所需要的计算顺序并控制相关的副作用?就算括号不行,操作符优先级是否能够控制计算顺序呢? 35
3.6 可是&&和||操作符呢?我看到过类似while((c = getchar()) != EOF && c != '\n')的代码…… 35
3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个表达式的结果,则右边的表达式不会被求值? 36
3.8 为什么表达式printf("%d %d", f1(), f2()); 先调用了f2?我觉得逗号表达式应该确保从左到右的求值顺序。 36
3.9 怎样才能理解复杂表达式并避免写出未定义的表达式?“序列点”是什么? 36
3.10 在a[i] = i++;中,如果不关心a[]的哪一个分量会被写入,这段代码就没有问题,i也的确会增加1,对吗? 38
3.11 人们总是说i=i++的行为是未定义的。可我刚刚在一个ANSI编译器上尝试过,其结果正如我所期望的。 38
3.12 我不想学习那些复杂的规则,怎样才能避免这些未定义的求值顺序问题呢? 38
其他的表达式问题 39
*3.13 ++i和i++有什么区别? 39
3.14 如果我不使用表达式的值,那我应该用i++还是++i来做自增呢? 39
3.15 我要检查一个数是不是在另外两个数之间,为什么if(a b c)不行? 40
3.16 为什么如下的代码不对?int a=1000, b=1000; long int c=a * b; 40
3.17 为什么下面的代码总是给出0?double degC, degF; degC= 5.0 / 9 * (degF - 32); 40
3.18 需要根据条件把一个复杂的表达式赋给两个变量中的一个。可以用下面这样的代码吗?((condition) ? a : b)= complicated_expression; 41
3.19 我有些代码包含这样的表达式。a ? b=c : d 有些编译器可以接受,有些却不能。为什么? 41
保护规则 42
3.20 “semantics of‘’change in ANSI C”的警告是什么意思? 42
3.21 “无符号保护”和“值保护”规则的区别在哪里? 42
第4章 指针 45
基本的指针应用 45
4.1 指针到底有什么好处? 45
4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char *p; *p =malloc(10); 45
4.3 *p++自增p还是p所指向的变量? 46
指针操作 46
4.4 我用指针操作int数组的时候遇到了麻烦。 46
4.5 我有一个char *型指针碰巧指向一些int型变量,我想跳过它们。为什么((int *)p)++; 这样的代码不行? 47
4.6 为什么不能对void *指针进行算术操作? 47
4.7 我有些解析外部结构的代码,但是它却崩溃了,显示出了“unaligned access”(未对齐的访问)的信息。这是什么意思? 47
作为函数参数的指针 47
4.8 我有个函数,它应该接受并初始化一个指针:void f(int *ip){ static int dummy = 5; ip = &dummy;}但是当我如下调用时:int *ip; f(ip); 调用者的指针没有任何变化。 47
4.9 能否用void ** 通用指针作为参数,使函数模拟按引用传递参数? 48
4.10 我有一个函数extern intf(int *); ,它接受指向int型的指针。我怎样用引用方式传入一个常数?调用f(&5);似乎不行。 49
4.11 C语言可以“按引用传参”吗? 50
其他指针问题 50
4.12 我看到了用指针调用函数的不同语法形式。到底怎么回事? 50
4.13 通用指针类型是什么?当我把函数指针赋向void *类型的时候,编译通不过。 51
4.14 怎样在整型和指针之间进行转换?能否暂时把整数放入指针变量中,或者相反? 51
*4.15 我怎样把一个int变量转换为char *型?我试了类型转换,但是不行。 52
第5章 空指针 53
空指针和空指针常量 53
5.1 臭名昭著的空指针到底是什么? 53
5.2 怎样在程序里获得一个空指针? 54
5.3 用缩写的指针比较“if(p)”检查空指针是否有效?如果空指针的内部表达不是0会怎样? 55
NULL 宏 56
5.4 NULL是什么,它是怎么定义的? 56
5.5 在使用非零位模式作为空指针的内部表示的机器上,NULL 是如何定义的? 56
5.6 如果NULL定义成#define NULL((char *)0) ,不就可以向函数传入不加转换的NULL 了吗? 57
5.7 我的编译器提供的头文件中定义的NULL为0L。为什么? 57
5.8 NULL可以合法地用作函数指针吗? 57
5.9 如果NULL和0作为空指针常量是等价的,那我到底该用哪一个呢? 58
5.10 但是如果NULL的值改变了,比如在使用非零内部空指针的机器上,用NULL(而不是0)
不是更好吗? 58
5.11 我曾经使用过一个编译器,不使用NULL就不能编译。 58
5.12 我用预处理宏#define Nullptr(type)(type *)0帮助创建正确类型的空指针。 59
回顾 59
5.13 这有点奇怪:NULL可以确保是0,但空(null)指针却不一定? 59
5.14 为什么有那么多关于空指针的疑惑?为什么这些问题如此频繁地出现? 60
5.15 有没有什么简单点儿的办法理解所有这些与空指针有关的东西呢? 60
5.16 考虑到有关空指针的所有这些困惑,要求它们的内部表示都必须为0不是更简单吗? 60
5.17 说真的,真有机器用非零空指针吗,或者不同类型用不同的表示? 61
地址0 上到底有什么? 61
5.18 运行时的整数值0转换为指针以后一定是空指针吗? 61
5.19 如何访问位于机器地址0处的中断向量?如果我将指针值设为0,编译器可能会自动将它转换为非零的空指针内部表示。 62
5.20 运行时的“null pointer assignment”错误是什么意思?应该怎样捕捉它? 62
第6章 数组和指针 63
数组和指针的基本关系 63
6.1 我在一个源文件中定义了char a[6],在另一个源文件中声明了extern char *a。为什么不行? 63
6.2 可是我听说char a[]和char *a是等价的。是这样的吗? 63
6.3 那么,在C语言中“指针和数组等价”到底是什么意思? 64
6.4 既然它们这么不同,那为什么作为函数形参的数组和指针声明可以互换呢? 65
数组不能被赋值 66
6.5 为什么不能这样向数组赋值?extern char *getpass(); char str[10]; str=getpass("Enter password:"); 66
6.6 既然不能向数组赋值,那这段代码为什么可以呢?int f(char str[]){ if(str[0] == '\0') str="none";…} 66
6.7 如果你不能给它赋值,那么数组如何能成为左值呢? 66
回顾 67
6.8 现实地讲,数组和指针的区别是什么? 67
6.9 有人跟我讲,数组不过是常指针。这样讲准确吗? 67
6.10 我还是很困惑。到底指针是一种数组,还是数组是一种指针? 67
6.11 我看到一些“搞笑”的代码,包含5["abcdef"]这样的“表达式”。这为什么是合法的C语言表达式呢? 68
数组的指针 68
6.12 既然数组引用会退化为指针,如果array是数组,那么array和&array又有什么区别呢? 68
6.13 如何声明一个数组的指针? 69
动态数组分配 70
6.14 如何在运行时设定数组的大小?怎样才能避免固定大小的数组? 70
6.15 我如何声明大小和传入的数组一样的局部数组? 70
6.16 如何动态分配多维数组? 71
6.17 有个很好的窍门,如果我这样写:int realarray[10]; int *array = &realarray[-1]; 我就可以把“array”当作下标从1 开始的数组。 72
函数和多维数组 73
6.18 当我向一个接受指针的指针的函数传入二维数组的时候,编译器报错了。 73
6.19 我怎样编写接受编译时宽度未知的二维数组的函数? 74
6.20 我怎样在函数参数传递时混用静态和动态多维数组? 74
数组的大小 75
6.21 当数组是函数的参数时,为什么sizeof不能正确报告数组的大小? 76
6.22 如何在一个文件中判断声明为extern的数组的大小(例如,数组定义和大小在另一个文件中)?sizeof操作符似乎不行。 76
6.23 sizeof返回的大小是以字节计算的,怎样才能判断数组中有多少个元素呢? 76
第7 章 内存分配 77
基本的内存分配问题 77
7.1 为什么这段代码不行?char *answer; printf("Type something:\n"); gets(answer); printf("You typed \"%s\"\n", answer); 77
7.2 我的strcat() 不行。我试了下面的代码:char *s1= "Hello,"; char *s2= "world!"; char *s3= strcat(s1, s2);但是我得到了奇怪的结果。 78
7.3 但是strcat的文档说它接受两个char *型参数。我怎么知道(空间)分配的事情呢? 78
*7.4 我刚才试了这样的代码:char *p; strcpy(p, "abc");它运行正常。怎么回事?为什么它没有出错? 79
*7.5 一个指针变量分配多少内存? 79
7.6 我使用fgets将文件的所有行读入一个数组,为什么读入的每一行都是最后一行的内容呢? 79
7.7 我有个函数,本该返回一个字符串,但当它返回调用者的时候,返回的字符串却是垃圾信息。
为什么? 80
*7.8 那么返回字符串或其他聚集的正确方法是什么呢? 81
调用malloc 81
7.9 为什么在调用malloc()时报出了“waring: assignment of pointer from integer lacks a cast”? 81
7.10 为什么有些代码小心翼翼地把malloc返回的值转换为分配的指针类型? 81
*7.11 在调用malloc()的时候,错误“不能把void * 转换为int * ”是什么意思? 82
7.12 我看到下面这样的代码:char *p = malloc(strlen(s) + 1); strcpy(p,s); 难道不应该是malloc ((strlen(s) + 1) * sizeof(char)) 吗? 82
7.13 我为malloc写了一个小小的封装函数。它为什么不行? 82
7.14 我想声明一个指针并向它分配一些内存,但是不行。这样的代码有什么问题?char *p; *p = malloc(10); 82
7.15 我如何动态分配数组? 83
7.16 怎样判断还有多少内存? 83
7.17 malloc(0)是返回空指针还是指向0个字节的指针? 83
7.18 我听说有的操作系统在程序使用的时候才真正分配malloc申请的内存。这合法吗? 83
有关malloc 的问题 83
7.19 为什么malloc返回了离谱的指针值?我的确读过问题7.9,而且也在调用之前包含了extern void *malloc();声明。 83
7.20 我用一行这样的代码分配一个巨大的数组,用于数值运算:double *array = malloc (256 *256 *sizeof(double));malloc()并没有返回空指针,但是程序运行得有些奇怪,好像改写了某些内存,或者malloc()并没有分配我申请的那么多内存。为什么? 84
7.21 我的PC机有8兆内存。为什么我只能分配640K左右的内存? 84
7.22 我的应用程序非常依赖数据结构的节点的动态分配,而malloc/free的代价成了瓶颈。我该怎么做? 84
7.23 我的程序总是崩溃,显然发生在malloc内部的某个地方。但是我看不出哪里有问题。是malloc有bug吗? 84
释放内存 85
7.24 动态分配的内存一旦释放之后就不能再使用,是吧? 85
7.25 为什么在调用free()之后指针没有变空?使用(赋值、比较)释放之后的指针有多么不安全? 86
7.26 当我调用malloc()为一个函数的局部指针分配内存时,我还需要用free()显式地释放吗? 86
7.27 我在分配一些结构,它们包含指向其他动态分配的对象的指针。我在释放结构的时候,还需要释放每一个下级指针吗? 86
7.28 我必须在程序退出之前释放分配的所有内存吗? 86
7.29 我有个程序分配了大量的内存,然后又释放了。但是从操作系统看,内存的占用率却并没有变回去。 87
分配内存块的大小 87
7.30 free()怎么知道有多少字节需要释放? 87
7.31 那么我能否查询malloc包,以查明可分配的最大块是多大? 87
7.32 为什么sizeof不能告诉我它所指的内存块的大小? 87
其他分配函数 88
7.33 (像问题6.14中那样)动态分配数组之后,还能改变它的大小吗? 88
7.34 向realloc()的第一个参数传入空指针合法吗?你为什么要这样做? 89
7.35 calloc()和malloc()有什么区别?应该用哪一个?利用calloc 的零填充功能安全吗?free()可以释放calloc()分配的内存吗,还是需要一个cfree()? 90
7.36 alloca是什么?为什么不提倡使用它? 91
第8章 字符和字符串 92
8.1 为什么strcat(string, '!'); 不行? 92
8.2 我想检查一个字符串是否跟某个值匹配。为什么这样不行?if(string == "value") 92
8.3 如果我可以写char a[] = "Hello, world!"; 那为什么不能写char a[14]; a = "Hello, world!"; 93
8.4 为什么我的strcat 不行?我试了char *s1="Hello,"; char *s2="world!"; char *s3 =strcat(s1, s2);可得到的结果很奇怪。 93
8.5 char a[]= "string literal"; 和char *p= "string literal"; 初始化有什么区别?当我对p[i]赋值的时候,程序崩溃了。 93
8.6 我怎么得到与字符相对应的数字(即ASCII 或其他字符集下的)值?反过来又该怎么做? 94
8.7 C语言有类似其他语言的"substr"(提取子串)这样的函数吗? 94
8.8 我将用户键入的字符串读入数组,然后再显示出来。当用户键入\n这样的序列时,为什么不能正确处理呢? 94
8.9 我注意到sizeof('a')是2而不是1(即不是sizeof(char)),是不是我的编译器有问题? 94
8.10 我正开始考虑多语言字符集的问题。是否有必要担心sizeof(char)会被定义为2,以便表达16位的字符集呢? 95
第9章 布尔表达式和变量 96
9.1 C语言中布尔值该用什么类型?为什么它不是一个标准类型?我应该用#define或enum定义真值和假值吗? 96
9.2 既然在C 语言中所有的非零值都被看作“真”,那是不是把TRUE 定义为1很危险?如果某个内建的函数或关系操作符“返回”不是1的其他值怎么办? 97
9.3 当p是指针时,if(p)是合法的条件表达式吗? 98
9.4 我该使用像TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 98
9.5 我准备使用的一个第三方头文件定义了自己的TRUE和FALSE,它们跟我已经开发的部分不兼容。我该怎么办? 98
第10章 C预处理器 99
宏定义 99
10.1 我想定义一些函数式的宏,例如:#define square(x)x * x但它们并不总是正确的。为什么? 99
10.2 这里有一些的预处理宏,使用它们,我可以写出更像Pascal的C代码。你觉得怎么样? 100
10.3 怎么写一个交换两个值的通用宏? 101
10.4 书写多语句宏的最好方法是什么? 101
10.5 用typdef和预处理宏生成用户定义类型有什么区别? 102
头文件 102
10.6 我第一次把一个程序分成多个源文件,我不知道该把什么放到.c文件,把什么放到.h文件。(“.h”到底是什么意思?) 102
10.7 可以在一个头文件中包含另一头文件吗? 103
10.8 完整的头文件搜索规则是怎样的? 104
10.9 我在文件的第一个声明就遇到奇怪的语法错误,但是看上去没什么问题。 104
10.10 我使用了来自两个不同的第三方库的头文件,它们都定义了相同的宏,如TRUE、FALSE、Min()和Max()等,但是它们的定义相互冲突,而且跟我在自己的头文件中的定义也有冲突。我该怎么办? 104
10.11 我在编译一个程序,看起来我好像缺少需要的一个或多个头文件。谁能发给我一份? 105
条件编译 105
10.12 怎样构造比较字符串的#if预处理表达式? 105
10.13 sizeof操作符可以用在#if预处理指令中吗? 106
10.14 我可以像这样在#define行里使用#ifdef来定义两个不同的东西吗? 106
10.15 对typedef的类型定义有没有类似#ifdef的东西? 106
10.16 我如何用#if表达式来判断机器是高字节在前还是低字节在前? 107
10.17 为什么在我用#ifdef关掉的代码行中报出了奇怪的语法错误? 107
10.18 我拿到了一些代码,里边有太多的#ifdef。我不想使用预处理器把所有的#include 和#ifdef都扩展开,有什么办法只保留一种条件的代码呢? 107
10.19 如何列出所有的预定义宏? 107
奇异的处理 108
10.20 我有些旧代码,试图用这样的宏来构造标识符:#define Paste(a, b) a/**/b 但是现在不行了。为什么? 108
10.21 我有一个旧宏:#define CTRL(c) ('c' & 037)现在不能用了。为什么? 108
10.22 为什么宏#define TRACE(n) printf("TRACE: \%d\n", n) 报出警告“macro replacement within a string literal ”?它似乎把TRACE(count);扩展成了printf("TRACE: \%d\count", count); 109
10.23 如何在宏扩展的字符串字面量中使用宏参数? 109
10.24 我想用ANSI 的“字符串化”预处理操作符#将符号常量的值放入消息中,但它总是对宏名称而不是它的值进行字符串化。这是什么原因? 109
10.25 我想用预处理器做某件事情,但却不知道如何下手。 110
可变参数列表的宏 110
10.26 怎样写可变参数宏?如何用预处理器“关掉”具有可变参数的函数调用? 110
10.27 如何在通用的调试宏中包含__FILE__和__LINE__宏? 111
第11章 ANSI/ISO标准C 113
标准 113
11.1 什么是“ANSI C标准”? 113
11.2 如何得到一份标准的副本? 114
*11.3 我在哪里可以找到标准的更新? 115
函数原型 115
11.4 为什么我的ANSI编译器对用float声明的参数会警告类型不匹配? 115
11.5 能否混用旧式的和新型的函数语法? 116
*11.6 为什么下述声明报出了一个奇怪的警告信息“Struct X declared inside parameter list”? extern int f(struct x *p); 116
11.7 有个问题一直困扰着我,它是由这一行printf ("%d", n); 导致的,因为n是个long int型。难道 ANSI 的函数原型不能检查这种函数的参数不匹配问题吗? 116
11.8 我听说必须在调用printf之前包含stdio.h。为什么? 117
const 限定词 117
11.9 为什么不能在初始化和数组维度中使用const值?例如const int n = 5; int a[n]; 117
11.10 “const char *p”、“char const *p ”和“char * const p ”有何区别? 117
11.11 为什么不能向接受const char ** 的函数传入char **? 118
11.12 我这样声明:typedef char * charp; const charp p; 为什么是p而不是它所指向的字符为const? 118
main()函数的使用 119
11.13 能否通过将main声明为void来关掉“main没有返回值”的警告? 119
11.14 main()的第3个参数envp是怎么回事? 120
11.15 我觉得把main()声明为void也不会失败,因为我调用了exit()而不是return,况且我的操作系统也忽略了程序的退出/返回状态。 120
*11.16 那么到底会出什么问题?真的有什么系统不支持void main()吗? 120
11.17 为什么以前流行的那些C 语言书总是使用void main()? 120
11.18 在main()中调用exit(status)和返回同样的status真的等价吗? 121
预处理功能 121
11.19 我试图用ANSI“字符串化”预处理操作符'#'向信息中插入符号常量的值,但它字符串化的总是宏的名字而不是它的值。为什么? 121
11.20 警告信息“warning: macro replacement within a string literal”是什么意思? 121
11.21 为什么在我用#ifdef去掉的代码里出现了奇怪的语法错误? 122
11.22 #pragma是什么,有什么用? 122
11.23 “#pragma once”什么意思?我在一些头文件中看到了它。 122
其他的ANSI C 问题 123
11.24 char a[3] = "abc";合法吗?它是什么意思? 123
11.25 既然对数组的引用会退化为指针,那么,如果array是数组,array和&array之间有什么区别呢? 123
11.26 为什么我不能对void *指针进行算术运算? 123
11.27 memcpy()和memmove() 有什么区别? 124
11.28 malloc(0)有什么用?返回一个空指针还是指向0字节的指针? 124
11.29 为什么ANSI 标准规定了外部标识符的长度和大小写限制? 125
11.30 noalias是怎么回事?在它身上发生了什么? 125
老的或非标准的编译器 125
11.31 为什么我的编译器对最简单的测试程序都报出了一大堆的语法错误?对这段代码的第一行就报错了:main(int argc. char **argv) { return0; } 125
11.32 为什么有些 ASNI/ISO 标准库函数未定义?我明明使用的就是ANSI 编译器。 126
11.33 谁有可以在旧的C 程序和ANSI C 之间相互转换的工具,或者自动生成原型的工具? 127
11.34 为什么声称兼容ANSI 的编译器不能编译这些代码?我知道这些代码是 ANSI 的,因为gcc 可以编译。 127
兼容性 127
11.35 人们好像有些在意实现定义的(implementation-defined)、不确定的(unspecified)和未定义的(undefined) 行为的区别。它们的区别到底在哪里? 128
*11.36 一个程序“合法(legal)”、“有效(valid)”或“符合标准的”(conforming )到底是什么意思? 128
11.37 我很吃惊,ANSI 标准竟然有那么多未定义的东西。标准的唯一任务不就是让这些东西标准化吗? 129
11.38 有人说i=i++的行为是未定义的,但是我刚在一个兼容ANSI 的编译器上测试,得到了我希望的结果。它真的是未定义的吗? 129
第12章 标准输入输出库 130
基本输入输出 130
12.1 这样的代码有什么问题?char c; while((c = getchar()) != EOF) 130
12.2 我有个读取直到EOF的简单程序,但是我如何才能在键盘上输入那个“\EOF”呢?我看stdio.h 中定义的EOF 是-1,是不是说我该输入-1? 131
12.3 为什么这些代码把最后一行复制了两遍?while(!feof(infp)){fgets(buf, MAXLINE, infp); fputs(buf, outfp);} 131
12.4 我用fgets将文件的每行内容读入指针数组。为什么结果所有的行都是最后一行的内容呢? 132
12.5 我的程序的屏幕提示和中间输出有时没有在屏幕上显示,尤其是当我用管道通过另一个程序输出的时候。为什么? 132
12.6 我怎样才能不等待回车键而一次输入一个字符? 132
printf格式 132
12.7 如何在printf 的格式串中输出一个'%'字符?我试过\%,但是不行。 132
12.8 为什么这么写不对?long int n = 123456; printf("%d\n", n); 133
12.9 有人告诉我不能在printf 中使用%lf。为什么printf() 用%f输出double 型,而scanf 却用%lf 呢? 133
*12.10 对于size_t 那样的类型定义,当我不知道它到底是long 还是其他类型的时候,我应该使用什么样的printf格式呢? 134
12.11 如何用printf 实现可变的域宽度?就是说,我想在运行时确定宽度而不是使用%8d? 134
12.12 如何输出在千位上用逗号隔开的数字?货币格式的数字呢? 135
12.13 为什么scanf("%d", i) 调用不行? 136
*12.14 为什么char s[30]; scamf("%s", s); 不用&也可以?我原以为传给scanf的每个变量都要带&。 136
12.15 为什么这些代码不行?double d; scanf("%f", &d); 136
12.16 为什么这段代码不行?short int s; scanf("%d", &s); 136
12.17 怎样在scanf 格式串中指定可变的宽度? 136
12.18 怎样从特定格式的数据文件中读取数据?怎样读入10个float 而不用使用包含10次%f的奇怪格式?如何将一行的任意多个域读入一个数组中? 137
scanf问题 138
12.19 我像这样用"%d\n"调用scanf 从键盘读取数字:int n; scanf("%d\n",&n); printf("you typed %d\ n", n);好像要多输入一行才返回。为什么? 138
12.20 我用scanf 和%d读取一个数字,然后再用gets() 读取字符串,但是编译器好像跳过了gets() 调用! 139
12.21 我发现如果坚持检查返回值以确保用户输入的是我期待的数值,则scanf 的使用会安全很多。但有的时候好像会陷入无限循环。为什么? 139
12.22 为什么大家都说不要使用scanf?那我该用什么来代替呢? 140
其他stdio 函数 141
12.23 我怎样才知道对于任意的sprintf 调用需要多大的目标缓冲区?怎样才能避免sprintf 目标缓冲区溢出? 141
12.24 sprintf的返回值是什么?是int 还是char *? 142
12.25 为什么大家都说不要使用gets? 142
12.26 我觉得我应该在一长串的printf 调用之后检查errno ,以确定是否有失败的调用。为什么当我将输出重定向到文件的时候会输出奇怪的“printf failed: Not a typewriter ”信息? 142
12.27 fgetops/fsetops和ftell/fseek之间有什么区别?fgetops和fsetops 到底有什么用处? 143
12.28 如何清除用户的多余输入,以防止在下一个提示符下读入?用fflush(stdin) 可以吗? 143
打开和操作文件 144
12.29 我写了一个函数用来打开文件:myfopen(char *filename, FILE *fp){fp = fopen(filename, "r");}可我这样调用的时候:FILE *infp; myfopen("filename.dat", infp);,infp 指针并
没有正确设置。为什么? 144
12.30 连一个最简单的fopen调用都不成功!这个调用有什么问题?FILE *fp = fopen(filename, 'r'); 145
12.31 为什么我不能用完整路径名打开一个文件?这个调用总是失败:fopen("c:\newdir\ file. dat", "r"); 145
12.32 我想用fopen模式"r+"打开一个文件,读出一个字符串,修改之后再写入,从而就地更新一个文件。可是这样不行。为什么? 145
12.33 如何在文件中间插入或删除一行(一条记录)? 145
12.34 怎样从打开的流中恢复文件名? 145
重定向stdin 和stdout 146
12.35 怎样在程序里把stdin或stdout重定向到文件? 146
12.36 一旦使用freopen之后,怎样才能恢复原来的stdout (或stdin)? 146
12.37 如何判断标准输入或输出是否经过了重定向,即是否在命令行上使用了“”或“”? 147
12.38 我想写个像"more"那样的程序。怎样才能在stdin 被重定向之后再回到交互键盘? 147
*12.39 怎样同时向两个地方输出,如同时输出到屏幕和文件? 147
“二进制”输入输出 148
12.40 我希望按字节在内存和文件之间直接读写数字,而不像fprintf和fscanf进行格式化。我该怎么办? 148
12.41 怎样正确地读取二进制文件?有时看到0x0a和0x0d容易混淆,而且如果数据中包含0x1a的话,我好像会提前遇到EOF。 148
12.42 我在写一个二进制文件的“过滤器”,但是stdin和stdout却被作为文本流打开了。怎样才能把它们的模式改为二进制? 148
12.43 文本和二进制输入输出有什么区别? 149
12.44 如何在数据文件中读写结构? 149
12.45 怎样编写符合旧的二进制数据格式的代码? 149
第13章 库函数 151
字符串函数 151
13.1 怎样把数字转为字符串(与atoi相反)?有itoa函数吗? 151
13.2 为什么strncpy不能总在目标串放上终止符'\0'? 152
13.3 C 语言有类似于其他语言中的“substr ”(取出子串)的例程吗? 152
13.4 怎样把一个字符串中所有字符转换成大写或小写? 153
13.5 为什么有些版本的toupper对大写字符会有奇怪的反应?为什么有的代码在调用toupper 前先调用islower? 153
13.6 怎样将字符串分割成用空白分隔的字段?怎样实现类似main 处理argc和argv的过程? 153
13.7 哪里可以找到处理正则表达式或通配符匹配的代码? 155
排序 156
13.8 我想用strcmp作为比较函数,调用qsort对一个字符串数组排序,但是不行。为什么? 156
13.9 我想用qsort()对一个结构数组排序。我的比较函数接受结构指针,但是编译器认为这个函数不是qsort需要的类型。我要怎样转换这个函数指针才能避免这样的警告? 156
13.10 怎样对一个链表排序? 158
13.11 怎样对大于内存容量的数据排序? 158
日期和时间 159
13.12 怎样在C 程序中取得当前日期或时间? 159
13.13 我知道库函数localtime可以把time_t转换成结构struct tm,而ctime可以把time_t转换成为可打印的字符串。怎样才能进行反向操作,把struct tm或一个字符串转换成time_t? 159
13.14 怎样在日期上加n天?怎样取得两个日期的时间间隔? 160
随机数 162
13.15 怎么生成一个随机数? 162
13.16 怎样获得某一范围内的随机整数? 163
13.17 每次执行程序,rand都返回相同的数字序列。为什么? 164
13.18 我需要随机的真/假值,所以我就直接用rand()%2,可是我得到交替的0, 1, 0, 1, 0 …。为什么? 164
13.19 如何获取根本不重复的随机数? 165
13.20 怎样产生正态分布或高斯分布的随机数? 165
13.21 我在移植一个程序,里边调用了一个函数drand48 ,而我的库又没有这个。这是个什么函数? 167
其他库函数 168
13.22 exit(status)是否真的跟从main 函数返回status 等价? 168
13.23 memcpy和memmove 有什么区别? 168
13.24 我想移植这个旧程序。为什么报出这些“undefined external”错误:index? 、rindex?、bcopy?、bcmp?、bzero?? 168
13.25 我不断得到库函数未定义错误,但是我已经包含了所有用到的头文件了。 168
13.26 虽然我在连接时明确地指定了正确的函数库,我还是得到库函数未定义错误。 168
13.27 一个最简单的程序,不过在一个窗口里打印出“Hello,World”,为什么会编译出巨大的可执行代码(数百K)?我该少包含一些头文件吗? 169
13.28 连接器报告_end未定义代表什么意思? 169
*13.29 我的编译器提示printf未定义!这怎么可能? 169
第14章 浮点运算 170
14.1 一个float变量赋值为3.1时,为什么printf输出的值为3.0999999? 170
14.2 我想计算一些平方根,我把程序简化成这样:main(){printf ("%f\h", sqrt(144.)); 可得到的结果却是疯狂的数字。为什么? 170
14.3 我想做一些简单的三角函数运算,也包含了math.h ,但连接器总是提示sin、cos这样的函数未定义。为什么? 171
14.4 我的浮点数计算程序表现得很奇怪,在不同的机器上给出了不同的结果。为什么? 171
14.5 有什么好的方法来检查浮点数在“足够接近”情况下的相等? 171
14.6 怎样取整? 172
14.7 为什么C语言不提供乘幂的操作符? 173
14.8 为什么我机器上的math.h没有预定义常量M_PI? 173
14.9 怎样将变量置为IEEE NaN(“Not a Number”)或检测变量是否为NaN及其他特殊值? 173
14.10 如何简洁地处理浮点异常? 174
14.11 在C语言中如何很好地实现复数? 174
14.12 我要寻找一些实现以下功能的程序源代码:快速傅立叶变换(FFT)、矩阵算术(乘法、求逆等函数)、复数算术。 175
14.13 Turbo C的程序崩溃,显示错误为“floating point formats not linked”(浮点格式未连接)。我还缺点儿什么呢? 175
第15章 可变参数列表 176
调用变参函数 176
15.1 为什么调用printf前必须要包含stdio.h? 176
15.2 为什么%f可以在printf参数中同时表示float和double?它们难道不是不同类型吗? 177
15.3 我遇到了一个令人十分受挫的问题,后来发现是这行代码造成的:printf("%d", n);原来n 是longint型。难道ANSI的函数原型不就是用来防止这类的参数类型不匹配吗? 177
15.4 怎样写一个接受可变参数的函数? 177
15.5 怎样写一个函数,像printf那样接受一个格式串和可变参数,然后再把参数传给printf去完成大部分工作? 180
15.6 怎样写类似scanf的函数,再把参数传给scanf去完成大部分工作? 180
15.7 我用的是ANSI前的编译器,没有stdarg.h文件。我该怎么办? 181
提取可变参数 182
15.8 怎样知道实际上有多少个参数传入函数? 182
15.9 为什么编译器不允许我定义一个没有固定参数项的可变参数函数? 182
15.10 我有个接受float型的变参函数,为什么va_arg(argp, float)却不行? 183
15.11 为什么va_arg不能得到类型为函数指针的参数? 183
困难的问题 184
15.12 怎样实现一个可变参数函数,它把参数再传给另一个可变参数函数? 184
15.13 怎样调用一个在运行时才构建参数列表的函数? 186
第16 章 奇怪的问题 187
16.1 为什么这个循环只执行了一次?for(i=start;i end ; i ++);{printf("%d\n",i);} 187
*16.2 遇到不可理解的不合理语法错误,似乎大段的程序没有编译。 187
*16.3 为什么过程调用不起作用?编译器似乎直接跳过去了。 187
16.4 程序在执行之前就崩溃了!(用调试器单步跟踪,在main函数的第一个语句之前就死了。)为什么? 188
16.5 程序执行正确,但退出时在main函数的最后一个语句之后崩溃了。为什么会这样? 188
16.6 程序在一台机器上运行完美,但在另一台上却得到怪异的结果。更奇怪的是,增加或去除调试的打印语句,就改变了症状…… 188
16.7 为什么下面的代码会崩溃?char *p = "hello, world!"; p[0] = 'H'; 189
16.8 我有些代码是用来解析外部结构的,但它却崩溃了,报了“unaligned access ”(未对齐的访问)错误。这是什么意思? 190
16.9 “Segmentation violation”、“Bus error”和“General protection fault”是什么意思? 191
第17章 风格 192
17.1 什么是C最好的代码布局风格? 192
17.2 如何在源文件中合理分配函数? 193
17.3 用if(!strcmp(s1, s2))比较两个字符串是否相等是个好风格吗? 193
17.4 为什么有的人用if(0== x)而不是if(x== 0)? 193
17.5 为什么有些代码在每次调用printf 前增加了类型转换(void)? 194
17.6 既然NULL和0都是空指针常量,我到底该用哪一个? 194
17.7 是该用TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 194
17.8 什么是“匈牙利表示法”(Hungarian Notation )?是否值得一试? 194
17.9 哪里可以找到“Indian Hill Style Guide ”及其他编码标准? 194
17.10 有人说goto是邪恶的,永远都不该用它。这是否太极端了? 195
17.11 人们总是说良好的风格很重要,但当他们使用良好的风格写出清晰易读的程序后,又发现程序的效率似乎降低了。既然效率那么重要,是否可以为了效率牺牲一些风格和可读性呢? 196
第18章 工具和资源 197
18.1 能否列一个常用工具列表? 197
18.2 怎样捕获棘手的malloc问题? 198
18.3 有什么免费或便宜的编译器可以使用? 198
lint 198
18.4 刚刚输入完一个程序,但它表现得很奇怪。你能发现有什么错误的地方吗? 199
18.5 如何关掉lint对每个malloc调用报出的“warning: possible pointer alignment problem”警告消息? 199
18.6 哪里可以找到兼容ANSI的lint? 199
18.7 难道ANSI函数原型说明没有使lint过时吗? 199
资源 200
18.8 网上有哪些C语言的教程或其他资源? 200
*18.9 哪里可以找到好的源代码实例,以供研究和学习? 201
18.10 有什么好的学习C语言的书?有哪些高级的书和参考? 201
18.11 哪里能找到K&R的练习答案? 201
18.12 哪里能找到Numerical Recipes in C 、Plauger的The Standard C Library或Kernighan和Pike的The UNIX Programming Enviroment等书里的源码? 201
18.13 哪里可以找到标准C函数库的源代码? 202
18.14 是否有一个在线的C参考指南? 202
18.15 我需要分析和评估表达式的代码。从哪里可以找到? 202
18.16 哪里可以找到C的BNF或YACC语法? 202
*18.17 谁有C编译器的测试套件? 203
*18.18 哪里有一些有用的源代码片段和例子的收集? 203
*18.19 我需要执行多精度算术的代码。 203
18.20 在哪里和怎样取得这些可自由发布的程序? 203
第19章 系统依赖 205
键盘和屏幕I/O 205
19.1 怎样从键盘直接读入字符而不用等回车键?怎样防止字符输入时的回显? 205
19.2 怎样知道有未读的字符(如果有,有多少)?另外,如何在没有字符的时候不阻塞读入? 209
19.3 怎样显示一个在原地更新自己的百分比或“旋转棒”的进度指示器? 209
19.4 怎样清屏?怎样反色输出?怎样把光标移动到指定的x, y位置? 210
19.5 怎样读入方向键、功能键? 210
其他I/O 211
19.6 怎样读入鼠标输入? 211
19.7 怎样做串口(“comm”)的输入输出? 211
19.8 怎样直接输出到打印机? 211
19.9 怎样发送转义字符序列控制终端或其他设备? 211
19.10 怎样做图形? 212
*19.11 怎样显示GIF和JPEG图像? 212
文件和目录 212
19.12 怎样检验一个文件是否存在?如果请求的输入文件不存在,我希望向用户提出警告。 212
19.13 怎样在读入文件前,知道文件大小? 213
*19.14 怎样得到文件的修改日期和时间? 213
19.15 怎样原地缩短一个文件而不用清除或重写? 213
19.16 怎样在文件中插入或删除一行(或一条记录)? 214
19.17 怎样从一个打开的流或文件描述符得到文件名? 214
19.18 怎样删除一个文件? 214
*19.19 怎样复制文件? 215
19.20 为什么用了详尽的路径还不能打开文件?下面的代码会返回错误。Fopen("c:\newdir\file.dat", "r") 215
*19.21 fopen不让我打开文件"$HOME/.profile"和"~~/.myrcfile"。 215
*19.22 怎样制止MS-DOS下令人恐怖的“Abort,Retry,Ignore? ”信息? 215
19.23 遇到“Too many open files(打开文件太多)”的错误,怎样增加同时打开文件的允许数目? 215
19.24 如何得到磁盘的可用空间大小? 216
19.25 怎样在C语言中读入目录? 216
19.26 如何创建目录?如何删除目录(及其内容)? 217
访问原始内存 217
19.27 怎样找出系统还有多少内存可用? 217
19.28 怎样分配大于64K的数组或结构? 217
19.29 错误信息“DGROUP data allocation exceeds 64K(DGROUP 数据分配内存超过64K)”什么意思?我应该怎么做?我以为使用了大内存模型,就可以使用大于64K的数据! 217
19.30 怎样访问位于某特定地址的内存(内存映射的设备或图形显示内存)? 218
19.31 如何访问机器地址0处的中断向量?如果将指针设为0,编译器可能把它转成一个非零的内部空指针值。 218
“系统”命令 219
19.32 怎样在一个C程序中调用另一个程序(独立可执行的程序或系统命令)? 219
19.33 如果运行时才知道要执行的命令的参数(文件名等),应该如何调用system? 219
19.34 在MS-DOS上如何得到system返回的准确错误状态? 220
19.35 怎样调用另一个程序或命令,然后获取它的输出? 220
进程环境 220
19.36 怎样才能发现程序自己的执行文件的全路径? 220
19.37 怎样找出和执行文件在同一目录的配置文件? 221
19.38 进程如何改变它的调用者的环境变量? 221
19.39 如何打开命令行给出的文件并解析选项? 221
19.40 exit(status)是否真的和从main函数返回同样的status等价? 221
19.41 怎样读入一个对象文件并跳跃到其中的函数? 221
其他系统相关的操作 222
19.42 怎样以小于1秒的精度延时或计算用户响应时间? 222
19.43 怎样捕获或忽略control-C这样的键盘中断? 222
19.44 怎样简洁地处理浮点异常? 223
19.45 怎样使用socket?如何联网?如何写客户/服务器程序? 223
*19.46 怎样调用BIOS函数?如何写ISR?如何创建TSR? 224
*19.47 什么是“near”和“far”指针? 224
回顾 224
19.48 我不能使用这些非标准、依赖系统的函数,程序需要兼容ANSI! 224
19.49 为什么这些内容没有在C语言中进行标准化?任何现实程序都会用到这些东西。 224
第20章 杂项 226
20.1 怎样从函数返回多个值? 226
20.2 用什么数据结构存储文本行最好?我开始用固定大小的char型数组的数组,但是有很多局限。 227
20.3 怎样打开命令行提到的文件并处理参数? 229
20.4 如何正确地使用errno? 231
20.5 怎样写数据文件,使之可以在不同字大小、字节顺序或浮点格式的机器上读入? 232
20.6 怎样用char *指针指向的函数名调用函数? 232
位和字节 233
20.7 如何操作各个位? 233
20.8 怎样实现位数组或集合? 234
20.9 怎样判断机器的字节顺序是高字节在前还是低字节在前? 235
*20.10 怎样调换字节? 236
20.11 怎样将整数转换到二进制或十六进制? 237
20.12 可以使用二进制常数(类似0b101010这样的东西)吗?printf有二进制的格式说明符吗? 237
效率 238
20.13 用什么方法计算整数中为1的位的个数最高效? 238
20.14 怎样提高程序的效率? 238
20.15 指针真的比数组快吗?函数调用会拖慢程序多少?++i比i=i+1快吗? 240
20.16 用移位操作符替换乘法和除法是否有价值? 240
*20.17 人们说编译器优化得很好,我们不再需要为速度而写汇编了,但我的编译器连用移位代替i/=2都做不到。 240
*20.18 怎样不用临时变量而交换两个值? 241
switch 语句 241
20.19 switch语句和if/else链哪个更高效? 241
20.20 是否有根据字符串进行条件切换的方法? 241
20.21 是否有使用非常量case行标的方法(如范围或任意的表达式)? 242
各种语言功能 243
20.22 return语句外层的括号是否真的可选择? 243
20.23 为什么C语言的注释不能嵌套?怎样注释掉含有注释的代码?引号包含的字符串内的注释是否合法? 243
20.24 为什么C语言的操作符不设计得更全面一些?好像还缺了一些^^、&&=和-=这样的操作符。 244
*20.25 C语言有循环移位操作符吗? 244
*20.26 C是个伟大的语言还是别的什么东西?哪个其他语言可以写出像a+++++b这样的代码? 244
20.27 如果赋值操作符是:=,是不是就不容易意外地写出if(a=b)了? 245
20.28 C语言有和Pascal 的with等价的语句吗? 245
20.29 为什么C语言没有嵌套函数? 245
*20.30 assert是什么?如何使用? 246
其他语言 246
20.31 怎样从C中调用FORTRAN(C++、BASIC、Pascal、Ada、LISP)的函数?反之如何? 246
20.32 有什么程序可以将Pascal或FORTRAN(或LISP、Ada、awk、“老”C)程序转化为C程序? 246
20.33 C++是C的超集吗?可以用C++编译器来编译C代码吗? 247
20.34 我需要用到“近似”的strcmp例程,比较两个字符串的近似度,并不需要完全一样。有什么好办法? 247
20.35 什么是散列法? 248
20.36 如何生成正态或高斯分布的随机数? 248
20.37 如何知道某个日期是星期几? 249
20.38 (year % 4== 0)是否足以判断闰年?2000年是闰年吗? 250
20.39 为什么tm结构中的tm_sec的范围是0到61,暗示一分钟有62秒? 250
琐事 250
20.40 一个难题:怎样写一个输出自己源代码的程序? 250
20.41 什么是“达夫设备”(Duff’s Device)? 251
20.42 下届国际C语言混乱代码竞赛(International Obfuscated C Code Contest,IOCCC)什么时候进行?哪里可以找到当前和以前的获胜代码? 251
20.43 K&R1提到的关键字entry是什么? 252
20.44 C的名字从何而来? 252
20.45 “char”如何发音? 252
*20.46 “lvalue”和“rvalue”代表什么意思? 252
20.47 哪里可以获得本书的在线版? 252
术语表 253
参考文献 261~
1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。
2.继承:
继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
3.封装:
封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
4. 多态性:
多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
5、String是最基本的数据类型吗?
基本数据类型包括byte、int、char、long、float、double、boolean和short。
java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类
6、int 和 Integer 有什么区别
Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。Java为每个原始类型提供了封装类。
原始类型 封装类
boolean Boolean
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double
引用类型和原始类型的行为完全不同,并且它们具有不同的语义。引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为 null,而原始类型实例变量的缺省值与它们的类型有关。
7、String 和StringBuffer的区别
JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用 StringBuffers来动态构造字符数据。
8、运行时异常与一般异常有何异同?
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。
9、说出Servlet的生命周期,并说出Servlet和CGI的区别。
Servlet被服务器实例化后,容器运行其init方法,请求到达时运行其service方法,service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其destroy方法。
与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。
10、说出ArrayList,Vector, LinkedList的存储性能和特性
ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
11、EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean和StatelessBean的区别。
EJB包括Ses
简单地说,TinyXML解析一个XML文档并由此生成一个可读可修改可保存的文档对象模型(DOM)。
XML的意思是“可扩展标记语言“(eXtensible Markup Language)。它允许你创建你自己的文档标记。在为浏览器标记文档方面HTML做得很好,然而XML允许你定义任何文档标记,比如可以为一个组织者应用程序定义一个描述“to do”列表的文档。 XML拥有一个结构化并且方便的格式,所有为存储应用程序数据而创建的随机文件格式都可以用XML代替,而这一切只需要一个解析器。
最全面正确的说明可以在http://www.w3.org/TR/2004/REC-xml-20040204/找到,但坦白地说,它很晦涩难懂。事实上我喜欢http://skew.org/xml/tutorial上关于XML的介绍。
有不同的方法可以访问和与XML数据进行交互。TinyXML使用文档对象模型(DOM),这意味着XML数据被解析成一个可被浏览和操作的C++对象,然后它可以被写到磁盘或者另一个输出流中。你也可以把C++对象构造成一个XML文档然后把它写到磁盘或者另一个输出流中。
TinyXML被设计得容易快速上手。它只有两个头文件和四个cpp文件。只需要把它们简单地加到你的项目中就行了。有一个例子文件——xmltest.cpp来引导你该怎么做。
TinyXML以Zlib许可来发布,所以你可以在开源或者商业软件中使用它。许可证更具体的描述在每个源代码文件的顶部可以找到。
TinyXML在保证正确和恰当的XML输出的基础上尝试成为一个灵活的解析器。TinyXML可以在任何合理的C++适用系统上编译。它不依赖于异常或者运行时类型信息,有没有STL支持都可以编译。TinyXML完全支持UTF-8编码和前64k个字符实体(译注:如果你不明白这句译文,可能你需要了解一下Unicode编码)。
它无法做些什么
TinyXML不解析不使用DTDs(文档类型定义)或者XSLs(可扩展样式表语言)。有其它解析器(到www.sourceforge.org搜索一下XML)具有更加全面的特性,但它们也就更大,需要花更长的时间来建立你的项目,有更陡的学习曲线,而且经常有一个更严格的许可协议。如果你是用于浏览器或者有更复杂的XML需要,那么TinyXML不适合你。
下面的DTD语法在TinyXML里是不做解析的:
<!DOCTYPE Archiv [
因为TinyXML把它看成是一个带着非法嵌入!ELEMENT结点的!DOCTYPE结点。或许这在将来会得到支持。
有耐性些,这是一份能很好地指导你怎么开始的指南,它(非常短小精悍)值得你花时间完整地读上一遍。
TinyXML指南
TinyXML是成熟且经过测试的代码,非常健壮。如果你发现了漏洞,请提交漏洞报告到sourcefore网站上 (www.sourceforge.net/projects/tinyxml)。 我们会尽快修正。
有些地方可以让你得到提高,如果你对TinyXML的工作感兴趣的话可以上sourceforge查找一下。
你也许会觉得TinyXML很有用!(简介由项目提供)
TinyXPath (http://tinyxpath.sourceforge.net). TinyXPath是一个小巧的XPath语法译码器脚本,用C++写成。
TinyXML++ (http://code.google.com/p/ticpp/). TinyXML++是一个全新的TinyXML接口,使用了许多诸如模板,异常处理和更好的错误处理这些C++强项技术。
使用STL
TinyXML可以被编译成使用或不使用STL。如果使用STL,TinyXML会使用std::string类,而且完全支持std::istream,std::ostream,operator<>。许多API方法都有 ‘const char*’和’const std::string&’两个版本。
如果被编译成不使用STL,则任何STL都不会被包含。所有string类都由TinyXML它自己实现。所有API方法都只提供’const char*’传入参数。
使用运行时定义:
TIXML_USE_STL
来编译成不同的版本。这可以作为参数传给编译器或者在“tinyxml.h”文件的第一行进行设置。
注意:如果在Linux上编译测试代码,设置环境变量TINYXML_USE_STL=YES/NO可以控制STL的编译。而在Windows上,项目文件提供了STL和非STL两种目标文件。在你的项目中,在tinyxml.h的第一行添加"#define TIXML_USE_STL"应该是最简单的。
UTF-8
TinyXML支持UTF-8,所以可以处理任何语言的XML文件,而且TinyXML也支持“legacy模式”——一种在支持UTF-8之前使用的编码方式,可能最好的解释是“扩展的ascii”。
正常情况下,TinyXML会检测出正确的编码并使用它,然而,通过设置头文件中的TIXML_DEFAULT_ENCODING值,TinyXML可以被强制成总是使用某一种编码。
除非以下情况发生,否则TinyXML会默认使用Legacy模式:
如果文件或者数据流以非标准但普遍的"UTF-8引导字节" (0xef 0xbb 0xbf)开始,TinyXML会以UTF-8的方式来读取它。
如果包含有encoding="UTF-8"的声明被读取,那么TinyXML会以UTF-8的方式来读取它。
如果读取到没有指定编码方式的声明,那么TinyXML会以UTF-8的方式来读取它。
如果包含有encoding=“其它编码”的声明被读取,那么TinyXML会以Legacy模式来读取它。在Legacy模式下,TinyXML会像以前那样工作,虽然已经不是很清楚这种模式是如何工作的了,但旧的内容还得保持能够运行。
除了上面提到的情况,TinyXML会默认运行在Legacy模式下。
如果编码设置错误或者检测到错误会发生什么事呢?TinyXML会尝试跳过这些看似不正确的编码,你可能会得到一些奇怪的结果或者乱码,你可以强制TinyXML使用正确的编码模式。
通过使用LoadFile( TIXML_ENCODING_LEGACY )或者LoadFile( filename, TIXML_ENCODING_LEGACY ), 你可以强制TinyXML使用Legacy模式。你也可以通过设置TIXML_DEFAULT_ENCODING = TIXML_ENCODING_LEGACY来强制一直使用Legacy模式。同样的,你也可以通过相同的方法来强制设置成TIXML_ENCODING_UTF8。
对于使用英文XML的英语用户来说,UTF-8跟low-ASCII是一样的。你不需要知道UTF-8或者一点也不需要修改你的代码。你可以把UTF-8当作是ASCII的超集。
UTF-8并不是一种双字节格式,但它是一种标准的Unicode编码!TinyXML当前不使用或者直接支持wchar,TCHAR,或者微软的_UNICODE。"Unicode"这个术语被普遍地认为指的是UTF-16(一种unicode的宽字节编码)是不适当的,这是混淆的来源。
对于“high-ascii”语言来说——几乎所有非英语语言,只要XML被编码成UTF-8, TinyXML就能够处理。说起来可能有点微妙,比较旧的程序和操作系统趋向于使用“默认”或者“传统”的编码方式。许多应用程序(和几乎所有现在的应用程序)都能够输出UTF-8,但是那些比较旧或者难处理的(或者干脆不能使用的)系统还是只能以默认编码来输出文本。
比如说,日本的系统传统上使用SHIFT-JIS编码,这种情况下TinyXML就无法读取了。但是一个好的文本编辑器可以导入SHIFT-JIS的文本然后保存成UTF-8编码格式的。
Skew.org link上关于转换编码的话题做得很好。
测试文件“utf8test.xml”包含了英文、西班牙文、俄文和简体中文(希望它们都能够被正确地转化)。“utf8test.gif”文件是从IE上截取的XML文件快照。请注意如果你的系统上没有正确的字体(简体中文或者俄文),那么即使你正确地解析了也看不到与GIF文件上一样的输出。同时要注意在一个西方编码的控制台上(至少我的Windows机器是这样),Print()或者printf()也无法正确地显示这个文件,这不关TinyXML的事——这只是操作系统的问题。TinyXML没有丢掉或者损坏数据,只是控制台无法显示UTF-8而已。
TinyXML认得预定义的特殊“字符实体”,即:
Far & Away
从TiXmlText 对象查询出来时会变成"Far & Away"这样的值,而写回XML流/文件时会以“&”的方式写回。老版本的TinyXML“保留”了字符实体,而在新版本中它们会被转化成字符串。
另外,所有字符都可以用它的Unicode编码数字来指定, " "和" "都表示不可分的空格字符。
TinyXML有几种不同的方式来打印输出,当然它们各有各的优缺点。
Print( FILE* ):输出到一个标准C流中,包括所有的C文件和标准输出。
"相当漂亮的打印", 但你没法控制打印选项。
输出数据直接写到FILE对象中,所以TinyXML代码没有内存负担。
被Print()和SaveFile()调用。
operator<<:输出到一个c++流中。
与C++ iostreams集成在一起。
在"network printing"模式下输出没有换行符,这对于网络传输和C++对象之间的XML交换有好处,但人很难阅读。
TiXmlPrinter:输出到一个std::string或者内存缓冲区中。
API还不是很简练。
将来会增加打印选项。
在将来的版本中可能有些细微的变化,因为它会被改进和扩展。
设置了TIXML_USE_STL,TinyXML就能支持C++流(operator <>)和C(FILE*)流。但它们之间有些差异你需要知道:
C风格输出:
基于FILE*
用Print()和SaveFile()方法
生成具有很多空格的格式化过的输出,这是为了尽可能让人看得明白。它们非常快,而且能够容忍XML文档中的格式错误。例如一个XML文档包含两个根元素和两个声明仍然能被打印出来。
C风格输入:
基于FILE*
用Parse()和LoadFile()方法
速度快,容错性好。当你不需要C++流时就可以使用它。
C++风格输出:
基于std::ostream
operator<>
从流中读取XML使其可用于网络传输。通过些小技巧,它知道当XML文档读取完毕时,流后面的就一定是其它数据了。TinyXML总假定当它读取到根结点后XML数据就结束了。换句话说,那些具有不止一个根元素的文档是无法被正确读取的。另外还要注意由于STL的实现和TinyXML的限制,operator>>会比Parse慢一些。
对是保留还是压缩空格这一问题人们还没达成共识。举个例子,假设‘_’代表一个空格,对于"Hello____world",HTML和某些XML解析器会解释成"Hello_world",它们压缩掉了一些空格。而有些XML解析器却不会这样,它们会保留空格,于是就是“Hello____world”(记住_表示一个空格)。其它的还建议__Hello___world__应该变成Hello___world 。
这是一个解决得不能让我满意的问题。TinyXML一开始就两种方式都支持。调用TiXmlBase::SetCondenseWhiteSpace( bool )来设置你想要的结果,默认是压缩掉多余的空格。
如果想要改变默认行为,你应该在解析任何XML数据之前调用TiXmlBase::SetCondenseWhiteSpace( bool ) ,而且我不建议设置之后再去改动它。
想要健壮地读取一个XML文档,检查方法调用后的返回值是否为null是很重要的。一种安全的检错实现可能会产生像这样的代码:
TiXmlElement* root = document.FirstChildElement( "Document" );
if ( root )
TiXmlElement* element = root->FirstChildElement( "Element" );
if ( element )
TiXmlElement* child = element->FirstChildElement( "Child" );
if ( child )
TiXmlElement* child2 = child->NextSiblingElement( "Child" );
if ( child2 )
// Finally do something useful.
用句柄的话就不会这么冗长了,使用TiXmlHandle类,前面的代码就会变成这样:
TiXmlHandle docHandle( &document );
TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement();
if ( child2 )
// do something useful
这处理起来容易多了。 查阅TiXmlHandle可以得到更多的信息。
对于某些应用程序来说,能够追踪节点和属性在它们源文件中的原始位置是很重要的。另外,知道解析错误在源文件中的发生位置可以节省大量时间。
TinyXML能够追踪所有结点和属性在文本文件中的行列原始位置。TiXmlBase::Row() 和 TiXmlBase::Column() 方法返回结点在源文件中的原始位置。正确的制表符号可以经由TiXmlDocument::SetTabSize() 来配置。
使用与安装
编译与运行xmltest:
提供了一个Linux Makefile和一个Windows Visual C++ .dsw 文件。只需要简单地编译和运行,它就会在你的磁盘上生成demotest.xml文件并在屏幕上输出。它还尝试用不同的方法遍历DOM并打印出结点数。
那个Linux makefile很通用,可以运行在很多系统上——它目前已经在mingw和MacOSX上测试过。你不需要运行 ‘make depend’,因为那些依赖关系已经硬编码在文件里了。
用于VC6的Windows项目文件
tinyxml: tinyxml 库,非STL
tinyxmlSTL: tinyxml 库,STL
tinyXmlTest: 用于测试的应用程序,非STL
tinyXmlTestSTL: 用于测试的应用程序,STL
Makefile
在makefile的顶部你可以设置:
PROFILE,DEBUG,和TINYXML_USE_STL。makefile里有具体描述。
在tinyxml目录输入“make clean”然后“make”,就可以生成可执行的“xmltest”文件。
在某一应用程序中使用:
把tinyxml.cpp,tinyxml.h, tinyxmlerror.cpp, tinyxmlparser.cpp, tinystr.cpp, 和 tinystr.h 添加到你的项目和makefile中。就这么简单,它可以在任何合理的C++适用系统上编译。不需要为TinyXML打开异常或者运行时类型信息支持。
TinyXML怎么工作
举个例子可能是最好的办法,理解一下:
它称不上是一个To Do列表,但它已经足够了。像下面这样读取并解析这个文件(叫“demo.xml”)你就能创建一个文档:
TiXmlDocument doc( "demo.xml" );
doc.LoadFile();
现在它准备好了,让我们看看其中的某些行和它们怎么与DOM联系起来。
TiXmlDocument "demo.xml"
TiXmlDeclaration "version=’1.0′" "standalone=no"
TiXmlComment " Our to do list data"
TiXmlElement "ToDo"
TiXmlElement "Item" Attribtutes: priority = 1
TiXmlText "Go to the "
TiXmlElement "bold"
TiXmlText "Toy store!"
TiXmlElement "Item" Attributes: priority=2
TiXmlText "Do bills"
本文档由Doxygen使用‘dox’配置文件生成。
TinyXML基于zlib许可证来发布:
本软件按“现状”提供(即现在你看到的样子),不做任何明确或隐晦的保证。由使用此软件所引起的任何损失都决不可能由作者承担。
只要遵循下面的限制,就允许任何人把这软件用于任何目的,包括商业软件,也允许修改它并自由地重新发布:
1. 决不能虚报软件的来源;你决不能声称是你是软件的第一作者。如果你在某个产品中使用了这个软件,那么在产品文档中加入一个致谢辞我们会很感激,但这并非必要。
2. 修改了源版本就应该清楚地标记出来,决不能虚报说这是原始软件。
3. 本通告不能从源发布版本中移除或做修改。
万维网联盟是定制XML的权威标准机构,它的网页上有大量的信息。
权威指南:http://www.w3.org/TR/2004/REC-xml-20040204/
我还要推荐由OReilly出版由Robert Eckstein撰写的"XML Pocket Reference"……这本书囊括了入门所需要的一切。
捐助者,联系人,还有简史
非常感谢给我们建议,漏洞报告,意见和鼓励的所有人。它们很有用,并且使得这个项目变得有趣。特别感谢那些捐助者,是他们让这个网站页面生机勃勃。
有很多人发来漏洞报告和意见,与其在这里一一列出来不如我们试着把它们写到“changes.txt”文件中加以赞扬。
TinyXML的原作者是Lee Thomason(文档中还经常出现“我”这个词) 。在Yves Berquin,Andrew Ellerton,和tinyXml社区的帮助下,Lee查阅修改和发布新版本。
最近在做prototxt文本文件解析:主要参考的代码是:https://github.com/HoboChen/hoboprototxt
在这个照虎画猫的过程中,然后就发生了一些错误,记录如下:
使用过程中出现的错误:
1.error: no matching function for call to ‘std::basic_ifstream<char>::open(const ...
fp = fopen("test.txt", "r");
接着,使用fgets函数从文件中读取字符串,并存储在指定的缓冲区中。fgets函数的第一个参数是要读取的缓冲区,第二个参数是缓冲区的大小,第三个参数是要从中读取字符串的文件指针。如下所示:
fgets(buffer, 100, fp);
最后,需要使用fclose函数关闭文件,并释放相关资源。如下所示:
fclose(fp);
完整的代码示例如下所示:
#include <stdio.h>
int main() {
FILE *fp;
char buffer[100];
fp = fopen("test.txt", "r");
fgets(buffer, 100, fp);
printf("%s", buffer);
fclose(fp);
return 0;
以上代码将读取名为test.txt的文件中的第一行,并将其输出到控制台上。
高启强668:
学英语好文
qq_42458873:
Mac mini 2011 到 macOS Catalina
一生财富:
Mac mini 2011 到 macOS Catalina
m0_67342208:
ddd 调试器配置
一个疯子大蕉: