程序员学数据结构

978-7-115-48280-8
作者: [美]威廉·史密斯(William Smith)
译者: 崔敖
编辑: 武晓燕

图书目录:

详情

本书浅入深地用多种语言详细讲解了计算机存储使用的多种数据结构。本书首先讲解了初级的数据结构如表、栈、队列和堆等,以及它们的详细分析及其典型的应用程序等。除此之外,本书还讨论了更高级的数据结构,如泛型集合、排序、搜索和递归等,以及如何在日常应用中使用这些数据结构。本书通过实际案例向读者介绍了多种数据结构及其它们的潜在应用,教会读者如何分析问题、选择合适的数据结构解决方案等。

图书摘要

版权信息

书名:程序员学数据结构

ISBN:978-7-115-48280-8

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。


著    [美] 威廉•史密斯(William Smith)

译    崔 敖

责任编辑 武晓燕

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


Copyright ©2017 Packt Publishing. First published in the English language under the title Everyday Data Structures.

All rights reserved.

本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。

版权所有,侵权必究。


本书由浅入深地详细讲解了计算机存储使用的多种数据结构。本书首先讲解了初级的数据结构(如表、栈、队列和堆等),具体包括它们的工作原理、功能实现以及典型的应用程序等;然后讨论了更高级的数据结构,如泛型集合、排序、搜索和递归等;最后介绍了如何在日常应用中使用这些数据结构。

本书通过实际案例向读者介绍了多种数据结构及其潜在应用,教会读者如何分析问题、选择合适的数据结构解决方案等。本书的一大特色是使用多种语言(C#、Java、Objective-C和Swift)进行讲述。

本书适合初学编程或自学编程的人员以及计算机相关专业的教师和学生阅读,也非常适合程序员参考。


William Smith早年获得了环境科学与商务管理学位,在环境领域从事了数年的专业工作。他的软件开发经历始于1988年,并在从事环境领域工作时,始终将编程作为他的兴趣爱好,不断进行软件开发。后来他进入了马里兰大学深造,并获得了计算机科学学位。

William现在是一名独立软件开发工程师和专业技术图书的作者。他成立了Appsmiths公司,该公司的主要业务是软件开发和咨询,致力于使用原生工具和跨平台工具(如Xamarin和Monogame)来进行移动应用和游戏开发。

William与他的夫人和孩子一起居住在西佛吉尼亚州的乡村,全家享受着打猎、钓鱼和露营带给他们的乐趣。


Aditya Abhay Halabe是Springer Natrue科技部门的一名全栈Web应用工程师。他的编程经验非常丰富,精通Scala、Java、Graph等语言,主要进行多种框架下的文档存储数据库工作和微型Web服务开发。他热衷于开发工作,并乐于接受技术上的新挑战和新职责。在这之前,Aditya还作为顾问和开发工程师先后供职于Oracle和John Deere Ltd。


作为软件开发人员,在面对全新的任务和挑战时,我们常常会将这些问题分解为自己所熟知的各类解决方案和代码片段,并根据客户需求和任务截止日期(或称为发薪日),选出最快的方案进行开发。但是,这样做只是单纯地完成了工作要求,有时对于学到更多的开发技巧和理念从而成为一名更优秀、更高效的开发者的帮助并没有想象中的那么大。

本书涵盖了数据类型和数据结构的相关知识,能够帮助编程新手、胸怀抱负的开发人员或者是有一定经验却疲于奔命的程序员理清上述领域的基础概念。为此,本书会从常用的数据类型和数据结构开始,对它们的创建方式、工作原理、功能实现以及日常应用的适用范围等话题展开详细的介绍。通过本书,读者不仅能掌握更多的基础知识、编程技巧和开发能力,还能学到新的开发理念,从而进一步利用好这些基本的数据结构。

第1章“数据类型:基本的数据结构”概述了构成数据结构的基本数据类型。本章对基本数据类型做了快速回顾,某些读者甚至都已熟知了其中所讨论的部分内容。读者需要特别注意这些数据类型所适用的典型应用、最佳实现以及在不同开发平台上它们之间的区别。

第2章“数组:基本数据集”介绍了数组。本章将会对数组这种数据结构的具体细节、典型应用和它在不同开发语言中的区别展开详细讨论。本章是重要的基础性章节,后续讨论到的很多数据结构都是基于数组构建的。

第3章“列表:线性数据集”涵盖了列表数据结构的具体细节,包含列表的常用操作、典型应用以及它在不同开发语言中的区别。

第4章“栈:后入先出的数据集”介绍了栈这种数据结构。读者将会从本章学习到栈的具体细节,其中包括栈的常用操作、典型应用以及它在不同开发语言中的区别。

第5章“队列:先入先出的数据集”介绍了队列数据结构的具体细节,包括队列的常用操作、典型应用以及它在不同开发语言中的区别。

第6章“字典:关键字数据集”深入探讨了字典数据结构的具体细节,包括字典最常用的操作、典型应用以及它在不同开发语言中的区别。

第7章“集合:不包含重复项的数据集”讨论了集合数据结构的具体细节,其中包括集合论的基础知识、集合的常用操作、典型应用以及它在不同开发语言中的区别。

第8章“结构体:更为复杂的数据类型”探索了结构体的具体细节,包括结构体的常用操作、典型应用以及它在不同开发语言中的区别。

第9章“树:非线性数据结构”介绍了抽象树结构,尤其是二叉树的具体细节,其中包括树结构的常用操作、典型应用以及它在不同开发语言中的区别。

第10章“堆:有序树”深入探讨了堆数据结构的具体细节,包括堆的常用操作、典型应用以及它在不同开发语言中的区别。

第11章“图:互相连接的对象”介绍了图这种数据结构的具体细节,包括图的常用操作、典型应用和它在不同开发语言中的区别。

第12章“排序:为混乱带来秩序”是本书的高级章节,引出了排序的基本概念,重点介绍了一些常用的排序算法,其中还包括了这些排序算法的复杂度、典型应用以及它们在不同开发语言中的区别。

第13章“查找:找你所需”同样是本书的高级章节,引出了在特定数据结构上进行查找操作的概念,重点介绍了一些常用的查找算法,其中还包括了这些查找算法的复杂度、典型应用以及它们在不同开发语言中的区别。

本书为使用Mac、PC甚至是Linux计算机的读者提供了丰富的代码示例。为了充分理解本书中的内容,读者需要具备一台现代计算机,以及一个在该计算机上正常运行的开发环境,如Visual Studio、XCode、Eclipse或NetBeans等,以便运行这些示例代码。

本书能够帮助读者提高他们在数据结构相关领域的编程知识和技巧。具体来说,本书的目标读者是初学编程或自学编程的人员,以及那些编程经验不满4年的开发人员。本书主要通过移动应用开发中最常用的4种编程语言来对书中的内容进行讲解,因此目标读者还包含那些对移动应用开发感兴趣的编程人员。本书的读者应具有基本的编程概念,能够创建控制台应用程序,并且能使用相关的集成开发环境(IDE)。

本书的正文部分会根据内容使用不同的格式加以区分。以下是这些格式的示例和它们所代表的意义。

本书中的每章都会包含对应的案例学习或相似的代码示例,用以详细介绍特定数据结构的使用。因此,本书会含有很多示例代码。

示例代码段会以下列格式印刷:

public boolean isEmpty()
{
    return this._commandStack.empty();
}

需要重点关注的代码会用粗体进行标注:

func canAddUser(user: EDSUser) -> Bool
{
 if (_users.contains(user))
 {
 return false;
 } else {
 return true;
}

新名词关键词会以粗体印刷。可能出现在菜单或对话框中的语句会用以下字体表示,如isFull()等。

本书还会对算法涉及的数学概念进行讨论,会使用大O记号来标注所有的算法复杂度,如“然而,这只能算是一个很小的心理安慰,因为整个选择排序算法的复杂度为O(n2)”中所示。


需要注意的内容将以这种格式呈现。



提示和技巧将以这种格式呈现。

我们欢迎您对本书的反馈。若需对本书提出任何意见,请将您的反馈用电子邮件发送至 feedback@packtpub.com,并在邮件标题中标明本书的书名。我们将根据您的反馈进行评估。若您希望成为一名作者,愿意在您精通的领域发表著作,可以访问Packt官网获得更多信息。

我们为每一位拥有Packt 图书的读者都提供了相应服务。

您可访问Packt官网下载本书所有的示例代码文件。若您在别的地方购买了本书,可访问Packt官网并进行注册,我们会通过电子邮件将这些示例代码文件发送给您。

您也可通过以下步骤下载本书的示例代码文件:

1.在我们的网站上使用电子邮件地址注册或登录您的账户;

2.将鼠标指针移动至网页顶端的SUPPORT标签上;

3.单击Code Downloads & Errata

4.在Search框中输入本书的书名;

5.选出您所查找的书目;

6.从下拉菜单中选择您在何处购买到本书的;

7.单击Code Download

下载到了文件后,请您确保拥有以下解压缩软件的最新版本,以便文件得到正确解压:

本书的示例代码文件也托管在GitHub中,您也可在异步社区(www.epubit.com)上下载。我们还在GitHub中托管了其他大量的图书和视频,欢迎查阅!

我们使用了各种手段,尽可能地保证本书内容的正确性,但事无绝对,书中可能还存在未发现的错误。若您发现书中内容或代码存在错误之处,请及时向我们反馈,我们会非常感谢您的帮助。您可访问Packt官网选中出错的图书,单击Errata Submission Form链接,输入错误的详细内容,来向我们报告这些错误。我们会对您提交的内容进行核实,若属实,我们进行对应的勘误,并将该内容添加至对应图书的勘误表中。

若要查看之前提交的勘误内容,可访问Packt官网在搜索框中输入对应的书名进行查看,相应的内容会出现在Errata中。

互联网上的盗版问题是所有媒体都正面临的严峻问题。Packt公司对待版权保护和授权工作的态度非常严肃。若您在互联网上遇到了我公司所有内容的非法复制品,无论该复制品是以何种方式进行呈现,我们都希望您能立即向我们提供展示该复制品的网站地址和网站名称,以便我们进行补救。

可将涉嫌盗版的材料通过 copyright@packtpub.com发送给我们。

我们非常感谢您对作者和我们内容保护工作所提供的支持。

若您对本书有任何疑问,可通过 questions@packtpub.com 与我们取得联系,我们将尽可能地帮助您解决问题。


本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。

本书提供如下资源:

要获得以上配套资源,请在异步社区本书页面中点击,跳转到下载界面,按提示进行操作即可。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。

提交勘误

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,点击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

与我们联系

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。

如果您是学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

关于异步社区和异步图书

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


将数据类型称作基本的数据结构可能有些用词不当,但开发人员往往使用这些数据类型来构建他们自己的类和数据集,因此从他们的角度思考的话,这样的称呼也未尝不可。所以,在我们进一步学习数据结构之前,最好先快速地回顾一下数据类型,毕竟这些数据类型是本书内容的基础。本章旨在从全局角度回顾那些最常用和最重要的基础数据类型。如果你已经对这些基础概念有了较深刻的理解,可视情况略读或跳过本章。

本章将涵盖以下主要内容:

C#、Java、Objective-C和Swift这4种语言中全部数值数据类型的详细说明都可以再写一本书了。这里,我们只回顾每种语言中最常用的数值数据类型标识符。评价这些数据类型最简单的方法是基于其实际数据大小用每个语言分别举例,并在同一个框架内来分析讨论。


看起来一样,实际却不一样!

当在多个移动平台上开发应用时,应当注意到不同的语言可能会共用同一个/套数据类型标识符或关键字,但从底层来看,这些标识符并不一定等价。同样地,同一种数据类型在不同的语言中也可能会有不同的标识符。以16位无符号整型(16-bit unsigned integer)为例,在Objective-C中它被称作unsigned short类型。但在C#或Swift里,却分别用ushort类型和UInt16类型来表示。Java规定16位无符号整型只能用作char类型,尽管这个类型通常不用于数值型数据。以上每一种数据类型都表示一个16位无符号整型,只是名称有所不同。这貌似问题不大,但当你分别使用每个平台的原生语言为多个设备进行应用开发时,为保证一致性,需注意到这种差别。否则,将会带来不同平台上出现特定错误/漏洞的风险,这将是非常难以检测和判断的。

整型数据类型的定义为表示有符号(负值、零或正值)或无符号(零或正值)的整数。对于整型,每种语言都有其特定的标识符和关键字,因此按照存储长度来思考最为简便。为达到我们的目的,我们只讨论表示8、16、32和64位存储对象的整型。

8位数据类型,或统称为字节byte),是我们探讨的最小的数据类型。如果你复习过二进制数学,你会知道1个8位的内存块可表示28或256个值。有符号字节的可取值范围为−128~127或−27~27−1。无符号字节的可取值范围为0~255或0~28−1。

除了一些特殊情况,16位数据类型通常称为短整型short)。这种数据类型可表示216个值。有符号短整型的可取值范围为−215~215−1。无符号短整型的可取值范围为0~216−1。

32位数据类型一般被认为是整型,有些时候也会被认为是长整型long)。整型可表示232个值。有符号整型的可取值范围为−231~231−1。无符号整型的可取值范围为0~232−1。

最后,64位数据类型一般被认为是长整型,而Objective-C中规定其为双长整型long long)。长整型可以表示264个值。有符号长整型的可取值范围为−263~263−1。无符号长整型的可取值范围为0~264−1。


需注意的是,以上所提到的取值在我们所使用的4种语言中是一致的,但在其他的语言中可能会有细微的变化。熟悉所用语言数值标识符的详细细节总是一个好主意,尤其是当你需要用到标识符规定极值的情况下。

C#

C#用整型来表示整数类型。它提供bytesbyte两种机制来生成8位整型。这两种整型都能表示256个值,无符号的字节可取值范围为0~255。有符号的字节对负值提供支持,因此取值范围为−128~127,具体代码如下。

// C#
sbyte minSbyte = -128;
byte maxByte = 255;
Console.WriteLine("minSbyte: {0}", minSbyte);
Console.WriteLine("maxByte: {0}", maxByte);

/*
  输出结果
  minSbyte: -128
  maxByte: 255
*/

有趣的是,对于更长位的标识符C#改变了其命名模式。它用u作无符号(unsigned)的前缀,而不是使用sbytes作为有符号(signed)的前缀。因此C#分别使用shortushortintuintlongulong作为16位、32位以及64位的整型标识符,其代码实现如下。

short minShort = -32768;
ushort maxUShort = 65535;
Console.WriteLine("minShort: {0}", minShort);
Console.WriteLine("maxUShort: {0}", maxUShort);

int minInt = -2147483648;
uint maxUint = 4294967295;
Console.WriteLine("minInt: {0}", minInt);
Console.WriteLine("maxUint: {0}", maxUint);

long minLong = -9223372036854775808;
ulong maxUlong = 18446744073709551615;
Console.WriteLine("minLong: {0}", minLong);
Console.WriteLine("maxUlong: {0}", maxUlong);

/*
  输出结果
  minShort: -32768
  maxUShort: 65535
  minInt: -2147483648
  maxUint: 4294967295
  minLong: -9223372036854775808
  maxUlong: 18446744073709551615
*/

Java

Java将整型作为其原始数据类型的一部分。Java只提供一种建立8位类型的方式,即byte。这是一个有符号的数据类型,因此可表示−127~128的取值。Java还提供了名为Byte的包装类,其不仅包装了原始值,并对像“42”这些能够转换为数值的可解析字符串或文本提供了额外的构造函数支持。这种模式重复体现在16位、32位和64位类型中。

//Java
byte myByte = -128;
byte bigByte = 127;

Byte minByte = new Byte(myByte);
Byte maxByte = new Byte("128");
System.out.println(minByte);
System.out.println(bigByte);
System.out.println(maxByte);

/*
  输出结果
  -128
  127
  127
*/

Java和C#共用了所有整型的标识符,这意味着Java对8位、16位、32位和64位类型也提供了byteshortintlong标识符。Java中只有一个例外,即提供了16位无符号数据类型的标识符char。值得注意的是,char类型通常只用作分配ASCII字符,而不是实际的整数数值。

//Short Class
Short minShort = new Short(myShort);
Short maxShort = new Short("32767");
System.out.println(minShort);
System.out.println(bigShort);
System.out.println(maxShort);
int myInt = -2147483648;
int bigInt = 2147483647;

//Integer class
Integer minInt = new Integer(myInt);
Integer maxInt = new Integer("2147483647");
System.out.println(minInt);
System.out.println(bigInt);
System.out.println(maxInt);
long myLong = -9223372036854775808L;
long bigLong = 9223372036854775807L;

//Long class
Long minLong = new Long(myLong);
Long maxLong = new Long("9223372036854775807");
System.out.println(minLong);
System.out.println(bigLong);
System.out.println(maxLong);

/*
  输出结果
  -32768
  32767
  32767
  -2147483648
  2147483647
  2147483647
  -9223372036854775808
  9223372036854775807
  9223372036854775807
*/

在以上的代码中,须注意int类型和Integer类。不同于其他原始包装类,Integer并不和其支持的标识符共用名称。

此外,注意long类型和其分配的数值。在每个例子中,这些值都有后缀L。这是Java对long类型的要求,因为编译器将所有的数值文字默认翻译为32位整数。当需要明确说明字面数值是长于32位时,必须为其加上后缀L。不然的话,编译器可能会报错。然而,当给Long类型构造函数传递字符串值时,则不受这种限制:

Long maxLong = new Long("9223372036854775807");

Objective-C

对于8位数据,Objective-C提供了有符号和无符号两种格式的char类型。与其他语言相同,有符号的数据类型取值为−127~128,而无符号的类型取值为0~255。开发人员还可以选择使用Objective-C的定宽整型int8_tuint8_t。这种模式重复体现在16位、32位和64位类型中。最后,Objective-C还以NSNumber类的形式对每种整型提供了面向对象的包装类。


char或其他整型和其定宽整型有非常显著的区别。除了char类型总是为1字节长度以外,其他Objective-C中的整型长度会根据实现方式和底层架构的不同而改变。这是因为Objective-C是基于C语言的,而C语言被设计成能够在不同种类的底层架构上高效工作。尽管可以在运行和编译时就确定整型数据结构的确切长度,但在一般情况下,你只能确定的是short <= int <= long <= long long

这时定宽整型就派上用场了。在需要严格控制字节长度的情况下,(u)int<n>_t整型可以让你精确定义出8位、16位、32位或64位长度的整数。

//Objective-C
char number = -127;
unsigned char uNumber = 255;
NSLog(@"Signed char number: %hhd", number);
NSLog(@"Unsigned char uNumber: %hhu", uNumber);
//固定宽度
int8_t fixedNumber8 = -127;
uint8_t fixedUNumber8 = 255;
NSLog(@"fixedNumber8: %hhd", fixedNumber8);
NSLog(@"fixedUNumber8: %hhu", fixedUNumber8);

NSNumber *charNumber = [NSNumber numberWithChar:number];
NSLog(@"Char charNumber: %@", [charNumber stringValue]);

/*
  输出结果
  Signed char number: -127
  Unsigned char uNumber: 255
  fixedNumber8: -127
  fixedUNumber8: 255
  Char charNumber: -127
*/

从上面的例子可以看出,当在代码中使用char类型时,必须指定标识符unsigned,例如unsigned charsignedchar类型的默认标识符,可以省略,这也意味着char类型与signed char等价。Objective-C中的其他整型也遵循这种模式。

Objective-C中更大的整型包括用于16位的short类型,用于32位的int类型以及用于64位的long long类型。以上每种整型都依照(u)int<n>_t的模式有其定宽整型。NSNumber对每种整型都提供了支持方法。

//更大的Objective-C整型
short aShort = -32768;
unsigned short anUnsignedShort = 65535;
NSLog(@"Signed short aShort: %hd", aShort);
NSLog(@"Unsigned short anUnsignedShort: %hu", anUnsignedShort);

int16_t fixedNumber16 = -32768;
uint16_t fixedUNumber16 = 65535;
NSLog(@"fixedNumber16: %hd", fixedNumber16);
NSLog(@"fixedUNumber16: %hu", fixedUNumber16);

NSNumber *shortNumber = [NSNumber numberWithShort:aShort];
NSLog(@"Short shortNumber: %@", [shortNumber stringValue]);

int anInt = -2147483648;
unsigned int anUnsignedInt = 4294967295;
NSLog(@"Signed Int anInt: %d", anInt);
NSLog(@"Unsigned Int anUnsignedInt: %u", anUnsignedInt);

int32_t fixedNumber32 = -2147483648;
uint32_t fixedUNumber32 = 4294967295;
NSLog(@"fixedNumber32: %d", fixedNumber32);
NSLog(@"fixedUNumber32: %u", fixedUNumber32);

NSNumber *intNumber = [NSNumber numberWithInt:anInt];
NSLog(@"Int intNumber: %@", [intNumber stringValue]);

long long aLongLong = -9223372036854775808;
unsigned long long anUnsignedLongLong = 18446744073709551615;
NSLog(@"Signed long long aLongLong: %lld", aLongLong);
NSLog(@"Unsigned long long anUnsignedLongLong: %llu", 
anUnsignedLongLong);

int64_t fixedNumber64 = -9223372036854775808;
uint64_t fixedUNumber64 = 18446744073709551615;
NSLog(@"fixedNumber64: %lld", fixedNumber64);
NSLog(@"fixedUNumber64: %llu", fixedUNumber64);

NSNumber *longlongNumber = [NSNumber numberWithLongLong:aLongLong];
NSLog(@"Long long longlongNumber: %@", [longlongNumber stringValue]);

/*
  输出结果
  Signed short aShort: -32768
  Unsigned short anUnsignedShort: 65535
  fixedNumber16: -32768
  fixedUNumber16: 65535
  Short shortNumber: -32768
  Signed Int anInt: -2147483648
  Unsigned Int anUnsignedInt: 4294967295
  fixedNumber32: -2147483648
  fixedUNumber32: 4294967295
  Int intNumber: -2147483648
  Signed long long aLongLong: -9223372036854775808
  Unsigned long long anUnsignedLongLong: 18446744073709551615
  fixedNumber64: -9223372036854775808
  fixedUNumber64: 18446744073709551615
  Long long longlongNumber: -9223372036854775808
*/

Swift

Swift语言和其他语言类似,对于有符号和无符号的整数提供了各自的标识符,如Int8UInt8。依据标识符名称来确定可应用的数据类型,这样的方式适用于Swift的每种整型,也使得Swift也许会成为最简单的语言。

//Swift
var int8 : Int8 = -127
var uint8 : UInt8 = 255
print("int8: \(int8)")
print("uint8: \(uint8)")

/*
  输出结果
  int8: -127
  uint8: 255
*/

为清晰地展示声明过程,上面的例子使用:Int8:UInt8标识符明确地声明了数据类型。在Swift里,还可以不加这些标识符,让Swift在运行时动态地推断出数据类型。

//更大的Swift整型
var int16 : Int16 = -32768
var uint16 : UInt16 = 65535
print("int16: \(int16)")
print("uint16: \(uint16)")

var int32 : Int32 = -2147483648
var uint32 : UInt32 = 4294967295
print("int32: \(int32)")
print("uint32: \(uint32)")

var int64 : Int64 = -9223372036854775808
var uint64 : UInt64 = 18446744073709551615
print("int64: \(int64)")
print("uint64: \(uint64)")

/*
  输出结果
  int16: -32768
  uint16: 65535
  int32: -2147483648
  uint32: 4294967295
  int64: -9223372036854775808
  uint64: 18446744073709551615
*/

我为什么需要知道这些?你可能会问,我为什么需要知道数据类型的这些复杂细节?我难道不能只声明一个int对象或其他类似的东西后去写一些有趣的代码?现代计算机甚至是移动设备都能够提供近乎无穷的资源,所以这没什么大不了的,对吧?

事实并非如此。在你日常编程的经历中,大多数情况下随便使用哪一个数据类型可能都行。比如,遍历出任意一天西佛吉尼亚州全州车管部门签发的牌照列表,结果可能从几十个到上百个。你可以使用一个短整型变量或一个双长整型变量来控制for循环迭代。无论选用何种方式,这个循环为你的系统性能所带来的影响几乎可以忽略。

假设你在处理一组数据,这组数据中的每个离散结果都与16位类型匹配,而你习惯性地使用32位类型来处理,这会导致什么结果呢?这样做的结果会使处理这个数据集所需的内存空间翻倍。当离散结果只有100个或100 000个的时候,这样做可能并没什么不妥。但如果要处理的数据集很大,有百万个以及更多的离散结果的时候,这么做肯定会给系统性能带来非常大的影响。

单精度浮点single precision floating point类型通常称为单精度类型float),用32位浮点容器能够存储比整型更高精度的数值,通常有6~7位有效数字。多种语言使用float关键字/标识符来标记单精度浮点数值,本书所讨论的4种语言也是如此。

需要注意的是,由于浮点数值不能精确地表示以10为基的数字,因此其精度受限于归零误差。浮点类型的数值算法非常复杂,无论何时其中的细节都与大部分开发人员不太相关。然而,学习浮点类型可以加深对底层技术及每种语言实现细节的了解。


由于我并不是这方面的专家,因此只简单了解一下这些类型背后的科学原理,并不涉及具体的数学算法。我在本章末尾的附加资料中列出了这个领域专家们的一些研究成果,强烈建议你们学习。

C#

C#使用float关键字标识32位浮点值。C#中float类型精度为6位有效数字,近似取值范围从−3.4×1038~+3.4×1038

//C#
float piFloat = 3.14159265358979323846264338327f;
Console.WriteLine("piFloat: {0}", piFloat);

/*
  输出结果
  piFloat: 3.141593
*/

从上面的代码可以看出,使用float在赋值时有f作为后缀。这是因为C#和其他基于C的语言一样,在处理赋值语句右边的小数数字时,默认其为双精度型double,稍后讨论)。如果在赋值时不用fF后缀,而直接将一个双精度浮点的数值赋给单精度浮点类型,则会产生编译错误。

此外,注意到最后一位的归零误差。我们将30位有效数字的圆周率赋值给piFloat。但由于float只能保留6位有效数字,其后数字都会被约去。若直接对圆周率值保留6位有效数字,我们得到3.141592,但由于归零误差,浮点数的实际值为3.141593。

Java

与C#相同,Java使用float标识符确定浮点值。Java中float类型精度为6或7位有效数字,近似取值范围为−3.4×1038~+3.4×1038

//Java
float piFloat = 3.141592653589793238462643383279f;
System.out.println(piFloat);

/*
  输出结果
  3.1415927
*/

从上面的代码可以看出,浮点赋值操作有f后缀。这是因为Java和其他基于C的语言一样,在处理赋值语句右边的小数数字时,默认其为双精度型。如果在赋值时不加入fF后缀,而直接将一个双精度浮点的数值赋给单精度浮点类型,则会产生编译错误。

Objective-C

Objective-C使用float标识符确定浮点值。在Objective-C中,float类型精度为6位有效数字,近似取值范围从−3.4×1038~+3.4×1038

//Objective-C
float piFloat = 3.14159265358979323846264338327f;
NSLog(@"piFloat: %f", piFloat);

NSNumber *floatNumber = [NSNumber numberWithFloat:piFloat];
NSLog(@"floatNumber: %@", [floatNumber stringValue]);

/*
  输出结果
  piFloat: 3.141593
  floatNumber: 3.141593
*/

从上面的代码可以看出,浮点赋值操作有f后缀。这是因为Objective-C和其他基于C的语言一样,在处理赋值语句右边的小数数字时,默认其为双精度型。如果在赋值时不加入fF后缀,而直接将一个双精度浮点的数值赋给单精度浮点类型,则会产生编译错误。

此外,注意到最后一位的归零误差。我们将30位有效数字的圆周率赋值给piFloat。但由于float只能保留6位有效数字,其后数字都会被约去。若直接对圆周率值保留6位有效数字,我们得到3.141592,但由于归零误差,浮点数的实际值为3.141593。

Swift

Swift使用float标识符确定浮点值。在Swift中,float类型精度为6位有效数字,近似取值范围从−3.4×1038~+3.4×1038

//Swift
var floatValue : Float = 3.141592653589793238462643383279
print("floatValue: \(floatValue)")

/*
  输出结果
  floatValue: 3.141593
*/

从上面的代码可以看出,浮点赋值操作有f后缀。这是因为Swift和其他基于C的语言一样,在处理赋值语句右边的实数数字时,默认其为双精度型。如果在赋值时不加入fF后缀,而直接将一个双精度浮点的数值赋给单精度浮点类型,则会产生编译错误。

此外,注意到最后一位的归零误差。我们将30位有效数字的圆周率赋值给floatValue。但由于float只能保留6位有效数字,其后数字都会被约去。若直接对圆周率值保留6位有效数字,我们得到3.141 592,但由于归零误差,浮点数的实际值为3.141 593。

查看上面的代码,注意到最后一位的归零误差。我们将30位有效数字的圆周率赋值给piDouble。但double只能保留15位有效数字,其后数字都会被约去。若直接对圆周率值保留15位有效数字,我们得到3.141 592 653 589 793 2,但由于归零误差,浮点数的实际值为3.141 592 653 589 793。

双精度浮点double precision floating point类型通常称为双精度型double)。用64位浮点容器能够存储比整型更高精度的数值,该类型通常有15位有效数字。多种语言使用double关键字/标识符来标记双精度浮点数值,我们所讨论的4种语言也是如此。


在大多数情况下,无论选用float还是double都无关紧要,除非内存空间较为紧张,这时应该尽可能选择float。很多人认为在多数情况下floatdouble更高效,一般来说,也确实是这样。但在一些情况下,double会比float更高效。事实上,由于存在太多无法在这里详述的标准,每种类型的效率会因情况而异。因此,如果需要在特定应用中达到最高的效率,你需要仔细研究各种影响因素来选用最合适的类型。如果对效率并不是那么在意,那就任选一个合适的类型,接着干活。

C#

C#使用double关键字标识64位浮点数值。在C#中,double类型的精度为14或15位有效数字,近似取值范围从±5.0×10−324~±1.7×10308

//C#
double piDouble = 3.14159265358979323846264338327;
double wholeDouble = 3d;
Console.WriteLine("piDouble: {0}", piDouble);
Console.WriteLine("wholeDouble: {0}", wholeDouble);

/*
  输出结果
  piDouble: 3.14159265358979
  wholeDouble: 3
*/

从上面的代码可以看出,wholeDouble变量的赋值操作加了d后缀。这是因为C#和其他基于C的语言一样,在处理赋值语句右边的整数数字时,默认其为整型。如果在赋值时不加入dD后缀,而试图直接将一个整型数值赋给双精度型,则会收到编译错误。

此外,注意到最后一位的归零误差。我们将30位有效数字的圆周率赋值给piDouble。但double只能保留14位有效数字,其后数字都会被约去。若直接对圆周率值保留15位有效数字,我们得到3.141 592 653 589 793,但由于归零误差,浮点数的实际值为3.141 592 653 589 79。

Java

Java使用double关键字标识64位浮点数值。在Java中,double类型的精度为15或16位有效数字,近似取值范围为±4.9×10−324~±1.8×10308

double piDouble = 3.141592653589793238462643383279;
System.out.println(piDouble);

/*
  输出结果
  3.141592653589793
*/

Objective-C

Objective-C也使用double标识符来确定64位浮点数值。在Objective-C中,double类型的精度为15位有效数字,近似取值范围从2.3×10−308到1.7×10308。为进一步提高精确性,Objective-C还提供了一个更高精度版本的double类型,即长双精度型long double)。long double类型能够存储80位浮点数值,精度为19位有效数字,近似取值范围从3.4×10−4932~1.1×104932

//Objective-C
double piDouble = 3.14159265358979323846264338327;
NSLog(@"piDouble: %.15f", piDouble);

NSNumber *doubleNumber = [NSNumber numberWithDouble:piDouble];
NSLog(@"doubleNumber: %@", [doubleNumber stringValue]);

/*
  输出结果
  piDouble: 3.141592653589793
  doubleNumber: 3.141592653589793
*/

查看上面的代码,注意到最后一位的归零误差。我们将30位有效数字的圆周率赋值给piDouble。但double只能保留15位有效数字,其后数字都会被约去。若直接对圆周率值保留15位有效数字,我们得到3.141 592 653 589 793 2,但由于归零误差,浮点数的实际值为3.141 592 653 589 793。

Swift

Swift使用double标识符来确定64位浮点数值。在Swift中,double类型的精度为15位有效数字,近似取值范围从2.3×10−308~1.7×10308。需注意的是,根据Apple公司的Swift文档,当floatdouble类型均能满足需求时,推荐使用double类型:

//Swift
var doubleValue : Double = 3.141592653589793238462643383279
print("doubleValue: \(doubleValue)")

/*
  输出结果
  doubleValue: 3.14159265358979
*/

查看上面的代码,注意最后一位的归零误差。我们将30位有效数字的圆周率赋值给doubleValue。但由于double只能保留15位有效数字,其后数字都会被约去。若直接对圆周率值保留15位有效数字,我们得到3.141 592 653 589 793,但由于归零误差,浮点数的实际值为3.141 592 653 589 79。


透露一下,对于这些定制类型,还有一种简单的、可以说是更为优雅的替代方法。可使用intlong类型来进行货币计算,用分代替元来计数:

由于浮点运算事实上是基于二进制数学的,有着固有的不准确性,因此单精度和双精度浮点类型无法精确地表示我们使用的十进制货币。乍一看,将货币用单精度或双精度浮点类型表示或许是个好主意,因为它能够约去运算过程带来的细微误差。但是,当把这些本来就不怎么精确的结果再进行大量、复杂的运算后,误差会不断累积,造成严重的偏差和难以跟踪的错误。这使得单精度和双精度浮点类型无法用于对精确度要求近乎完美的十进制货币。幸运的是,对于货币和其他需要进行高精度十进制运算的数学问题,我们所讨论的这4种语言都提供了相应机制。

C#

//C# long total = 316;
//$3.16

C#使用decimal关键字来标识精确浮点值。在C#中,decimal的精度为28或29位有效数字,取值范围为±1.0×10−28~±7.9×1028

    var decimalValue =
NSDecimalNumber.init(string:"3.141592653589793238462643383279")
    print("decimalValue \(decimalValue)")

    /*
      输出结果
      piDecimal: 3.1415926535897932384626433833
    */

上述代码中,我们将30位有效数字的圆周率赋值给decimal Value,但它只保留了28位有效数字。

Java

Java以BigDecimal类的形式对货币类问题提供了一种面向对象的方案:

    BigDecimal piDecimal = new 
BigDecimal("3.141592653589793238462643383279");
    System.out.println(piDecimal);

    /*
      输出结果
      3.141592653589793238462643383279
    */

在上述代码中,我们把十进制值以文本形式作为构造函数的参数来初始化BigDecimal类。程序运行结果表明BigDecimal类返回了30位有效数字,没有精度损失。

Objective-C

Objective-C以NSDecimalNumber类的形式对货币类问题提供了一种面向对象的方案:

    //Objective-C
    NSDecimalNumber *piDecimalNumber = [[NSDecimalNumber alloc]
initWithDouble:3.14159265358979323846264338327];
    NSLog(@"piDecimalNumber: %@", [piDecimalNumber stringValue]);

    /*
      输出结果
      piDecimalNumber: 3.141592653589793792
    */

Swift

Swift用与Objective-C中同名的类NSDecimalNumber对货币类问题提供了一种面向对象的方案。这个类在Swift和Objective-C中的初始化操作有些区别,但功能并无二致。

var decimalValue =
NSDecimalNumber.init(string:"3.141592653589793238462643383279")
print("decimalValue \(decimalValue)")

/*
  输出结果
  decimalValue 3.141592653589793238462643383279
*/

注意,Objective-C和Swift两例的输出结果都有30位有效数字,这说明NSDecimal Number类适用于处理货币及其他十进制数值。

在计算机科学领域中,类型转换type conversiontypecasting)是指将对象或数据从一种类型转换到另一种类型。例如,你调用了一个返回类型为整型的方法,并需要将这个返回值作为另一个方法的传入参数,但第二个方法要求传入参数必须是长整型。由于整型数值在定义上存在于长整型所允许的数值范围之内,因此int值可以重定义为long类型。

通常,可通过隐式转换implicit conversion)或显式转换(也叫强制类型转换, explicit conversion)进行类型转换。此外,我们还需要了解静态类型语言static languages)和动态类型语言dynamic languages)的区别,才能完全领会类型转换的意义。

1.静态类型语言和动态类型语言

静态类型语言会在编译时进行类型检查type checking)。这意味着当你试图生成解决方案时,编译器会检查和实施程序中所有数据类型的约束条件。如果检查失败,会停止生成并报错。C#、Java以及Swift均是静态类型语言。

另一方面,动态类型语言会在执行时进行大多数甚至所有的类型检查。这意味着如果开发人员在编程时有所疏忽,程序或许在生成阶段一切正常,但在执行时可能会出错。Objective-C混用了静态和动态类型对象,它是一种动态类型语言。本章之前所讨论的用于存储数值型数据的纯C对象均为静态类型,而Objective-C中的NSNumberNSDecimalNumber类均为动态类型。思考下面的Objective-C代码示例:

double myDouble = @"chicken";
NSNumber *myNumber = @"salad";

编译器会对第一行代码报错,内容为“Initializing 'double' with an expression of incompatible type 'NSString *'”。这是因为double是一个纯C的静态类型。编译器甚至在生成之前就知道应该怎样处理这个静态类型,因此这段代码通不过检查。

然而,对于第二行代码,编译器只会发出内容为“Incompatible pointer types initializing 'NSNumber *' with an expression of type
'NSString *'”的警告。这是因为Objective-C的NSNumber类是一个动态类型。编译器很智能,能够发现错误,但仍然会允许进行生成(除非你在生成设置里指示过编译器将警告视为错误)。


显然,前面的例子在运行时会出现错误,但在有些情况下,即使存在警告,你的应用依然会正常运行。然而,无论你使用的是哪种语言,最好不断地清除掉已有的警告,再继续编程。这样有助于保持代码的整洁,并避免出现一些难以诊断的运行错误。

有时也许并不能及时地处理警告,这时应当清楚地记录下代码并说明警告源,以便其他开发人员了解来龙去脉。在万不得已的时候,可以利用宏和预处理器(预编译器)命令来一条条地忽略警告。

2.隐式转换和显式转换

不需要在源代码中使用任何特殊语法的类型转换为隐式转换implicit casting)。隐式转换较为方便。思考下面的C#代码示例:

int a = 10;
double b = a++;

在上面的例子中,由于a既可以定义为int类型,也可以定义为double类型,且这两种类型都经过了人为定义,因此可以将a转换为double类型。然而,由于隐式转换并不一定要进行人为的类型定义,因此编译器不一定能完全判断类型转换所适用的约束条件,所以,直到程序运行前,编译器都无法进行类型转换检查。这样会使隐式转换存在一定的风险。思考下面的C#代码示例:

double x = "54";

上面的例子并没有告诉编译器该如何处理字符串值,因此这是一个隐式转换。在这种情况下进行应用生成,编译器会针对这行代码报错,内容为“Cannot implicitly convert type 'string' to 'double'”。现在,思考同样例子的显式转换:

double x = double.Parse("42");
Console.WriteLine("40 + 2 = {0}", x);

/*
  输出结果
  40 + 2 = 42
*/

假设字符串是可解析的,上述类型转换即显式转换,因此是类型安全的。

3.缩限转换和扩展转换

两种数据类型在进行类型转换时,转换结果是否在目标数据结构所允许的范围之内非常关键。如果源数据类型比目标数据类型所支持的字节数多,则这种类型转换为缩限转换narrowing conversion)。

缩限转换不是什么情况下都能够进行,并且在转换过程中很可能会丢失信息。举例来说,将浮点类型转换为整型会丢失数据(损失精度),转换结果会被近似为与原始值最接近的整数。在绝大多数静态类型语言中,缩限转换是不能被隐式执行的。以本章之前出现过的单精度和双精度类型的C#代码为例,将双精度缩限转换为单精度:

//C#
piFloat = piDouble;

在这个例子中,编译器会报错,内容为“Cannot implicitly convert type 'double' to 'float'. And explicit conversion exists
(Are you missing a cast?)”。编译器发现了这个缩限转换,并将精度损失视作错误。错误信息建议我们使用显式转换来解决问题。

//C#
piFloat = (float)piDouble;

我们现在使用显式转换将double类型的piDouble转换为单精度型,编译器不会再因为精度损失而报错。

如果源数据类型比目标数据类型所支持的字节数少,则这种类型转换为扩展转换widening conversion)。扩展转换会保留源类型的值,但可能会改变值的表示方法。大多数静态类型语言都允许隐式扩展转换。还是以前面的C#代码为例:

//C#
piDouble = piFloat;

本例中,隐式转换不会引起编译器报错,应用也能正常生成。将这个例子进一步拓展:

//C#
piDouble = (double)piFloat;

上面的显式转换能提高代码的可靠性,但无论如何都不会改变这条语句的本质。即便这样会显得比较冗余,但不会引起编译器出错。除了提高可靠性之外,显式的扩展转换不会对程序造成其他额外的影响。因此,可根据个人喜好来选用隐式或显式的扩展转换。

布尔数据类型旨在符号化二进制数值,通常由1和0,truefalse,有时是YESNO来表示。布尔类型用于表示基于布尔代数的真值逻辑。这里所说的布尔值是用在逻辑判断或条件循环中的条件语句,如ifwhile

等于运算包含能够比较两个实体值的任意一种运算。等于运算符有:

关系运算包含能够测试两个实体关系的任意一种运算。关系运算符有:

逻辑运算包含程序中能够计算和控制布尔值的任意一种运算。一般有与(AND)、或(OR)和非(NOT)3大逻辑运算符。此外,还有较不常用的异或exclusive orXOR)运算符。这4种基本运算符可用来构建所有布尔类型的函数和语句。

与运算符是最为互斥的比较器。给定两个布尔变量AB,当且仅当AB均为true时,与运算才会返回true。通常,我们会使用真值表这个工具来表示布尔变量。思考下面的与运算符真值表:

A B A^B
0 0 0
0 1 0
1 0 0
1 1 1

上表展示了与运算符。当在检查条件语句时,0被作为false,而非0值都会被作为true。只有当AB均为true时,A^B的比较结果才为true

或运算符是包含运算符。给定两个布尔变量ABAB有一个为true,或两者都为true时,或运算就会返回true。思考下面的或运算符真值表:

A B AvB
0 0 0
0 1 1
1 0 1
1 1 1

接下来为非运算符。当Ature时,非A结果为false;当Afalse时,非Atrue。思考下面的非运算符真值表:

A !A
0 1
1 0

最后介绍一下异或运算符。当AB有一个为true,且不全为true时,异或运算才会返回true。换句话说,当AB不同的时候,异或为true。有时,使用这种方式来进行表达式判断会非常方便,因此大部分计算机架构都含有这种运算。思考下面的异或运算符真值表:

A B AXORB
0 0 0
0 1 1
1 0 1
1 1 0

如同算术运算一样,比较和布尔运算也有运算符优先级。这意味着从架构上来说,一种运算符会优先于另一种运算符。一般来说,所有语言的布尔运算优先级如下所示:

在使用布尔值时,懂得运算符的优先级是非常重要的。如果没搞懂系统是如何进行复杂逻辑计算的话,会使代码出现令人难以理解和处理的问题。如果在编程时有不确定的地方,可以像使用算术运算中的括号那样,将需要进行高优先级运算的对象放进括号里。

上一节提到,当两个操作数均为true时,与运算返回true;只要有一个操作数为true,或运算就会返回true。这种特性让我们能够仅通过检测一个操作数便可以得到整个表达式结果。程序在能够确定表达式整体结果时就立即终止求值过程,这就是短路求值short-circuiting)。为什么需要在编程时采用短路求值?有3个主要原因。

第一,通过限制代码所需要的运算次数,短路求值能够提高程序的性能。第二,若前操作数可能会令后操作数产生潜在错误时,短路求值能够在运算到会带来更高风险的操作数之前,停止执行。第三,通过减少嵌套逻辑语句,短路求值能够提高代码的可读性和复杂性。

C#

C#使用bool关键字来作为System.Boolean的别名。bool关键字用来存放truefalse的值:

//C#
bool a = true;
bool b = false;
bool c = a;

Console.WriteLine("a: {0}", a);
Console.WriteLine("b: {0}", b);
Console.WriteLine("c: {0}", c);
Console.WriteLine("a AND b: {0}", a && b);
Console.WriteLine("a OR b: {0}", a || b);
Console.WriteLine("NOT a: {0}", !a);
Console.WriteLine("NOT b: {0}", !b);
Console.WriteLine("a XOR b: {0}", a ^ b);
Console.WriteLine("(c OR b) AND a: {0}", (c || b) && a);

/*
  输出结果
  a: True
  b: False
  c: True
  a AND b: False
  a OR b: True
  NOT a: False
  NOT b: True
  a XOR b: True
  (c OR b) AND a: True
*/

Java

Java使用boolean关键字来表示其原生的布尔类型。Java还为同样的原生布尔类型提供了一个Boolean包装类。

//Java
boolean a = true;
boolean b = false;
boolean c = a;

System.out.println("a: " + a);
System.out.println("b: " + b);
System.out.println("c: " + c);
System.out.println("a AND b: " + (a && b));
System.out.println("a OR b: " + (a || b));
System.out.println("NOT a: " + !a);
System.out.println("NOT b: " + !b);
System.out.println("a XOR b: " + (a ^ b));
System.out.println("(c OR b) AND a: " + ((c || b) && a));

/*
  输出结果
  a: true
  b: false
  c: true
  a AND b: false
  a OR b: true
  NOT a: false
  NOT b: true
  a XOR b: true
  (c OR b) AND a: true
*/

Objective-C

Objective-C使用BOOL标识符来表示布尔值:

//Objective-C
BOOL a = YES;
BOOL b = NO;
BOOL c = a;

NSLog(@"a: %hhd", a);
NSLog(@"b: %hhd", b);
NSLog(@"c: %hhd", c);
NSLog(@"a AND b: %d", a && b);
NSLog(@"a OR b: %d", a || b);
NSLog(@"NOT a: %d", !a);
NSLog(@"NOT b: %d", !b);
NSLog(@"a XOR b: %d", a ^ b);
NSLog(@"(c OR b) AND a: %d", (c || b) && a);

/*
  输出结果
  a: 1
  b: 0
  c: 1
  a AND b: 0
  a OR b: 1
  NOT a: 0
  NOT b: 1
  a XOR b: 1
  (c OR b) AND a: 1
*/


无独有偶,Objective-C为Boolean类型提供了5个标识符和类,再一次说明了Objective-C比其他的语言更为复杂。这门语言为逻辑值提供了5个标识符和类。简单起见(编辑也不会给我更多篇幅),我们在这本书中只使用BOOL。如果想了解更多内容,我鼓励你查阅本章末尾的附加资源。  

Swift

Swift使用Bool关键字标识其原始布尔类型:

//Swift
var a : Bool = true
var b : Bool = false
var c = a

print("a: \(a)")
print("b: \(b)")
print("c: \(c)")
print("a AND b: \(a && b)")
print("a OR b: \(a || b)")
print("NOT a: \(!a)")
print("NOT b: \(!b)")
print("a XOR b: \(a != b)")
print("(c OR b) AND a: \((c || b) && a)")

/*
  输出结果
  a: true
  b: false
  c: true
  a AND b: false
  a OR b: true
  NOT a: false
  NOT b: true
  a XOR b: true
  (c OR b) AND a: true
*/

在上面的例子里,布尔对象c并没有直接声明为Bool,但其已隐含地归类为Bool了。从Swift的角度而言,这里的数据类型是被推断出来的。此外,需要注意的是,Swift并没有提供一个特定的异或运算符,因此,你应该用(a!=b)的形式来进行异或运算。


Objective-C的nil值

在Objective-C中,nil值的结果也为false。虽然对于其他语言来说,必须小心使用NULL对象,但当开发人员试图对nil对象进行计算时,Objective-C并不会崩溃。曾经学过C#或Java的开发人员,肯定会认为未处理的NULL对象会导致崩溃,从而对Objective-C的这种特点会感到有些困惑。然而,Objective-C的开发人员却常常对这个特性加以利用。很多时候,简单测试下一个对象是不是为nil,便能确认某个操作有没有被顺利执行,让人从编写冗长的逻辑比较式中得以解脱。  

字符串不是严格意义上的数据类型,但作为开发人员,我们经常将字符串当作一种数据类型。事实上,字符串是值为文本的简单对象。在底层看来,字符串包含一个由只读char对象组成的有序集。字符串对象的这种只读特性会令字符串具有不可变性(immutable),这意味着,一旦字符串对象在内存中建立,便不能更改。

需要重点了解的是,不光是字符串,更改任何的不可变对象,实际上都意味着程序在内存中新建了一个对象,同时释放掉旧有的对象。相较于单纯更改内存地址中的值,更改不可变对象是种需要更多操作的密集运算。将两个字符串合并起来的操作称为字符串连接(concatenation)。这相当于在建立一个新的对象之前,需要将两个对象转移,是种代价更高的操作。如果程序频繁地修改字符串值或进行字符串连接,则程序的效率会降低。

在C#、Java和Objective-C中,字符串是严格不可变的。有意思的是,Swift的文档指出其字符串是可变的。然而,Swift的行为类似于Java,当修改一个字符串时,这个字符串会被赋值为另一个对象。因此,即使文档表示其字符串可变,但实际却是不可变的。

C#

C#使用string关键字来声明字符串类型:

//C#
string one = "One String";
Console.WriteLine("One: {0}", one);

String two = "Two String";
Console.WriteLine("Two: {0}", two);

String red = "Red String";
Console.WriteLine("Red: {0}", red);

String blue = "Blue String";
Console.WriteLine("Blue: {0}", blue);

String purple = red + blue;
Console.WriteLine("Concatenation: {0}", purple);

purple = "Purple String";
Console.WriteLine("Whoops! Mutation: {0}", purple);

Java

Java使用系统类String来声明字符串类型:

//Java
String one = "One String";
System.out.println("One: " + one);

String two = "Two String";
System.out.println("Two: " + two);

String red = "Red String";
System.out.println("Red: " + red);

String blue = "Blue String";
System.out.println("Blue: " + blue);

String purple = red + blue;
System.out.println("Concatenation: " + purple);

purple = "Purple String";
System.out.println("Whoops! Mutation: " + purple);

Objective-C

Objective-C提供NSString类来创建字符串对象:

//Objective-C
NSString *one = @"One String";
NSLog(@"One: %@", one);

NSString *two = @"Two String";
NSLog(@"Two: %@", two);

NSString *red = @"Red String";
NSLog(@"Red: %@", red);

NSString *blue = @"Blue String";
NSLog(@"Blue: %@", blue);

NSString *purple = [[NSArray arrayWithObjects:red, blue, nil]
componentsJoinedByString:@""];
NSLog(@"Concatenation: %@", purple);

purple = @"Purple String";
NSLog(@"Whoops! Mutation: %@", purple);

在查看Objective-C的例子时,你也许会好奇为什么创建purple对象需要那么多额外的代码。这是因为Objective-C并不像其他3种语言那样提供了进行字符串连接的简易机制。因此,在这种情况下,我将两个字符串放到数组里并调用NSArraycomponentsJoined ByString:方法。我还可以使用NSMultableString类,它提供了连接字符串的方法。但是,由于在选定的4种语言里都没有讨论可变字符串类,因此我决定不使用这种方式。

Swift

Swift提供String类来建立字符串对象:

//Swift
var one : String = "One String"
print("One: \(one)")

var two : String = "Two String"
print("Two: \(two)")

var red : String = "Red String"
print("Red: \(red)")

var blue : String = "Blue String"
print("Blue: \(blue)")

var purple : String = red + blue
print("Concatenation: \(purple)")

purple = "Purple String";
print("Whoops! Mutation: \(purple)")

/*
  每个例子的输出结果:
  One: One String
  Two: Two String
  Red: Red String
  Blue: Blue String
  Concatenation: Red StringBlue String
  Whoops! Mutation: Purple String
*/

本章,我们学习了4种最常用的移动开发语言所提供的基本数据类型。从底层架构及语言规范角度学习了数值和浮点数据类型的特点和操作。我们还学习了将对象从一种类型转换到另一种类型的方法,以及根据转换中源类型和目标类型的大小不同如何进行扩展转换和缩限转换。接着,我们讨论了布尔类型、它在比较器中的应用以及它如何影响程序的流程和执行。其中,我们还讨论了运算符优先级和嵌套运算,学习了如何使用短路求值来提升代码性能。最后,我们还探讨了字符串类型以及可变对象的意义。


相关图书

递归算法与项目实战
递归算法与项目实战
群智能算法在人脑功能划分中的应用
群智能算法在人脑功能划分中的应用
数据结构抢分攻略  真题分类分级详解
数据结构抢分攻略 真题分类分级详解
量子计算:一种应用方法
量子计算:一种应用方法
数据结构与算法分析:C++语言描述(英文版·第4版)
数据结构与算法分析:C++语言描述(英文版·第4版)
数据分析的结构化表征学习
数据分析的结构化表征学习

相关文章

相关课程