!!!Swift的数据类型大体分为两大类,一类是值类型的,即函数传参的时候是传统C语言的值传递,另一种是引用类型的,即函数传参时是C++的引用传递,Swift中只有类是引用类型的,其余的一起数据类型都是值类型的,本节所讲的所有基本数据类型都是值类型的,包括之后要讲的结构体、集合、枚举等也都是值类型的!
1. 数值类型:
1) 整型分有符号无符号8种,分别表示8位、16位、32位、64位:
有符号:Int8、Int16、Int32、Int64
无符号:UInt8、UInt16、UInt32、UInt64
2) 两种特殊的类型别名,表示不定长的整型,即和平台有关的,在32位操作系统下为32位,64位操作系统下为64位:
32位操作系统:Int == Int32、UInt == UInt32
64位操作系统:Int == Int64、UInt == UInt64
3) 与平台无关的几种整型类型别名,都是定长的:
Byte == UInt8 // 单字节
SignedByte == Int8 // 带符号单字节
ShortFixed == Int16 // 短整型
Fixed == Int32 // 长整型
4) 浮点类型:单精度和双精度两种
Float:32位
Double:64位,等价于Float64
5) 自动推断默认类型:如果在定义初始化时使用自动推断,比如let a = 35,对于整型数自动推断为Int,浮点数自动推断为Double,都是不定长(表示机器的自然长度),如果对于一个没有超出16位限度的值非得想赋给16位变量则必须显式指定类型,比如let a:Int16 = 28
6) 类型转换:Swift要求类型绝对安全,窄化转换和自然转换都必须进行显示的强类型转换才行,否则就会报错!
var x: Byte = 10
var y: Byte = 20
var z: Int32 = x + y // Bad!
var z: Int32 = Int32(x) + Int32(y) // Good!
实质上所有基本类型都属于类(class),这里的类型转换其实是调用该类的构造函数构造出该类的一个新的对象;
!同样,窄化转换以及浮点转整型都是截断型转换,会损失精度,使用的时候需要注意!
混合类型运算同样需要强制类型转换,即Swift的原则就是表达式中所有项的类型必须统一,如果不统一需要根据赋值号左侧变量或常量的类型进行强制类型转换:
var aa: Int8 = 8
var bb: Int8 = 127
var cc: Int32 = 77
var dd: Int32 = cc + aa // Bad!aa没转
var dd: Int32 = cc + Int32(aa) // Good!
7) 溢出检查:
i) Swift最牛逼的一个地方就是可以检查运行时的数据溢出:
var aa: Int8 = 8
var bb: Int8 = 127
var cc: Int8
cc = aa + bb // 会抛出超出Int8范围的异常
通常的编译器(C、C++、Java等)溢出之后会得到一个未知的结果(假设有无限多位,溢出后截断至类型应有的位数),但是由于Swift要求表达式两侧数据类型严格一致(必须通过强类型转换实现),因此就可以在类型限制下对右边的表达式检查结果是否溢出;
ii) 当然,编译时的溢出检查就更不用说了,比如var a: Int8 = 250,var a: Int8 = 8 + 125等都会直接在编译阶段报错;
8) 获取类型的极值:可以通过Int32.min、UInt8.max的方式获取每种类型的最大值和最小值,注意!浮点类型没有max和min属性,因为浮点数可以表示任意大小的数,只不过太大或太小的值精度非常非常低而已;
当然也有内部变量的方式定义这些极值,比如INT16_MAX等,但是有些类型极值没定义,比如INT64_MIN就不存在,肯能Swift目前还处于测试版的原因吧!
2. 数值常量的表示方法:
1) 整型表示:没有后缀,只有四种表示方式,十进制、二进制、八进制和十六进制
十进制不带后缀:23、-32等
另外三种实质上是直接对内存单元的二进制位进行设置,因此不能带负号(虽然实际可以带,但是不建议带负号!):
二进制:0b打头
八进制:0o打头
十六进制:0x打头
其中b、o、x必须小写!大写会报错!
2)浮点数不带任何后缀,因此在初始化时想要赋给Float就不能使用类型推断,必须显示指定Float类型,比如let f: Float = 3.2,如果使用类型推断会变成默认的Double类型了;
除了正常的小数点表示以外还可以用指数表示,只是用E或e表示,指数部分必须是整数,可正可负,底数是一个浮点数,规则和C语言的一模一样!
3) 可读性表示:比如var a = 1_0_324__3_.1_0__350__,即表示数值类型常量的时候下划线可以随意加并且可以加任意多个(但是起始和.之后的起始位置不能加_),编译器都会将其忽略,这里的结果就是a = 103243.10350,这样表示可以使某些特别长的数值容易辨识,比如var output = 1_000_000_000,一看就知道是10亿而不用非常烦人地去数0的个数,但是没有意义的分割不提倡,就是之前提到过的103243.10350的例子
对于指数表示同样也奏效:var q = -3__2.3__5_E3_2__,结果是q = -3.235e+33,前面小数部分_的规则和之前讲过的相同,E后面的第一个不能是_,其它地方可以任意加_
3. 布尔类型:
1) 类型为Bool,值就只有true和false两种;
2) if、while等条件判断处规定只能使用布尔类型值,如果使用了非Bool类型值将会直接编译报错,这就限制了像C语言一样的用非0表示逻辑真0表示逻辑假的自由编程风格,将安全机制进行到底:
var b: Bool = false
if b // Good!
{
println("true")
}
else
{
println("false")
}
var i: Int
if i = 20 // Bad! Not Bool
if b = true // Bad! Even b is Bool
即使Bool变量的赋值表达式也不能作为判断条件,Swift规定判断条件处只能使用布尔表达式,诸如==、>等,或者是返回Bool值的函数,但是不可以是复制表达式,即使是Bool类型的复制表达式也不可以!
3. 字符类型:
1) Swift中字符类型Character和传统编程语言有很大的不同,由于完全支持Unicode编码Character编程了不定长类型,比如传统ASCII字符编码仍然和ASCII编码值一样,并且长度还是1个字节,但是对于英语系以外的语言,比如中文,一个字符就是2个字节,而对于一些特殊符号,比如笑脸等,一个字符就有4个字节,因此Swfit内部用处理字符串的方式处理Character,并且单个Character不再支持>、<、>=、<=这四个操作符了;
2) 由于一个字符可能超过1个字节,因此Swift中不再用''来表示一个字符了,而是和字符串一样使用""来指定一个字符,比如let c: Character = "a";
3) 自动推断的默认类型:当使用自动推断时,比如let c = "a",由于没指定类型,Swift默认推断为String类型,即字符串类型,因此想要指定该变量为Character类型除非定义常量或变量时直接指定类型,或者右边使用Character进行强行转换或者右边是一个返回Character类型的函数调用;
4) 定义Character量的时候右边只能有一个字符,如果超过1个字符就会报错(即使是使用定义字符串的方式来定义字符);
5) 同样对于Swift的Character也有两种两种表现形式,对于可显示的字符可以直接使用其本身来表示,比如字母'a',而那些不可显示的字符(比如一些控制字符等)可以用编码表示,但是对于任何一个字符都有自己的编码表示,由于是用Unicode进行编码,因此是不定长的,因此也总共有3中编码表示法:
单字节编码(主要是ASCII字符集和ASCII扩展字符集),比如"\x26" == "&"
双字节编码(主要是象形文字,希腊文、中文、日文、韩文等),比如"\u03bb" == "λ”
四字节编码(主要是一些表情符号),比如"\U0001f603" == 笑脸符号
!其中x、u、U的大小写不要弄错,x后面必须有两位,u后面必须有4位,U后面必须有8位,全部都是16进制数,不能忽略前导0!!!
6) 转义字符:和传统编程语言一样,使用\符号转义,转义在字符表也大体相似;
4. 字符串类型:
1) 类型名为String,并且是自动推导的类型;
2) 创建字符串:有两种方法,一种是使用""直接赋值的方法,还有一种是使用String类的构造器,这里特别要提的是创建空串初始化:
var str1 = "abc"
var str2 = String("abc")
var str3 = "" // Empty string
var str4 = String() // Empty string
println(str3 == str4 ? "Yes" : "No") // Yes,可见两种方式创建的字符串内容上是一样的!
3) 字符计数:使用全局函数countElements(str)可以返回字符串中字符的个数,即字符串的长度,由于里面的字符都是不定长的,因此技术要比C语言的按照字节技术麻烦很多,要以Character类对象为单位进行计数!
4) 字符串常量和变量:变量用var声明,常量用let声明,常量必须初始化,之后不能修改,变量可以随意修改,可以重新赋值,可以使用+=等符合运算符进行拼接等操作,比如:str += "tail",将"tail"接在str的尾部;
5) 字符串直接的比较:支持==、>、<、>=、<=、!=这几个比较符,意义和C语言等的strcmp一样,但是不支持===和!===比较符!但是Character类型不支持比较符号,但支持==和!=比较符号!
6) 字符串连接:使用+和+=进行连接,和Java的+表示字符串连接意义相同,但是Swift只支持String类型之间的连接操作,不允许Character和String之间连接和Character之间的连接,如果要在String和Character之间连接需要对Character类型进行强制类型转换才行!
!注意:Swift不允许Character类型以及Character和整型之间的算术运算,通常C语言中会对char进行一些加减运算来得到另一个字符,但这在Swift中不允许,这让代码变得更安全了,同时也更繁琐了!
7) 大小写转换:可以利用String类的两个属性String.uppercaseString和String.lowercaseString得到相应字符串的大小写版本(注意!只对其中的英文字母进行大小写转变,对于其它字符,比如数字、中文等其它,不做任何改变,并且该属性只是返回一个临时的值并不改变原来的值,如果想利用该临时值,应该用一个临时变量来保存它):
var s = "aBcDeFg"
println(s.uppercaseString)
println(s.lowercaseString)
5. 字符串迭代:
1) 字符串在Swift中其实是用集合实现的,字符串中的字符作为有序的元素构成字符串这个集合,因此要遍历其中的字符可以使用for循环迭代;
2) 得到字符串中每一个字符:
let charCollection = "获取字符串中的每一个字符"
for c in charCollection
{
print("<\(c)>")
} // <获><取><字><符><串><中><的><每><一><个><字><符>
3) 通过String的unicodeScalars属性获取字符串的Unicode编码序列(表示出来是十进制的!):
let strValue = "abcd上下左右"
for c in strValue.unicodeScalars
{
print("\(c.value) ")
} // 97 98 99 100 19978 19979 24038 21491
其实返回的并不是单纯的Unicode编码序列,序列中的每个元素都包含了不止一个域,其中value域就是Unicode编码,同时也包含字符域,就表示字符本身,如果直接println(s.unicodeScalars)将会显示原字符串而不是编码序列!
4) 查看前后缀:使用String的hasSuffix和hasSuffix成员函数查看字符串是否存在参数中指定的前缀或后缀:
let docFolder = [
"java.docx",
"JavaBean.docx",
"Objective-C.xlsx",
"Swift.docx"
]
var docxCount = 0
for doc in docFolder
{
if doc.hasSuffix(".docx")
{
docxCount++;
}
}
var javaCount = 0
for doc in docFolder
{
// 因为前缀并不固定,java单词里可能有大写也可能有小写,因此归一化为小写的
if doc.lowercaseString.hasPrefix("java")
{
javaCount++
}
}
println("Word文档个数:\(docxCount),Java文档个数:\(javaCount)") // 3和2
6. 元组:
1) 相当于数据库表中的一条记录,记录中字段有序排列,每个字段的数据类型是任意的没必要相同,元组作为函数参数返回时尤其有用!
2) 元组在Swift中并没有一个具体的类型名称,而是通过用圆括号()赋值的形式自动推断出这是一个元组类型,一旦元组确定,其元素个数、元素类型就确定了,之后这两个属性不可修改(不管是常量还是变量),但是变量可以修改元素的值而常量不可以!
var a = (1, 2, 3) // 圆括号表名这是一个元组,元素用逗号,隔开
a = (2, 3, 4, 5) // Bad! 元素个数不对
a = ("a", "b", "c") // Bad! 元素类型必须一一对应
a = (3, 4, 5) // Good!
3) 输出结果:带圆括号,并且字符串输出时不带"",括号内侧无空格,逗号前面无空格而后面有一个空格,在赋值的时候最好也遵循这个格式
var a = (1, 2, "haha")
println(a) // (1, 2, haha)
4) 获取元组中字段的值:一共有两种方法,第一种利用成员运算符.进行索引,第二种时对元组进行分解
i. .的索引:
下标索引:
var t = (1, "haha", Character("c")) // 可以利用构造器传参
println("\(t.0), \(t.1), \(t.2)") // 下表索引从0开始,越界直接编译报错!
索引越界是在编译阶段检查的!超牛逼!C\C++、Java这种都是运行时检查,很难发现错误!
键索引:建立元组的时候为字段取一个名字(标示符表示),这样名字就是键,元素值就是值,键-值对应非常清晰,但不用为所有字段都取键名,可以值为感兴趣的字段取键名,通过键名索引比下标索引更安全,可以避免运行时索引越界:
var t = (num: 10, name: "Peter", age: 15, 500.0)
println("number: \(t.num), name: \(t.name), age: \(t.age), Salary: \(t.3)")
ii. 对元组进行分解:定义变量时将变量写入元组中,接受赋值元组中相应的字段,对于不感兴趣的字段可以用占位符_忽略:
var t = (1, "haha", 23.3)
var (num, str, float) = t
println("num = \(num), str = \(str), float = \(float)")
var (e1, _, e3) = t // 使用占位符_进行忽略
println("e1 = \(e1), e3 = \(e3)")
7. 可选类型:
1) 主要用于解决变量和常量为空的情形,并以此来简化异常处理的机制,实际上Swift完全依赖可选类型来解决异常处理问题,因此Swift根本就没有异常处理机制(catch throw等);
2) 可选类型定义的时候必须用?作为类型的后缀,比如var x: Int?,该符号见名知意,即表示x要么是一个Int型值,要么就没有值(即到底有没有Int值的意思),如果使用类型推导,则赋值号右边必须是一个可选类型或者是一个返回可选类型的函数;
3) 可选类型和相应普通类型之间的兼容问题:可以将普通类型赋给可选类型但不可以将可选类型赋给普通类型,否则直接编译报错!
var x = 10
var y: Int? = x // Good!
var z: Int = y // Bad!
还有一点需要提的是,普通类型定义时如果没有初始化则此时它是一个空值,空值的普通类型是不能使用,一旦使用就会报错!
var x: Int
var y = x // Bad! Not initialized!
但是对于可选类型来说,没有初始化就代表一个空值,空的可选变量至少是可以使用的(但可使用的情景是很少的,一般用在异常处理的环节)!
var x: Int?
var y = x // Good! y的类型根据x的类型推断,为Int?
var z: Int? = x // Good! 可选类型之间相互赋值没问题,把空也当做值来赋
4) 可选类型用于异常处理的情形:主要用于判断一个函数是否正常执行,在Swift中不是通过中断函数抛出异常的方式来解决异常问题的,而是通过返回一个可选类型值来处理异常的,如果函数执行异常则返回nil,否则就返回一个正常的存在的数据,这样就避免了诸如C语言那样通过返回值是-1、0还是其它什么值来判断异常类型那样麻烦了;
诸如var ret: Int? = "123a".toInt();
该函数将字符串转换为Int值,但是这里的字符串包含了非数字字符a,因此转换成出现异常而返回一个Int?,而这里返回的是nil,然后可以通过检查ret是否为nil进行异常判断:
if ret == nil
{
println("转换成功")
}
但是每次调用函数后都进行这样的判断是非常浪费资源并且会让代码极大的冗余,这里就要引入assert的思想,所有程序都希望函数能正常运行,也就是希望函数最终返回的是一个非nil值,这样的话就可以使用assert(ret != nil)来断定函数执行完之后返回的应该是非nil值,如果是nil则中断程序打印错误消息,虽然assert函数要比if语句更加简洁好用但是仍然比较麻烦,每次使用之前都需要调用该函数(该函数其实是一种调试函数),那有没有更简洁优美的解决方案使得使用该变量的同时又能实时判断该变量是否有值呢?
对于Swift来说答案是肯定的,Swift要求使用可选类型变量或常量的时候同样需要使用!来标记可选变量,该符号叫做拆包,表示取可选变量所代表的值,比如c = a! + 1,有点儿类似于C语言的取值运算符*,如果可选变量有值,则拆包后返回的是一个同类型普通类型的值,如果没有值(即为nil)则拆包的时候会直接报错(运行时错误)并且会直接锁定代码中拆包错误的那个变量(高亮显示),这样就在使用的同时也具备了assert的功能,即当你要使用变量的时候肯定是断定该变量有值才会去用的,如果断定错误则就跟assert一样中断程序并抛出运行时错误(Xcode还可以直接锁定错误的那个拆包,这样调试也非常方便),从实现角度来看加感叹号其实就是为了在变量每值的时候为程序抛出异常的!
5) !解决异常的两种解决方案:
i) 将变量定义为?的可选变量,但是每次使用的时候必须使用拆包符号!(进行算术运算等操作,但是打印操作可以不加!拆包,如果为空则输出nil),但是每次使用可选变量进行一定操作的时候都需要加!也会感觉非常麻烦,但是不使用又会报错!因此又陷入了两难的困境!
var x: Int? = 5
var y = x + 1 // Bad! 没有对x进行拆包!
还好Swift还有一个更加优美的解决方案,这就是接下来要将的ii)
ii) 直接在定义可选变量的时候就断定它是有值的:
var x: Int! = "123".toInt()
var y = x + 1 // Good! 初始化断定过以后之后使用就不在使用!符号拆包了
x = 5 // 同样也可以重新赋值
x = nil // 赋nil同样也没有问题
x + 3 // 只有在使用它进行操作的时候才会抛出运行时错误!
var x: Int! = 5
println(x + 5) // Good!
这样x就保持了所有可选类型?所具有的一切性质,同时在使用的时候也不用加!进行拆包(就一直处在拆包的状态)
iii) 这两种方法各有各的好处,第一种在使用可选变量的时候需要加!,虽然麻烦但是到哪儿都可以知道该变量是一个可选类型的,第二种方法虽然简单,但是代码一长可能就很难判断该变量是一个可选类型的,当然如果出现拆包异常就会知道了,但是测试代码的人员刚开始理解代码的时候可能会遇到一些困难;