星期三, 二月 28, 2007

XML 中的常见问题

一般问题

什么是 XML?

可扩展标记语言 (XML) 是 Web 上的数据通用语言。它使开发人员能够将结构化数据,从许多不同的应用程序传递到桌面,进行本地计算和演示。XML 允许为特定应用程序创建唯一的数据格式。它还是在服务器之间传输结构化数据的理想格式。

什么是 MSXML?

MSXML 是提供核心 XML 服务的 Microsoft 软件组件。

Microsoft XML 分析器能够做什么?

最新版本的 Microsoft 核心 XML 服务提供下面四种不同的功能。

基于文档对象模型 (DOM) 的分析器,它能够获取文本流(能够转换到 XML 的文件、程序中的字符串或者任何其他文本)并将它转换为能够编程处理的可导航 XML 树结构。


SAX(Simple API for XML)分析器,它针对处理大型文档和高吞吐量的情况进行了优化的。SAX 是基于事件的分析器,它读取文档并将分析事件(例如元素的开始和结尾)直接报告给应用程序。用户创建的应用程序实现了处理不同事件的处理程序,这非常类似于处理图形用户界面 (GUI) 中的事件。


XSLT 处理器读取 XSLT 文件,并将可扩展样式表转换语言 (XSLT) 文件的指令应用到 XML 文件,以产生某些类型的输出。除了创建 XML结构之外,XSLT 处理器还可以在得到的 XSLT 过滤器上执行一定量的优化,另外,从技术的角度看,它更像是一种编译器。


验证分析器读取文档类型定义 (DTD) 或者 XML 架构,然后检验实际得到的文档的格式是否正确,以及是否不包含与架构冲突的数据。请注意,仅对架构而言,验证架构将返回架构本身作为对象,可以在以后在 HTML 列表框中创建选项时引用这个对象。
所有四种功能都包含在同一 MSXML 库软件包中,它可以从 MSDN XML 开发人员中心(英文)免费得到。

MSXML、MSXML2 和 MSXML3 之间的区别是什么?

在过去三年中 XML 经历了许多反复,所以目前存在不同版本的 Microsoft XML 分析器也不奇怪。Internet Explorer 4.0 包含早期版本的 XML 分析器,它比 XSL、XML 数据或者大多数其他的 XML 技术(并且有完全不同的 DOM 模型)要早。该早期版本的分析器包含在 MSXML.dll 库中。从 MSDN XML 开发人员中心(英文)可将分析器升级到较新的一种。

我们极力建议您升级到新的分析器,因为它要强大得多。Internet Explorer 5.0 包括 MSXML 2.0 分析器,它包含 XSL 和 XML 架构的基本版本。MSXML2 是 SQL Server 2000 附带的分析器版本。MSXML2 包含了许多性能增强的功能,并且在总体上提高了性能和可伸缩性。MSXML3 是当前作为“技术预览”附带的版本。MSXML3 包括 XSLT 和 XPath 支持以及 SAX 接口。

XML 可以代替 HTML 吗?

XML 比 HTML 提供更大的灵活性,但是它不可能很快代替 HTML。实际上,XML 和 HTML 能够很好地在一起工作。Microsoft 希望许多作者和开发人员都能同时使用 XML 和 HTML,比如用 XSLT 来生成 HTML。

在 HTML 中增加 XML 的好处是什么?

在 Web 上使用 XML 的好处有:

它提供用于本地计算的数据。传递到桌面的数据可以进行本地计算。XML 分析器可以读取数据,并将它递交给本地应用程序(例如浏览器)进一步查看或处理。数据也可以由使用 XML 对象模型的脚本或其他编程语言来处理。


向用户提供正确的结构化数据视图。传递到桌面的数据可以以多种方式表示。本地数据集,可以根据用户喜好和配置等因素,以适当的形式,在视图中动态表现给用户。


允许集成不同来源的结构化数据。一般情况下,使用代理,在中间层服务器上集成来自后端数据库和其他应用程序的数据,使该数据能够传递给桌面或者其他服务器,做进一步聚合、处理和分布。


描述来自多种应用程序的数据。由于 XML 是可扩展的,因此它可以用于描述来自多种应用程序的数据,从描述 Web 页面集合到数据记录。由于数据是自描述的,因此不需要数据的内置描述,也能够接收和处理数据。


通过粒度更新来提高性能。XML 允许粒度更新。开发人员不必在每次有改动时都发送整个结构化数据集。有了粒度更新后,只有改变的元素才必须从服务器发送到客户机。改变的数据可以在不必刷新整个页面或表的情况下显示。
XML 只适用于核心开发人员吗?

不。和 HTML 文档一样,XML 文档可以由任何人创建 — 甚至是没有任何编程经验的人。XML 仅仅是一种描述信息的标准方式。此外,它还是一种语言,可以在没有任何软件的情况下用它来编写。您可以在文本编辑器中编写 XML 文档,并且直接放入 Web 站点,不需要编写传统方式下的任何代码。

开始使用 XML 时需要什么?

要使用 XML,您需要能够读取 XMl 文档的 XML 分析器,并且使它的内容能被处理。Microsoft 提供了一种分析器,可以从 MSDN XML 开发人员中心(英文)下载。

要使用 XML 文档,可以使用文本编辑器(例如记事本)或任何其他可以用于创建 HTML 页面的编辑器。要创建完整形式的 XML 应用程序,请使用诸如 Microsoft® Visual Studio® 的编程环境。

有如何使用 XML 的实际例子吗?

XML 正在数量惊人的应用程序中使用,范围从 Web 站点创建和文档化到数据库集成和分布式编程。在下面几个领域中,XML 有其用武之地:

业务对业务的传输。业务数据(发票、购买定单、会计和税务信息等等)是以 XML 格式在厂商之间电子传输的。与旧的电子数据交换 (EDI) 格式相比,XML 提供了许多优点,而不仅仅是可以在传输中从一种发票格式转换到另一种。


分布式编程。XML 是非常理想的复杂多平台应用程序构造方案,这样就使得 Windows 服务器和其他操作系统的集成成为可能。


Web 站点体系结构。由于 XML 的层次结构和分布式特性,Web 站点开发人员在他们 Web 站点的总体体系结构和导航结构中越来越多地使用它。此外,越来越多的目录表和索引表、跟踪用户信息及 Web 站点状态的 XML 结构、基于 HTML 的组件和处理数据流的渠道,都用 XML 和 XSLT 编写。


数据库操作。XML 正成为与数据库交互的流行工具 — 无论从 SQL 查询中检索 XML 数据集,还是用 XML 记录更新数据库。而且我们还有分离实现的优点。通过将数据压缩为 SML,就不需要对实际的数据库结构有任何了解。


文档管理。目前的大多数公司都陷在文书工作的海洋里,而且会越来越糟。XML 正在越来越多地用于将文档编码成 XML,使文档更易于检索或提供链接到文档的注释上下文,以便更有效地进行参考。
我可以忽略 XML 吗?

如果您想在 Internet 世界中竞争的话,那就不可以忽略 XML。XML 是导致以我们考虑编程本身的方式进行模式转移的一种语言。传统的专用客户机/服务器应用程序,正在给“随时随地访问”的 Internet 服务让位,XML 是处理任何事情(从数据访问处理到在该新环境中表现数据)的逻辑媒体。

Microsoft Internet Explorer 4.0 支持 XML 吗?

是的。Internet Explorer 4.0 支持 XML 下列功能:

通用的 XML 分析器,它读取 XML 文件并将它们传递到应用程序(例如查看器)进行处理。应用程序开发人员可以使用 Microsoft 的两个分析器:C++ 中的 Microsoft XML 分析和 Java 中的 Microsoft XML 分析器。


XML 对象模型 (XML OM) 使用 World Wide Web Consortium (W3C) 标准文档对象模型 (DOM) 允许程序通过 XML 分析器访问结构化数据,使开发人员拥有交互和计算数据的能力。有关详细信息,请参见 DOM 规范(英文) 。


XML 数据源对象 (XML DSO) 允许开发人员连接到结构化 XML 数据,并用动态 HTML 的数据绑定机制将它提供给 HTML 页面。
Internet Explorer 5.0 对 XML 提供哪个级别的支持?

Internet Explorer 5 提供下列 XML 支持:

直接查看 XML。Microsoft XML 实现允许用户通过他们的 Web 浏览器使用 XSL 或者层叠样式表 (CSS) 查看 XML,就象查看 HTML 文档一样。


高性能、验证 XML 引擎。Internet Explorer 4.0 开发人员熟悉的 XML 引擎已经得到了很大的增强,并且完全支持 W3C XML 1.0 和 XML 名称空间,这样开发人员就可以在 Web 上统一限定元素名称,从而避免了相同名称的元素之间的冲突。Windows 中的本机 XML 支持意味着,当开发人员在应用程序和组件之间移动数据时,可以使用完整的 XML 处理功能来读取和处理数据。


可扩展样式转换语言 (XSLT) 支持。使用基于最新的 W3C Working Draft 的 Microsoft XSLT 处理器,开发人员可以对 XML 数据应用样式表,并以易于自定义的动态和灵活方式来显示数据。Microsoft XSLT 处理器的查询功能也允许开发人员用程序,在客户机或服务器上,在 XML 数据集内部查找和摘取信息。


XML 架构。架构(英文) 定义 XML 文档规则,包括元素名称和丰富的数据类型,它们的元素可以表现为组合,并且每个元素的属性都可用。为了启用多层应用程序,Microsoft 将根据提交给 W3C XML 工作组的缩减 XML 数据架构 (XDR) 来发布 XML 架构的技术预览。


服务器端 XML。服务器端 XML 处理允许使用 XML 作为在多个分布式应用程序服务器(甚至是跨越操作系统边界)之间传送数据的标准方式。


XML 文档对象模型 (DOM)。XML DOM 是标准对象应用程序编程接口,它使开发人员能够用程序控制 XML 文档内容、结构、格式等等。Microsoft XML 实现包括对 W3C XML DOM 建议的完全支持,并且可以用脚本、Visual Basic 开发系统、C++ 和其他编程语言来访问。


C++ XML 数据源对象。XML DSO 允许将 HTML 元素直接绑定到 XML 数据岛。此外,它还提高了性能,有强大的能力可以绑定到不同 XML 节点上,而且利用了 Internet Explorer 5 beta 中所有的新的数据绑定功能。
HTML、动态 HTML 和 XML 之间的关系是什么?

HTML 可以和 CSS 一起用于格式化和表示超链接页面。动态 HTML,通过 DOM,使 HTML 中的所有元素,都可以通过与语言无关的脚本和其他编程语言来访问,从而在很大程度上提高了客户机端的交互能力,而且不需要对服务器的额外需求。页面的对象模型允许动态更改任何方面的内容(包括增加、删除和移动)。

通过为结构化数据添加 XML,为开发人员提供了构造下一代丰富、灵活 Web 应用程序的技术。使用 XML,他们可以为桌面提供结构化数据,并且用 XML 对象模型计算数据。现在的开发人员可以在浏览器(例如 Microsoft Internet Explorer 4.0 和 Microsoft Internet Explorer 5)或者其他应用程序中,通过脚本显示基于 XML 的数据。此外,他们也可以对数据应用格式化规则,而不需要使用 XSLT 样式表的复杂脚本(实际上是将基于 XML 的数据转换为显示)。这两种显示基于 XML 数据的方法,都使得生成复杂数据的多个视图成为可能。

为什么 XML 这么重要?

XML 将成为计算领域的未来。作为一种技术,它的影响力将渗透到编程的每个方面,从嵌入式系统到图形界面,到分布式系统以及数据库管理。它实际上已经成为软件工业之间数据通信的标准,并且迅速代替 EDI 系统成为全球几乎所有工业进行业务交换的主要媒体。它很有可能会成为创建和存储大多数文档的语言,不管是否在 Internet 上,并且有可能成为 Internet 应用程序服务器的基础,有些人相信它将代替许多目前生产的小包装产品。

Microsoft 提供什么 XML 产品?

Microsoft 正在努力为许多领域开发 XML 资源:

数据访问。最新的 ActiveX® 数据对象 (ADO) 的化身,已经支持 XML 的生成和消费将近一年了,并且 Microsoft SQL Server 2000 为基于 SQL 的信息和 XML 之间的互相转换提供了许多接口。


浏览器支持。Microsoft 是第一个生产能够阅读 XML 文件(无论是用原始结构形式,还是通过 XSL 或 CSS 样式表)的商业浏览器 (Internet Explorer 5) 的公司。


BizTalk Server 2000。Microsoft® BizTalk(TM) Server 2000 提供了在大量不同格式(包括 XML)之间通信的方式,来帮助推动业务对业务的应用程序。BizTalk Server 也包括了许多架构生成和映射工具,从而大大简化了业务或商业需要的 XML 结构的开发。
在 Web 上传输,必须压缩 XML 吗?

一般来说,压缩 XML 的需要是与应用程序有关的,并且很大程度上是服务器和客户机之间移动数据量的函数。用于描述数据结构的标记的反复特性,使 XML 能够非常好地压缩数据。值得注意的是 HTTP 1.1 服务器和客户机的压缩都是标准的,XML 可以自动从中受益。

XML 作为数据格式有多安全?有没有增加 XML 安全性的计划?

XML 和 HTML 一样安全。正因为安全的 HTTP (HTTPS) 可以用于对 HTTP 添加密码技术,从而保护了 HTML,因此它也可以用于保护 XML。XML 是表示结构化数据的基于文本格式。这可以使数据的简单性和互操作性最大化。对 XML 格式增加安全性和验证能力时可以采取许多步骤。首先,XML 可以在传输到客户之前在服务器上加密,然后在客户机上解密。应用于数据本身的数字签名也可以验证 XML。

确认

什么是 DTD 及其用途?

文档类型定义 (DTD) 定义了一类 XML 文档的正确语法。也就是说,它列出了许多元素名称,什么元素可以和其他元素一起显示,什么属性可以用于每种元素类型等等。DTD 使用 XML 文档使用的不同语法形式。

Web 开发人员在使用 XML 描述数据时必须包括 DTD 吗?

不。XML 可以用于描述有或没有 DTD 的数据。术语“有效”XML 指代引用 DTD 的 XML 数据,而“良好形成的”XML 是未使用 DTD 的 XML。此外,“良好形成的”XML 还是 XML 和标准统一标记语言 (SGML) 的基本区别之一。很明显,在这两种情况下,XML 本身都必须遵守语言的标准(例如,所有标记都必须是闭合的,并且标记不能重叠)。

什么是 XML 架构?它们与 DTD 的区别是什么?

虽然 XML 1.0 提供了定义 XML 文档的内容模型的机制 — DTD — 但是很明显还需要定义内容模型的更全面更有效的方法。XML 架构是特殊的 XML 结构的定义(从其组织和数据类型两个方面)。XML 架构使用“XML 架构”语言来指定在架构中如何定义元素的每种类型,以及与元素关联的数据类型。与 DTD 相比,架构的最鲜明的特点,便是架构本身就是 XML 文档。这意味着阅读它所描述的 XML 的工具也可以阅读它。

Microsoft 的 XML 服务目前支持 XML 数据架构,它代表了在 1999 年 3 月 Internet Explorer 5 装运时 “W3C 架构”活动的快照。XML 数据架构允许开发人员在他们的 XML 文档中添加数据类型,以及定义开放的内容模型。这种对 DTD 功能的扩展对 XML 编程很关键。

但是 W3C 正在准备“XML 架构定义 (XSD)”,它将成为“XML 架构标准”。Microsoft 计划在此规范成为建议后,立即使对“XML 架构定义 (XSD)”的支持成为它核心 XML 服务的一部分。

什么是名称空间,它们为何重要?

名称空间是 XML 的另一个高级功能,并作为 XML 1.0 规范的一部分,在 W3C 笔记中作了概述。它们允许开发人员限定元素名称和关系。名称空间使元素名称唯一可识别,从而避免了名称相同,但是在不同词典中定义的元素之间发生名称冲突。它们允许混合来自不同名称空间的标记,这一点对多个来源的数据很重要。

例如,书店可以定义 <TITLE> 标记代表书名,它只包含在 <BOOK> 元素中。但是,个人目录可以定义 <TITLE> 代表个人的职位,例如:

<TITLE>President</TITLE>

名称空间有助于清晰地定义这种差别。

XSLT 和 XPATH

什么是 XSLT?

XSLT(英文) ,或者用于转换的可扩展样式表语言,它是在 1999 年 11 月 6 日获得批准的 W3C 建议。从标记和编程两重意义上说,这种语言提供了将 XML 结构转换到其他 XML 结构、HTML 或者任何数量的其他文本格式(例如 SQL)的机制。虽然它可以用于创建 Web 页面的显示输出,但是 XSLT 的实际功能在于能够改变底层结构,而不是简单地改变这些结构的媒体表示,就像在层叠样式表 (CSS) 中一样。

XSL、XQL、XSL 模式 和 XSLT 之间的区别是什么?

XSLT 源自 CSS 在对 XML 文档结构改动上的局限性,当时创建 XML 的原因更倾向于代替 HTML 而不是提供通用数据描述语言。因此可扩展样式表语言 (XSL) 成为构造格式化 XML 新方法的成果。

但是,“W3C 样式工作”组的成员和早期 XML 采用者很快就发现,能够将 XML 从一种格式转换到另一个格式的语言,可以极大地简化生成的大量代码。Microsoft 向 W3C 提交了一个建议,最初名称为 XML 查询语言(或 XQL),它随之被 W3C 采纳为 XSL 模式语言。该语言的大多数功能最终都被纳入了 XSLT 规范中。

最终标准结合了为不同初始条件而修改的 XSLT 参数,创建代码功能块的命名模板,以及大量用于数值和字符串处理的增强功能。XSLT 还为在语言中添加内置功能提供方便,提供了 Microsoft 可在自己的实现中用来添加许多极其有用功能(包括访问 COM 对象和脚本)的产品。

什么是 XPath?

XPath(英文) 是为 XML 定义的查询语言,它提供在文档中选择节点子集的简单语法。通过 XPath,通过指定类似于目录的路径(即名称)以及路径中的条件,可以检索元素集合。XPath 对 XSLT 和 XML DOM 都很重要,并与 XPointer 规范(允许根据统一资源定位符 [URL] 和 XPath 表达式的组合来选择文档片断)有联系。

为什么 XSLT 对 XML 很重要?

XSLT 是将一种 XML 文档转换为另一种的语言。这意味着它提供了单源 XML 数据的机制,可以在 Web 页面中创建由用户动态更改的丰富视图,可以为目标通信过滤数据。XSLT 对于业务规则编码已经足够强大。它可以从数据生成图形(不仅仅是 Web 页面)。它甚至可以处理与其他服务器的通信 — 特别是和能够集成到 XSLT 中的脚本模块协作 — 以及在 XSLT 自身内部生成适当的消息。虽然它不可能代替桌面系统中的大多数交互(出于性能和使用方便性两方面分原因),但是在未来的几年中,XSLT 很有可能最终成为系统之间进行通信的主要“编程”语言。

XSLT 和 CSS 之间的区别是什么?它们不是样式表吗?

层叠样式表 (CSS) 的任务是对 HTML 元素指定一组显示属性。CSS 决定了页面的视觉外观,但是不会改变源文档的结构。

另一方面,XSLT 也称为基于模板的语言,它允许将某种模式映射到源文档中,该源文档的输出是用 XML、HTML 或纯文本书写的。使用 XSLT,可以将 XML 文档的结构转换为不同的 XML 文档。例如,您可以更改 XML 文档的顺序、添加或删除元素、执行条件测试或者用元素的集合进行迭代。

XSLT 和 CSS 不是兼容标准。一种在 XML 中创建 Web 页面的有用技术,是用 XSLT 将 XML 转换为诸如列表或表的结构,然后将 CSS 应用于结果,控制这些结构在适当媒体中的显示。您甚至可以从 XSLT 创建 CSS。

标准

Microsoft 如何与 XML 标准兼容?

从 XML 语言开始产生起,Microsoft 就站在了 XML 的最前沿,而且很值得注意的是,W3C 在过去几年中所生成的大多数 XML 建议和工作草案,都包括来自至少一个(在某些情况下可能是数个)Microsoft 职员的输入和参与。Microsoft 已经多次被委以重任,与 W3C 标准组织一起,确保 XML 的开发能使所有用户受益,并且在许多不同领域的开发中都作出了重要贡献,包括 XML 规范、DOM、XSLT 和架构定义语言。Microsoft 承诺将与最新规范和标准保持一致。

XML 和 World Wide Web Consortium (W3C) 之间的关系是什么?

W3C 有一活跃的 XML 工作组。Microsoft 从 1996 年 6 月起就是该组织的共同发起者之一,从那时起许多业界企业都开始加入,包括 Netscape Communications Corp.、IBM 和 Oracle。有关 XML 标准过程的详细信息,请访问 W3C Web 站点(英文)。

XML 和 W3C 的地位关系如何?

XML 1.0 是在 1998 年 12 月被正式批准的,现在仍是个稳定的标准。有关当前 XML 规范的详细信息以及 W3C 内部的提交和审阅过程,请参见 W3C Web 站点(英文)。

DOM 在 W3C 中的地位如何?

DOM Level 1 的 W3C 文档状态为“推荐”。这说明 W3C 目前正在提议将它作为 World Wide Web 上的标准。有关 DOM 和在 W3C 内部提交和审阅过程的详细信息,请参见 DOM 规范(英文)。

工具支持

SQL Server 和 ADO 支持 XML 吗?

Microsoft ActiveX 数据对象 (ADO) 技术,提供许多将数据库记录集(数据记录集合)转换为 XML 格式的方法,以及从给定结构中获取 XML 并将其转换回 ADO 所支持数据库(包括 SQL Server 和 Oracle 数据库)的工具。此外,通过 MSXML2 和 MSXML3 中的 XML 数据源对象,还可以将任意 XML 直接加载到 ADO 中以生成记录集。

SQL Server 2000 还允许通过 URL 直接设置和检索 XML,方式和调用 Web 页面非常相似。这是处理数据的强大机制,因为它基本上意味着可以将 SQL Server 数据直接集成到 XML 过滤器和 Web 页面,基本上凡是有 XML 文档的地方都可以集成 SQL Server 数据。此外,还可以设置自定义模板,来控制如何从 SQL Server 数据中产生 XML,使数据库成为生成 XHTML 页面的强大工具。

最后,诸如 BizTalk Server 这样的应用程序还允许在任意数量的不同数据源(从 XML 文档到数据库到 Excel 和 Word 文档)之间进行映射、为 Web 体系结构创建复杂数据管道以及根据 XML 数据库需求构造有效的架构。

目前有 Microsoft 工具可以帮助我快速沿用 XML 吗?

Microsoft BizTalk Server 2000 是用于数据交换的、基于 XML 的服务器,它提供启用电子商务业务社会的基本结构和工具。BizTalk Server 的基础是基于规则的业务文档路由、转换和跟踪基本结构。该基本结构允许公司通过在组织边界之内或之间的应用程序中间交换业务文档,例如购买定单和发票,来集成、管理和自动化业务过程。有关详细信息,请参见Microsoft BizTalk Server 2000(英文) 。

什么是 SOAP?

SOAP 是简单对象访问协议,是创建大范围分布式、复杂计算环境的工具,该计算环境可使用现有的 Internet 基本结构运行在 Internet 上。SOAP 可以使应用程序用许许多多方法在 Internet 上互相直接通信。有关 SOAP 的详细信息,请参见 SOAP 规范(英文)。

XML 如何适用于 Microsoft Windows® Distributed InterNet Applications (Windows DNA) 策略来构造三层并具有 Web 功能的应用程序?

XML 正在快速成为将结构化数据从中间层传送到桌面的工具。基于 XML 的数据可以通过中间层代理与多种后端(数据库)源集成。架构(参见“XML 数据”一节)可以改进该过程,并且开发人员可以更精确地描述和交换数据。

问题和解决方案

为什么在调用了 Load() 方法后,我的文档对象仍然为空?

默认情况下操作是异步加载的。这意味着如果提供 http URL 地址,那么 load() 方法将直接返回,并且文档对象仍然为空,因为数据还没有从服务器返回。为了纠正这个问题,请在代码中添加下面的行:

xmldoc.async = false;

同时,如果是从独立的 C++ 应用程序加载 http XML 文档,那么必须查询消息队列才能继续加载。

如何加载有外国和特殊字符的文档?

文档可以包含外国字符,例如:

<test>foreign characters (úóíÿ) </test>

例如 磲 的外国字符必须在前面加上 escape 序列。外国字符可以是 UTF-8 编码或用不同编码指定,如下所示:
<?xml version="1.0" encoding="iso-8859-1"?>
<test>foreign characters (磲) </test>

现在可以正确加载 XML 了。

其他字符是保留在 XML 中的,并且需要以不同的方式处理。下面的 XML:

<foo>This & that</foo>
产生如下错误:
此处不允许有空格。
行 0000001: <foo>This & that</foo>
位置 0000012: ----------^

此处 & 是 XML 句法结构的一部分,如果它仅仅放在 XML 数据源内部,那么不能解释为 &。您需要替换称为“实体”的特殊字符序列。

<foo>This & that</foo>
下面的字符需要相应的实体:

< <
& &
> >
" "
' '

引号字符被用作标记中属性值的定界符,因此通常不能在属性值的内部使用。例如,下面的内容将返回错误:
<foo description="John´s Stuff">

此处的单引号既用作属性定界符,又在属性值自身中。为了纠正这个问题,可以将属性定界符换成双引号:

<foo description="John´s Stuff">
或者可以将单引号转义为实体 '

<foo description="John' Stuff">
上述两种方式都将通过 XML 对象模型中的 getAttribute 方法返回属性值 John's Stuff。同样,对于双引号,您可以使用实体
"。
也可以通过将文本放在 CDATA 节中来处理元素内容中的特殊字符。下面的内容是正确的:

<xml>
<![CDATA[ This & that <stuff> is just "text" content。 ]]>
</xml>
在本例子中,XML 对象模型将 CDATA 节点显示 xml 节点的子节点,它将返回字符串

This & that <stuff> is just "text" content.
作为 nodeValue。


如何在 Visual Studio 6.0 C++ 中使用 MSXML COM 组件?

在 Visual C++ 6.0 中使用 MSXML COM 组件的最简便方式是使用 #import 指令:

#import "msxml.dll" named_guids no_namespace#import "msxml.dll" named_guids no_namespace
它定义了所有 IXML* 接口和接口 ID,从而可以在应用程序中使用它们了。也可以从 INETSDK 获取 MSXML 类型库和头文件(英文),以及包含类 IIDs 的 uuid.lib。

如何在 XML 中使用 HTML 实体?

下面的 XML 包含 HTML 实体:

<copyright>Copyright © 2000, Microsoft Inc, All rights reserved.</copyright>

它产生下列错误:

引用未定义的实体 'copy'。
行: 1, 位置:23, 错误码:0xC00CE002
<copyright>Copyright © 2000, ...
----------------------^

这是因为 XML 只有五个内置实体。关于内置实体的详细信息,请参阅如何加载有外国和特殊字符的文档?。

要使用 HTML 实体,需要用 DTD 定义它们。有关 DTD 的详细信息,请参阅 W3C XML 建议(英文)。要使用该 DTD,请将它直接包括在 DOCTYPE 标记中,如下所示:
<!DOCTYPE foo SYSTEM "http://msdn.microsoft.com/xml/general/htmlentities.dtd">
<copyright>Copyright © 2000, Microsoft Inc, All rights reserved.</copyright>
要加载它,需要关闭 IXMLDOMDocument 接口的 validateOnParse 属性。请尝试将它粘贴到“Validator 测试页”中,关闭 DTD 验证,然后单击“验证”。请注意文档将加载,并且版权字符将显示在 validator 页面的末尾的 DOM 树中。

如果已经完成了 DTD 验证,那么必须将作为参数实体的 HTML 实体包括在现有的 DTD 中,如下所示:
<!ENTITY % HTMLENT SYSTEM "http://msdn.microsoft.com/xml/general/htmlentities.dtd">
%HTMLENT;<!ENTITY % HTMLENT SYSTEM "http://msdn.microsoft.com/xml/general/htmlentities.dtd">
%HTMLENT;
它将定义所有 HTML 实体,以便在 XML 文档中使用它们。

在元素内容中如何处理空白字符?

XML DOM 有三种访问元素文本内容的方式:

属性 行为
nodeValue 按照原始的 XML 源中指定的那样,返回 TEXT、CDATA、COMMENT 和 PI 节点上的原始文本内容(包括空白字符)。对于 ELEMENT 节点和 DOCUMENT 本身,则返回空值。
数据 与 nodeValue 相同
文本 重复连接指定子树中的多个 TEXT 和 CDATA 节点并返回组合结果。

注意: 空白字符包括新行、tab 和空格。

nodeValue 属性通常返回原始文档中的内容,与文档如何加载和当前 xml:space 范围无关。

文本属性连接指定子树中的所有文本并扩展实体。这与文档如何加载、preserveWhiteSpace 开关的当前状态和当前 xml:space 范围有关,请看如下所示:

preserveWhiteSpace = true when the document is loaded preserveWhiteSpace=true preserveWhiteSpace=true preserveWhiteSpace=false preserveWhiteSpace=false
xml:space=preserve xml:space=default xml:space=preserve xml:space=default
保留 保留 保留 保留并截断

preserveWhiteSpace = false when the document is loaded preserveWhiteSpace=true preserveWhiteSpace=true preserveWhiteSpace=false preserveWhiteSpace=false
xml:space=preserve xml:space=default xml:space=preserve xml:space=default
半保留 半保留并截断 半保留 半保留并截断


此处的保留表示和原始 XML 文档中完全相同的原始文本内容,截断意味着前导和尾部空格已经删除,半保留意味着保留了“重要的空白字符”并规范化了“不重要的空白字符”。重要的空白字符是文本内容内部的空白字符。不重要的空白字符是标记之间的空白字符,请看如下所示:

<name>\n
\t<first> Jane</first>\n
\t<last>Smith </last>\n
</name>
在本示例中,红色是可以忽略的不重要的空白字符,而绿色是重要的空白字符,因为它是文本内容的一部分,因此有不可忽略的重要含义。所以在本例中,文本属性返回下列结果:

状态 返回值
保留 "\n\t Jane\n\tSmith \n"

保留并截断 "Jane\n\tSmith"

半保留 " Jane Smith "

半保留并截断 "Jane Smith"


请注意“半保留”将规范化不重要的空白字符,例如,新行和 tab 字符将退化为单个空格。如果更改 xml:space 属性和 preserveWhiteSpace 开关,那么文本属性将返回相应的不同值。

CDATA and xml:space="preserve" subtree boundaries
在下面的例子中,CDATA 节点或“保留”节点的内容将得到连接,原因是它们不参与不重要的空白字符规范化。例如:

<name>\n
\t<first> Jane </first>\n
\t<last><![CDATA[ Smith ]></last>\n
</name>
在这种情况下,CDATA 节点内部的空白字符不再与“不重要”空白字符“合并”,并且不会截断。因此“半保留并截断”情况将返回下列内容:

"Jane Smith "
在此,</first> 和 <last> 标记之间的不重要的空白字符将包括在内,与 CDATA 节点的内容无关。如果用下列内容代替 CDATA,那么将返回相同结果:

<last xml:space="preserve"> Smith </last>
实体是特殊的

实体是作为 DTD 的一部分加载和分析的,并且显示在 DOCTYPE 节点下。它们不一定要有任何 xml:space 范围。例如:

<!DOCTYPE foo [
<!ENTITY Jane "<employee>\n
\t<name> Jane </name>\n
\t<title>Software Design Engineer</title>\n
</employee>">
]>
<foo xml:space="preserve">&Jane;</foo>
假定 preserveWhiteSpace=false(在 DOCTYPE 标记范围内),在分析实体时不重要的空白字符丢失。实体将不会有空白字符节点。树将类似于:

DOCTYPE foo
ENTITY: Jane
ELEMENT: employee
ELEMENT: name
TEXT: Jane
ELEMENT: title
TEXT>:Software Design Engineer
ELEMENT: foo
ATTRIBUTE: xml:space="preserve"
ENTITYREF: Jane

请注意,在 DOCTYPE 内部 ENTITY 节点下显露的 DOM 树不包含任何 WHITESPACE 节点。这意味着 ENTITYREF 节点的子节点也没有 WHITESPACE 节点,即使实体引用在 xml:space="preserve" 的范围内也是这样。

给定文档中引用的每个 ENTITY 的实例通常都有相同的树。

如果实体必须绝对保留空白字符,那么它必须在自己内部指定自己的 xml:space 属性,或者文档 preserveWhiteSpace 开关必须设置为 true。

如何处理属性中的空白字符?

有几种方式可以访问属性值。IXMLDOMAttribute 接口有 nodeValue 属性,它等价于作为 Microsoft 扩展的 nodeValue 和 text 属性。这些属性返回:属性 返回的文本
attrNode.nodeValue
attrNode.value
getAttribute("name") 返回和原始文档中完全相同的内容(和扩展的实体)。
attrNode.nodeTypedValue Null
attrNode.text 除了前导和尾部的空白字符已经截断之外,其他与 nodeValue 相同。


“XML 语言”规范为 XML 应用程序定义了下列行为:属性类型 返回的文本
CDATA ID、IDREF、IDREFS、ENTITY、ENTITIES、NOTATION、枚举
半规范化 全规范化
在此半规范化代表将新行和 tab 字符转换为空格,但是多个空格不会退化为一个空格。

在 XML 对象模型中如何处理空白字符?

有些时候,XML 对象模型将显示包含空白字符的 TEXT 节点。空白字符被截断后,多半会带来一些混乱。例如下面的 XML 例子:

<?xml version="1.0" ?>
<!DOCTYPE person [
<!ELEMENT person (#PCDATA|lastname|firstname)>
<!ELEMENT lastname (#PCDATA)>
<!ELEMENT firstname (#PCDATA)>
]>
<person>
<lastname>Smith</lastname>
<firstname>John</firstname>
</person>

生成下列树:

Processing Instruction: xml
DocType: person
ELEMENT: person
TEXT:
ELEMENT: lastname
TEXT:
ELEMENT: firstname
TEXT:

名字和姓氏两边是只包含空白字符的 TEXT 节点,因为“person”元素的内容模型是 MIXED;它包含 #PCDATA 关键字。MIXED 内容模型指定元素之间可以有文本存在。因此,下面的内容也是正确的:

<person>
My last name is <lastname>Smith</lastname> and my first name is
<firstname>John</firstname>
</person>

结果是类似于下面的树:

ELEMENT: person
TEXT: My last name is
ELEMENT: lastname
TEXT: and my first name is
ELEMENT: firstname
TEXT:

如果没有单词“is”之后和 <lastname>之前的空白字符,以及 </lastname>之后和单词“and”之前的空白字符,那么句子便无法理解。因此,对于 MIXED 内容模型来说,文字组合、空白字符和元素都是相关的。对于非 MIXED 内容模型来说则不是这样。

要使只有空白字符的 TEXT 节点消失,请从“person”元素声明中删除 #PCDATA 关键字:

<!ELEMENT person (lastname,firstname)>

结果是下面清晰的树:

Processing Instruction: xml
DocType: person
ELEMENT: person
ELEMENT: lastname
ELEMENT: firstname


XML 声明做什么?

XML 声明必须列在 XML 文档的顶部:

<?xml version="1.0" encoding="utf-8"?>
它指定下面的项目:

该文档是 XML 文档。在丢失或者还没有指定 MIME 类型时 MIME 探测器可以用它来检测文件是否为类型 text/xml。
文档符合 XML 1.0 规范。在以后 XML 有其他版本时这一点很重要。
文档字符编码。编码属性是可选的,默认为 UTF-8。
注意:XML 声明必须在 XML 文档的第一行,因此下面的 XML 文件:

<!--HEADLINE="Dow closes as techs get hammered"-->
<?xml version="1.0"?>
产生下面的分析错误:

无效的 xml 声明。
行 0000002: <?xml version="1.0"?>
位置 0000007: ------^
注意:XML 声明是可选的。如果需要在顶部指定注释或者处理指令,那么请不要放入 XML 声明。但是,默认的编码将为 UTF-8。

如何以可读格式打印我的 XML 文档?

在用 DOM 从零开始构造文档以产生 XML 文件时,任何内容都在一行上,相互之间没有空格。这是默认的行为。

构造在 Internet Explorer 5 中的默认 XSL 样式表,以可读格式显示和打印 XML 文档。例如,如果已经安装了 IE5,请尝试查看 nospace.xml 文件。浏览器中应该显示下面的树:

- <ORDER>
- <ITEM NAME="123">
<NAME>XYZ</NAME>
<PRICE>12.56</PRICE>
</ITEM>
</ORDER>
在 XML 中没有插入空白字符。

打印可读 XML 是非常有趣的,特别是有定义不同类型内容模型的 DTD 时。例如,在混合内容模型 (#PCDATA) 下不能插入空格,因为它可能改变内容的含义。比如请考虑下面的 XML:

<B>E</B><I>lephant</I>
这最好不输出为:

<B>E</B>
<I>lephant</I>
因为单词边界不再正确。

所有这些都使自动化打印成为问题。如果不需要打印可读 XML,那么可以使用 DOM 在适当的位置插入空白字符作为文本节点。


如何在 DTD 中使用名称空间? 要在 DTD 中使用名称空间,请在使用它的元素的 ATTLIST 声明中声明它,如下所示:

<!ELEMENT x:customer ANY >
<!ATTLIST x:customer xmlns:x CDATA #FIXED "urn:...">

名称空间类型必须为 #FIXED。属性的名称空间也是这样:
<!ELEMENT customer ANY >
<!ATTLIST customer
x:value CDATA #IMPLIED
xmlns:x CDATA #FIXED "urn:...">

名称空间和 XML 架构
DTD 和 XML 架构不能混合。例如,下面的

xmlns:x CDATA #FIXED "x-schema:myschema.xml"

将不导致使用在 myschema.xml 中定义的架构定义。对 DTD 和 XML 架构的使用是互斥的。

如何在 Visual Basic 中使用 XMLDSO?

使用下面的 XML 作为例子:

<contacts>
<person>
<name>Mark Hanson</name>
<telephone>206 765 4583</telephone>
</person>
<person>
<name>Jane Smith</name>
<telephone>425 808 1111</telephone>
</person>
</contacts>
可以按如下方式绑定到 ADO 记录集:

创建新的 VB 6.0 项目。
添加对 Microsoft ActiveX Data Objects 2.1 或更高版本、Microsoft Data Adapter Library 和 Microsoft XML 2.0 版的引用。
用下面的代码将 XML 数据加载到 XML DSO 控件中:
Dim dso As New XMLDSOControl
Dim doc As IXMLDOMDocument
Set doc = dso.XMLDocument
doc.Load ("d:\test.xml")

用下面的代码将 DSO 映射到使用 DataAdapter 的新记录集对象中:
Dim da As New DataAdapter
Set da.Object = dso
Dim rs As New ADODB.Recordset
Set rs.DataSource = da
访问数据:
MsgBox rs.Fields("name").Value

结果显示字符串“Mark Hanson”
如何在 Java 中使用 XML DOM?

必须已经安装 MSXML.DLL 的 IE5 版本。在 Visual J++ 6.0 中,从项目菜单选择添加 COM 包装程序,然后从 COM 对象列表中选择“Microsoft XML 1.0”。该操作将把所需的 Java 包装程序构造到称为“msxml”的新软件包中。这些预先构造的 Java 包装程序也可以下载。类可以按如下方法使用:

import com.ms.com.*;
import msxml.*;
public class Class1
{
public static void main (String[] args)
{
DOMDocument doc = new DOMDocument();
doc.load(new Variant("file://d:/samples/ot.xml"));
System.out.println("Loaded " + doc.getDocumentElement().getNodeName());
}
}
代码示例将从 sun religion 示例中加载 3.8MB 测试文件“ot.xml”。Variant 类用于包装 Win32 VARIANT 基本类型。

因为在每次检索节点时实际上都获得了新的包装程序,因此不能在节点上使用指针比较。因此,不要使用下面的代码,

IXMLDOMNode root1 = doc.getDocumentElement();
IXMLDOMNode root2 = doc.getDocumentElement();
if (root1 == root2)...
而要使用下面的代码:

if (ComLib.isEqualUnknown(root1, root2)) ....
.class 包装程序的总大小大约为 160KB。但是,为了与 W3C 规范完全符合,应该只使用 IXMLDOM* 包装程序。下面的类是旧的 IE 4.0 XML 接口,可以从 msxml 文件夹中删除它们:

IXMLAttribute*,
IXMLDocument*, XMLDocument*
IXMLElement*,
IXMLError*,
IXMLElementCollection*,
tagXMLEMEM_TYPE*
_xml_error*
这使大小减少为 147KB。同时还可以删除下面的项目:

DOMFreeThreadedDocument
在 Java 应用程序中从多个线程访问 XML 文档。
XMLHttpRequest
用 XML DAV HTTP 扩展与服务器通信。
IXTLRuntime
定义 XSL 样式表脚本对象。
XMLDSOControl
绑定到 HTML 页面中的 XML 数据。
XMLDOMDocumentEvents
在分析过程中返回回调。
这可以将大小减少到 116KB。要使它更小,请考虑 DOM 本身有两层的事实:核心层包括:

DOMDocument, IXMLDOMDocument
IXMLDOMNode*
IXMLDOMNodeList*
IXMLDOMNamedNodeMap*
IXMLDOMDocumentFragment*
IXMLDOMImplementation
IXMLDOMParseError
和用户可能需要保留的 DTD 信息:

IXMLDOMDocumentType
IXMLDOMEntity
IXMLDOMNotation
XML 文档中的所有节点类型都是 IXMLDOMNode,它提供全部功能,但是存在每种节点类型的更高级别的包装程序。因此,如果修改 DOMDocument 包装程序并将这些特定类型更改为使用 IXMLDOMNode,那么所有下面的接口都可以删除:

IXMLDOMAttribute
IXMLDOMCDATASection
IXMLDOMCharacterData
IXMLDOMComment
IXMLDOMElement
IXMLDOMProcessingInstruction
IXMLDOMEntityReference
IXMLDOMText
删除这些将使大小减少到 61KB。但是,对 IXMLDOMElement 来说,getAttribute 和 setAttribute 方法都是有用的。否则需要使用:

IXMLDOMNode.getAttributes().setNamedItem(...)

Shutdown命令说明

Shutdown命令说明
关键词: Shutdown

前一段时间的网络病毒非常猖獗,如震荡波、冲击波等就是针对WindowsXP的RPC(远程过程调用)漏洞设计的,它导致系统倒计时自动关机,很多同学、老师头痛不已。如下图:



更糟的是一般的用户不知道如何取消倒计时自动关机(只能眼睁睁地看着系统关闭);本文就此介绍shutdown这一个功能强大的命令;

1、用法: shutdown [-i | -l | -s | -r | -a] [-f] [-m \\computername] [-t xx] [-c "comment"] [-d up:xx:yy]

没有参数 显示此消息(与 ? 相同)

-i 显示 GUI 界面,必须是第一个选项

-l 注销(不能与选项 -m 一起使用)

-s 关闭此计算机

-r 关闭并重启动此计算机

-a 放弃系统关机

-m \\computername 远程计算机关机/重启动/放弃

-t xx 设置关闭的超时为 xx 秒

-c "comment" 关闭注释(最大 127 个字符)

-f 强制运行的应用程序关闭而没有警告

-d [u][p]:xx:yy 关闭原因代码

u 是用户代码

p 是一个计划的关闭代码

xx 是一个主要原因代码(小于 256 的正整数)

yy 是一个次要原因代码(小于 65536 的正整数)

2、举例:

关闭计算机 shutdown –s (方法:”开始”->”运行”->”shutdown -s”->”确定”)

延迟3秒关闭计算机 shutdown –s –t 3(方法:”开始”->”运行”->”shutdown –s –t 3”->”确定”)

取消关闭计算机 shutdown –a (方法:”开始”->”运行”->”shutdown –a”->”确定”)

3、应用:

(1)您如果想在1小时后自动关闭计算机,比如:您正在下载一个软件或一部电影,而您有急事要去丽水,您会怎么办?停止下载?太可惜了!继续?下载完后电脑怎么关闭?不用愁!您可以使用该命令解决:

shutdown –s –t 3600 (系统在3600秒后关闭计算机,时间自定)

您就放心地去丽水了,而不用担心您的计算机长期开着了。

(2)出现RPC漏洞而导致倒计时自动关机,使用该命令取消倒计时自动关机:

shutdown –a (方法:”开始”->”运行”->”shutdown –a”->”确定”)
 结束语:

WindowsXP中的shutdown命令功能很强大,你可以去尝试使用,挖掘出功能,为我们服务。

下面是shutdown的用法:
shutdown [-i | -l | -s | -r | -a] [-f] [-m \\computername] [-t xx] [
mment"] [-d up:xx:yy]

没有参数 显示此消息(与 ? 相同)
-i 显示 GUI (图形用户界面)界面,必须是第一个选项
-l 注销(不能与选项 -m 一起使用)
-s 关闭此计算机
-r 关闭并重启动此计算机
-a 放弃系统关机
-m \\computername 远程计算机关机/重启动/放弃
-t xx 设置关闭的超时为 xx 秒
-c "comment" 关闭注释(最大 127 个字符)
-f 强制运行的应用程序关闭而没有警告
-d [u][p]:xx:yy 关闭原因代码
u 是用户代码
p 是一个计划的关闭代码
xx 是一个主要原因代码(小于 256 的正整数)
yy 是一个次要原因代码(小于 65536 的正整数)

如:在运行或命令提示符中输入:shutdown -i可打开它的图形用户界面进行具体设置关机时间等)
如要放弃关机可输入shutdown -a。
也可以建立两个快捷方式来进行关机与放弃关机操作

VC调试入门

概述
调试是一个程序员最基本的技能,其重要性甚至超过学习一门语言。不会调试的程序员就意味着他即使会一门语言,却不能编制出任何好的软件。
这里我简要的根据自己的经验列出调试中比较常用的技巧,希望对大家有用。
本文约定,在选择菜单时,通过/表示分级菜单,例如File/Open表示顶级菜单File的子菜单Open。

设置
为了调试一个程序,首先必须使程序中包含调试信息。一般情况下,一个从AppWizard创建的工程中包含的Debug Configuration自动包含调试信息,但是是不是Debug版本并不是程序包含调试信息的决定因素,程序设计者可以在任意的Configuration中增加调试信息,包括Release版本。
为了增加调试信息,可以按照下述步骤进行:

打开Project settings对话框(可以通过快捷键ALT+F7打开,也可以通过IDE菜单Project/Settings打开)
选择C/C++页,Category中选择general ,则出现一个Debug Info下拉列表框,可供选择的调试信息 方式包括:
 命令行 Project settings 说明
无 None 没有调试信息
/Zd Line Numbers Only 目标文件或者可执行文件中只包含全局和导出符号以及代码行信息,不包含符号调试信息
/Z7 C 7.0- Compatible 目标文件或者可执行文件中包含行号和所有符号调试信息,包括变量名及类型,函数及原型等
/Zi Program Database 创建一个程序库(PDB),包括类型信息和符号调试信息。
/ZI Program Database for Edit and Continue 除了前面/Zi的功能外,这个选项允许对代码进行调试过程中的修改和继续执行。这个选项同时使#pragma设置的优化功能无效


选择Link页,选中复选框"Generate Debug Info",这个选项将使连接器把调试信息写进可执行文件和DLL
如果C/C++页中设置了Program Database以上的选项,则Link incrementally可以选择。选中这个选项,将使程序可以在上一次编译的基础上被编译(即增量编译),而不必每次都从头开始编译。
断点
断点是调试器设置的一个代码位置。当程序运行到断点时,程序中断执行,回到调试器。断点是 最常用的技巧。调试时,只有设置了断点并使程序回到调试器,才能对程序进行在线调试。

设置断点:可以通过下述方法设置一个断点。首先把光标移动到需要设置断点的代码行上,然后
按F9快捷键
弹出Breakpoints对话框,方法是按快捷键CTRL+B或ALT+F9,或者通过菜单Edit/Breakpoints打开。打开后点击Break at编辑框的右侧的箭头,选择 合适的位置信息。一般情况下,直接选择line xxx就足够了,如果想设置不是当前位置的断点,可以选择Advanced,然后填写函数、行号和可执行文件信息。
去掉断点:把光标移动到给定断点所在的行,再次按F9就可以取消断点。同前面所述,打开Breakpoints对话框后,也可以按照界面提示去掉断点。

条件断点:可以为断点设置一个条件,这样的断点称为条件断点。对于新加的断点,可以单击Conditions按钮,为断点设置一个表达式。当这个表达式发生改变时,程序就 被中断。底下设置包括“观察数组或者结构的元素个数”,似乎可以设置一个指针所指向的内存区的大小,但是我设置一个比较的值但是改动 范围之外的内存区似乎也导致断点起效。最后一个设置可以让程序先执行多少次然后才到达断点。

数据断点:数据断点只能在Breakpoints对话框中设置。选择“Data”页,就显示了设置数据断点的对话框。在编辑框中输入一个表达式,当这个 表达式的值发生变化时,数据断点就到达。一般情况下,这个表达式应该由运算符和全局变量构成,例如:在编辑框中输入 g_bFlag这个全局变量的名字,那么当程序中有g_bFlag= !g_bFlag时,程序就将停在这个语句处。

消息断点:VC也支持对Windows消息进行截获。他有两种方式进行截获:窗口消息处理函数和特定消息中断。
在Breakpoints对话框中选择Messages页,就可以设置消息断点。如果在上面那个对话框中写入消息处理函数的名字,那么 每次消息被这个函数处理,断点就到达(我觉得如果采用普通断点在这个函数中截获,效果应该一样)。如果在底下的下拉 列表框选择一个消息,则每次这种消息到达,程序就中断。


Watch
VC支持查看变量、表达式和内存的值。所有这些观察都必须是在断点中断的情况下进行。
观看变量的值最简单,当断点到达时,把光标移动到这个变量上,停留一会就可以看到变量的值。
VC提供一种被成为Watch的机制来观看变量和表达式的值。在断点状态下,在变量上单击右键,选择Quick Watch, 就弹出一个对话框,显示这个变量的值。
单击Debug工具条上的Watch按钮,就出现一个Watch视图(Watch1,Watch2,Watch3,Watch4),在该视图中输入变量或者表达式,就可以观察 变量或者表达式的值。注意:这个表达式不能有副作用,例如++运算符绝对禁止用于这个表达式中,因为这个运算符将修改变量的值,导致 软件的逻辑被破坏。

Memory
由于指针指向的数组,Watch只能显示第一个元素的值。为了显示数组的后续内容,或者要显示一片内存的内容,可以使用memory功能。在 Debug工具条上点memory按钮,就弹出一个对话框,在其中输入地址,就可以显示该地址指向的内存的内容。

Varibles
Debug工具条上的Varibles按钮弹出一个框,显示所有当前执行上下文中可见的变量的值。特别是当前指令涉及的变量,以红色显示。

寄存器
Debug工具条上的Reigsters按钮弹出一个框,显示当前的所有寄存器的值。

进程控制
VC允许被中断的程序继续运行、单步运行和运行到指定光标处,分别对应快捷键F5、F10/F11和CTRL+F10。各个快捷键功能如下:
 快捷键 说明
F5 继续运行
F10 单步,如果涉及到子函数,不进入子函数内部
F11 单步,如果涉及到子函数,进入子函数内部
CTRL+F10 运行到当前光标处。

Call Stack
调用堆栈反映了当前断点处函数是被那些函数按照什么顺序调用的。单击Debug工具条上的Call stack就显示Call Stack对话框。在CallStack对话框中显示了一个调用系列,最上面的是当前函数,往下依次是调用函数的上级函数。单击这些函数名可以跳到对应的函数中去。

其他调试手段
系统提供一系列特殊的函数或者宏来处理Debug版本相关的信息,如下:

宏名/函数名 说明
TRACE 使用方法和printf完全一致,他在output框中输出调试信息
ASSERT 它接收一个表达式,如果这个表达式为TRUE,则无动作,否则中断当前程序执行。对于系统中出现这个宏 导致的中断,应该认为你的函数调用未能满足系统的调用此函数的前提条件。例如,对于一个还没有创建的窗口调用SetWindowText等。
VERIFY 和ASSERT功能类似,所不同的是,在Release版本中,ASSERT不计算输入的表达式的值,而VERIFY计算表达式的值。

关注
一个好的程序员不应该把所有的判断交给编译器和调试器,应该在程序中自己加以程序保护和错误定位,具体措施包括:

对于所有有返回值的函数,都应该检查返回值,除非你确信这个函数调用绝对不会出错,或者不关心它是否出错。
一些函数返回错误,需要用其他函数获得错误的具体信息。例如accept返回INVALID_SOCKET表示accept失败,为了查明 具体的失败原因,应该立刻用WSAGetLastError获得错误码,并针对性的解决问题。
有些函数通过异常机制抛出错误,应该用TRY-CATCH语句来检查错误
程序员对于能处理的错误,应该自己在底层处理,对于不能处理的,应该报告给用户让他们决定怎么处理。如果程序出了异常, 却不对返回值和其他机制返回的错误信息进行判断,只能是加大了找错误的难度。
另外:VC中要编制程序不应该一开始就写cpp/h文件,而应该首先创建一个合适的工程。因为只有这样,VC才能选择合适的编译、连接 选项。对于加入到工程中的cpp文件,应该检查是否在第一行显式的包含stdafx.h头文件,这是Microsoft Visual Studio为了加快编译 速度而设置的预编译头文件。在这个#include "stdafx.h"行前面的所有代码将被忽略,所以其他头文件应该在这一行后面被包含。
对于.c文件,由于不能包含stdafx.h,因此可以通过Project settings把它的预编译头设置为“不使用”,方法是:
弹出Project settings对话框
选择C/C++
Category选择Precompilation Header
选择不使用预编译头。

[VC++]__fastcall具体含义(转)

__fastcall具体含义

在C语言中,假设我们有这样的一个函数:

int function(int a,int b)

调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级
语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没
有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。
也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者
和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。

栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中
第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作
被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修
改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变
成栈顶,栈顶指针随之修改。

函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得
数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈
恢复原装。

在参数传递中,有两个很重要的问题必须得到明确说明:

当参数个数多于一个时,按照什么顺序把参数压入堆栈
函数调用后,由谁来把堆栈恢复原装
在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:

stdcall
cdecl
fastcall
thiscall
naked call
stdcall调用约定
stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机
程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列
的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLB
ACK。

stdcall调用约定声明的语法为(以前文的那个函数为例):

int __stdcall function(int a,int b)

stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数
名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸

以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处
翻译成汇编语言将变成:


push 2 第二个参数入栈
push 1 第一个参数入栈
call function 调用参数,注意此时自动把cs:eip入栈


而对于函数自身,则可以翻译为:


push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出
时恢复
mov ebp,esp 保存堆栈指针
mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向
a
add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
mov esp,ebp 恢复esp
pop ebp
ret 8

而在编译时,这个函数的名字被翻译成_function@8

注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。其中
在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。

从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的
堆栈指针)的偏移量存取参数。函数结束后,ret 8表示清理8个字节的堆栈,函数自己
恢复了堆栈。

cdecl调用约定
cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:


int function (int a ,int b) //不加修饰就是C调用约定
int __cdecl function(int a,int b)//明确指出C调用约定


在写本文时,出乎我的意料,发现cdecl调用约定的参数压栈顺序是和stdcall是一样的
,参数首先由有向左压入堆栈。所不同的是,函数本身不清理堆栈,调用者负责清理堆
栈。由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大
特色。对于前面的function函数,使用cdecl后的汇编码变成:



作者: 61.187.54.* 2006-3-6 21:21   回复此发言

--------------------------------------------------------------------------------

2 [VC++]__fastcall具体含义(转)


调用处
push 1
push 2
call function
add esp,8 注意:这里调用者在恢复堆栈
被调用函数_function处
push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出
时恢复
mov ebp,esp 保存堆栈指针
mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向
a
add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
mov esp,ebp 恢复esp
pop ebp
ret 注意,这里没有修改堆栈


MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_f
unction,但是我在编译时似乎没有看到这种变化。

由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用
不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据
第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprin
tf函数,定义为:

int sprintf(char* buffer,const char* format,...)

由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。

fastcall
fastcall调用约定和stdcall类似,它意味着:

函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过
从右向左的顺序压栈
被调用函数清理堆栈
函数名修改规则同stdcall
其声明语法为:int fastcall function(int a,int b)

thiscall
thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成
员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理,th
iscall意味着:

参数从右向左入栈
如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针
在所有参数压栈后被压入堆栈。
对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈
为了说明这个调用约定,定义如下类和使用代码:

class A
{
public:
int function1(int a,int b);
int function2(int a,...);
};
int A::function1 (int a,int b)
{
return a+b;
}
#include
int A::function2(int a,...)
{
va_list ap;
va_start(ap,a);
int i;
int result = 0;
for(i = 0 i < a i ++)
{
result += va_arg(ap,int);
}
return result;
}
void callee()
{
A a;
a.function1 (1,2);
a.function2(3,1,2,3);
}

callee函数被翻译成汇编后就变成:


//函数function1调用
0401C1D push 2
00401C1F push 1
00401C21 lea ecx,[ebp-8]
00401C24 call function1 注意,这里this没有被入栈
//函数function2调用
00401C29 push 3
00401C2B push 2
00401C2D push 1
00401C2F push 3
00401C31 lea eax,[ebp-8] 这里引入this指针
00401C34 push eax
00401C35 call function2
00401C3A add esp,14h

可见,对于参数个数固定情况下,它类似于stdcall,不定时则类似cdecl

naked call
这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增
加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回
结果。这一般用于实模式驱动程序设计,假设定义一个求和的加法程序,可以定义为:


__declspec(naked) int add(int a,int b)
{
__asm mov eax,a
__asm add eax,b
__asm ret
}

注意,这个函数没有显式的return返回值,返回通过修改eax寄存器实现,而且连退出函
数的ret指令都必须显式插入。上面代码被翻译成汇编以后变成:


mov eax,[ebp+8]
add eax,[ebp+12]
ret 8


注意这个修饰是和__stdcall及cdecl结合使用的,前面是它和cdecl结合使用的代码,对
于和stdcall结合的代码,则变成:

__declspec(naked) int __stdcall function(int a,int b)
{
__asm mov eax,a
__asm add eax,b
__asm ret 8 //注意后面的8
}

至于这种函数被调用,则和普通的cdecl及stdcall调用函数一致。

函数调用约定导致的常见问题
如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题,下面是两
种常见的问题:

函数原型声明和函数体定义不一致
DLL导入函数时声明了不同的函数约定
以后者为例,假设我们在dll种声明了一种函数为:

__declspec(dllexport) int func(int a,int b);//注意,这里没有stdcall,使用的是
cdecl
使用时代码为:

typedef int (*WINAPI DLLFUNC)func(int a,int b);
hLib = LoadLibrary(...);
DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定
result = func(1,2);//导致错误

由于调用者没有理解WINAPI的含义错误的增加了这个修饰,上述代码必然导致堆栈被破
坏,MFC在编译时插入的checkesp函数将告诉你,堆栈被破坏了。

fastcall
1)参数压栈顺序从右到左。
2)如果可能使用寄存器(register)来传递参数。使用寄存器做参数比将参数压入堆栈(堆栈就是内存)快得多,所以叫fastcall。

(1) _stdcall调用
  _stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。
  WIN32 Api都采用_stdcall调用方式,这样的宏定义说明了问题:
  #define WINAPI _stdcall
  按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number。
(2) _cdecl调用
  _cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cedcl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。

  由于_cdecl调用方式的参数内存栈由调用者维护,所以变长参数的函数能(也只能)使用这种调用约定。关于C/C++中变长参数(…)的问题,笔者将另文详述。

  由于Visual C++默认采用_cdecl 调用方式,所以VC中中调用DLL时,用户应使用_stdcall调用约定。
  按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。

  (3) _fastcall调用
  _fastcall调用较快,它通过CPU内部寄存器传递参数。

  按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数,形如@functionname@number。

  关键字_stdcall、_cdecl和_fastcall可以直接加在函数前,也可以在Visual C++中设置,如图1。

贴子相关图片:

星期一, 二月 26, 2007

动态链接库的创建和调用

动态连接库的创建步骤:

一、创建Non-MFC DLL动态链接库

1、打开File —> New —> Project选项,选择Win32 Dynamic-Link Library —>sample project

—>工程名:DllDemo

2、新建一个.h文件DllDemo.h

#ifdef DllDemo_EXPORTS

#define DllAPI __declspec(dllexport)

#else

#define DllAPI __declspec(dllimport)

extern "C" //原样编译

{

DllAPI int __stdcall Max(int a,int b); //__stdcall使非C/C++语言内能够调用API

}

#endif



3、在DllDemo.cpp文件中导入DllDemo.h文件,并实现Max(int,int)函数

#include "DllDemo.h"

DllAPI int __stdcall Max(int a,int b)

{

if(a==b)

return NULL;

else if(a>b)

return a;

else

return b;

}

4、编译程序生成动态连接库

二、用.def文件创建动态连接库DllDemo.dll。

1、删除DllDemo工程中的DllDemo.h文件。

2、在DllDemo.cpp文件头,删除 #include DllDemo.h语句。

3、向该工程中加入一个文本文件,命名为DllDemo.def并写入如下语句:

LIBRARY MyDll
EXPORTS
Max@1

4、编译程序生成动态连接库。



动态链接的调用步骤:

一、隐式调用

1、 建立DllCnslTest工程

2、 将文件DllDemo.dll、DllDemo.lib拷贝到DllCnslTest工程所在的目录

3、 在DllCnslTest.h中添加如下语句:

#define DllAPI __declspec(dllimport)

#pragma comment(lib,"DllDemo.lib") //在编辑器link时,链接到DllDemo.lib文件

extern "C"

{

DllAPI int __stdcall Max(int a,int b);

}

4、在DllCnslTest.cpp文件中添加如下语句:

#include "DllCnslTest.h"//或者 #include "DllDemo.h"



void main()

{

int value;

value = Max(2,9);

printf("The Max value is %d\n",value);

}

5、编译并生成应用程序DllCnslTest.exe

二、显式调用

1、 建立DllWinTest工程

2、 将文件DllDemo.dll拷贝到DllWinTest工程所在的目录或Windows系统目录下。

3、 用vc/bin下的Dumpbin.exe的小程序,查看DLL文件(DllDemo.dll)中的函数结构。

4、 使用类型定义关键字typedef,定义指向和DLL中相同的函数原型指针。

例:

typedef int(*lpMax)(int a,int b); //此语句可以放在.h文件中

5、 通过LoadLibray()将DLL加载到当前的应用程序中并返回当前DLL文件的句柄。

例:

HINSTANCE hDll; //声明一个Dll实例文件句柄

hDll = LoadLibrary("DllDemo.dll");//导入DllDemo.dll动态连接库

6、 通过GetProcAddress()函数获取导入到应用程序中的函数指针。

例:

lpMax Max;

Max = (lpMax)GetProcAddress(hDLL,"Max");

int value;

value = Max(2,9);

printf("The Max value is %d",value);

7、 函数调用完毕后,使用FreeLibrary()卸载DLL文件。

FreeLibrary(hDll);

8、 编译并生成应用程序DllWinTest.exe

注:显式链接应用程序编译时不需要使用相应的Lib文件。

MFC中一些使用的方法

1. 在CSatic控件上增加图标及位图

CStatic *pStat = NULL;

pStat = (CStatic*)GetDlgItem(控件ID);

pStat->ModifyStyle(SWP_NOZORDER, SS_ICON);

可用两种方法增加图标

(1) pStat->SetIcon(AfxGetApp()->LoadIcon(图标ID));

(2) pStat->SetIcon(LoadIcon(::AfxGetInstanceHandle(),”图标名称”)); //图标名字改为“IDI_ICON”的形式,因为这里要求的是字符串形式的名称。

增加Bmp图片的形式同上,将ModifyStyle中的SS_ICON改为SS_BITMAP。用相应的位图增加函数即可。




2. CClientDC,CWindowDC

这两个类都从CDC类派生,CClientDC类用于获取客户区的DC(不包括标题栏,菜单栏,工具栏),创建了CClientDC类对象后不需用ReleaseDC来释放。如:

CCleintDC dc(this);获取本窗口的dc,CClientDC dc(GetParent())可获取父窗口DC,此时就可在非客户区进行dc操作。

CWindowDC 获取整个窗口的dc,可对整个窗口进行操作,CWindowDC(GetParent()) 注意在这获得的父窗口对于对话框情况下将是windows窗口,最后无需ReleaseDC释放




GetDesktopWindow()获取桌面窗口句柄。




::CreateIC("DISPLAY",NULL,NULL,NULL);获取桌面的HDC




3. 获取随机数rand()

要获取一定范围内的随机数可用:

Srand((unsigned) time(NULL)); //意思是每次产生的随机数都不相同.

(int)(rand())/(float) RAND_MAX * 10); //返回1-10之间的随机数




4. 获取路径:

char path[MAX_PATH] = "\0";

GetModuleFileName(NULL, path, MAX_PATH); //应用程序路径

GetSystemDirectory(); //获取Windows系统目录路径

GetWindowDirectory() //获取Windows目录路径




5. 动态加载ODBC数据源

//mdbName为数据库名称, DSN为数据源名称

void SetODBCSource(char * mdbName ,char * DSNName)

{

char path[256]="";

char pathMDB[256]="";

GetAppPath(path);

strcpy(pathMDB,path);

strcat(pathMDB, "\\");

strcat(pathMDB, mdbName);

char MdbConfig[256]= "DSN=" ;// pwd

strcat(MdbConfig , DSNName);

char * pMconfig = MdbConfig + strlen(MdbConfig) + 1 ;

strcpy(pMconfig,"DBQ=");

pMconfig += 4;

strcat(pMconfig,pathMDB);

pMconfig += strlen(pathMDB) + 1 ;

strcpy(pMconfig,"DEFAULTDIR=");

strcat(pMconfig,path);

strcat(pMconfig,"\0");

if(!SQLConfigDataSource(NULL,ODBC_ADD_SYS_DSN,"Microsoft Access

Driver (*.mdb)\0",MdbConfig)) //注意Driver后的空格

{

TRACE("加载数据库失败,请检查数据库是否存在\n");

return;

}




6. 线程退出

线程退出请使用return 或 ExitThread()来正常退出线程,尽是避免使用TerminateThread来终止线程,因为终止后线程资源将不会被释放。

一般情况下在CreateThread创建了线程后即使用CloseHandle()来关闭线程句柄,以防止TerminateThread得到句柄后,进行线程的强制终止。




7. 自定义宏检查错误BOOL错误。

#define ASSERT_ERROR(Code)\

{\

If (Code)\

AfxMessageBox(“提示1 “);\

Else \

AfxMessageBox(“错误1”);\

}

定义以后可在需要使用的地方使用,如:BOOL bStat = TRUE;

ASSERT_ERROR(bStat); //此时将执行“提示1”,bStat = FALSE时执行“错误1”




8. 将程序加入注册表启动项:

LPCTSTR lpcAppPath = “程序路径”;

CString str = “SOFTWARE\\Microsoft\\Windows\\CurrentverSion\\Run”;

HKEY hResult;

RegOpenKey(HKEY_LOCAL_MACHINE, (LPCSTR)str.GetBuffer(0), &hResult);

RegSetValueEx(hResult, “程序名称”, 0, REG_SZ, (const unsigned char*)lpcAppPath,

Sizeof(char) * strlen(lpcAppPath));

RegCloseKey(hResult);




9. 程序调试

在调试环境下的Vlaue窗口中输入” @err,hr” 可返回当前发生的错误原因




10. 获取工具条指针,工具条有一特殊标识(AFX_IDW_TOOLBAR)

CToolBar *pTool = (CToolBar*)AfxGetMainWnd()->GetDescendantWindow(特殊标识)

;

获取状态条指针。

(CStatusBar*)AfxGetMainWnd()->GetDescendantWindow(AFX_IDW_STATUS_BAR);




获取主窗口指针:

CMainFrame *pFrame = (CMainFrame*)(AfxGetApp()->m_pMainWnd);




获取视类指针:

CMyView *pView= CMyView*)((CMainFrame*)AfxGetApp()->m_pMainWnd)->GetActiveView();







CMyView *pView=(CMyView*)GetActiveView();






11.显示或隐藏任务栏:



::ShowWindow(::FindWindow(“Shell_TrayWnd”, NULL, SW_SHOW),隐藏用SW_HIDE







12.使用基于对话框的程序在任务栏隐藏。



This->ModifyStyleEx(WS_EX_APPWINDOW, 0);






13.只允许程序运行一个实例:



HANDLE h_Muex = CreateMutex(NULL, TRUE, m_pszAppName);



If (GetLastError() == ERROR_ALREADY_EXISTS)



Return False;

在VC中链接动态链接库(DLL)的方法

在VC中链接动态链接库(DLL)的方法简要说明



方法一:windows提供了一套函数,用于加载动态链接库中的符号(函数和变量),调用这些函数去加载:

1. HINSTANCE LoadLibrary( LPCTSTR lpLibFileName);

2. FARPROC GetProcAddress( HMODULE hModule, LPCWSTR lpProcName);

3. BOOL FreeLibrary( HMODULE hLibModule);

这最直观的一种方法,同时也是最麻烦的一种办法。


方法二:让调用者的工程依赖于动态链接库工程。步骤如下:

1. 让调用者的工程处于active状态下。

2. 打依赖设置对话框:Project-->dependencies。

3. 选择动态链接库工程。

这种方法比较方便,但要求有DLL的项目文件。


方法三:直接把动态链接库产生的.lib文件加入到调用者的工程中。


方法四:进入Link设置:Project-->settings-->Link,选择Categery中的Input,在object/library modules里输入的动态链接库对应的.lib文件名,在Additional library path中输入动态链接库对应的.lib的路径。


方法五:#pragma (lib, "filename.lilb")

《The C++ Programming Language (Special Edition)》中的忠告

第1章 致读者

[1] 在编写程序时,你是在为你针对某个问题的解决方案中的思想建立起一种具体表示。让程序的结构尽可能地直接反映这些思想:
[a] 如果你能把“它”看成一个独立的概念,就把它做成一个类。
[b] 如果你能把“它”看成一个独立地实体,就把它做成某个类的一个对象。
[c] 如果两个类有共同的界面,将此界面做成一个抽象类。
[d] 如果两个类的实现有某些显著的共同东西,静这些共性做成一个基类。
[e] 如果一个类是一种对象的容器,将它做成一个模板。
[f] 如果一个函数实现对某容器的一个算法,将它实现为对一族容器可用的模板函数。
[g] 如果一组类、模板等互相之间有逻辑关系,将它们放进一个名字空间力。
[2] 在你定义一个并不是实现某个像矩阵或复数这样的数学对象的类时,或者定义一个低层地类型如链接表的时候:
[a] 不要使用全局数据(使用成员)。
[b] 不要使用全局函数。
[c] 不要使用公用数据成员。
[d] 不要使用友元,除非为了避免[a]或[c]。
[e] 不要在一个类里面放“类型域”;采用虚函数。
[f] 不要使用在线函数,除非座位效果显著的优化。

第2章 C++概览

[1] 不用害怕,一切都会随着时间的推移而逐渐明朗起来。
[2] 你并不需要在知道了C++地所有细节之后才能写出好的C++程序。
[3] 请特别关注程序设计技术,而不是各种语言特征。

第3章 标准库概念

[1] 不要像重新发明车轮那样企图做每件事;去使用库。
[2] 不要相信奇迹;要理解你的库能做什么,它们如何做,它们做时需要多大代价。
[3] 当你遇到一个选择时,应该优先选择标准库而不是其他的库。
[4] 不要认为标准库对于任何事情都是最理想的。
[5] 切记#include你所用到的功能的头文件。
[6] 记住,标准库的功能定义在名字空间std中。
[7] 请用string,而不是char*。
[8] 如果怀疑,就用一个检查区间范围的向量。
[9] vector<T>、list<T>和map<key, value>都比T[]好。
[10] 如果要向一个容器中添加一个元素,用push_back()或back_insert()。
[11] 采用对vector的push_back(),而不是realloc()。
[12] 在main()中捕捉公共的异常。

第4章 类型和声明

[1] 保持较小的作用域。
[2] 不要在一个作用域和它外围的作用域里采用同样的名字。
[3] 在一个声明中(只)声明一个名字。
[4] 让常用的和局部的名字比较短,让不常用的和全局的名字比较长。
[5] 避免看起来类似的名字。
[6] 维持某种统一的命名风格。
[7] 仔细选择名字,反映其意义而不是反映实现方式。
[8] 如果所用的内部类型表示某种可能变化的值,请用typdef为它定义一个有意义的名字。
[9] 用typedef为类型定义同义词,用枚举或类去定义新类型。
[10] 切记每个声明中都必须描述一个类型(没有隐式的int)。
[11] 避免有关字符值的不必要的假设。
[12] 避免有关整数大小的不必要假设。
[13] 避免有关浮点类型表示范围的不必要假设。
[14] 优先使用普通的int而不是short int或者long int。
[15] 优先使用double而不是float或者long double。
[16] 优先使用普通的char而不是signed char或者unsigned char。
[17] 避免做出有关对象大小的不必要假设。
[18] 避免无符号算术。
[19] 应该带着疑问去看待从signed到unsigned,或者从unsigned到singed的转换。
[20] 应该带着疑问去看待从浮点到整数的转换。
[21] 应该带着疑问其看待向较小类型的转换,如将int转换到char。

第5章 指针、数组和结构

[1] 避免非平凡的指针算术。
[2] 当心,不要超出函数的界限去写。
[3] 尽量使用0而不是NULL。
[4] 尽量使用vector和valarrray而不是内部(C风格)的数组。
[5] 尽量使用string而不是以0结尾的char数组。
[6] 尽量少使用普通的引用参数。
[7] 避免void*,除了在某些低级代理。
[8] 避免在代码中使用非平凡的文字量(“神秘的数”)。相反,应该定义和使用各种符号常量。

第6章 表达式和语句

[1] 应尽量可能使用标准库,而不是其他的库和“手工打造的代码”。
[2] 避免过于复杂的表达式。
[3] 如果对运算符的优先级有疑问,加括号。
[4] 避免显式类型转换。
[5] 若必须做显式类型转换,提倡使用特殊强制运算符,而不是C风格的强制。
[6] 只对定义良好的构造使用T(e)记法。
[7] 避免带有无定义求值顺序的表达式。
[8] 避免goto。
[9] 避免do语句。
[10] 在你已经有了去初始化某个变量的值之前,不要去声明它。
[11] 式注释简洁、清晰、有意义。
[12] 保持一致的缩进编排风格。
[13] 倾向于去定义一个成员函数operator new()去取代全局的operator new()。
[14] 在读输入的时候,总应考虑病态形式的输入。

第7章 函数

[1] 质疑那些非const的引用参数;如果你想要一个函数去修改其参数,请使用指针或者返回值。
[2] 当你需要尽可能减少参数复制时,应该使用const引用参数。
[3] 广泛而一致地使用const。
[4] 避免宏。
[5] 避免不确定数目的参数。
[6] 不要返回局部变量的指针或者引用。
[7] 当一些函数对不同的类型执行概念上相同的工作时,请使用重载。
[8] 在各种整数上重载时,通过提供函数去消除常见的歧义性。
[9] 在考虑使用指向函数的指针时,请考虑虚函数或模板是不是更好的选择。
[10] 如果你必须使用宏,请使用带有许多大写字母的丑陋的名字。

第8章 名字空间和异常

[1] 用名字空间表示逻辑结构。
[2] 将每个非局部的名字放入某个名字空间里,除了main()之外。
[3] 名字空间的设计应该让你能很方便地使用它,而又不会意外地访问了其他的无关名字空间。
[4] 避免对名字空间使用很短的名字。
[5] 如果需要,通过名字空间别名去缓和和长名字空间的影响。
[6] 避免给你的名字空间的用户添加太大的记法负担。
[7] 在定义名字空间的成员时使用namespace::member的形式。
[8] 只在转换时,或者在局部作用域里,才用using namespace。
[9] 利用异常去松弛“错误”处理代码和正常处理代码之间的联系。
[10] 采用用户定义类型作为异常,不用内部类型。
[11] 当局部控制结构足以应付问题,不要使用异常。

第9章 源文件和程序

[1] 利用头文件去表示界面和强调逻辑结构。
[2] 用#include将头文件包含到实现有关功能的源文件里。
[3] 不要在不同编译单位里定义具有同样名字,意义类似但又不同的全局变量。
[4] 避免在头文件里定义非inline函数。
[5] 只在全局作用域或名字空间里使用#include。
[6] 只用#include包含完整的定义。
[7] 使用包含保护符。
[8] 用#include将C头文件包含到名字空间里,以避免全局名字。
[9] 将头文件做成自给自足的。
[10] 区分用户界面和实现界面。
[11] 区分一般用户界面和专家用户界面。
[12] 在有意向用于非C++程序组成部分的代码中,应避免需要运行时初始化的非局部对象。

第10章 类

[1] 用类表示概念。
[2] 只将public数据(struct)用在它实际杀过那仅仅时数据,而且对于这些数据成员并不存在不变式的地方。
[3] 一个具体类型属于最简单的类。如果有用的话,就应该尽可能使用具体类型,而不要采用更复杂的阿里,也不要用简单的数据结构。
[4] 只将那些需要直接访问类的表示的函数作为成员函数。
[5] 采用名字空间,使类与其协助函数之间的关系更明确。
[6] 将那些不修改对象值的成员函数做成const成员函数。
[7] 将那些需要访问类的表示,但无须针对特定对象调用的成员函数做成static成员函数。
[8] 通过构造函数建立起类的不变式。
[9] 如果构造函数申请某种资源,析构函数就应该释放一资源。
[10] 如果在一个类里有指针成员,它就要有复制操作(包括复制构造函数和复制赋值)。
[11] 如果在一个类里有引用成员,它就可能需要有复制操作(包括复制构造函数和复制赋值)。
[12] 如果一个类需要复制操作或析构函数,它多半还需要有构造函数、析构函数、复制赋值函数和复制构造函数。
[13] 在复制赋值函数里需要检查自我赋值。
[14] 在写复制构造函数时,请小心地复制每个需要复制的元素(当心默认的初始式)。
[15] 在向某个类中添加新成员函数时,一定要仔细检查,看是否存在需要更新的用户定义构造函数,以使它能够初始化新成员。
[16] 在类声明中需要定义整型常量时,请使用枚举。
[17] 在构造全局的和名字空间的对象时,应避免顺序依赖性。
[18] 用第一次开关去缓和顺序依赖性问题。
[19] 请记住,临时对象将在建立它们的那个完整表达式结束时销毁。

第11章 运算符重载

[1] 定义运算符主要是为了模仿习惯使用方式。
[2] 对于大型运算对象,请使用const引用参数类型。
[3] 对于大型的结果,请考虑优化返回方式。
[4] 如果默认复制操作对一个类和合适,最好是直接用它。
[5] 如果默认复制操作对一个类不和合适,重新定义它,或者禁止它。
[6] 对于需要访问表示的操作,优先考虑作为成员函数而不是作为非成员函数。
[7] 对于不访问表示的操作,优先考虑作为非成员函数而不是作为成员函数。
[8] 用名字空间将协助函数与“它们的”类关联起来。
[9] 对于对称的运算符采用非成员函数。
[10] 用()作为多维数组的下标。
[11] 将只有一个“大小参数”的构造函数做成explicit。
[12] 对于非特殊的使用,最好是用标准string而不是你自己的练习。
[13] 要注意引进隐式转换的问题。
[14] 用成员函数表达那些需要左值作为其左运算对象的运算符。

第12章 派生类

[1] 避免类型域。
[2] 用指针和引用避免切割问题。
[3] 用抽象类将设计的中心集中到提供清晰的界面方面。
[4] 用抽象类是界面最小化。
[5] 用抽象类从界面中排除实现细节。
[6] 用虚函数是新的实现能够添加进来,又不会影响用户代码。
[7] 用抽象类去尽可能减少用户代码的重新编译。
[8] 用抽象类是不同的实现能够共存。
[9] 一个有虚函数的类应该有一个虚析构函数。
[10] 抽象类通常不需要构造函数。
[11] 让不同概念的表示也不同。

第13章 模板

[1] 用模板描述需要使用到许多参数类型上去的算法。
[2] 用模板表述容器。
[3] 为指针的容器提供专门化,以减小代码规模。
[4] 总是在专门化之前声明模板的一般形式。
[5] 在专门化的使用之前先声明它。
[6] 尽量减少模板定义对于实例化环境的依赖性。
[7] 定义你所声明的每一个专门化。
[8] 考虑一个模板是否需要有针对C风格字符串和数组的专门化。
[9] 用表述策略的对象进行参数化。
[10] 用专门化和重载为同一概念的针对不同类型的实现提供统一界面。
[11] 为简单情况提供简单界面,用重载和默认参数去表述不常见的情况。
[12] 在修改为通用模板之前,在具体实例上排除程序错误。
[13] 如果模板定义需要在其他编译单位里访问,请记住写export。
[14] 对大模板和带有非平凡环境依赖性的模板,应采用分开编译的方式。
[15] 用模板表示转换,但要非常小心地定义这些转换。
[16] 如果需要,用constraint()成员函数给模板的实参增加限制。
[17] 通过显式实例化减少编译和连接时间。
[18] 如果运行时的效率非常重要,那么最好用模板而不是派生类。
[19] 如果增加各种变形而又不重新编译是很重要的,最好用派生类而不是模板。
[20] 如果无法定义公共的基类,最好用模板而不是派生类。
[21] 当有兼容性约束的内部类型和结构非常重要时,最好用模板而不是派生类。

第14章 异常处理

[1] 用异常做错误处理。
[2] 当更局部的控制机构足以应付时,不要使用异常。
[3] 采用“资源申请即初始化”技术去管理资源。
[4] 并不是美国程序都要求具有异常时的安全性。
[5] 才用“资源申请即初始化”技术和异常处理器去维持不变式。
[6] 尽量少用try块,用“资源申请即初始化”技术,而不是显式的处理器代码。
[7] 并不是美国函数都需要处理每个可能的错误。
[8] 在构造函数里通过抛出异常指明出现失败。
[9] 在从赋值中抛出异常之前,式操作对象处于合法状态。
[10] 避免从析构函数里抛出异常。
[11] 让main()捕捉并报告所有的异常。
[12] 使正常处理代码和错误处理代码相互分离。
[13] 在构造函数里抛出异常之前,应保证释放在此构造函数里申请的所有资源。
[14] 使资源管理具有层次性。
[15] 对于主要界面使用异常描述。
[16] 当心通过new分配的内存在发生异常时没有释放,并由此而导致存储的流失。
[17] 如果一函数可能抛出某个异常,就应该假定它一定会抛出这个异常。
[18] 不要假定所有异常都时由excepion类派生出来的。
[19] 库不应该单方面终止程序。相反,应该抛出异常,让调用者去做决定。
[20] 库不应该生成面向最终用户的错误信息。相反,它应该抛出异常,让调用者去做决定。
[21] 在设计的前期开发出一种错误处理策略。

第15章 类层次结构

[1] 利用常规的多重继承表述特征的合并。
[2] 利用多重继承完成实现细节与界面分离。
[3] 用virtual基类表达在类层次结构里对某些类(不是全部类)共同的东西。
[4] 避免显式的类型转换(强制)。
[5] 在不可避免地需要漫游类层次结构的地方,使用dynamic_cast。
[6] 尽量使用dynamic_cast而不是typeid。
[7] 尽量使用private而不是protected。
[8] 不要声明protected数据成员。
[9] 如果某个类定义了operator delete(),它也应该有虚析构函数。
[10] 在构造和析构期间不要调用虚函数。
[11] 尽量少用为解析成员名而写的显式限定词,最好时在覆盖函数里用它。

第16章 库组织和容器

[1] 利用标准库功能,以维持可移植性。
[2] 决不要另行定义标准库的功能。
[3] 决不要认为标准库比什么都好。
[4] 在定义一种新功能时,应考虑它是否能纳入标准库所提供的框架中。
[5] 记住标准库功能都定义在名字空间std里。
[6] 通过包含保准卡头文件声明其功能,不要自己另行显式声明。
[7] 利用后续抽象的优点。
[8] 避免肥大的界面。
[9] 与自己写按照反向顺序的显式循环相比,最好是写利用反向迭代器的算法。
[10] 用base()从reverse_iterator抽取出iterator。
[11] 通过引用传递容器。
[12] 用迭代器类型,如list<char>::iterator,而不要采用索引容器元素的指针。
[13] 在不需要修改容器元素时,使用const迭代器。
[14] 如果希望检查访问范围,请(直接或间接)使用at()。
[15] 多用容器和push_back()或resize(),少用数组和realloc().
[16] vector改变大小之后,不要使用指向其中的迭代器。
[17] 利用reserve()避免使迭代器非法。
[18] 在需要的时候,reserve()可以使执行情况更容易预期。

第17章 标准库容器

[1] 如果要用容器,首先考虑用vector。
[2] 了解你经常使用的每个操作的代价(复杂性,大O度量)。
[3] 容器的界面、实现和表示使不同的概念,不要混淆。
[4] 你可以依据多种不同准则去排序和搜索。
[5] 不要用C风格的字符串作为关键码,除非你提供了一种适当的比较准则。
[6] 你可以定义这样的比较准则,使等价的但是不相同的关键码值映射到同一个关键码。
[7] 在插入和删除元素时,最好时使用序列末端的操作(back操作)。
[8] 当你需要在容器的前端或中间做许多插入和删除时,请用list。
[9] 当你主要通过关键码访问元素时,请用map或multimap。
[10] 尽量用最小的操作集合,以取得最大的灵活性。
[11] 如果要保持元素的顺序性,选用map而不是hash_map。
[12] 如果查找速度极其重要,选hash_map而不是map。
[13] 如果无法对元素定义小于操作时,选hash_map而不是map。
[14] 当你需要检查某个关键码是否在关联容器里的时候,用find()。
[15] 用equal_range()在关联容器里找出所有具有给定关键码的所有元素。
[16] 当具有同样关键码的多个值需要保持顺序时,用multimap。
[17] 当关键码本身就是你需要保存的值时,用set或multiset。

第18章 算法和函数对象

[1] 多用算法,少用循环。
[2] 在写循环时,考虑是否能将它表述为一个通用的算法。
[3] 常规性地重温算法集合,看卡是不是能将新应用变得更明晰。
[4] 保证一对迭代器参数确实表述了一个序列。
[5] 设计时应该让使用最频繁的操作时简单而安全的。
[6] 吧测试表述成能够作为谓词使用的形式。
[7] 切记谓词是函数和对象,不是类型。
[8] 你可以用约束器从二元谓词做出一元谓词。
[9] 利用mem_fun()和mem_fun_ref()将算法应用于容器。
[10] 当你需要将一个参数约束到一个函数上时,用ptr_fun()。
[11] 切记srrcmp()用0表示“相等”,与==不同。
[12] 仅在没有更特殊的算法时,才使用for_each()和tranform()。
[13] 利用谓词,以便能一各种比较准则和相等准则使用算法。
[14] 利用谓词和其他函数对象,以使标准算法能用于表示范围广泛的意义。
[15] 运算符<和==在指针上的默认意义很少适用于标准算法。
[16] 算法并不直接为它们的参数序列增加或减少元素。
[17] 应保证用于同一个序列的小于和相等谓词相互匹配。
[18] 有时排好序的序列用起来更有效且优雅。
[19] 仅为兼容性而使用qsort()和bsearch()。

第19章 迭代器和分配器

[1] 在写一个算法时,设法确定需要用哪种迭代器才能提供可接受的效率,并(只)使用这种迭代器所支持的操作符去表述算法。
[2] 当给定的迭代器参数提供了多于算法所需 的最小支持时,请通过重载为该算法提供效率更高的实现。
[3] 利用istream_traits为不同迭代器类别描述适当的算法。
[4] 记住在istream_iterator和ostream_iterator的访问之前使用++。
[5] 用插入器避免容器溢出。
[6] 在排错时使用额外的检查,后面只在必须时才删除这些检查。
[7] 多用++p,少用p++。
[8] 使用未初始化的存储去改善那些扩展数据结构的算法性能。
[9] 使用临时缓冲区去改善需要临时数据结构的算法的性能。
[10] 在写自己的分配器之前三思。
[11] 避免malloc()、free()、realloc()等。
[12] 你可以通过为rebind所用的技术去模拟对模板的typedef。

第20章 串

[1] 尽量使用string操作,少用C风格字符串函数。
[2] 用string作为变量或者成员,不作为基类。
[3] 你可以将string作为参数值或者返回值,让系统去关心存储管理问题。
[4] 当你希望做范围检查时,请用at()而不是迭代器或者[]。
[5] 当你希望优化速度时,请用迭代器或[]而不是at()。
[6] 直接或者间接地使用substr()去读字子串,用replace()去写子串。
[7] 用find()操作在string里确定值的位置(而不是写一个显式的循环)。
[8] 在你需要高效率地添加字符时,请在string的后面附加。
[9] 在没有极端时间要求情况下用string作为字符输入的目标。
[10] 用string::npos表示“sring的剩余部分”。
[11] 如果必要,就采用低级操作去实现极度频繁使用的strng(而不是到处用低级数据结构)。
[12] 如果你使用string,请在某些地方捕捉length_error和out_of_rang异常、
[13] 小心,不要将带值0的char*传递给字符串函数。
[14] 只是到必须做的时候,(再)用c_str()产生string的C风格表示。
[15] 当你需要知道字符串的类别时,用isalpha()、isdigit()等函数,不要自己去写对字符值的检测。

第21章 流

[1] 在为用户定义类型的值定义<<和>>时,应该采用意义清晰的正文表达形式。
[2] 在打印包含低优先级运算符的表达式时需要用括号。
[3] 在添加新的<<和>>运算符时,你不必修改istream或ostream。
[4] 你可以定义函数,时其能基于第二个(或更后面的)参数,具有像virtual函数那样的行为。
[5] 切记,按默认约定>>跳过所有空格。
[6] 使用低级输入函数(如get()和read())主要是为了实现高级输入函数。
[7] 在使用get()、getline()和read()时留心其终止准则。
[8] 在控制I/O时,尽量采用操控符,少用状态标志。
[9] (只)用异常去捕捉罕见的I/O错误。
[10] 联结用于交互式I/O的流。
[11] 使用哨位将许多函数的入口和出口代码集中到一个地方。
[12] 在无参数操控符最后不要写括号。
[13] 使用标准操控符式应记住写#include <iomanip>。
[14] 你可以通过定义一个简单函数对象得到三元运算符的效果(和效率)。
[15] 切记,width描述只应用于随后的一个I/O操作。
[16] 切记precision描述只对所后所的浮点数输出操作有效。
[17] 用字符串流做内存里的格式化。
[18] 你可以描述一个文件流的模式。
[19] 在扩充I/O系统时,应该清楚地区分格式化(iostream)和缓冲(streambuf)。
[20] 将传输值的非标准方式实现为流缓冲。
[21] 将格式化值的非标准方式实现为流操作。
[22] 你可以利用一对函数隔离和封装其对用户定义代码的调用。
[23] 你可以在读入之前用in_avail()去确定输入操作是否会被阻塞。
[24] 划分清楚需要高效的简单操作和实现某种策略的操作(将前者做成inline,将后者做成virtual)。
[25] 用locale将“文化差异”局部化。
[26] 用sync_with_stdio(x)去混合C风格和C++风格的I/O,或者离解C风格和C++风格的I/O。
[27] 当心C风格I/O的类型错误。

第22章 数值

[1] 数值问题常常和微妙。如果你对数值问题的数学方面不是100%有把握,请去找专家或者做试验。
[2] 用numberic_limits去确定内部类型的性质。
[3] 为用户定义的标量类型描述numberic_limits。
[4] 如果运行时效率比对于操作和元素的灵活性更重要的话,那么请用valarray去做数值计算。
[5] 用切割表述在数组的一部分上的操作,而不是用循环。
[6] 利用组合器,通过清除临时量和更好的算法来获得效率。
[7] 用std::complex做复数算术。
[8] 你可以把使用complex类的老代码通过一个typedef转为用str::complex模板。
[9] 在写循环从一个表出发计算某个值之前,先考虑一下accumulate()、inner_produce()、partial_sum()和adjacent_difference()。
[10] 最好使用具有特定分布的随机数,少直接用rand()。
[11] 注意是你的随机数充分随机。

第23章 开发和设计

[1] 知道你试图达到什么目的。
[2] 心中牢记软件开发是一项人的活动。
[3] 用类比来证明是有意的欺骗。
[4] 保持一个特定的实实在在的目标。
[5] 不要试图用技术方式去解决社会问题。
[6] 在设计和对待人员方面都应该有长期考虑。
[7] 对于什么程序在编码之前先行设计是有意义的,在程序规模上并没有下限。
[8] 设计过程应鼓励反馈。
[9] 不要将做事情都当做取得了进展。
[10] 不要推广到超出了所需要的、你已有直接经验的和已经测试过的东西。
[11] 将概念表述为类。
[12] 系统里也存在一些不应该用类表述的性质。
[13] 将概念间的层次关系用类层次结构表示。
[14] 主动到应用和实现中去寻找概念间的共性,将由此得到的一般性概念表示为基类。
[15] 在其他领域中的分类方式未必适合作为应用中的继承模型的分类方式。
[16] 基于行为和不变式设计类层次结构。
[17] 考虑用例。
[18] 考虑用CRC卡。
[19] 用现存系统作为模型、灵感的源泉和出发点。
[20] 意识到视觉图形工程的重要性。
[21] 在原型成为负担时就抛弃它。
[22] 为变化而设计,将注意力集中到灵活性、可扩展性、可移植性和重用。
[23] 将注意力集中到组件设计。
[24] 让每个界面代表在一个抽象层次中的一个概念。
[25] 面向变化进行设计,以求得稳定性。
[26] 通过将广泛频繁使用的界面做得最小、最一般和抽象来使设计稳定。
[27] 保持尽可能小,不为“特殊需要”增加新特征。
[28] 总考虑类的其他表示方式。如果不可能有其他方式,这个类可能就没有代表某个清晰的概念。
[29] 反复评审、精化设计和实现。
[30] 采用那些能用于调试,用于分析问题、设计和实现的最好工具。
[31] 尽早、尽可能频繁地进行试验、分析和测试。
[32] 不要忘记效率。
[33] 保持某种适合项目规模的规范性水平。
[34] 保证有人负责项目的整体设计。
[35] 为可重用组件做文档、推介和提供支持。
[36] 将目标与细节一起写进文档里。
[37] 将为新开发者提供的教许材料作为文档的一部分。
[38] 鼓励设计、库和类的重用,并给予回报。

第24章 设计和编程

[1] 应该向数据抽象和面向对象设计的方向发展。
[2] (仅仅)根据需要去使用C++的特征和技术。
[3] 设计应与编程风格相互匹配。
[4] 将类/概念作为设计中最基本的关注点,而不是功能/处理。
[5] 用类表示概念。
[6] 用继承(仅仅)表示概念间的层次结构关系。
[7] 利用应用层静态类型的方式给出有关界面的更强的保证。
[8] 使用程序生成器和直接界面操作工具去完成定义良好的工作。
[9] 不要去使用那些与任何通用程序设计语言之间都没有清晰界面的程序生成器或者直接界面操作工具。
[10] 保存不同层次的抽象相互分离。
[11] 关注组件设计。
[12] 保证虚函数有定义良好的意义,每个覆盖函数都实现预期行为。
[13] 公用界面表示的是“是一个”关系。
[14] 成员表示的是“有一个”关系。
[15] 在表示简单包容时最好用直接成员,不用指向单独分配的对象的指针。
[16] 设法保证使用依赖关系为易理解的,尽可能不出现循环,而且最小。
[17] 对于所有的类,定义好不变式。
[18] 显式地将前条件、后条件和其他断言表述为断言(可能使用Assert())。
[19] 定义的界面应该只暴露初尽可能少的信息。
[20] 尽可能减少一个界面对其他界面的依赖性。
[21] 保持界面为强类型的。
[22] 利用应用层的类型来表述界面。
[23] 将界面表述得使请求可以传递给远程得服务器。
[24] 避免肥大的界面。
[25] 尽可能地使用private数据和成员函数。
[26] 用protected/private区分开派生类的设计者与一般用户间的不同需要。
[27] 使用模板去做通用型程序设计。
[28] 使用模板去做算法策略的参数化。
[29] 如果需要在编译时做类型解析,情使用模板。
[30] 如果需要在运行时做类型解析,请使用层次结构。

第25章 类的作用

[1] 应该对一个类的使用方式做出有意识的决策(作为设计师或者作为用户)。
[2] 应注意到涉及不同种类的类之间的权衡问题。
[3] 用具体类型去表示简单的独立概念。
[4] 用具体类型去表示那些最佳效果及其关键的概念。
[5] 不要从具体类派生。
[6] 用抽象类去表示那些对象的表示可能变化的界面。
[7] 用抽象类去表示那些可能出现多种对象表示共存情况的界面。
[8] 用抽象类去表示现存类型的新界面。
[9] 当类似概念共享许多实现细节时,应该使用结点类。
[10] 用结点类去逐步扩充一个实现。
[11] 用运行时类型识别从对象获取界面。
[12] 用类去表示具有与之关联的状态信息的动作。
[13] 用类去表示需要存储、传递或者延迟执行的动作。
[14] 利用界面类去为某种新的用法而调整一个类(不修改这个类)。
[15] 利用界面类增加检查。
[16] 利用句柄去避免直接使用指针和引用。
[17] 利用句柄去管理共享的表示。
[18] 在那些能预先定义控制结构的应用领域中使用应用框架。

终于写打完了,累得手都发酸了!其实,书看了只是知道了里面的知识而已,而要想真正的把书上的知识成为自己的,还得下大功夫去实践。搞编程尤其是这样,高手都是写程序写出来的,而不是看书看出来的。只有在写程序的时候经常想起这些忠告,慢慢的把它们应用到自己的开发过程中,这本书才算没有白看。

borland c++ bulder的文件操作总结-3

在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:

1、插入器(>)
  从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。

  在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h。下面就把此类的文件操作过程一一道来。

一、打开文件
  在fstream类中,有一个成员函数open(),就是用来打开文件的,其原型是:

void open(const char* filename,int mode,int access);

参数:

filename:  要打开的文件名
mode:    要打开文件的方式
access:   打开文件的属性
打开文件的方式在类ios(是所有流式I/O类的基类)中定义,常用的值如下:

ios::app:   以追加的方式打开文件
ios::ate:   文件打开后定位到文件尾,ios:app就包含有此属性
ios::binary:  以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
ios::in:    文件以输入方式打开
ios::out:   文件以输出方式打开
ios::nocreate: 不建立文件,所以文件不存在时打开失败 
ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
ios::trunc:  如果文件存在,把文件长度设为0
  可以用“或”把以上属性连接起来,如ios::out|ios::binary

  打开文件的属性取值是:

0:普通文件,打开访问
1:只读文件
2:隐含文件
4:系统文件
  可以用“或”或者“+”把以上属性连接起来 ,如3或1|2就是以只读和隐含属性打开文件。

  例如:以二进制输入方式打开文件c:\config.sys

  fstream file1;
  file1.open("c:\\config.sys",ios::binary|ios::in,0);

  如果open函数只有文件名一个参数,则是以读/写普通文件打开,即:

  file1.open("c:\\config.sys");file1.open("c:\\config.sys",ios::in|ios::out,0);

  另外,fstream还有和open()一样的构造函数,对于上例,在定义的时侯就可以打开文件了:

  fstream file1("c:\\config.sys");

  特别提出的是,fstream有两个子类:ifstream(input file stream)和ofstream(outpu file stream),ifstream默认以输入方式打开文件,而ofstream默认以输出方式打开文件。

  ifstream file2("c:\\pdos.def");//以输入方式打开文件
  ofstream file3("c:\\x.123");//以输出方式打开文件

  所以,在实际应用中,根据需要的不同,选择不同的类来定义:如果想以输入方式打开,就用ifstream来定义;如果想以输出方式打开,就用ofstream来定义;如果想以输入/输出方式来打开,就用fstream来定义。

二、关闭文件
  打开的文件使用完成后一定要关闭,fstream提供了成员函数close()来完成此操作,如:file1.close();就把file1相连的文件关闭。

三、读写文件
  读写文件分为文本文件和二进制文件的读取,对于文本文件的读取比较简单,用插入器和析取器就可以了;而对于二进制的读取就要复杂些,下要就详细的介绍这两种方式

  1、文本文件的读写
  文本文件的读写很简单:用插入器(>)从文件输入。假设file1是以输入方式打开,file2以输出打开。示例如下:

  file2>i;//从文件输入一个整数值。

  这种方式还有一种简单的格式化能力,比如可以指定输出为16进制等等,具体的格式有以下一些

操纵符 功能 输入/输出
dec 格式化为十进制数值数据 输入和输出
endl 输出一个换行符并刷新此流 输出
ends 输出一个空字符 输出
hex 格式化为十六进制数值数据 输入和输出
oct 格式化为八进制数值数据 输入和输出
setpxecision(int p) 设置浮点数的精度位数 输出

  比如要把123当作十六进制输出:file1

C++中的文件输入/输出(1)

简介

本教程将以C++最基本的文件I/O(输出/输出)开始。此后,我将从更深入的方面,为你展示一些技巧,并分析给出一些有用的函数。

你需要对C++有一个较好的理解,否则这个教程于你而言将是陌生而毫无用处。



你的第一个程序



首先我将给出一段代码,接着再逐行进行解释。我们的第一个程序将建立一个文件,并写入一些字符:



#include



void main() // 程序从这里开始运行
{

ofstream SaveFile(“cpp-home.txt”);

SaveFile << “Hello World, from www.cpp-home.com and Loobian!”;

SaveFile.close();
}

仅仅如此吗?没错!这个程序将在当前运行目录下建立一个名为cpp-home.txt的文件,并向它写入“Hello World, from www.cpp-home.com and Loobian!”。



下面给出各行的含义:



#include —— 你需要包含此文件以使用C++的文件输入/输出函数。注意:一旦包含了这个文件,你不再需要(为了使用cout/cin)包含iostream.h,因为fstream.h已经自动包含了它。

在这个头文件中声明了若干个类,包括ifstream,ofstream及fstream,它们都继承自istream和ostream类。



ofstream SaveFile(“cpp-home.txt”);

1)ofstream即“output file stream(输出文件流)”。它将建立一个句柄(handle),以便我们以后能以一个文件流的形式写入文件。

2)SaveFile —— 这是文件句柄的名字,当然,你还可以换用任何一个你想要的名称。

3)(“cpp-home.txt”); —— 打开名为cpp-home.txt的文件。如果程序运行的当前目录已经存在这样一个文件,则它将被替换掉;万一不存在,程序也会为你创建一个为文件,你不必为此而担心。

现在,让我们稍微深入一点点。首先,我要指出的是:ofstream是一个类。因此ofstream SaveFile(“cpp-home.txt”);这一语句将创建一个该类的对象;而我们在括号中所传递的参数实际上将传给构造函数:在这里我们将我们要建立的文件的名称作为实际参数传递给了该类的构造函数。当然,我们还可以传递其它的一些信息,不过我以后再对其进行讲解。



SaveFile << “Hello World, from www.cpp-home.com and Loobian!”; —— “<<”看起来是不是很亲切?不错,想必你已经在cout << 中见到过。这是一个预定义好的运算符。不管怎么说,这行语句所做的,是将上面的那段文本写入文件。正如前面所提到的,SaveFile是一个文件句柄,它关联一个打开的流式文件。所以,我们只须输入句柄名,再跟着输入“<<”,然后接着写下一串用引号括起来的文本,就可以实现对文件的写入。如果我们想写入的是某个变量的值而不是带引号的文本,也只须像通常使用cout << 一样将变量传递给句柄对象,像这样:

SaveFile << variablename;

就可以了!



SaveFile.close(); —— 既然我们打开了一个流文件,那么当我们用完它之后,就必须关闭它。SaveFile是ofstream类的一个对象,而该类(ofstream)有一个用于关闭文件的成员函数,即close() 函数。因此,我们只要依次输入文件句柄名,点号和close(),就可以关闭该文件!

注意:一旦你关闭文件,在你重新打开它以前,就再不能对它进行访问。



以上就是一个可以写文件的最简单程序。的确很容易!不过,正如你即将在以后部分的教程中所看到的,还有更多的东西要学呢!

C++中的文件输入/输出(2)

读取文件



你已经看到了应该如何写文件。现在,当我们已经得到cpp-home.txt文件时,我们将要读取它,并且将内容打印在屏幕上。

首先,我要指出的是,有很多种方法可以读取文件。以后我会向你们介绍所有的方法(就我所知的)。此刻,我先向你展示最佳的方法(我认为的)。

正如你已经熟悉的——我将首先给出一段程序代码,然后,我会详细地对它进行解释说明:



#include



void main() //程序从这里开始

{

ifstream OpenFile("cpp-home.txt");



char ch;

while(!OpenFile.eof())

{

OpenFile.get(ch);

cout << ch;

}

OpenFile.close();

}



你想必已经了解首行的意义所在,而剩下的部分将由我为你解释。

ifstream OpenFile(“cpp-home.txt”) —— 我猜它对现在的你而言多少会熟悉些!ifstream表示“input file stream(输入文件流)”。在前一节的程序中,出现的则是ofstream,它的意义是“output file stream(输出文件流)”。前一节的程序是进行文件的写操作,这就是它用“output(输出)”来表示的原因。而本节的程序则是读取一个文件,这就是它用“input(输入)”来表示的原因。这一行剩下的代码于你而言应当是熟悉的了:OpenFile是ifstream类的一个对象,它将关联一个输入文件流;而用引号括住的内容,就是将要打开的文件的名称。

请注意:这里没有对要打开的文件是否存在进行测试!以后我将向你指出如何进行检测。



char ch; —— 声明一个字符数组(array of type char)。只是有一点要提醒你:这样的数组(arrays)只能存储一个ASCII字符。



while(!OpenFile.eof()) —— 如果已经到达文件末尾,eof( )函数将返回一个非零值。因此我们所设计的这个循环将一直持续,直至我们的文件操作到达文件末尾。这样我们就可以遍历整个文件,以便对它进行读取。



OpenFile.get(ch); —— OpenFile是类ifstream的一个对象。该类声明了一个名为get( )的成员函数。只要我们拥有该对象,我们自然就可以调用这个函数。get( )函数从相应的流文件中读出一个字符,并将其返回给变量。在本例中,get( )函数只带一个参数——用于存储所读取的字符的变量。所以,调用OpenFile.get(ch)后程序将会从OpenFile流中读取一个字符并存入变量ch中。

注意:如果你再次调用该函数,它将读取下一个字符,而不是原来的那一个!你过后将理解为什么会这样。

这就是我们要不断反复循环直至读操作到达文件尾的原因。每循环一次,我们将读出一个字符并将它保存在ch中。



cout << ch; —— 显示ch变量值,它保存了读取得到的字符。



File.close(); —— 我们打开了一个流式文件,就需要关闭它。使用close()函数即可将它关闭,这和前一节的一样!

注意:一旦你关闭了一个文件,在你重新打开它之前,你不能再对它进行访问。



大功告成了!我希望你能明白我的解释。当你编译并运行这个程序的时候,它应当会输出:

“Hello World, from www.cpp-home.com and Loobian!”

C++中的文件输入/输出(3)

掌握输入/输出流



在这一章里,我会提及一些有用的函数。我将为你演示如何打开一个可以同时进行读、写操作的文件;此外,我还将为你介绍其它打开文件的方法,以及如何判断打开操作是否成功。因此,请接着往下读!

到目前为止,我已为你所展示的只是单一的打开文件的途径:要么为读取而打开,要么为写入而打开。但文件还可以以其它方式打开。迄今,你应当已经认识了下面的方法:



ifstream OpenFile(“cpp-home.txt”);



噢,这可不是唯一的方法!正如以前所提到的,以上的代码创建一个类ifstream的对象,并将文件的名字传递给它的构造函数。但实际上,还存在有不少的重载的构造函数,它们可以接受不止一个的参数。同时,还有一个open()函数可以做同样的事情。下面是一个以上代码的示例,但它使用了open()函数:



ifstream OpenFile;

OpenFile.open(“cpp-home.txt”);



你会问:它们之间有什么区别吗?哦,我曾做了不少测试,结论是没有区别!只不过如果你要创建一个文件句柄但不想立刻给它指定一个文件名,那么你可以使用open()函数过后进行指定。顺便再给出一个要使用open()函数的例子:如果你打开一个文件,然后关闭了它,又打算用同一个文件句柄打开另一个文件,这样一来,你将需要使用open()函数。

考虑以下的代码示例:



#include



void read(ifstream &T) //pass the file stream to the function

{

//the method to read a file, that I showed you before

char ch;



while(!T.eof())

{

T.get(ch);

cout << ch;

}



cout << endl << "--------" << endl;

}



void main()

{

ifstream T("file1.txt");

read(T);

T.close();



T.open("file2.txt");

read(T);

T.close();

}



据此,只要file1.txt和file2.txt并存储了文本内容,你将看到这些内容。

现在,该向你演示的是,文件名并不是你唯一可以向open()函数或者构造函数(其实都一样)传递的参数。下面是一个函数原型:



ifstream OpenFile(char *filename, int open_mode);



你应当知道filename表示文件的名称(一个字符串),而新出现的则是open_mode(打开模式)。open_mode的值用来定义以怎样的方式打开文件。下面是打开模式的列表:

名称
描述

ios::in
打开一个可读取文件

ios::out
打开一个可写入文件

ios::app
你写入的所有数据将被追加到文件的末尾,此方式使用ios::out

ios::ate
你写入的所有数据将被追加到文件的末尾,此方式不使用ios::out

ios::trunk
删除文件原来已存在的内容(清空文件)

ios::nocreate
如果要打开的文件并不存在,那么以此参数调用open()函数将无法进行。

ios::noreplace
如果要打开的文件已存在,试图用open()函数打开时将返回一个错误。

ios::binary
以二进制的形式打开一个文件。




实际上,以上的值都属于一个枚举类型的int常量。但为了让你的编程生涯不至于太痛苦,你可以像上表所见的那样使用那些名称。

下面是一个关于如何使用打开模式的例子:



#include



void main()

{

ofstream SaveFile("file1.txt", ios::ate);



SaveFile << "That's new!\n";



SaveFile.close();

}



正如你在表中所看到的:使用ios::ate将会从文件的末尾开始执行写入。如果我没有使用它,原来的文件内容将会被重新写入的内容覆盖掉。不过既然我已经使用了它,那么我只会在原文件的末尾进行添加。所以,如果file1.txt原有的内容是这样:

Hi! This is test from www.cpp-home.com!

那么执行上面的代码后,程序将会为它添上“That’s new!”,因此它看起来将变成这样:

Hi! This is test from www.cpp-home.com!That’s new!

假如你打算设置不止一个的打开模式标志,只须使用OR操作符或者是 | ,像这样:



ios::ate | ios::binary



我希望现在你已经明白“打开模式”是什么意思了!

现在,是时候向你展示一些真正有用的东西了!我敢打赌你现在还不知道应当怎样打开一个可以同时进行读取和写入操作的文件!下面就是实现的方法:



fstream File(“cpp-home.txt”,ios::in | ios::out);



实际上,这只是一个声明语句。我将在下面数行之后给你一个代码示例。但此时我首先想提及一些你应当知道的内容。

上面的代码创建了一个名为“File”的流式文件的句柄。如你所知,它是fstream类的一个对象。当使用fstream时,你应当指定ios::in和ios::out作为文件的打开模式。这样,你就可以同时对文件进行读、写,而无须创建新的文件句柄。噢,当然,你也可以只进行读或者写的操作。那样的话,相应地你应当只使用ios::in或者只使用ios::out —— 要思考的问题是:如果你打算这么做,为什么你不分别用ifstream及ofstream来实现呢?

下面就先给出示例代码:



#include



void main()
{

fstream File("test.txt",ios::in | ios::out);



File << "Hi!"; //将“Hi!”写入文件

static char str[10]; //当使用static时,数组会自动被初始化

//即是被清空为零




File.seekg(ios::beg); // 回到文件首部

// 此函数将在后面解释

File >> str;

cout << str << endl;



File.close();
}



OK,这儿又有一些新东西,所以我将逐行进行解释:



fstream File(“test.txt”, ios::in | ios::out); —— 此行创建一个fstream对象,执行时将会以读/写方式打开test.txt文件。这意味着你可以同时读取文件并写入数据。



File << “Hi!”; —— 我打赌你已经知道它的意思了。



static char str[10]; —— 这将创建一个容量为10的字符数组。我猜static对你而言或者有些陌生,如果这样就忽略它。这只不过会在创建数组的同时对其进行初始化。



File.seekg(ios::beg); —— OK,我要让你明白它究竟会做些什么,因此我将以一些有点儿离题、但挺重要的内容开始我的解释。

还记得它么:



while(!OpenFile.eof())

{

OpenFile.get(ch);

cout << ch;

}



你是不是曾经很想知道那背后真正执行了什么操作?不管是或不是,我都将为你解释。这是一个while型循环,它会一直反复,直至程序的操作到达文件的尾端。但这个循环如何知道是否已经到了文件末尾?嗯,当你读文件的时候,会有一个类似于“内置指针(inside-pointer)”的东西,它表明你读取(写入也一样)已经到了文件的哪个位置,就像记事本中的光标。而每当你调用OpenFile.get(ch)的时候,它会返回当前位置的字符,存储在ch变量中,并将这一内置指针向前移动一个字符。因此下次该函数再被调用时,它将会返回下一个字符。而这一过程将不断反复,直到读取到达文件尾。所以,让我们回到那行代码:函数seekg()将把内置指针定位到指定的位置(依你决定)。你可以使用:



ios::beg —— 可将它移动到文件首端

ios::end —— 可将它移动到文件末端



或者,你可以设定向前或向后跳转的字符数。例如,如果你要向定位到当前位置的5个字符以前,你应当写:

File.seekg(-5);

如果你想向后跳过40个字符,则应当写:

File.seekg(40);

同时,我必须指出,函数seekg()是被重载的,它也可以带两个参数。另一个版本是这样子的:

File.seekg(-5,ios::end);

在这个例子中,你将能够读到文件文本的最后4个字符,因为:

1)你先到达了末尾(ios::end)

2)你接着到达了末尾的前五个字符的位置(-5)

为什么你会读到4个字符而不是5个?噢,只须把最后一个看成是“丢掉了”,因为文件最末端的“东西”既不是字符也不是空白符,那只是一个位置(译注:或许ios::end所“指”的根本已经超出了文件本身的范围,确切的说它是指向文件最后一个字符的下一个位置,有点类似STL中的各个容器的end迭代点是指向最后一个元素的下一位置。这样设计可能是便于在循环中实现遍历)。

你现在可能想知道为什么我要使用到这个函数。呃,当我把“Hi”写进文件之后,内置指针将被设为指向其后面……也就是文件的末尾。因此我必须将内置指针设回文件起始处。这就是这个函数在此处的确切用途。

File >> str; —— 这也是新鲜的玩意儿!噢,我确信这行代码让你想起了cin >> .实际上,它们之间有着相当的关联。此行会从文件中读取一个单词,然后将它存入指定的数组变量中。

例如,如果文件中有这样的文本片断:

Hi! Do you know me?

使用File >> str,则只会将“Hi!”输出到str数组中。你应当已经注意到了,它实际上是将空格作为单词的分隔符进行读取的。

由于我存入文件中的只是单独一个“Hi!”,我不需要写一个while循环,那会花费更多的时间来写代码。这就是我使用此方法的原因。顺便说一下,到目前为止,我所使用的读取文件的while循环中,程序读文件的方式是一个字符一个字符进行读取的。然而你也可以一个单词一个单词地进行读取,像这样:



char str[30]; // 每个单词的长度不能超过30个字符

while(!OpenFile.eof())

{

OpenFile >> str;

cout << str;

}



你也可以一行一行地进行读取,像这样:



char line[100]; // 每个整行将会陆续被存储在这里
while(!OpenFile.eof())
{

OpenFile.getline(line,100); // 100是数组的大小

cout << line << endl;
}



你现在可能想知道应当使用哪种方法。嗯,我建议你使用逐行读取的方式,或者是最初我提及的逐字符读取的方式。而逐词读取的方式并非一个好的方案,因为它不会读出新起一行这样的信息,所以如果你的文件中新起一行时,它将不会将那些内容新起一行进行显示,而是加在已经打印的文本后面。而使用getline()或者get()都将会向你展现出文件的本来面目!

现在,我将向你介绍如何检测文件打开操作是否成功。实现上,好的方法少之又少,我将都会涉及它们。需要注意的是,出现“X”的时候,它实际可以以“o”、 “i”来代替,或者也可以什么都不是(那将是一个fstream对象)。

例1:最通常的作法



Xfstream File(“cpp-home.txt”);

if (!File)
{

cout << “Error opening the file! Aborting…\n”;

exit(1);
}





例2:如果文件已经被创建,返回一个错误



ofstream File("unexisting.txt", ios::nocreate);



if(!File)

{

cout << “Error opening the file! Aborting…\n”;

exit(1);

}



例3:使用fail()函数



ofstream File("filer.txt", ios::nocreate);



if(File.fail())

{

cout << “Error opening the file! Aborting…\n”;

exit(1);

}



例3中的新出现的东西,是fail()函数。如果有任何输入/输出错误(不是在文件末尾)发生,它将返回非零值。

我也要讲一些我认为非常重要的内容!例如,如果你已经创建一个流文件对象,但你没有进行打开文件操作,像这样:



ifstream File; //也可以是一个ofstream



这样,我们就拥有一个文件句柄,但我们仍然没有打开文件。如果你打算迟些打开它,那么可以用open()函数来实现,我已经在本教程中将它介绍了。但如果在你的程序的某处,你可能需要知道当前的句柄是否关联了一个已经打开的文件,那么你可以用is_open()来进行检测。如果文件没有打开,它将返回0 (false);如果文件已经打开,它将返回1 (true)。例如:



ofstream File1;

File1.open("file1.txt");

cout << File1.is_open() << endl;



上面的代码将会返回1(译注:指File1.is_open()函数,下句同),因为我们已经打开了一个文件(在第二行)。而下面的代码则会返回0,这是由于我们没有打开文件,而只是创建了一个流文件句柄:

ofstream File1;

cout << File1.is_open() << endl;

好啦,这一章讲得够多啦。

C++中的文件输入/输出(4)

检测输入/输出的状态标志



在此我不打算解释“标志(flags)”一词的含义,不过假如你真的完全不理解关于这方面的概念,那么将本章读过一遍之后也许你对此会得到一些认识,我也相信你同样能理解这部分的理论。尽管如此,如果你还是不明白标志在C++中的含义,我推荐你阅读一些关于这个主题的资料。

好,让我们开始吧。

C++中负责的输入/输出的系统包括了关于每一个输入/输出操作的结果的记录信息。这些当前的状态信息被包含在io_state类型的对象中。io_state是一个枚举类型(就像open_mode一样),以下便是它包含的值(译注:表中第一列为枚举值的名称,第二列为该值相应含义的描述):



godbit
无错误

Eofbit
已到达文件尾

failbit
非致命的输入/输出错误

badbit
致使的输入/输出错误




有两种方法可以获得输入/输出的状态信息。一种方法是通过调用rdstate()函数,它将返回当前状态的错误标记(上表中提到的)。例如,假如没有任何错误,则rdstate()会返回goodbit.

另一种方法则是使用下面任何一个函数来检测相应的输入/输出状态:



bool bad();

bool eof(); //还记得它么?“不断读取文件内容直到到达文件末尾!”

bool fail(); //噢,这也是老朋友……检测一个打开操作是否成功

bool good();



假如badbit标志被标设(译注:原文为“If the badbit flag is up”,这里将“is up”译为“标设”,意即出现了badbit对应的错误,badbit状态被置为当前的错误状态,下同),则bad()函数返回true;假如failbit标志被标设,则fail()函数返回true;假如没有错误发生(goodbit标志被标设),则good()函数返回true;假如操作已经到达了文件末尾(eofbit被标设),则eof()函数返回true.

如果错误发生,你必须清除这些错误状态,以使你的程序能正确适当地继续运行——如果你这么打算的话。要清除错误状态,需使用clear()函数。此函数带一个参数,它是你将要设为当前状态的标志值。假使你想让你的程序“清清爽爽”地运行下去,只要将ios::goodbit作为实参。你将在以下内容中看到示例代码。

我将向你展示示例代码,以巩固你所学到的理论知识。



示例1:简单的状态检测



// 实际应用中可将 FileStream替换成你相应在使用的文件流句柄

if(FileStream.rdstate() == ios::eofbit)

cout << "End of file!\n";

if(FileStream.rdstate() == ios::badbit)

cout << "Fatal I/O error!\n";

if(FileStream.rdstate() == ios::failbit)

cout << "Non-fatal I/O error!\n";

if(FileStream.rdstate() == ios::goodbit)

cout << "No errors!\n";



示例2:clear()函数



#include



void main()

{

ofstream File1("file2.txt"); //建立file2.txt

File1.close();



// 下面的检测代码将会返回错误,这是因为我使用了ios::noreplace打开模式

// 它模式在试图打开一个已存在的文件时会返回错误

ofstream Test("file2.txt",ios::noreplace);



// 上一行将导致ios::failbit错误,我们这就将其演示出来

if(Test.rdstate() == ios::failbit)

cout << "Error...!\n";



Test.clear(ios::goodbit); // 将当前状态重置为ios::goodbit



if(Test.rdstate() == ios::goodbit) // 检测程序是否已经正确地施行了设置

cout << "Fine!\n";



Test.clear(ios::eofbit); // 将状态标志设为ios::eofbit. 无实际用途.



if(Test.rdstate() == ios::eofbit) // 检测是否已经正确地施行了设置 cout << "EOF!\n";



Test.close();



}



除了使用标记值判断,你也可以使用函数(译注:指bad()、eof()、fail()、good()这些函数)的形式进行判断,两者实际上是一样的——都是检测某个标记是否被标设。这些函数前面已经介绍过,我就不再重复了。如果你对如何使用它们还不是十分确定,那就重新回顾一下本教程中我曾经为你演示的应该如何检测一个文件打开操作是否成功的那部分内容。在那里我就使用了fail()函数。

C++中的文件输入/输出(5)

二进制文件的处理



虽然有规则格式(formatted)的文本(到目前为止我所讨论的所有文件形式)非常有用,但有时候你需要用到无格式(unformatted)的文件——二进制文件。它们和你的可执行程序看起来一样,而与使用<<及>>操作符创建的文件则大不相同。get()函数与put()函数则赋予你读/写无规则格式文件的能力:要读取一个字节,你可以使用get()函数;要写入一个字节,则使用put()函数。你应当回想起get()——我曾经使用过它。你可能会疑惑为什么当时我们使用它时,输出到屏幕的文件内容看起来是文本格式的?嗯,我猜这是因为我此前使用了<<及>>操作符。



译注:作者的所谓“规则格式文本(formatted text)”即我们平时所说的文本格式,而与之相对的“无格式文件(unformatted files)”即以存储各类数据或可执行代码的非文本格式文件。通常后者需要读入内存,在二进制层次进行解析,而前者则可以直接由预定好的<<及>>操作符进行读入/写出(当然,对后者也可以通过恰当地重载<<及>>操作符实现同样的功能,但这已经不是本系列的讨论范围了)。



get()函数与都各带一个参数:一个char型变量(译注:指get()函数)或一个字符(译注:指put()函数,当然此字符也可以以char型变量提供)。

假如你要读/写一整块的数据,那么你可以使用read()和write()函数。它们的原型如下:



istream &read(char *buf, streamsize num);

ostream &write(const char *buf, streamsize num);



对于read()函数,buf应当是一个字符数组,由文件读出的数据将被保存在这儿。对于write()函数,buf是一个字符数组,它用以存放你要写入文件的数据。对于这两个函数,num是一个数字,它指定你要从文件中读取/写入的字节数。

假如在读取数据时,在你读取“num”个字节之前就已经到达了文件的末尾,那么你可以通过调用gcount()函数来了解实际所读出的字节数。此函数会返回最后一次进行的对无格式文件的读入操作所实际读取的字节数。

在给出示例代码之前,我要补充的是,如果你要以二进制方式对文件进行读/写,那么你应当将ios::binary作为打开模式加入到文件打开的参数表中。

现在就让我向你展示示例代码,你会看到它是如何运作的。



示例1:使用get( )和put( )



#include



void main()

{

fstream File("test_file.txt",ios::out | ios::in | ios::binary);



char ch;

ch='o';



File.put(ch); // 将ch的内容写入文件



File.seekg(ios::beg); // 定位至文件首部



File.get(ch); // 读出一个字符



cout << ch << endl; // 将其显示在屏幕上



File.close();

}



示例2:使用read( )和write( )



#include

#include



void main()

{

fstream File("test_file.txt",ios::out | ios::in | ios::binary);



char arr[13];

strcpy(arr,"Hello World!"); //将Hello World!存入数组



File.write(arr,5); // 将前5个字符——"Hello"写入文件



File.seekg(ios::beg); // 定位至文件首部



static char read_array[10]; // 在此我将打算读出些数据



File.read(read_array,3); // 读出前三个字符——"Hel"



cout << read_array << endl; // 将它们输出



File.close();

}

用C++实现简单的文件I/O操作(ifstream,ofstream)

文件 I/O 在C++中比烤蛋糕简单多了。 在这篇文章里,我会详细解释ASCII和二进制文件的输入输出的每个细节,值得注意的是,所有这些都是用C++完成的。

  一、ASCII 输出

  为了使用下面的方法, 你必须包含头文件(译者注:在标准C++中,已经使用取代< fstream.h>,所有的C++标准头文件都是无后缀的。)。这是 的一个扩展集, 提供有缓冲的文件输入输出操作. 事实上, 已经被包含了, 所以你不必包含所有这两个文件, 如果你想显式包含他们,那随便你。我们从文件操作类的设计开始, 我会讲解如何进行ASCII I/O操作。如果你猜是"fstream," 恭喜你答对了! 但这篇文章介绍的方法,我们分别使用"ifstream"?和 "ofstream" 来作输入输出。

  如果你用过标准控制台流"cin"?和 "cout," 那现在的事情对你来说很简单。 我们现在开始讲输出部分,首先声明一个类对象。ofstream fout;

  这就可以了,不过你要打开一个文件的话, 必须像这样调用ofstream::open()。

fout.open("output.txt");

  你也可以把文件名作为构造参数来打开一个文件.

ofstream fout("output.txt");

  这是我们使用的方法, 因为这样创建和打开一个文件看起来更简单. 顺便说一句, 如果你要打开的文件不存在,它会为你创建一个, 所以不用担心文件创建的问题. 现在就输出到文件,看起来和"cout"的操作很像。 对不了解控制台输出"cout"的人, 这里有个例子。

int num = 150;
char name[] = "John Doe";
fout << "Here is a number: " << num << "\n";
fout << "Now here is a string: " << name << "\n";

  现在保存文件,你必须关闭文件,或者回写文件缓冲. 文件关闭之后就不能再操作了, 所以只有在你不再操作这个文件的时候才调用它,它会自动保存文件。 回写缓冲区会在保持文件打开的情况下保存文件, 所以只要有必要就使用它。回写看起来像另一次输出, 然后调用方法关闭。像这样:

fout << flush; fout.close();

   现在你用文本编辑器打开文件,内容看起来是这样:

  Here is a number: 150 Now here is a string: John Doe

  很简单吧! 现在继续文件输入, 需要一点技巧, 所以先确认你已经明白了流操作,对 "<<" 和">>" 比较熟悉了, 因为你接下来还要用到他们。继续…

  二、ASCII 输入

  输入和"cin" 流很像. 和刚刚讨论的输出流很像, 但你要考虑几件事情。在我们开始复杂的内容之前, 先看一个文本:

  12 GameDev 15.45 L This is really awesome!

  为了打开这个文件,你必须创建一个in-stream对象,?像这样。

ifstream fin("input.txt");

  现在读入前四行. 你还记得怎么用"<<" 操作符往流里插入变量和符号吧?好,?在 "<<" (插入)?操作符之后,是">>" (提取) 操作符. 使用方法是一样的. 看这个代码片段.

int number;
float real;
char letter, word[8];
fin >> number; fin >> word; fin >> real; fin >> letter;

  也可以把这四行读取文件的代码写为更简单的一行。

fin >> number >> word >> real >> letter;

  它是如何运作的呢? 文件的每个空白之后, ">>" 操作符会停止读取内容, 直到遇到另一个>>操作符. 因为我们读取的每一行都被换行符分割开(是空白字符), ">>" 操作符只把这一行的内容读入变量。这就是这个代码也能正常工作的原因。但是,可别忘了文件的最后一行。

  This is really awesome!

  如果你想把整行读入一个char数组, 我们没办法用">>"?操作符,因为每个单词之间的空格(空白字符)会中止文件的读取。为了验证:

char sentence[101]; fin >> sentence;

  我们想包含整个句子, "This is really awesome!" 但是因为空白, 现在它只包含了"This". 很明显, 肯定有读取整行的方法, 它就是getline()。这就是我们要做的。

fin.getline(sentence, 100);

  这是函数参数. 第一个参数显然是用来接受的char数组. 第二个参数是在遇到换行符之前,数组允许接受的最大元素数量. 现在我们得到了想要的结果:“This is really awesome!”。

  你应该已经知道如何读取和写入ASCII文件了。但我们还不能罢休,因为二进制文件还在等着我们。

  三、二进制 输入输出

  二进制文件会复杂一点, 但还是很简单的。首先你要注意我们不再使用插入和提取操作符(译者注:<< 和 >> 操作符). 你可以这么做,但它不会用二进制方式读写。你必须使用read() 和write() 方法读取和写入二进制文件. 创建一个二进制文件, 看下一行。

ofstream fout("file.dat", ios::binary);

  这会以二进制方式打开文件, 而不是默认的ASCII模式。首先从写入文件开始。函数write() 有两个参数。 第一个是指向对象的char类型的指针, 第二个是对象的大小(译者注:字节数)。 为了说明,看例子。

int number = 30; fout.write((char *)(&number), sizeof(number));

  第一个参数写做"(char *)(&number)". 这是把一个整型变量转为char *指针。如果你不理解,可以立刻翻阅C++的书籍,如果有必要的话。第二个参数写作"sizeof(number)". sizeof() 返回对象大小的字节数. 就是这样!

  二进制文件最好的地方是可以在一行把一个结构写入文件。 如果说,你的结构有12个不同的成员。 用ASCII?文件,你不得不每次一条的写入所有成员。 但二进制文件替你做好了。 看这个。

struct OBJECT { int number; char letter; } obj;
obj.number = 15;
obj.letter = ‘M’;
fout.write((char *)(&obj), sizeof(obj));

  这样就写入了整个结构! 接下来是输入. 输入也很简单,因为read()?函数的参数和 write()是完全一样的, 使用方法也相同。

ifstream fin("file.dat", ios::binary); fin.read((char *)(&obj), sizeof(obj));

  我不多解释用法, 因为它和write()是完全相同的。二进制文件比ASCII文件简单, 但有个缺点是无法用文本编辑器编辑。 接着, 我解释一下ifstream 和ofstream 对象的其他一些方法作为结束.

  四、更多方法

  我已经解释了ASCII文件和二进制文件, 这里是一些没有提及的底层方法。

  检查文件

  你已经学会了open() 和close() 方法, 不过这里还有其它你可能用到的方法。

  方法good() 返回一个布尔值,表示文件打开是否正确。

  类似的,bad() 返回一个布尔值表示文件打开是否错误。 如果出错,就不要继续进一步的操作了。

  最后一个检查的方法是fail(), 和bad()有点相似, 但没那么严重。

  读文件

  方法get() 每次返回一个字符。

  方法ignore(int,char) 跳过一定数量的某个字符, 但你必须传给它两个参数。第一个是需要跳过的字符数。 第二个是一个字符, 当遇到的时候就会停止。 例子,

fin.ignore(100, ‘\n’);

  会跳过100个字符,或者不足100的时候,跳过所有之前的字符,包括 ‘\n’。

  方法peek() 返回文件中的下一个字符, 但并不实际读取它。所以如果你用peek() 查看下一个字符, 用get() 在peek()之后读取,会得到同一个字符, 然后移动文件计数器。

  方法putback(char) 输入字符, 一次一个, 到流中。我没有见到过它的使用,但这个函数确实存在。

  写文件

  只有一个你可能会关注的方法.?那就是 put(char), 它每次向输出流中写入一个字符。

  打开文件

  当我们用这样的语法打开二进制文件:

ofstream fout("file.dat", ios::binary);

  "ios::binary"是你提供的打开选项的额外标志. 默认的, 文件以ASCII方式打开, 不存在则创建, 存在就覆盖. 这里有些额外的标志用来改变选项。

  ios::app 添加到文件尾
  ios::ate 把文件标志放在末尾而非起始。
  ios::trunc 默认. 截断并覆写文件。
  ios::nocreate 文件不存在也不创建。
  ios::noreplace 文件存在则失败。

  文件状态

  我用过的唯一一个状态函数是eof(), 它返回是否标志已经到了文件末尾。 我主要用在循环中。 例如, 这个代码断统计小写‘e’ 在文件中出现的次数。

ifstream fin("file.txt");
char ch; int counter;
while (!fin.eof()) {
ch = fin.get();
if (ch == ‘e’) counter++;
}
fin.close();

外星代码生成术之逗号运算符

缩减代码不意味着增加可读性,恰恰相反,过分的缩减代码反而会使代码更难懂,难以维护。本文不提倡滥用扭曲化的外星C语法,只是作为一个集趣式的记录,为初学C派系语言的读者简单介绍一下那些莫名其妙的代码是如何造出来的。

C派系语言为我们提供了许许多的奇怪的运算符,最奇怪的运算符之一当数逗号运算符,它本身并没有太多实际意义“运算”,只是计算各个运算项的值,然后将最右边的运算项的值返回。但这一点使得我们有可能将多个表达式合成为一个:

a = 1;

b++;

c *= 4;



合并为:

a = i, b++, c *= i;

呵,好像意义不大,但如果是在循环中使用,就不一样了:

while (i < n)

{

a = i;

b++;

i++;

}

可简写为:

while (a = i++, b++, i < n) ; /* 注意分号不能少 */

在C++中,由于使用cout等流对象进行的输出语句实际上也是一个表达式,所以对于如下常见的打印整个数组的值的代码:

for (i = 0; i < size; ++i)

cout << a[i] << endl;

使用逗号运算符并利用好自加运算的特性,可以简写为:

for (i = 0; i < size; cout << a[i++] << endl) ; /* 注意分号不能少 */

而对于打印一个二维数组这样的操作,由于循环完一行之后要输出换行,所以外层循环(遍历每一行)不得不使用复合语句:

for (i = 0; i < size; ++i)

{

for (j = 0; j < size; ++j)

cout << a[i][j];

cout << endl;

}

但如果把cout移到循环内,就可以显著减少行数:

for (i = 0; i < size; cout << endl; ++i)

for (j = 0; j < size; cout << a[i][j++]) ; /* 注意分号不能少 */



当然,也可以是

for (i = 0; cout << endl, i < size; ++i)

for (j = 0; cout << a[i][j], j < size; ++j) ; /* 注意分号不能少 */



但使用逗号运算符时一定要注意其隐含的不确定性,如表达式:

++i, cout << a[i], x + y;

如果语言中对各个表达式的求值顺序不确定,那么cout的子表达式所输出的a[i]就无法确定是自加以前的i还是自加以后的i. 使用逗号运算符,以及相关的重载运算符的连续表达式时应当格外注意这一点。

注:由于时间仓促,本文程序片断未经调试,如有错误,欢迎批评指正。

C++中的文件输入/输出(6):一些有用的函数

tellg() ——返回一个int型数值,它表示“内置指针”的当前位置。此函数仅当你在读取一个文件时有效。例如:
#include

void main()
{
// 假如我们已经在test_file.txt中存有了“Hello”的内容
ifstream File("test_file.txt");

char arr[10];

File.read(arr,10);

// 由于Hello占5个字符,因此这里将返回5
cout << File.tellg() << endl;

File.close();
}

tellp() —— 与tellg()有同样的功能,但它用于写文件时。总而言之:当我们读取一个文件,并要知道内置指针的当前位置时,应该使用tellg();当我们写入一个文件,并要知道内置指针的当前位置时,应该使用tellp(). 由于此函数的用法与tellg()完全一样,我就不给出示例代码了。

seekp() —— 还记得seekg()么?当我在读取一个文件,并想到达文件中某个特定位置时,就曾使用过它。seekp()亦如此,只不过它用于写入一个文件的时候。例如,假如我在进行文件读写,而要定位到当前位置的三个字符之前,则需调用FileHandle.seekg(-3). 但如果我是在写入一个文件,并且比如我要重写后5个字符的内容,我就必须往回跳转5个字符,因而,我应该使用FileHandle.seekp(-5) .

ignore() —— 使用于读取文件之时。如果你想略过一定数量的字符,只需使用此函数。实际上,你也可以使用seekg()来代替,然而使用ignore()有一个优点——你可以指定一个特定“界限规则(delimiter rule)”,同样使得ignore()在指定的位置停下。函数原型如下:

istream& ignore( int nCount, delimiter );

nCount表示要略过的字符数量,而delimiter —— 与它的名称有着同样的含义:假如你想在文件末尾停下,则可使用EOF值传入,这样一来此函数就等同于seekg();但该参数还可以使用其他值,例如‘\n’这样可以在换行的同时定位在新行处。下面是示例:
#include

void main()
{
// 假设test_file.txt中已经存有"Hello World"这一内容
ifstream File("test_file.txt");

static char arr[10];

// 假如一直没有遇到字符"l",则向前定位直到跳过6个字符
// 而如果期间遇到"l",则停止向前,定位在该处
File.ignore(6,'l');

File.read(arr,10);

cout << arr << endl; // 它将显示"lo World!"

File.close();

}

getline() —— 虽然前面的章节中我曾提到过这个函数,但还有一些内容我们未曾涉及:此函数不但可用于逐行读取,而且它还可以设为遇到某个特定字符后停止读取。下面给出传递这一参数的方法:

getline(array,array_size,delim);

以下为示例代码:

#include

void main()
{
// 假设test_file.txt中已经存有"Hello World"这一内容
ifstream File("test_file.txt");

static char arr[10];

/* 读取,直到满足下面的条件之一:
1)已经读取10个字符
2)遇到字母"o"
3)出现新一行
*/
File.getline(arr,10,'o');

cout << arr << endl; // 将显示"Hell"
File.close();
}

peek() —— 此函数将返回输入流文件的下一个字符,但它不移动内置指针。我想你该记得,像get()这样的函数也返回输入流文件的下一个字符,而与此同时它将移动内置指针。所以当你再次调用get()函数的时候,它会返回再下一个字符,而非前面那个。哦,使用peek()也会返回字符,但它不会移动“光标”。所以,假如你连续两次调用peek()函数,它会返回同一个字符。考虑以下代码:

#include

void main()
{
// 假设test_file.txt中已经存有"Hello World"这一内容
ifstream File("test_file.txt");

char ch;

File.get(ch);
cout << ch << endl; // 将显示"H"

cout << char(File.peek()) << endl; //将显示"e"
cout << char(File.peek()) << endl; //将再次显示"e"

File.get(ch);
cout << ch << endl; // 还是显示"e"

File.close();

}

顺便说一下,我忘了讲——peek()函数实质上返回的是字符的ASCII码,而非字符本身。因此,假如你想看到字符本身,你得像我在示例中做的那样进行调用(译注:即要转为char类型)。

_unlink() —— 删除一个文件。假如你要使用此函数,需要在你的程序中包含io.h头文件。下面是示例代码:

#include
#include

void main()
{
ofstream File;

File.open("delete_test.txt"); //创建一个文件
File.close();

_unlink("delete_test.txt"); // 删除这个文件

// 试图打开此文件,但假如它已不存在
// 函数将返回一个ios::failbit错误值
File.open("delete_test.txt",ios::nocreate);

// 验证它是否返回该值
if(File.rdstate() == ios::failbit)
cout << "Error...!\n"; // 耶,成功了

File.close();

}

putback() —— 此函数将返回最后一个所读取字符,同时将内置指针移动-1个字符。换言之,如果你使用get()来读取一个字符后再使用putback(),它将为你返回同一个字符,然而同时会将内置指针移动-1个字符,所以你再次使用get()时,它还是会为你返回同样的字符。下面是示例代码:

#include

void main()
{
// test_file.txt应包含内容"Hello World"
ifstream File("test_file.txt");

char ch;

File.get(ch);

cout << ch << endl; // 将显示"H"

File.putback(ch);
cout << ch << endl; // 仍将显示"H"

File.get(ch);
cout << ch << endl; // 再一次显示"H"

File.close();
}

flush() —— 在处理输出流文件的时候,你所存入的数据实际上并非立刻写入文件,而是先放入一个缓冲区中,直到该缓冲区放满数据之后,这些数据才被存入真正的文件中(在你的磁盘上)。旋即缓冲区会被清空,再重新进行下一轮写入。
但假如你想在缓冲区写满之前就将其中的数据写入磁盘,则使用flush()函数。只须像这样进行调用:FileHandle.flush(),这样缓冲区内的数据将会写入实际的物理文件,而后缓冲区被清空。
再补充一点(高阶的)内容:flush()函数会调用与相应流缓冲(streambuf)相联系的sync()函数(出自MSDN)。

结语

嗯,我希望你现在可以实现你的文件输入/输出程序了。我已经将自己所知的内容都涉及到,我想它们比你所需要的还要多。即使如此,还是会有一些内容我没有提及的……不过我想你或许都不会使用到那些方面的内容。因此,如果你还需要了解更多关于某些特定主题的高阶的理论,可以在互联网上搜索,例如,可以尝试一下google.com,但你可别来问我!我不对任何询问我关于如何实现某个程序此类问题的邮件作答复。
假如你喜欢这个系列的教程,或者相反,我都会非常高兴能看到你的意见。所以请随意地在网上联系我:loobian@cpp-home.com
想获取更多的C++教程,请访问:www.cpp-home.com

将整个文件读入string中

IOStream著名专家Dietmar Kuehl给过两个方法

std::ifstream in("some.file");
std::istreambuf_iterator beg(in), end;
std::string str(beg, end);


#include
std::ifstream in("some.file");
std::ostringstream tmp;
tmp << in.rdbuf();
std::string str = tmp.str();

星期日, 二月 18, 2007

中文字库常见编码简介

本文主要介绍中文字库有关的常见编码:单字节编码、GB2312-80、GB12345-90、GBK、Unicode编码、ISO10646 / Unicode字符集、GB18030-2000、BIG5编码、方正748编码
中文字库常见编码简介

所谓编码,是以固定的顺序排列字符,并以此做为记录、存贮、传递、交换的统一内部特征,这个字符排列顺序被称为“编码”。
字库的编码是字库组织的依据,也是文字处理的基础。不同国家和地区有不同的编码标准,和中文字库有关的常见编码有:单字节编码、GB2312-80、 GB12345-90、GBK、Unicode编码、ISO10646 / Unicode字符集、GB18030-2000、BIG5编码、方正748 编码,下面简要介绍一下:
■ 单字节编码
MS Windows:Windows Latin 1(ANSI)
MS-DOS:MS-DOS Latin US
Macintosh:Macintosh Roman
■ GB2312-80
全称是GB2312-80《信息交换用汉字编码字符集基本集》,1980年发布,是中文信息处理的国家标准,在大陆及海外使用简体中文的地区(如新加坡 等)是强制使用的唯一中文编码。P-Windows3.2和苹果OS就是以GB2312为基本汉字编码, Windows 95/98则以GBK为基本汉 字编码、但兼容支持GB2312。
双字节编码
范围:A1A1~FEFE
A1-A9:符号区,包含682个符号
B0-F7:汉字区,包含6763个汉字
GB码共收录6763个简体汉字、682个符号,其中汉字部分:一级字3755,以拼音排序,二级字3008,以偏旁排序。该标准的制定和应用为规范、推动中文信息化进程起了很大作用。
■ GB12345-90
1990 年制定了繁体字的编码标准GB12345-90《信息交换用汉字编码字符集第一辅助集》,目的在于规范必须使用繁体字的各种场合,以及古籍整理等。该标准 共收录6866个汉字(比GB2312多103个字,其它厂商的字库大多不包括这些字),纯繁体的字大概有2200余个。
双字节编码
范围:A1A1~FEFE
A1-A9:符号区,增加竖排符号
B0-F9:汉字区,包含6866个汉字
■ Unicode编码(Universal Multiple Octet Coded Character Set)
国际标准组织于1984年4月成立ISO/IEC JTC1/SC2/WG2工作组,针对各国文字、符号进行统一性编码。1991年美国跨国公司成立 Unicode Consortium,并于1991年10月与WG2达成协议,采用同一编码字集。目前Unicode是采用16位编码体系,其字符集内 容与ISO10646的BMP(Basic Multilingual Plane)相同。Unicode于1992年6月通过DIS (Draf International Standard),目前版本V2.0于1996公布,内容包含符号6811个,汉字20902个,韩文拼音 11172个,造字区6400个,保留20249个,共计65534个。
■ ISO10646 / Unicode字符集
全球可以共享的编码字符集。
UCS-4:组八位 平面八位 行八位 字位八位
UCS-2:00组中的00平面是基本多文种平面(BMP),4E00~9FFF 中日韩文字
Ext A(CJK):3400~4DB7,共6584字
Ext B(CJK):42,807个汉字,在第2平面的0100~A836
■ GBK编码(Chinese Internal Code Specification)
GBK编码是中国大陆制订的、等同于UCS的新的中文编码扩展国家标准。GBK工作小组于1995年10月,同年12月完成GBK规范。该编码标准兼容GB2312,共收录汉字21003个、符号883个,并提供1894个造字码位,简、繁体字融于一库。
Windows95/98简体中文版的字库表层编码就采用的是GBK,通过GBK与UCS之间一一对应的码表与底层字库联系。
英文名:Chinese Internal Code Specification
中文名:汉字内码扩展规范1.0版
双字节编码,GB2312-80的扩充,在码位上和GB2312-80兼容
范围:8140~FEFE(剔除xx7F)共23940个码位
包含21003个汉字,包含了ISO/IEC 10646-1中的全部中日韩汉字
■ GB18030-2000
英文名:Chinese Internal Code Specification
中文名:信息技术 信息交换用汉字编码字符集
基本集的扩充( 2000-03-17发布和实施)
单字节、双字节、四字节编码
向下与国家标准GB 2312信息处理交换码所对应的事实上的内码标准兼容。
在字汇上支持GB 13000.1的全部中、日、韩(CJK)统一汉字字符和全部CJK统一汉字扩充A的字符。
■ BIG5编码
是目前台湾、香港地区普遍使用的一种繁体汉字的编码标准,包括440个符号,一级汉字5401个、二级汉字7652个,共计13060个汉字。
■ 方正748编码
所谓748编码,是指方正系统在长期应用过程中实施、制定的简、繁体字库编码方式,简体兼容GB2312且有所扩展,共7156字;繁体兼容 GB12345并扩展全部BIG-5汉字,计14943字。此外,方正748编码还含有丰富的符号库。748编码仅用于方正软件和系统

星期三, 二月 14, 2007

进程和线程编程

进程和线程编程


目 录
进程和线程编程
原始管道
pipe()
dup()
dup2()
popen()和pclose()
命名管道
创建FIFO
操作FIFO
阻塞FIFO
消息队列
msgget()
msgsnd()
msgrcv()
msgctl()
信号量
semget()
semop()
semctl()
共享内存
shmget()
shmat()
shmctl()
shmdt()
线程
线程同步
使用信号量协调程序
代码例子
newthread
exitthead
getchannel
def
release
redezvous
unbouded

--------------------------------------------------------------------------------


进程和线程编程

看一下UNIX系统中的进程和Mach的任务和线程之间的关系。在UNIX系统中,一个进程包括一个可执行的程序和一系列的资源,例如文件描述符表和地址空间。在Mach中,一个任务仅包括一系列的资源;线程处理所有的可执行代码。一个Mach的任务可以有任意数目的线程和它相关,同时每个线程必须和某个任务相关。和某一个给定的任务相关的所有线程都共享任务的资源。这样,一个线程就是一个程序计数器、一个堆栈和一系列的寄存器。所有需要使用的数据结构都属于任务。一个UNIX系统中的进程在Mach中对应于一个任务和一个单独的线程。

[目录]

--------------------------------------------------------------------------------


原始管道

使用C语言创建管道要比在shell下使用管道复杂一些。如果要使用C语言创建一个简单的管道,可以使用系统调用pipe()。它接受一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。
可以通过打开两个管道来创建一个双向的管道。但需要在子进程中正确地设置文件描述必须在系统调用fork()中调用pipe(),否则子进程将不会继承文件描述符。当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。而在命名管道中却不是这样。


[目录]

--------------------------------------------------------------------------------


pipe()

系统调用:pipe();
原型:intpipe(intfd[2]);
返回值:如果系统调用成功,返回0
如果系统调用失败返回-1:errno=EMFILE(没有空闲的文件描述符)
EMFILE(系统文件表已满)
EFAULT(fd数组无效)
注意fd[0]用于读取管道,fd[1]用于写入管道。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
main()
{
intfd[2];
pipe(fd);
..
}
一旦创建了管道,我们就可以创建一个新的子进程:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
main()
{
intfd[2];
pid_t childpid;
pipe(fd);
if((childpid=fork())==-1)
{
perror("fork");
exit(1);
}..
}
如果父进程希望从子进程中读取数据,那么它应该关闭fd1,同时子进程关闭fd0。反之,如果父进程希望向子进程中发送数据,那么它应该关闭fd0,同时子进程关闭fd1。因为文件描述符是在父进程和子进程之间共享,所以我们要及时地关闭不需要的管道的那一端。单从技术的角度来说,如果管道的一端没有正确地关闭的话,你将无法得到一个EOF。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
main()
{
intfd[2];
pid_t childpid;
pipe(fd);
if((childpid=fork())==-1)
{
perror("fork");
exit(1);
}
if(childpid==0)
{
/*Child process closes up in put side of pipe*/
close(fd[0]);
}
else
{
/*Parent process closes up out put side of pipe*/
close(fd[1]);
}..
}

正如前面提到的,一但创建了管道之后,管道所使用的文件描述符就和正常文件的文件描述符一样了。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
intmain(void)
{
intfd[2],nbytes;
pid_tchildpid;
charstring[]="Hello,world!\n";
charreadbuffer[80];
pipe(fd);
if((childpid=fork())==-1)
{
perror("fork");
exit(1);
}
if(childpid==0)
{
/*Child process closes up in put side of pipe*/
close(fd[0]);
/*Send"string"through the out put side of pipe*/
write(fd[1],string,strlen(string));
exit(0);
}
else
{
/*Parent process closes up out put side of pipe*/
close(fd[1]);
/*Readinastringfromthepipe*/
nbytes=read(fd[0],readbuffer,sizeof(readbuffer));
printf("Receivedstring:%s",readbuffer);
}
return(0);
}

一般情况下,子进程中的文件描述符将会复制到标准的输入和输出中。这样子进程可以使用exec()执行另一个程序,此程序继承了标准的数据流。




[目录]

--------------------------------------------------------------------------------


dup()

系统调用:dup();
原型:intdup(intoldfd);
返回:如果系统调用成功,返回新的文件描述符
如果系统调用失败,返回-1:errno=EBADF(oldfd不是有效的文件描述符)
EBADF(newfd超出范围)
EMFILE(进程的文件描述符太多)
注意旧文件描述符oldfd没有关闭。虽然旧文件描述符和新创建的文件描述符可以交换使用,但一般情况下需要首先关闭一个。系统调用dup()使用的是号码最小的空闲的文件描述符。

再看下面的程序:
..
childpid=fork();
if(childpid==0)
{
/*Close up standard input of the child*/
close(0);
/*Dup licate the input side of pipe to stdin*/
dup(fd[0]);
execlp("sort","sort",NULL);
.
}
因为文件描述符0(stdin)被关闭,所以dup()把管道的输入描述符复制到它的标准输入中。这样我们可以调用execlp(),使用sort程序覆盖子进程的正文段。因为新创建的程序从它的父进程中继承了标准输入/输出流,所以它实际上继承了管道的输入端作为它的标准输入端。现在,最初的父进程送往管道的任何数据都将会直接送往sort函数。




[目录]

--------------------------------------------------------------------------------


dup2()

系统调用:dup2();
原型:intdup2(intoldfd,intnewfd);
返回值:如果调用成功,返回新的文件描述符
如果调用失败,返回-1:errno=EBADF(oldfd不是有效的文件描述符)
EBADF(newfd超出范围)
EMFILE(进程的文件描述符太多)
注意dup2()将关闭旧文件描述符。
使用此系统调用,可以将close操作和文件描述符复制操作集成到一个系统调用中。另外,此系统调用保证了操作的自动进行,也就是说操作不能被其他的信号中断。这个操作将会在返回系统内核之前完成。如果使用前一个系统调用dup(),程序员不得不在此之前执行一个close()操作。请看下面的程序:
..

childpid=fork();
if(childpid==0)
{
/*Close stdin,dup licate the input side of pipe to stdin*/
dup2(0,fd[0]);
execlp("sort","sort",NULL);
..
}




[目录]
popen()和pclose()

如果你认为上面创建和使用管道的方法过于繁琐的话,你也可以使用下面的简单的方法:
库函数:popen()和pclose();
原型:FILE*popen(char*command,char*type);
返回值:如果成功,返回一个新的文件流。
如果无法创建进程或者管道,返回NULL。
此标准的库函数通过在系统内部调用pipe()来创建一个半双工的管道,然后它创建一个子进程,启动shell,最后在shell上执行command参数中的命令。管道中数据流的方向是由第二个参数type控制的。此参数可以是r或者w,分别代表读或写。但不能同时为读和写。在Linux系统下,管道将会以参数type中第一个字符代表的方式打开。所以,如果你在参数type中写入rw,管道将会以读的方式打开。

虽然此库函数的用法很简单,但也有一些不利的地方。例如它失去了使用系统调用pipe()时可以有的对系统的控制。尽管这样,因为可以直接地使用shell命令,所以shell中的一些通配符和其他的一些扩展符号都可以在command参数中使用。
使用popen()创建的管道必须使用pclose()关闭。其实,popen/pclose和标准文件输入/输出流中的fopen()/fclose()十分相似。


库函数:pclose();
原型:intpclose(FILE*stream);
返回值:返回系统调用wait4()的状态。
如果stream无效,或者系统调用wait4()失败,则返回-1。
注意此库函数等待管道进程运行结束,然后关闭文件流。库函数pclose()在使用popen()创建的进程上执行wait4()函数。当它返回时,它将破坏管道和文件系统。
在下面的例子中,用sort命令打开了一个管道,然后对一个字符数组排序:

#include<stdio.h>
#defineMAXSTRS5
intmain(void)
{
intcntr;
FILE*pipe_fp;
char*strings[MAXSTRS]={"echo","bravo","alpha",
"charlie","delta"};
/*Createonewaypipelinewithcalltopopen()*/
if((pipe_fp=popen("sort","w"))==NULL)
{
perror("popen");
exit(1);
}
/*Processingloop*/
for(cntr=0;cntr<MAXSTRS;cntr++){
fputs(strings[cntr],pipe_fp);
fputc('\n',pipe_fp);
}
/*Closethepipe*/
pclose(pipe_fp);
return(0);
}
因为popen()使用shell执行命令,所以所有的shell扩展符和通配符都可以使用。此外,它还可以和popen()一起使用重定向和输出管道函数。再看下面的例子:
popen("ls~scottb","r");
popen("sort>/tmp/foo","w");
popen("sort|uniq|more","w");
下面的程序是另一个使用popen()的例子,它打开两个管道(一个用于ls命令,另一个用于
sort命令):
#include<stdio.h>
intmain(void)
{
FILE*pipein_fp,*pipeout_fp;
charreadbuf[80];
/*Createonewaypipelinewithcalltopopen()*/
if((pipein_fp=popen("ls","r"))==NULL)
{
perror("popen");
exit(1);
}
/*Createonewaypipelinewithcalltopopen()*/
if((pipeout_fp=popen("sort","w"))==NULL)
{
perror("popen");
exit(1);
}
/*Processingloop*/
while(fgets(readbuf,80,pipein_fp))
fputs(readbuf,pipeout_fp);
/*Closethepipes*/
pclose(pipein_fp);
pclose(pipeout_fp);
return(0);
}
最后,我们再看一个使用popen()的例子。此程序用于创建一个命令和文件之间的管道:
#include<stdio.h>
intmain(intargc,char*argv[])
{
FILE*pipe_fp,*infile;
charreadbuf[80];
if(argc!=3){
fprintf(stderr,"USAGE:popen3[command][filename]\n");
exit(1);
}
/*Open up input file*/
if((infile=fopen(argv[2],"rt"))==NULL)
{
perror("fopen");
exit(1);
}
/*Create one way pipe line with call topopen()*/
if((pipe_fp=popen(argv[1],"w"))==NULL)
{
perror("popen");
exit(1);
}
/*Processingloop*/
do{
fgets(readbuf,80,infile);
if(feof(infile))break;
fputs(readbuf,pipe_fp);
}while(!feof(infile));
fclose(infile);
pclose(pipe_fp);
return(0);
}
下面是使用此程序的例子:
popen3sortpopen3.c
popen3catpopen3.c
popen3morepopen3.c
popen3catpopen3.c|grepmain




[目录]

--------------------------------------------------------------------------------


命名管道

命名管道和一般的管道基本相同,但也有一些显著的不同:
*命名管道是在文件系统中作为一个特殊的设备文件而存在的。
*不同祖先的进程之间可以通过管道共享数据。
*当共享管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中以便以后使用。

一个管道必须既有读取进程,也要有写入进程。如果一个进程试图写入到一个没有读取进程的管道中,那么系统内核将会产生SIGPIPE信号。当两个以上的进程同时使用管道时,这一点尤其重要。


[目录]

--------------------------------------------------------------------------------


创建FIFO

可以有几种方法创建一个命名管道。头两种方法可以使用shell。
mknodMYFIFOp
mkfifoa=rwMYFIFO
上面的两个命名执行同样的操作,但其中有一点不同。命令mkfifo提供一个在创建之后直接改变FIFO文件存取权限的途径,而命令mknod需要调用命令chmod。
一个物理文件系统可以通过p指示器十分容易地分辨出一个FIFO文件。

$ls-lMYFIFO
prw-r--r--1rootroot0Dec1422:15MYFIFO|

请注意在文件名后面的管道符号“|”。
我们可以使用系统调用mknod()来创建一个FIFO管道:

库函数:mknod();
原型:intmknod(char*pathname,mode_tmode,dev_tdev);
返回值:如果成功,返回0
如果失败,返回-1:errno=EFAULT(无效路径名)
EACCES(无存取权限)
ENAMETOOLONG(路径名太长)
ENOENT(无效路径名)
ENOTDIR(无效路径名)

下面看一个使用C语言创建FIFO管道的例子:

mknod("/tmp/MYFIFO",S_IFIFO|0666,0);

在这个例子中,文件/tmp/MYFIFO是要创建的FIFO文件。它的存取权限是0666。存取权限
也可以使用umask修改:

final_umask=requested_permissions&~original_umask

一个常用的使用系统调用umask()的方法就是临时地清除umask的值:
umask(0);
mknod("/tmp/MYFIFO",S_IFIFO|0666,0);

另外,mknod()中的第三个参数只有在创建一个设备文件时才能用到。它包括设备文件的
主设备号和从设备号。
}
}






[目录]

--------------------------------------------------------------------------------


操作FIFO

FIFO上的I/O操作和正常管道上的I/O操作基本一样,只有一个主要的不同。系统调用open用来在物理上打开一个管道。在半双工的管道中,这是不必要的。因为管道在系统内核中,而不是在一个物理的文件系统中。在我们的例子中,我们将像使用一个文件流一样使用管道,也就是使用fopen()打开管道,使用fclose()关闭它。
请看下面的简单的服务程序进程:
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<unistd.h>
#include<linux/stat.h>
#defineFIFO_FILE"MYFIFO"
intmain(void)
{
FILE*fp;
charreadbuf[80];
/*CreatetheFIFOifitdoesnotexist*/
umask(0);
mknod(FIFO_FILE,S_IFIFO|0666,0);
while(1)
{
fp=fopen(FIFO_FILE,"r");
fgets(readbuf,80,fp);
printf("Receivedstring:%s\n",readbuf);
fclose(fp);
return(0);
因为FIFO管道缺省时有阻塞的函数,所以你可以在后台运行此程序:
$fifoserver&
再来看一下下面的简单的客户端程序:
#include<stdio.h>
#include<stdlib.h>
#defineFIFO_FILE"MYFIFO"
intmain(int argc,char* argv[])
{
FILE*fp;
if(argc!=2){
printf("USAGE:fifoclient[string]\n");
exit(1);
}
if((fp=fopen(FIFO_FILE,"w"))==NULL){
perror("fopen");
exit(1);
}
fputs(argv[1],fp);
fclose(fp);
return(0);
}


[目录]

--------------------------------------------------------------------------------


阻塞FIFO

一般情况下,FIFO管道上将会有阻塞的情况发生。也就是说,如果一个FIFO管道打开供读取的话,它将一直阻塞,直到其他的进程打开管道写入信息。这种过程反过来也一样。如果你不需要阻塞函数的话,你可以在系统调用open()中设置O_NONBLOCK标志,这样可以取消缺省的阻塞函数。



[目录]

--------------------------------------------------------------------------------


消息队列

在UNIX的SystemV版本,AT&T引进了三种新形式的IPC功能(消息队列、信号量、以及共享内存)。但BSD版本的UNIX使用套接口作为主要的IPC形式。Linux系统同时支持这两个版本。

[目录]

--------------------------------------------------------------------------------


msgget()

系统调用msgget()
如果希望创建一个新的消息队列,或者希望存取一个已经存在的消息队列,你可以使用系统调用msgget()。

系统调用:msgget();
原型:intmsgget(key_t key,int msgflg);
返回值:如果成功,返回消息队列标识符
如果失败,则返回-1:errno=EACCESS(权限不允许)
EEXIST(队列已经存在,无法创建)
EIDRM(队列标志为删除)
ENOENT(队列不存在)
ENOMEM(创建队列时内存不够)
ENOSPC(超出最大队列限制)

系统调用msgget()中的第一个参数是关键字值(通常是由ftok()返回的)。然后此关键字值将会和其他已经存在于系统内核中的关键字值比较。这时,打开和存取操作是和参数msgflg中的内容相关的。
IPC_CREAT如果内核中没有此队列,则创建它。
IPC_EXCL当和IPC_CREAT一起使用时,如果队列已经存在,则失败。

如果单独使用IPC_CREAT,则msgget()要么返回一个新创建的消息队列的标识符,要么返回具有相同关键字值的队列的标识符。如果 IPC_EXCL和IPC_CREAT一起使用,则msgget()要么创建一个新的消息队列,要么如果队列已经存在则返回一个失败值-1。 IPC_EXCL单独使用是没有用处的。
下面看一个打开和创建一个消息队列的例子:
intopen_queue(key_t keyval)
{
intqid;
if((qid=msgget(keyval,IPC_CREAT|0660))==-1)
{
return(-1);
}
return(qid);
}




[目录]

--------------------------------------------------------------------------------


msgsnd()

系统调用msgsnd()
一旦我们得到了队列标识符,我们就可以在队列上执行我们希望的操作了。如果想要往队列中发送一条消息,你可以使用系统调用msgsnd():

系统调用:msgsnd();
原型:intmsgsnd(int msqid,struct msgbuf*msgp,int msgsz,int msgflg);
返回值:如果成功,0。
如果失败,-1:errno=EAGAIN(队列已满,并且使用了IPC_NOWAIT)
EACCES(没有写的权限)
EFAULT(msgp地址无效)
EIDRM(消息队列已经删除)
EINTR(当等待写操作时,收到一个信号)
EINVAL(无效的消息队列标识符,非正数的消息类型,或
者无效的消息长度)
ENOMEM(没有足够的内存复制消息缓冲区)

系统调用msgsnd()的第一个参数是消息队列标识符,它是由系统调用msgget返回的。第二个参数是msgp,是指向消息缓冲区的指针。参数msgsz中包含的是消息的字节大小,但不包括消息类型的长度(4个字节)。
参数msgflg可以设置为0(此时为忽略此参数),或者使用IPC_NOWAIT。

如果消息队列已满,那么此消息则不会写入到消息队列中,控制将返回到调用进程中。如果没有指明,调用进程将会挂起,直到消息可以写入到队列中。
下面是一个发送消息的程序:

intsend_message(int qid,struct mymsgbuf *qbuf)
{
intresult,length;
/*The length is essentially the size of the structure minus sizeof(mtype)*/
length=sizeof(structmymsgbuf)-sizeof(long);
if((result=msgsnd(qid,qbuf,length,0))==-1)
{
return(-1);
}
return(result);
}

这个小程序试图将存储在缓冲区qbuf中的消息发送到消息队列qid中。下面的程序是结合了上面两个程序的一个完整程序:

#include<stdio.h>
#include<stdlib.h>
#include<linux/ipc.h>
#include<linux/msg.h>
main()
{
intqid;
key_t msgkey;
struct mymsgbuf{
longmtype;/*Message type*/
intrequest;/*Work request number*/
doublesalary;/*Employee's salary*/
}msg;
/*Generateour IPC key value*/
msgkey=ftok(".",'m');
/*Open/createthequeue*/
if((qid=open_queue(msgkey))==-1){
perror("open_queue");
exit(1);
}
/*Load up the message with a r bitrary test data*/
msg.mtype=1;/*Messagetypemustbeapositivenumber!*/
msg.request=1;/*Dataelement#1*/
msg.salary=1000.00;/*Data element #2(my yearly salary!)*/
/*Bombsaway!*/
if((send_message(qid,&msg))==-1){
perror("send_message");
exit(1);
}
}
在创建和打开消息队列以后,我们将测试数据装入到消息缓冲区中。最后调用send_messag把消息发送到消息队列中。现在在消息队列中有了一条消息,我们可以使用ipcs命令来查看队列的状态。下面讨论如何从队列中获取消息。可以使用系统调用msgrcv():



[目录]
msgrcv()

系统调用:msgrcv();
原型:intmsgrcv(intmsqid,structmsgbuf*msgp,intmsgsz,longmtype,intmsgflg);
返回值:如果成功,则返回复制到消息缓冲区的字节数。
如果失败,则返回-1:errno=E2BIG(消息的长度大于msgsz,没有MSG_NOERROR)
EACCES(没有读的权限)
EFAULT(msgp指向的地址是无效的)
EIDRM(队列已经被删除)
EINTR(被信号中断)
EINVAL(msgqid无效,或者msgsz小于0)
ENOMSG(使用IPC_NOWAIT,同时队列中的消息无法满足要求)
很明显,第一个参数用来指定将要读取消息的队列。第二个参数代表要存储消息的消息缓冲区的地址。第三个参数是消息缓冲区的长度,不包括mtype的长度,它可以按照如下的方法计算:
msgsz=sizeof(structmymsgbuf)-sizeof(long);
第四个参数是要从消息队列中读取的消息的类型。如果此参数的值为0,那么队列中最长时间的一条消息将返回,而不论其类型是什么。
如果调用中使用了IPC_NOWAIT作为标志,那么当没有数据可以使用时,调用将把ENOMSG返回到调用进程中。否则,调用进程将会挂起,直到队列中的一条消息满足msgrcv()的参数要求。如果当客户端等待一条消息的时候队列为空,将会返回EIDRM。如果进程在等待消息的过程中捕捉到一个信号,则返回EINTR。
下面就是一个从队列中读取消息的程序:

intread_message(int qid,long type,struct mymsgbuf*qbuf)
{
intresult,length;
/*The length is essentially the size of the structure minus sizeof(mtype)*/
length=sizeof(structmymsgbuf)-sizeof(long);
if((result=msgrcv(qid,qbuf,length,type,0))==-1)
{
return(-1);
}
return(result);
}
在成功地读取了一条消息以后,队列中的这条消息的入口将被删除。
参数msgflg中的MSG_NOERROR位提供一种额外的用途。如果消息的实际长度大于msgsz,同时使用了MSG_NOERROR,那么消息将会被截断,只有与msgsz长度相等的消息返回。一般情况下,系统调用msgrcv()会返回-1,而这条消息将会继续保存在队列中。我们可以利用这个特点编制一个程序,利用这个程序可以查看消息队列的情况,看看符合我们条件的消息是否已经到来:

intpeek_message(int qid,long type)
{
intresult,length;
if((result=msgrcv(qid,NULL,0,type,IPC_NOWAIT))==-1)
{
if(errno==E2BIG)
return(TRUE);
}
return(FALSE);
}
在上面的程序中,我们忽略了缓冲区的地址和长度。这样,系统调用将会失败。尽管如此,我们可以检查返回的E2BIG值,它说明符合条件的消息确实存在。



[目录]

--------------------------------------------------------------------------------


msgctl()

系统调用msgctl()
下面我们继续讨论如何使用一个给定的消息队列的内部数据结构。我们可以使用系统调用msgctl ( )来控制对消息队列的操作。

系统调用: msgctl( ) ;
调用原型: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
返回值: 0 ,如果成功。
- 1,如果失败:errno = EACCES (没有读的权限同时cmd 是IPC_STAT )
EFAULT (buf 指向的地址无效)
EIDRM (在读取中队列被删除)
EINVAL (msgqid无效, 或者msgsz 小于0 )
EPERM (IPC_SET或者IPC_RMID 命令被使用,但调用程序没有写的权限)
下面我们看一下可以使用的几个命令:
IPC_STAT
读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中。
IPC_SET
设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。
IPC_RMID
从系统内核中移走消息队列。
我们在前面讨论过了消息队列的数据结构(msqid_ds)。系统内核中为系统中的每一个消息队列保存一个此数据结构的实例。通过使用IPC_STAT命令,我们可以得到一个此数据结构的副本。下面的程序就是实现此函数的过程:

int get_queue_ds( int qid, struct msgqid_ds *qbuf )
{
if( msgctl( qid, IPC_STAT, qbuf) == -1)
{
return(-1);
}
return(0);
}

如果不能复制内部缓冲区,调用进程将返回-1。如果调用成功,则返回0。缓冲区中应该包括消息队列中的数据结构。
消息队列中的数据结构中唯一可以改动的元素就是ipc_perm。它包括队列的存取权限和关于队列创建者和拥有者的信息。你可以改变用户的id、用户的组id以及消息队列的存取权限。
下面是一个修改队列存取模式的程序:

int change_queue_mode(int qid, char *mode )
{
struct msqid_ds tmpbuf;
/* Retrieve a current copy of the internal data structure */
get_queue_ds( qid, &tmpbuf);
/* Change the permissions using an old trick */
sscanf(mode, "%ho", &tmpbuf.msg_perm.mode);
/* Update the internal data structure */
if( msgctl( qid, IPC_SET, &tmpbuf) == -1)
{
return(-1);
}
return(
}

我们通过调用get_queue_ds来读取队列的内部数据结构。然后,我们调用sscanf( )修改数据结构msg_perm中的mode 成员的值。但直到调用msgctl()时,权限的改变才真正完成。在这里msgctl()使用的是IPC_SET命令。
最后,我们使用系统调用msgctl ( )中的IPC_RMID命令删除消息队列:

int remove_queue(int qid )
{
if( msgctl( qid, IPC_RMID, 0) == -1)
{
return(-1);
}
return(0);
}
};



[目录]

--------------------------------------------------------------------------------


信号量

信号量是一个可以用来控制多个进程存取共享资源的计数器。它经常作为一种锁定机制来防止当一个进程正在存取共享资源时,另一个进程也存取同一资源。下面先简要地介绍一下信号量中涉及到的数据结构。
1.内核中的数据结构semid_ds
和消息队列一样,系统内核为内核地址空间中的每一个信号量集都保存了一个内部的数据结构。数据结构的原型是semid_ds。它是在linux/sem.h中做如下定义的:
/*One semid data structure for each set of semaphores in the system.*/
structsemid_ds{
structipc_permsem_perm;/*permissions..seeipc.h*/
time_tsem_otime;/*last semop time*/
time_tsem_ctime;/*last change time*/
structsem*sem_base;/*ptr to first semaphore in array*/
structwait_queue*eventn;
structwait_queue*eventz;
structsem_undo*undo;/*undo requestson this array*/
ushortsem_nsems;/*no. of semaphores in array*/
};
sem_perm是在linux/ipc.h定义的数据结构ipc_perm的一个实例。它保存有信号量集的存取权限的信息,以及信号量集创建者的有关信息。
sem_otime最后一次semop()操作的时间。
sem_ctime最后一次改动此数据结构的时间。
sem_base指向数组中第一个信号量的指针。
sem_undo数组中没有完成的请求的个数。
sem_nsems信号量集(数组)中的信号量的个数。

2.内核中的数据结构sem
在数据结构semid_ds中包含一个指向信号量数组的指针。此数组中的每一个元素都是一个
数据结构sem。它也是在linux/sem.h中定义的:
/*One semaphore structure for each semaphore in the system.*/
structsem{
shortsempid;/*pid of las toperation*/
ushortsemval;/*current value*/
ushortsemncnt;/*num procs awaiting increase in semval*/
ushortsemzcnt;/*num procs awaiting semval=0*/
};
sem_pid最后一个操作的PID(进程ID)。
sem_semval信号量的当前值。
sem_semncnt等待资源的进程数目。
sem_semzcnt等待资源完全空闲的进程数目。


[目录]
semget()

我们可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:
系统调用:semget();
原型:intsemget(key_t key,int nsems,int semflg);
返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1:errno=EACCESS(没有权限)
EEXIST(信号量集已经存在,无法创建)
EIDRM(信号量集已经删除)
ENOENT(信号量集不存在,同时没有使用IPC_CREAT)
ENOMEM(没有足够的内存创建新的信号量集)
ENOSPC(超出限制)

系统调用semget()的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比较。打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和 IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是在linux/sem.h中定义的:

#defineSEMMSL32/*<=512maxnumofsemaphoresperid*/
下面是一个打开和创建信号量集的程序:
intopen_semaphore_set(key_t keyval,int numsems)
{
intsid;
if(!numsems)
return(-1);
if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)
{
return(-1);
}
return(sid);
}
};






[目录]

--------------------------------------------------------------------------------


semop()

系统调用:semop();
调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);
返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目)
EACCESS(权限不够)
EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行)
EFAULT(sops指向的地址无效)
EIDRM(信号量集已经删除)
EINTR(当睡眠时接收到其他信号)
EINVAL(信号量集不存在,或者semid无效)
ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构)
ERANGE(信号量值超出范围)
第一个参数是关键字值。第二个参数是指向将要操作的数组的指针。第三个参数是数组中的操作的个数。参数sops指向由sembuf组成的数组。此数组是在linux/sem.h中定义的:

/*semop systemcall takes an array of these*/
structsembuf{
ushortsem_num;/*semaphore index in array*/
shortsem_op;/*semaphore operation*/
shortsem_flg;/*operation flags*/
sem_num将要处理的信号量的个数。
sem_op要执行的操作。
sem_flg操作标志。

如果sem_op是负数,那么信号量将减去它的值。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。如果sem_op是正数,则信号量加上它的值。这也就是进程释放信号量控制的资源。最后,如果sem_op是0,那么调用进程将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。






[目录]

--------------------------------------------------------------------------------


semctl()

系统调用:semctl();
原型:int semctl(int semid,int semnum,int cmd,union semunarg);
返回值:如果成功,则为一个正数。
如果失败,则为-1:errno=EACCESS(权限不够)
EFAULT(arg指向的地址无效)
EIDRM(信号量集已经删除)
EINVAL(信号量集不存在,或者semid无效)
EPERM(EUID没有cmd的权利)
ERANGE(信号量值超出范围)
系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。因为信号量一般是作为一个信号量集使用的,而不是一个单独的信号量。所以在信号量集的操作中,不但要知道IPC关键字值,也要知道信号量集中的具体的信号量。这两个系统调用都使用了参数cmd,它用来指出要操作的具体命令。两个系统调用中的最后一个参数也不一样。在系统调用msgctl中,最后一个参数是指向内核中使用的数据结构的指针。我们使用此数据结构来取得有关消息队列的一些信息,以及设置或者改变队列的存取权限和使用者。但在信号量中支持额外的可选的命令,这样就要求有一个更为复杂的数据结构。
系统调用semctl()的第一个参数是关键字值。第二个参数是信号量数目。

参数cmd中可以使用的命令如下:
·IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
·IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
·IPC_RMID将信号量集从内存中删除。
·GETALL用于读取信号量集中的所有信号量的值。
·GETNCNT返回正在等待资源的进程数目。
·GETPID返回最后一个执行semop操作的进程的PID。
·GETVAL返回信号量集中的一个单个的信号量的值。
·GETZCNT返回这在等待完全空闲的资源的进程数目。
·SETALL设置信号量集中的所有的信号量的值。
·SETVAL设置信号量集中的一个单独的信号量的值。

参数arg代表一个semun的实例。semun是在linux/sem.h中定义的:
/*arg for semctl systemcalls.*/
unionsemun{
intval;/*value for SETVAL*/
structsemid_ds*buf;/*buffer for IPC_STAT&IPC_SET*/
ushort*array;/*array for GETALL&SETALL*/
structseminfo*__buf;/*buffer for IPC_INFO*/
void*__pad;

val当执行SETVAL命令时使用。buf在IPC_STAT/IPC_SET命令中使用。代表了内核中使用的信号量的数据结构。array在使用GETALL/SETALL命令时使用的指针。
下面的程序返回信号量的值。当使用GETVAL命令时,调用中的最后一个参数被忽略:

intget_sem_val(intsid,intsemnum)
{
return(semctl(sid,semnum,GETVAL,0));
}

下面是一个实际应用的例子:

#defineMAX_PRINTERS5
printer_usage()
{
int x;
for(x=0;x<MAX_PRINTERS;x++)
printf("Printer%d:%d\n\r",x,get_sem_val(sid,x));
}

下面的程序可以用来初始化一个新的信号量值:

void init_semaphore(int sid,int semnum,int initval)
{
union semunsemopts;
semopts.val=initval;
semctl(sid,semnum,SETVAL,semopts);
}

注意系统调用semctl中的最后一个参数是一个联合类型的副本,而不是一个指向联合类型的指针。




[目录]

--------------------------------------------------------------------------------


共享内存

共享内存就是由几个进程共享一段内存区域。这可以说是最快的IPC形式,因为它无须任何的中间操作(例如,管道、消息队列等)。它只是把内存段直接映射到调用进程的地址空间中。这样的内存段可以是由一个进程创建的,然后其他的进程可以读写此内存段。
每个系统的共享内存段在系统内核中也保持着一个内部的数据结构shmid_ds。此数据结构是在linux/shm.h中定义的,如下所示:

/* One shmid data structure for each shared memory segment in the system. */
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
time_t shm_atime; /* last attach time */
time_t shm_dtime; /* last detach time */
time_t shm_ctime; /* last change time */
unsigned short shm_cpid; /* pid of creator */
unsigned short shm_lpid; /* pid of last operator */
short shm_nattch; /* no. of current attaches */
/* the following are private */
unsigned short shm_npages; /* size of segment (pages) */
unsigned long *shm_pages; /* array of ptrs to frames -> SHMMAX */
struct vm_area_struct *attaches; /* descriptors for attaches */
};

shm_perm 是数据结构ipc_perm的一个实例。这里保存的是内存段的存取权限,和其他的有关内存段创建者的信息。
shm_segsz 内存段的字节大小。
shm_atime 最后一个进程存取内存段的时间。
shm_dtime 最后一个进程离开内存段的时间。
shm_ctime 内存段最后改动的时间。
shm_cpid 内存段创建进程的P I D。
shm_lpid 最后一个使用内存段的进程的P I D。
shm_nattch 当前使用内存段的进程总数。


[目录]
shmget()

系统调用:shmget();
原型:int shmget(key_t key,int size,int shmflg);
返回值:如果成功,返回共享内存段标识符。如果失败,则返回-1:errno=EINVAL(无效的内存段大小)
EEXIST(内存段已经存在,无法创建)
EIDRM(内存段已经被删除)
ENOENT(内存段不存在)
EACCES(权限不够)
ENOMEM(没有足够的内存来创建内存段)
系统调用shmget()中的第一个参数是关键字值(它是用系统调用ftok()返回的)。其他的操作都要依据shmflg中的命令进行。
·IPC_CREAT如果系统内核中没有共享的内存段,则创建一个共享的内存段。
·IPC_EXCL当和IPC_CREAT一同使用时,如果共享内存段已经存在,则调用失败。
当IPC_CREAT单独使用时,系统调用shmget()要么返回一个新创建的共享内存段的标识符,要么返回一个已经存在的共享内存段的关键字值。如果 IPC_EXCL和IPC_CREAT一同使用,则要么系统调用新创建一个共享的内存段,要么返回一个错误值-1。IPC_EXCL单独使用没有意义。

下面是一个定位和创建共享内存段的程序:

int open_segment(key_t keyval,int segsize)
{
int shmid;
if((shmid=shmget(keyval,segsize,IPC_CREAT|0660))==-1)
{
return(-1);
}
return(shmid);
}

一旦一个进程拥有了一个给定的内存段的有效IPC标识符,它的下一步就是将共享的内存段映射到自己的地址空间中。





[目录]

--------------------------------------------------------------------------------


shmat()

系统调用: shmat();
原型:int shmat ( int shmid, char *shmaddr, int shmflg);
返回值:如果成功,则返回共享内存段连接到进程中的地址。如果失败,则返回- 1:errno = EINVAL (无效的IPC ID 值或者无效的地址)
ENOMEM (没有足够的内存)
EACCES (存取权限不够)
如果参数a d d r的值为0,那么系统内核则试图找出一个没有映射的内存区域。我们推荐使用这种方法。你可以指定一个地址,但这通常是为了加快对硬件设备的存取,或者解决和其他程序的冲突。
下面的程序中的调用参数是一个内存段的I P C标识符,返回内存段连接的地址:

char *attach_segment(int shmid)
{
return(shmat(shmid, 0, 0));
}

一旦内存段正确地连接到进程以后,进程中就有了一个指向该内存段的指针。这样,以后就可以使用指针来读取此内存段了。但一定要注意不能丢失该指针的初值。




[目录]

--------------------------------------------------------------------------------


shmctl()

系统调用:shmctl ( ) ;
原型:int shmctl( int shmqid, int cmd, struct shmid_ds *buf );
返回值: 0 ,如果成功。
-1,如果失败:errno = EACCES (没有读的权限,同时命令是IPC_STAT)
EFAULT(buf指向的地址无效,同时命令是IPC_SET和IPC_STAT )
EIDRM (内存段被移走)
EINVAL (shmqid 无效)
EPERM (使用IPC_SET 或者IPC_RMID 命令,但调用进程没有写的权限)
IPC_STAT 读取一个内存段的数据结构shmid_ds,并将它存储在buf参数指向的地址中。
IPC_SET 设置内存段的数据结构shmid_ds中的元素ipc_perm的值。从参数buf中得到要设置的值。
IPC_RMID 标志内存段为移走。
命令IPC_RMID并不真正从系统内核中移走共享的内存段,而是把内存段标记为可移除。进程调用系统调用shmdt()脱离一个共享的内存段。




[目录]

--------------------------------------------------------------------------------


shmdt()

系统调用:shmdt();
调用原型:int shmdt ( char *shmaddr );
返回值:如果失败,则返回- 1:errno = EINVAL (无效的连接地址)
当一个进程不在需要共享的内存段时,它将会把内存段从其地址空间中脱离。但这不等于将共享内存段从系统内核中移走。当进程脱离成功后,数据结构shmid_ds中元素shm_nattch将减1。当此数值减为0以后,系统内核将物理上把内存段从系统内核中移走。




[目录]

--------------------------------------------------------------------------------


线程

线程通常叫做轻型的进程。虽然这个叫法有些简单化,但这有利于了解线程的概念。因为线程和进程比起来很小,所以相对来说,线程花费更少的CPU资源。进程往往需要它们自己的资源,但线程之间可以共享资源,所以线程更加节省内存。Mach的线程使得程序员可以编写并发运行的程序,而这些程序既可以运行在单处理器的机器上,也可以运行在多处理器的机器中。另外,在单处理器环境中,当应用程序执行容易引起阻塞和延迟的操作时,线程可以提高效率。
用子函数pthread_create创建一个新的线程。它有四个参数:一个用来保存线程的线程变量、一个线程属性、当线程执行时要调用的函数和一个此函数的参数。例如:
pthread_ta_thread ;
pthread_attr_ta_thread_attribute ;
void thread_function(void *argument);
char * some_argument;
pthread_create( &a_thread, a_thread_attribute, (void *)&thread_function,
(void *) &some_argument);
线程属性只指明了需要使用的最小的堆栈大小。在以后的程序中,线程的属性可以指定其他的值,但现在大部分的程序可以使用缺省值。不像UNIX系统中使用 fork系统调用创建的进程,它们和它们的父进程使用同一个执行点,线程使用在pthread_create中的参数指明要开始执行的函数。

现在我们可以编制第一个程序了。我们编制一个多线程的应用程序,在标准输出中打印“Hello Wo r l d”。首先我们需要两个线程变量,一个新线程开始执行时可以调用的函数。我们还需要指明每一个线程应该打印的信息。一个做法是把要打印的字符串分开,给每一个线程一个字符串作为开始的参数。请看下面的代码:
void print_message_function( void *ptr );
main( )
{
pthread_t thread1, thread2;
char *message1 = "Hello";
char *message2 = "Wo r l d " ;
pthread_create( &thread1, pthread_attr_default,
(void*)&print_message_function, (void*) message1);
pthread_create(&thread2, pthread_attr_default,
(void*)&print_message_function, (void*) message2);
exit( 0 ) ;
}
void print_message_function( void *ptr )
{
char *message;
message = (char *) ptr;
printf("%s ", message);
}

程序通过调用pthread_create创建第一个线程,并将“Hello”作为它的启动参数。第二个线程的参数是“World”。当第一个线程开始执行时,它使用参数“Hello”执行函数print_message_function。它在标准输出中打印“Hello”,然后结束对函数的调用。线程当离开它的初始化函数时就将终止,所以第一个线程在打印完“Hello”后终止。当第二个线程执行时,它打印“World”然后终止。但这个程序有两个主要的缺陷。
首先也是最重要的是线程是同时执行的。这样就无法保证第一个线程先执行打印语句。所以你很可能在屏幕上看到“World Hello”,而不是“Hello World”。请注意对exit的调用是父线程在主程序中使用的。这样,如果父线程在两个子线程调用打印语句之前调用exit,那么将不会有任何的打印输出。这是因为exit函数将会退出进程,同时释放任务,所以结束了所有的线程。任何线程(不论是父线程或者子线程)调用exit 都会终止所有其他线程。如果希望线程分别终止,可以使用pthread_exit函数。
我们可以使用一个办法弥补此缺陷。我们可以在父线程中插入一个延迟程序,给子线程足够的时间完成打印的调用。同样,在调用第二个之前也插入一个延迟程序保证第一个线程在第二个线程执行之前完成任务。

void print_message_function( void *ptr );
main ( )
{
pthread_t thread1, thread2;
char *message1 = "Hello”;
char *message2 = "Wo r l d " ;
pthread_create( &thread1, pthread_attr_default,
(void *) &print_message_function, (void *) message1);
sleep (10) ;
pthread_create(&thread2, pthread_attr_default,
(void *) &print_message_function, (void *) message2);
sleep ( 10 ) ;
exit (0) ;
}
void print_message_function( void *ptr )
{
char *message;
message = (char *) ptr;
printf("%s", message);
pthread_exit(0) ;
}

这样是否达到了我们的要求了呢?不尽如此,因为依靠时间的延迟执行同步是不可靠的。这里遇到的情形和一个分布程序和共享资源的情形一样。共享的资源是标准的输出设备,分布计算的程序是三个线程。
其实这里还有另外一个错误。函数sleep和函数e x i t一样和进程有关。当线程调用sleep时,整个的进程都处于睡眠状态,也就是说,所有的三个线程都进入睡眠状态。这样我们实际上没有解决任何的问题。希望使一个线程睡眠的函数是pthread_delay_np。例如让一个线程睡眠2秒钟,用如下程序:

struct timespec delay;
delay.tv_sec = 2;
delay.tv_nsec = 0;
pthread_delay_np( &delay );
}


[目录]
线程同步

POSIX提供两种线程同步的方法,mutex和条件变量。mutex是一种简单的加锁的方法来控制对共享资源的存取。我们可以创建一个读/写程序,它们共用一个共享缓冲区,使用mutex来控制对缓冲区的存取。
void reader_function(void);
void writer_function(void);
char buf f e r ;
int buffer_has_item = 0;
pthread_mutex_t mutex;
struct timespec delay;
main( )
{
pthread_t reader;
delay.tv_sec = 2;
delay.tv_nsec = 0;
pthread_mutex_init(&mutex, pthread_mutexattr_default);
pthread_create( &reader, pthread_attr_default, (void*)&reader_function,
N U L L ) ;
writer_function( )
void writer_function(void)
{
while( 1 )
{
pthread_mutex_lock( &mutex );
if ( buffer_has_item == 0 )
{
buffer = make_new_item();
buffer_has_item = 1;
}
pthread_mutex_unlock( &mutex );
pthread_delay_np( &delay );
}
}
void reader_function(void)
{
while( 1 )
{
pthread_mutex_lock( &mutex );
if ( buffer_has_item == 1)
{
consume_item( buffer );
buffer_has_item = 0;
}
pthread_mutex_unlock( &mutex );
pthread_delay_np( &delay );
}
}
在上面的程序中,我们假定缓冲区只能保存一条信息,这样缓冲区只有两个状态,有一条信息或者没有信息。使用延迟是为了避免一个线程永远占有mutex。
但mutex的缺点在于它只有两个状态,锁定和非锁定。POSIX的条件变量通过允许线程阻塞和等待另一个线程的信号方法,从而弥补了mutex的不足。当接受到一个信号时,阻塞线程将会被唤起,并试图获得相关的mutex的锁。



[目录]

--------------------------------------------------------------------------------


使用信号量协调程序

我们可以使用信号量重新看一下上面的读/写程序。涉及信号量的操作是semaphore_up、semaphore_down、 semaphore_init、semaphore_destroy和semaphore_decrement。所有这些操作都只有一个参数,一个指向信号量目标的指针。
void reader_function(void);
void writer_function(void);
char buffer ;
Semaphore writers_turn;
Semaphore readers_turn;
main( )
{
pthread_t reader;
semaphore_init( &readers_turn );
semaphore_init( &writers_turn );
/* writer must go first */
semaphore_down( &readers_turn );
pthread_create( &reader, pthread_attr_default,
(void *)&reader_function, NULL);
w r i t e r _ f u n c t i o n ( ) ;
}
void writer_function(void)
{
w h i l e ( 1 )
{
semaphore_down( &writers_turn );
b u ffer = make_new_item();
semaphore_up( &readers_turn );
}
}
void reader_function(void)
{
w h i l e ( 1 )
{
semaphore_down( &readers_turn );
consume_item( buffer );
semaphore_up( &writers_turn );
}
}
这个例子也没有完全地利用一般信号量的所有函数。我们可以使用信号量重新编写“Hello world” 的程序:
void print_message_function( void *ptr );
Semaphore child_counter;
Semaphore worlds_turn;
main( )
{
pthread_t thread1, thread2;
char *message1 = "Hello";
char *message2 = "Wo r l d " ;
semaphore_init( &child_counter );
semaphore_init( &worlds_turn );
semaphore_down( &worlds_turn ); /* world goes second */
semaphore_decrement( &child_counter ); /* value now 0 */
semaphore_decrement( &child_counter ); /* value now -1 */
/*
* child_counter now must be up-ed 2 times for a thread blocked on it
* to be released
*
* /
pthread_create( &thread1, pthread_attr_default,
(void *) &print_message_function, (void *) message1);
semaphore_down( &worlds_turn );
pthread_create(&thread2, pthread_attr_default,
(void *) &print_message_function, (void *) message2);
semaphore_down( &child_counter );
/* not really necessary to destroy since we are exiting anyway */
semaphore_destroy ( &child_counter );
semaphore_destroy ( &worlds_turn );
e x i t ( 0 ) ;
}
void print_message_function( void *ptr )
{
char *message;
message = (char *) ptr;
printf("%s ", message);
fflush(stdout);
semaphore_up( &worlds_turn );
semaphore_up( &child_counter );
p t h r e a d _ e x i t ( 0 ) ;
}
信号量c h i l d _ c o u n t e r用来强迫父线程阻塞,直到两个子线程执行完p r i n t f语句和其后的semaphore_up( &child_counter )语句才继续执行。
Semaphore.h

#ifndef SEMAPHORES
#define SEMAPHORES
#include
#include
typedef struct Semaphore
{
int v;
pthread_mutex_t mutex;
pthread_cond_t cond;
}
S e m a p h o r e ;
int semaphore_down (Semaphore * s);
int semaphore_decrement (Semaphore * s);
int semaphore_up (Semaphore * s);
void semaphore_destroy (Semaphore * s);
void semaphore_init (Semaphore * s);
int semaphore_value (Semaphore * s);
int tw_pthread_cond_signal (pthread_cond_t * c);
int tw_pthread_cond_wait (pthread_cond_t * c, pthread_mutex_t * m);
int tw_pthread_mutex_unlock (pthread_mutex_t * m);
int tw_pthread_mutex_lock (pthread_mutex_t * m);
void do_error (char *msg);
# e n d i f

Semaphore.c

#include "semaphore.h"
/ *
* function must be called prior to semaphore use.
*
* /
v o i d
semaphore_init (Semaphore * s)
{
s->v = 1;
if (pthread_mutex_init (&(s->mutex), pthread_mutexattr_default) == -1)
do_error ("Error setting up semaphore mutex");
if (pthread_cond_init (&(s->cond), pthread_condattr_default) == -1)
do_error ("Error setting up semaphore condition signal");
* function should be called when there is no longer a need for
* the semaphore.
*
* /
v o i d
semaphore_destroy (Semaphore * s)
{
if (pthread_mutex_destroy (&(s->mutex)) == -1)
do_error ("Error destroying semaphore mutex");
if (pthread_cond_destroy (&(s->cond)) == -1)
do_error ("Error destroying semaphore condition signal");
}
/ *
* function increments the semaphore and signals any threads that
* are blocked waiting a change in the semaphore.
*
* /
i n t
semaphore_up (Semaphore * s)
{
int value_after_op;
tw_pthread_mutex_lock (&(s->mutex));
( s - > v ) + + ;
value_after_op = s->v;
tw_pthread_mutex_unlock (&(s->mutex));
tw_pthread_cond_signal (&(s->cond));
return (value_after_op);
}
/ *
* function decrements the semaphore and blocks if the semaphore is
* <= 0 until another thread signals a change.
*
* /
i n t
semaphore_down (Semaphore * s)
{
int value_after_op;
tw_pthread_mutex_lock (&(s->mutex));
while (s->v <= 0)
{
tw_pthread_cond_wait (&(s->cond), &(s->mutex));
}
( s - > v ) - - ;
value_after_op = s->v;
tw_pthread_mutex_unlock (&(s->mutex));
return (value_after_op);
}
/ *
* function does NOT block but simply decrements the semaphore.
* should not be used instead of down -- only for programs where
* multiple threads must up on a semaphore before another thread
* can go down, i.e., allows programmer to set the semaphore to
* a negative value prior to using it for synchronization.
*
* /
i n t
semaphore_decrement (Semaphore * s)
{
int value_after_op;
tw_pthread_mutex_lock (&(s->mutex));
s - > v - - ;
value_after_op = s->v;
tw_pthread_mutex_unlock (&(s->mutex));
return (value_after_op);
}
/ *
* function returns the value of the semaphore at the time the
* critical section is accessed. obviously the value is not guarenteed
* after the function unlocks the critical section. provided only
* for casual debugging, a better approach is for the programmar to
* protect one semaphore with another and then check its value.
* an alternative is to simply record the value returned by semaphore_up
* or semaphore_down.
*
* /
i n t
semaphore_value (Semaphore * s)
{
/* not for sync */
int value_after_op;
tw_pthread_mutex_lock (&(s->mutex));
value_after_op = s->v;
tw_pthread_mutex_unlock (&(s->mutex));
return (value_after_op);
}
/* -------------------------------------------------------------------- */
/* The following functions replace standard library functions in that */
/* they exit on any error returned from the system calls. Saves us */
/* from having to check each and every call above. */
/* -------------------------------------------------------------------- */
i n t
tw_pthread_mutex_unlock (pthread_mutex_t * m)
{
int return_value;
if ((return_value = pthread_mutex_unlock (m)) == -1)
do_error ("pthread_mutex_unlock");
return (return_value);
}
i n t
tw_pthread_mutex_lock (pthread_mutex_t * m)
{
int return_value;
if ((return_value = pthread_mutex_lock (m)) == -1)
do_error ("pthread_mutex_lock");
return (return_value);
}
i n t
tw_pthread_cond_wait (pthread_cond_t * c, pthread_mutex_t * m)
{
int return_value;
if ((return_value = pthread_cond_wait (c, m)) == -1)
do_error ("pthread_cond_wait");
return (return_value);
}
i n t
tw_pthread_cond_signal (pthread_cond_t * c)
{
int return_value;
if ((return_value = pthread_cond_signal (c)) == -1)
do_error ("pthread_cond_signal");
return (return_value);
}
/ *
* function just prints an error message and exits
*
* /
v o i d
do_error (char *msg)
{
perror (msg);
exit (1);
}




[目录]

--------------------------------------------------------------------------------


代码例子


[目录]
newthread

/***********************************************************************
Case study source code from the book `The Linux A to Z'
by Phil Cornes. Published by Prentice Hall, 1996.
Copyright (C) 1996 Phil Cornes
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/

new_thread(int (*start_addr)(void), int stack_size)
{
struct context *ptr;
int esp;

/* 1 */
if (!(ptr = (struct context *)malloc(sizeof(struct context))))
return 0;

/* 2 */
if (!(ptr->stack = (char *)malloc(stack_size)))
return 0;

/* 3 */
esp = (int)(ptr->stack+(stack_size-4));
*(int *)esp = (int)exit_thread;
*(int *)(esp-4) = (int)start_addr;
*(int *)(esp-8) = esp-4;
ptr->ebp = esp-8;

/* 4 */
if (thread_count++)
{
/* 5 */
ptr->next = current->next;
ptr->prev = current;
current->next->prev = ptr;
current->next = ptr;
}
else
{
/* 6 */
ptr->next = ptr;
ptr->prev = ptr;
current = ptr;
switch_context(&main_thread, current);
}

return 1;
}




[目录]

--------------------------------------------------------------------------------


exitthead

/***********************************************************************
Case study source code from the book `The Linux A to Z'
by Phil Cornes. Published by Prentice Hall, 1996.
Copyright (C) 1996 Phil Cornes
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/

static exit_thread(void)
{
struct context dump, *ptr;

/* 1 */
if (--thread_count)
{
/* 2 */
ptr = current;
current->prev->next = current->next;
current->next->prev = current->prev;
current = current->next;
free(ptr->stack);
free(ptr);
switch_context(&dump, current);
}
else
{
/* 3 */
free(current->stack);
free(current);
switch_context(&dump, &main_thread);
}
}




[目录]

--------------------------------------------------------------------------------


getchannel

/***********************************************************************
Case study source code from the book `The Linux A to Z'
by Phil Cornes. Published by Prentice Hall, 1996.
Copyright (C) 1996 Phil Cornes
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/

get_channel(int number)
{
struct channel *ptr;

/* 1 */
for (ptr = channel_list; ptr; ptr = ptr->link)
if (ptr->number==number)
return((int)ptr);

/* 2 */
if (!(ptr = (struct channel *)malloc(sizeof(struct channel))))
return 0;

/* 3 */
ptr->number = number;
ptr->message_list = 0;
ptr->message_tail = 0;
ptr->sr_flag = 0;
ptr->link = channel_list;
channel_list = ptr;
return((int)ptr);
}




[目录]

--------------------------------------------------------------------------------


def

/***********************************************************************
Case study source code from the book `The Linux A to Z'
by Phil Cornes. Published by Prentice Hall, 1996.
Copyright (C) 1996 Phil Cornes
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/

#include <string.h>

struct context /* One structure for each thread */
{
int ebp; /* Base pointer (stack frame pointer) store */
char *stack; /* Pointer to memory block for thread stack */
struct context *next; /* Round robin circular list pointer */
struct context *prev; /* Round robin circular list pointer */
};

struct channel /* One structure for each communication channel */
{
int number; /* Channel number */
int sr_flag; /* 0=no queue, 1=send queued, 2=receive queued */
struct channel *link; /* Link to next channel in list */
struct message *message_list; /* Head of message queue */
struct message *message_tail; /* Tail of message queue */
};

struct message /* One structure for each pending send/receive */
{
int size; /* Size of message in bytes */
char *addr; /* Pointer to start of message */
struct message *link; /* Link to next message in queue */
struct context *thread; /* Which thread blocks on this struct */
};

static struct context main_thread; /* Storage for main() details */
static struct context *current; /* Currently executing thread */
static int thread_count = 0; /* Number of threads to schedule */
static struct channel *channel_list = 0; /* List of all channels */

static int switch_context(struct context *, struct context *);
static int exit_thread(void);
static int rendezvous(struct channel *, char *, int, int);




[目录]
release

/***********************************************************************
Case study source code from the book `The Linux A to Z'
by Phil Cornes. Published by Prentice Hall, 1996.
Copyright (C) 1996 Phil Cornes
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/

release(void)
{
/* 1 */
if (thread_count<=1)
return 0;

/* 2 */
current = current->next;
switch_context(current->prev, current);
return 1;
}


static switch_context(struct context *from, struct context *to)
{
/* 3 */
__asm__
(
"movl 8(%ebp),%eax\n\t"
"movl %ebp,(%eax)\n\t"
"movl 12(%ebp),%eax\n\t"
"movl (%eax),%ebp\n\t"
);
}




[目录]

--------------------------------------------------------------------------------


redezvous

/***********************************************************************
Case study source code from the book `The Linux A to Z'
by Phil Cornes. Published by Prentice Hall, 1996.
Copyright (C) 1996 Phil Cornes
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/

send(int chan, char *addr, int size)
{
/* 1 */
return(rendezvous((struct channel *)chan, addr, size, 1));
}


receive(int chan, char *addr, int size)
{
/* 2 */
return(rendezvous((struct channel *)chan, addr, size, 2));
}


static int rendezvous(struct channel *chan, char *addr,
int size, int sr_flag)
{
struct message *ptr;
int nbytes;

/* 3 */
if (sr_flag==3-chan->sr_flag)
{
/* 4 */
ptr = chan->message_list;
chan->message_list = ptr->link;
ptr->thread->next = current->next;
ptr->thread->prev = current;
current->next->prev = ptr->thread;
current->next = ptr->thread;
++thread_count;
/* 5 */
nbytes = (size<ptr->size)?size:ptr->size;
ptr->size = nbytes;

/* 6 */
if (sr_flag==1)
memcpy(ptr->addr, addr, nbytes);
else
memcpy(addr, ptr->addr, nbytes);

/* 7 */
if (!chan->message_list)
chan->sr_flag = 0;

return(nbytes);
}
else
{
/* 8 */
ptr = (struct message *)malloc(sizeof(struct message));

if (!chan->message_list)
chan->message_list = ptr;
else
chan->message_tail->link = ptr;

chan->message_tail = ptr;
ptr->link = 0;
ptr->size = size;
ptr->addr = addr;
chan->sr_flag = sr_flag;
ptr->thread = current;
current->prev->next = current->next;
current->next->prev = current->prev;

/* 9 */
if (--thread_count)
{
current = current->next;
switch_context(ptr->thread, current);
}
else
switch_context(ptr->thread, &main_thread);

/* 10 */
nbytes = ptr->size;
free(ptr);
return(nbytes);
}
}




[目录]

--------------------------------------------------------------------------------


unbouded

/***********************************************************************
Case study source code from the book `The Linux A to Z'
by Phil Cornes. Published by Prentice Hall, 1996.
Copyright (C) 1996 Phil Cornes
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/

int buffer(void);
int start(void);

int ch_desc1, ch_desc2;

main(void)
{
ch_desc1 = get_channel(1);
ch_desc2 = get_channel(2);
new_thread(start, 1024);
}

start(void)
{
int i, n;

new_thread(buffer, 1024);

for (i = 0; i<10, ++i)
{
send(ch_desc1, &i, sizeof(int));
release();
}

for (i = 0; i<10, ++i)
{
receive(ch_desc2, &n, sizeof(int));
printf("i=%d n=%d\n", i, n);
release();
}
}

buffer(void)
{
int i;

receive(ch_desc1, &i, sizeof(int));
new_thread(buffer, 1024);
send(ch_desc2, &i, sizeof(int));
}




[目录]