Swift 基本运算符

运算符是检查、改变、合并值的特殊符号或短语。例如,加号(+)将两个数字相加(如let I = 1+2)。更复杂的运算例子包括逻辑与运算符&&(如if EnterdDoorCode && PassedRetinaScan)。

Swift支持支持大部分C语言运算符,且为了减少常见编码错误做出了部分改进。如:赋值符(=)不再会有返回值,这样就消除了手误将判等运算符(==)写成赋值符导致代码错误的缺陷。算术运算符(+,-,*,/,% 等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。具体我会在 溢出运算符 一节中讲到。

Swift 还提供了 C 语言没有的区间运算符,例如 a..<b 或 a…b,这方便我们表达一个区间内的数值。

本章节只描述了 Swift 中的基本运算符,高级运算符 这章会包含 Swift 中的高级运算符,及如何自定义运算符,及如何进行自定义类型的运算符重载。


术语

运算符分为一元、二元和三元运算符:

一元运算符对单一操作对象操作(如 -a)。一元运算符分前置运算符和后置运算符,前置运算符需紧跟在操作对象之前(如 !b),后置运算符需紧跟在操作对象之后(如 c!)。

二元运算符操作两个操作对象(如 2 + 3),是中置的,因为它们出现在两个操作对象之间。

三元运算符操作三个操作对象,和 C 语言一样,Swift 只有一个三元运算符,就是三目运算符(a ? b : c)。

受运算符影响的值叫操作数,在表达式 1 + 2 中,加号 + 是二元运算符,它的两个操作数是值 1 和 2。


赋值运算符、算术运算符、求余运算符、一元负号运算符、一元正号运算符


组合赋值运算符

如同 C 语言,Swift 也提供把其他运算符和赋值运算(=)组合的组合赋值运算符,组合加运算(+=)是其中一个例子:

var a = 1
a += 2

表达式 a += 2 是 a = a + 2 的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。

注意

复合赋值运算没有返回值,let b = a += 2 这类代码是错误。这不同于上面提到的自增和自减运算符。


比较运算符(Comparison Operators)

如果两个元组的元素相同,且长度相同的话,元组就可以被比较。比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如:

(1, "zebra") < (2, "apple")   // true,因为 1 小于 2
(3, "apple") < (3, "bird")    // true,因为 3 等于 3,但是 apple 小于 bird
(4, "dog") == (4, "dog")      // true,因为 4 等于 4,dog 等于 dog

在上面的例子中,你可以看到,在第一行中从左到右的比较行为。因为 1 小于 2,所以 (1, “zebra”) 小于 (2, “apple”),不管元组剩下的值如何。所以 “zebra” 大于 “apple” 对结果没有任何影响,因为元组的比较结果已经被第一个元素决定了。不过,当元组的第一个元素相同时候,第二个元素将会用作比较-第二行和第三行代码就发生了这样的比较。

当元组中的元素都可以被比较时,你也可以使用这些运算符来比较它们的大小。例如,像下面展示的代码,你可以比较两个类型为 (String, Int) 的元组,因为 Int 和 String 类型的值可以比较。相反,Bool 不能被比较,也意味着存有布尔类型的元组不能被比较。

("blue", -1) < ("purple", 1)       // 正常,比较的结果为 true
("blue", false) < ("purple", true) // 错误,因为 < 不能比较布尔类型

三元运算符

三元运算符的特殊在于它是有三个操作数的运算符,它的形式是 问题 ? 答案 1 : 答案 2。它简洁地表达根据 问题成立与否作出二选一的操作。如果 问题 成立,返回 答案 1 的结果;反之返回 答案 2 的结果。

三元运算符是以下代码的缩写形式:

if question {
    answer1
} else {
    answer2
}

这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出 50 点;如果没有表头,只需高出 20 点:

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)

上面的写法比下面的代码更简洁:

let contentHeight = 40
let hasHeader = true
var rowHeight = contentHeight
if hasHeader {
    rowHeight = rowHeight + 50
} else {
    rowHeight = rowHeight + 20
}

第一段代码例子使用了三元运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 rowHeight 定义成变量,因为它的值无需在 if 语句中改变。

三元运算为二选一场景提供了一个非常便捷的表达形式。不过需要注意的是,滥用三元运算符会降低代码可读性。所以我们应避免在一个复合语句中使用多个三元运算符。


空合运算符(Nil Coalescing Operator)

空合运算符(a ?? b)将对可选类型 a 进行空判断,如果 a 包含一个值就进行解包,否则就返回一个默认值 b。表达式 a 必须是 Optional 类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。

空合运算符是对以下代码的简短表达方法:

a != nil ? a! : b

上述代码使用了三元运算符。当可选类型 a 的值不为空时,进行强制解封(a!),访问 a 中的值;反之返回默认值 b。无疑空合运算符(??)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。

下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择:

let defaultColorName = "red"
var userDefinedColorName: String?   //默认值为 nil
var colorNameToUse = userDefinedColorName ?? defaultColorName

userDefinedColorName 变量被定义为一个可选的 String 类型,默认值为 nil。由于 userDefinedColorName 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 colorNameToUse 的变量赋予一个字符串类型初始值。 由于 userDefinedColorName 值为空,因此表达式 userDefinedColorName ?? defaultColorName 返回 defaultColorName 的值,即 red。

如果你分配一个非空值(non-nil)给 userDefinedColorName,再次执行空合运算,运算结果为封包在 userDefaultColorName 中的值,而非默认值。

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName

区间运算符(Range Operators)

Swift 提供了几种方便表达一个区间的值的区间运算符。

  1. 闭区间运算符

闭区间运算符(a…b)定义一个包含从 a 到 b(包括 a 和 b)的所有值的区间。a 的值不能超过 b。

闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 for-in 循环中:

for index in 1...5 {
    print("\(index) * 5 = \(index * 5)")
}
// 1 * 5 = 5
// 2 * 5 = 10
// 3 * 5 = 15
// 4 * 5 = 20
// 5 * 5 = 25
  1. 半开区间运算符

半开区间运算符(a..<b)定义一个从 a 到 b 但不包括 b 的区间。 之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。

半开区间的实用性在于当你使用一个从 0 开始的列表(如数组)时,非常方便地从0数到列表的长度。

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
    print("第 \(i + 1) 个人叫 \(names[i])")
}
// 第 1 个人叫 Anna
// 第 2 个人叫 Alex
// 第 3 个人叫 Brian
// 第 4 个人叫 Jack

数组有 4 个元素,但 0..<count 只数到3(最后一个元素的下标),因为它是半开区间。

  1. 单侧区间

闭区间操作符有另一个表达形式,可以表达往一侧无限延伸的区间 —— 例如,一个包含了数组从索引 2 到结尾的所有值的区间。在这些情况下,你可以省略掉区间操作符一侧的值。这种区间叫做单侧区间,因为操作符只有一侧有值。例如:

for name in names[2...] {
    print(name)
}
// Brian
// Jack

for name in names[...2] {
    print(name)
}
// Anna
// Alex
// Brian

半开区间操作符也有单侧表达形式,附带上它的最终值。就像你使用区间去包含一个值,最终值并不会落在区间内。例如:

for name in names[..<2] {
    print(name)
}
// Anna
// Alex

单侧区间不止可以在下标里使用,也可以在别的情境下使用。你不能遍历省略了初始值的单侧区间,因为遍历的开端并不明显。你可以遍历一个省略最终值的单侧区间;然而,由于这种区间无限延伸的特性,请保证你在循环里有一个结束循环的分支。你也可以查看一个单侧区间是否包含某个特定的值,就像下面展示的那样。

let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

逻辑运算符(Logical Operators)

逻辑运算符的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。

逻辑非(!a)

逻辑与(a && b)

逻辑或(a || b)

  1. 逻辑非运算符

逻辑非运算符(!a)对一个布尔值取反,使得 true 变 false,false 变 true。

它是一个前置运算符,需紧跟在操作数之前,且不加空格。读作 非 a ,例子如下:

let allowedEntry = false
if !allowedEntry {
    print("ACCESS DENIED")
}

if !allowedEntry 语句可以读作「如果非 allowedEntry」,接下一行代码只有在「非 allowedEntry」为 true,即 allowEntry 为 false 时被执行。

在示例代码中,小心地选择布尔常量或变量有助于代码的可读性,并且避免使用双重逻辑非运算,或混乱的逻辑语句。

  1. 逻辑与运算符

逻辑与运算符(a && b)表达了只有 a 和 b 的值都为 true 时,整个表达式的值才会是 true。

只要任意一个值为 false,整个表达式的值就为 false。事实上,如果第一个值为 false,那么是不去计算第二个值的,因为它已经不可能影响整个表达式的结果了。这被称做短路计算(short-circuit evaluation)。

以下例子,只有两个 Bool 值都为 true 的时候才允许进入 if:

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
  1. 逻辑或运算符

逻辑或运算符(a || b)是一个由两个连续的 | 组成的中置运算符。它表示了两个逻辑表达式的其中一个为 true,整个表达式就为 true。

同逻辑与运算符类似,逻辑或也是「短路计算」的,当左端的表达式为 true 时,将不计算右边的表达式了,因为它不可能改变整个表达式的值了。

以下示例代码中,第一个布尔值(hasDoorKey)为 false,但第二个值(knowsOverridePassword)为 true,所以整个表达是 true,于是允许进入:

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}

逻辑运算符组合计算

我们可以组合多个逻辑运算符来表达一个复合逻辑:

if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}

这个例子使用了含多个 && 和 || 的复合逻辑。但无论怎样,&& 和 || 始终只能操作两个值。所以这实际是三个简单逻辑连续操作的结果。我们来解读一下:

如果我们输入了正确的密码并通过了视网膜扫描,或者我们有一把有效的钥匙,又或者我们知道紧急情况下重置的密码,我们就能把门打开进入。

前两种情况,我们都不满足,所以前两个简单逻辑的结果是 false,但是我们是知道紧急情况下重置的密码的,所以整个复杂表达式的值还是 true。

注意:Swift 逻辑操作符 && 和 || 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。


使用括号来明确优先级

为了一个复杂表达式更容易读懂,在合适的地方使用括号来明确优先级是很有效的,虽然它并非必要的。在上个关于门的权限的例子中,我们给第一个部分加个括号,使它看起来逻辑更明确:

if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}

这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。

Swift 基础部分


Swift是一门开发 iOS、MacOS、WatchOS和TVOS应用的语言。然而,如果你有学过C或者Object-C开发的经验的话,你会发现Swift的很多内容都是你熟悉的。但个人认为Swift的形式很像Python。

Swift包含了C和Object-C的所有基础数据类型,Int表示整型;DoubleFoloat表示浮点型;Bool表示布尔类型;String是文本型数据。Swift还提供了三个基本集合类型:ArraySetDictionary,这个之后的‘集合类型’一文中会详细介绍。

就像C语言一样,Swift使用变量来进行存储并通过变量名来关联值。在Swift中,广泛的使用着值不可变的变量,它们就是常量,而且比C语言的常量更强大。在Swift中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰的表达你的意图。

除了我们熟悉的类型,Swift还增加了Object-C中没有的高阶数据类型比如说元组Tuple。元组可以让你创建或传递一组数据,比如作为函数的返回值,比如作为函数的返回值时,你可以用一个元组返回多个值。

Swift还添加了可选Optional类型,用于处理值缺失情况。可选表示“有一个值,并且它等于X”或者“没有值”。可选类型有点类似于Object-C中的nil,但是它可以使用在任何类型上,不仅仅是类。可选类型比Object-C中的nil指针更加安全也更加具有表现力,它是Swift许多强大特性的重要组成部分。

Swift是一门类型安全的语言,这意味着Swift可以让你清楚的知道值的类型。如果你的代码需要一个String,类型安全会阻止你不小心传入一个Int。同样的,如果你的代码需要一个String,类型安全会阻止你意外传入一个可选的String。类型安全可以帮助你在开发阶段早期发现并修正错误。


常量和变量

常量和变量把一个名字(例如MaximumNumberOfLoginAttemps或者WelcomeMessage)和一个指定类型的值(比如说数字10或者字符串Hello)关联起来。常量的值一旦设定就不能改变,而变量的值可以随意更改。


声明常量和变量

常量和变量必须在使用前声明,使用let来声明常量,用var来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数。

let MaximumNumberOfLoginAttempts = 10
var CurrentLoginAttemts = 0

这两段代码可以理解为:

声明一个名为MaximumNumberOfloginAttempts的新常量,并给它一个值10。然后声明一个名为CurrentloginAttempts的新变量并将它的值初始化为0

在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。

你可以在一行中声明多个常量和多个变量,用逗号隔开:

var x=0.0,y=0.0,z=0.0

类型注解

当你声明变量或者常量的时候可以加上类型注解(type annotation),说明常量或变量中要存储的值的类型。如果要添加类型注解,需要在常量或变量名后面加上一个冒号和一个空格,然后加上类型名称。

var WelcomMessage = "Hello"

这个例子给WelcomMessage变量添加了类型注解,表示这个变量可以存储String类型的值。

var WelcomMessage: String

你可以子啊一行定义中定义多个同种类型的变量,用逗号分隔,并在最后一个变量名之后添加一个类型注解:

var red,green,blue: Double

常量和变量的命名

常量和变量可以包含任何字符,包括Unicode字符:

let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "DogAndCow"

常量与变量名不能包含数字符号,箭头,保留的(或非法的)Unicode码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。

一旦你将常量或者变量声明为确定的类型,你不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时你也不能将常量与变量进行互转。

对于变量,你可以改现有的值为其他同类型的值,例如:

var FriendWelcom = "Hello!"
FriendWelcom = "Hola"

但常量的值一旦确定就不能修改了。下面这个例子会导致编译时报错。

let LanguageName = "Swift"
LanguageName = "Swift++"

输出常量和变量

你可以使用print(_:separator:teminator:)函数来输出当前常量或者变量的值:

print(FriendWelcom)

print(_:separator:teminator:)是用来输出一个或者多个值到适当输出区的全局函数。如果你用Xcode的话,print(_:separator:teminator)将会输出内容到“console”面板上。separatorreminator参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,改参数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给termination参数—-例如,print(SomeValue,terminator:””)


注释

单行注释//,多行注释/*……*/

Swift的多行注释可以嵌套。

/* 这是第一个多行注释的开头
/* 这是第二个被嵌套的多行注释 */
这是第一个多行注释的结尾 */

分号

与其他大部分编程语言不同,Swift 并不强制要求你在每条语句的结尾处使用分号;,当然,你也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句:

let Cat = "🐱"; print(Cat)

整数

整数就是没有小数部分的数字。整数可以是有符号的或者无符号的。

Swift提供了8、16、32和64位的有符号和无符号整型这些整型类型和C语言的命名方式很类似比如8位无符号整型是UInt8,32位有符号整型就是Int32。就像Swift其他类型一样,整数类型采用大写命名法。


整数范围

你可以访问不同整型的minmax属性来获取对应类型的最大值和最小值:

let MinValue = UInt8.min // 0
let MaxValue = UInt8.max // 256

maxmin所传回值的类型是其对应的整数类型(如上例中的UInt8,所传回的类型是UInt8),科用在表达式中相同类型值旁。


整型 Int&UInt

一般来说你不需要指定整数的长度。Swift提供一个特殊的整型Int、无符号整型UInt,长度与当前平台原生字长相同:

  • 在32位平台上,IntInt32长度相同,UIntUInt32长度相同。
  • 在64位平台上,IntInt64长度相同,UIntUInt64长度相同。

除非你需要特定长度的整型,一般就用Int。这可以提高代码的一致性和可复用性。


浮点数 Float&Double

浮点数是有小数部分的数字。

浮点数类型比整数类型表示的范围更大(科学计数法)。

Swift提供了两种有符号浮点类型:

  • Double表示64位浮点数。
  • Float表示32位浮点数。

类型安全和类型判断

Swift 是一个类型安全(type safe)的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个 String,你绝对不可能不小心传进去一个 Int

由于 Swift 是类型安全的,所以它会在编译你的代码时进行类型检查(type checks),并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。

当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型,Swift 会使用类型推断(type inference)来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。

因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。

当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如 42 和 3.14159 。)

例如,如果你给一个新常量赋值 42 并且没有标明类型,Swift 可以推断出常量类型是 Int ,因为你给它赋的初始值看起来像一个整数:

let MeaningOfLife = 42

同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是 Double

let Pi = 3.14159

当推断浮点数的类型时,Swift 总是会选择 Double 而不是 Float

如果表达式中同时出现了整数和浮点数,会被推断为 Double 类型:

let AnotherPi = 3 + 0.14159

原始值 3 没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为 Double 类型。


数值型字面量

整数字面量可以被写作:

  • 一个十进制数,没有前缀
  • 一个二进制数,前缀是 0b
  • 一个八进制数,前缀是 0o
  • 一个十六进制数,前缀是 0x

下面的所有整数字面量的十进制值都是 17:

let decimalInteger = 17
let binaryInteger = 0b10001
let octalInteger = 0o21
let hexadecimalInteger = 0x11

浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是 0x )。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 e 来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 p 来指定。

如果一个十进制数的指数为 exp,那这个数相当于基数和 10^exp 的乘积:

  • 1.25e2 表示 1.25 × 10^2,等于 125.0。
  • 1.25e-2 表示 1.25 × 10^-2,等于 0.0125。

如果一个十六进制数的指数为 exp,那这个数相当于基数和 2^exp 的乘积:

  • 0xFp2 表示 15 × 2^2,等于 60.0。
  • 0xFp-2 表示 15 × 2^-2,等于 3.75。

下面的这些浮点字面量都等于十进制的 12.1875:

let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0

数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:

let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1

数值类型转换

通常来讲,即使代码中的整数常量和变量已知非负,也请使用 Int 类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。

只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。


整数转换

不同整数类型的变量和常量可以存储不同范围的数字。Int8 类型的常量或者变量可以存储的数字范围是 -128~127,而 UInt8 类型的常量或者变量能存储的数字范围是 0~255。如果数字超出了常量或者变量可存储的范围,编译的时候会报错:

let CannotBeNegative: UInt8 = -1
let TooBig: Int8 = Int8.max + 1

由于每种整数类型都可以存储不同范围的值,所以你必须根据不同情况选择性使用数值型类型转换。这种选择性使用的方式,可以预防隐式转换的错误并让你的代码中的类型转换意图变得清晰。

要将一种数字类型转换成另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。在下面的例子中,常量 TwoThousandUInt16 类型,然而常量 OneUInt8 类型。它们不能直接相加,因为它们类型不同。所以要调用 UInt16(One) 来创建一个新的 UInt16 数字并用 One 的值来初始化,然后使用这个新数字来计算:

let TwoThousand: UInt16 = 2000
let One: UInt8 = 1
let TwoThousandAndOne = TwoThousand + UInt16(One)

现在两个数字的类型都是 UInt16,可以进行相加。目标常量 TwoThousandAndOne 的类型被推断为 UInt16,因为它是两个 UInt16 值的和。

SomeType(ofInitialValue) 是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,UInt16 有一个构造器,可以接受一个 UInt8 类型的值,所以这个构造器可以用现有的 UInt8 来创建一个新的 UInt16。注意,你并不能传入任意类型的值,只能传入 UInt16 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型)。


整数和浮点数转换

整数和浮点数的转换必须显式指定类型:

let Three = 3
let PointOneFourOneFiveNine = 0.14159
let Pi = Double(Three) + PointOneFourOneFiveNine

这个例子中,常量 Three 的值被用来创建一个 Double 类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。

浮点数到整数的反向转换同样行,整数类型可以用 Double 或者 Float 类型来初始化:

let IntegerPi = Int(Pi)

当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 4.75 会变成 4-3.9 会变成 -3


类型别名

类型别名(type aliases)就是给现有类型定义另一个名字。你可以使用 typealias 关键字来定义类型别名。

当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据:

typealias AudioSample = UInt16

定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:

var MaxAmplitudeFound = AudioSample.min // 0

本例中,AudioSample 被定义为 UInt16 的一个别名。因为它是别名,AudioSample.min 实际上是 UInt16.min,所以会给 MaxAmplitudeFound 赋一个初值 0。


布尔值

Swift有一个基本的布尔(boolean)类型,叫做Bool。布尔值指逻辑上的值,因为它们只能是真或者假。Swift布尔值有两个常量,truefalse

let OrangesAreOrange = true
let TurnipsAreDelicious = false

OrangeAreOrangeTurnipsAreDelicious的类型会被推断为Bool,因为它们的初值是布尔字面量。就和之前的Int以及Double一样,如果你创建变量的时候给它们赋值为true或者false,那你不需要将常量和变量赋值为Bool类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让代码更加简洁并且可读性更高。

当你编写条件语句比如if的时候,布尔值非常有用:

if TurnipsAreDelicious {
	print("Mmm, tasty turnips!")
} else {
	print("Eww, turnips are horrible.")
}

条件语句在之后的控制流中会详细讲解。

如果你在需要使用布尔值的地方使用了非布尔值,Swift的类型安全机制会报错。比如下面的例子会报错:

let I: Int = 1
if I {
}

但是下面这个是合法的:

let I: Int = 1
if I == 1 {
}

I == 1的比较结果是布尔类型,所以第二个例子可以通过类型检查,类似I == 1 这种类型的比较,在下一节基本操作符会详细讲解。


元组

元组(tuples)把多个值组合成一个符合值,元组内的值可以是任何类型,并不要求是相同类型。

下面这个例子中,(404, "Not Found" )是一个描述HTTP状态码(HTTP status code)的元组。HTTP状态码是当你请求网页的时候Web服务器返回的一个特殊值。如果你请求的网页不存在的话就会返回这个状态码。

let Http404Error = (404,"Not Found”)

(404, "Not Found" )元组把一个Int值和一个String值组合起来表示HTTP状态码的两个部分:一个数字和一个可读的描述。这个元组可以被描述为“一个类型为(Int,String)的元组”。

你可以把任意顺序的类型组成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为(Int,Int,Int)或者(String,Bool)或者其他任何你想要的组合的元组。

你可以将一个元组的内容分解(decompose)单独成的常量或者变量,然后你就可以正常使用它们了:

let (StatusCode, StatusMessage) = Http404Error
print("The status code is \(StatusCode)")
print("The status message is \(StatusMessage)")

如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(_)标记:

let (JustTheStatusCode, _) = Http404Error
print("The status code is \(JustTheStatusCode)")

此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:

print("The status code is \(Http404Error.0)")
print("The status message is \(Http404Error.1)")

你可以在定义元组的时候给单个元素命名:

let Http200Status = (StatusCode: 200, Description: "OK")

给元组中的元素命名后,你可以通过名字来获取这些元素的值:

print("The status code is \(Http200Status.StatusCode)")
print("The status message is \(Http200Status.Description)")

作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 (Int, String) 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。这个会在之后函数参数与返回值一节中讲到。


可选类型

使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示两种可能: 或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。

C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回 nil,nil 表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如 NSNotFound)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值。

来看一个例子。Swift 的 Int 类型有一种构造器,作用是将一个 String 值转换成一个 Int 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 “123” 可以被转换成数字 123 ,但是字符串 “hello, world” 不行。

下面的例子使用这种构造器来尝试将一个 String 转换成 Int:

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)

因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。一个可选的 Int 被写作 Int? 而不是 Int。问号暗示包含的值是可选类型,也就是说可能包含 Int 值也可能不包含值。(不能包含其他任何值比如 Bool 值或者 String 值。只能是 Int 或者什么都没有。)


nil

你可以给可选变量赋值为 nil 来表示它没有值:

var ServerResponseCode: Int? = 404
ServerResponseCode = nil

nil 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。

如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil:

var SurveyAnswer: String?

Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。


if 语句以及强制解析

你可以使用 if 语句和 nil 比较来判断一个可选值是否包含值。你可以使用“相等”(==)或“不等”(!=)来执行比较。

如果可选类型有值,它将不等于 nil:

if ConvertedNumber != nil {
    print("convertedNumber contains some integer value.")
}

当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping):

if ConvertedNumber != nil {
    print("convertedNumber has an integer value of \(ConvertedNumber!).")
}

更多关于 if 语句的内容,会在 控制流一节中详细说。

使用 ! 来获取一个不存在的可选值会导致运行时错误。使用 ! 来强制解析值之前,一定要确定可选包含一个非 nil 的值。


可选绑定

使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 if 和 while 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。

像下面这样在 if 语句中写一个可选绑定:

if let ConstantName = SomeOptional {
    Statements
}

你可以像上面这样使用可选绑定来重写 在 可选类型 举出的 possibleNumber 例子:

if let actualNumber = Int(possibleNumber) {
    print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
    print("\'\(possibleNumber)\' could not be converted to an integer")
}

这段代码可以被理解为:

“如果 Int(possibleNumber) 返回的可选 Int 包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。”

如果转换成功,actualNumber 常量可以在 if 语句的第一个分支中使用。它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。在这个例子中,actualNumber 只被用来输出转换结果。

你可以在可选绑定中使用常量和变量。如果你想在 if 语句的第一个分支中操作 actualNumber 的值,你可以改成 if var actualNumber,这样可选类型包含的值就会被赋给一个变量而非常量。d

你可以包含多个可选绑定或多个布尔条件在一个 if 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为 nil,或者任意一个布尔条件为 false,则整个 if 条件判断为 false,这时你就需要使用嵌套 if 条件语句来处理,如下所示:

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        if firstNumber < secondNumber && secondNumber < 100 {
            print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}

在 if 条件语句中使用常量和变量来创建一个可选绑定,仅在 if 语句的句中(body)中才能获取到值。相反,在 guard 语句中使用常量和变量来创建一个可选绑定,仅在 guard 语句外且在语句后才能获取到值,会在提前退出一节中详细讲。


隐式解析可选类型

如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过 if 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。

有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。

这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型。

当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考 无主引用以及隐式解析可选属性。

一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 String 和隐式解析可选类型 String 之间的区别:

let PossibleString: String? = "An optional string."
let ForcedString: String = PossibleString!
let AssumedString: String! = "An implicitly unwrapped optional string."
let ImplicitString: String = AssumedString

你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。

如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。

你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值:

if AssumedString != nil {
    print(AssumedString!)
}

你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值:

if let definiteString = assumedString {
    print(definiteString)
}

如果一个变量之后可能变成 nil 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 nil 的话,请使用普通可选类型。


错误处理

你可以使用 错误处理(error handling) 来应对程序执行中可能会遇到的错误条件。

相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。

当一个函数遇到错误条件,它能报错。调用函数的地方能抛出错误消息并合理处理。

func CanThrowAnError() throws {
    // 这个函数有可能抛出错误
}

一个函数可以通过在声明中添加 throws 关键词来抛出错误消息。当你的函数能抛出错误消息时,你应该在表达式中前置 try 关键词。

do {
    try canThrowAnError()
    // 没有错误消息抛出
} catch {
    // 有一个错误消息抛出
}

一个 do 语句创建了一个新的包含作用域,使得错误能被传播到一个或多个 catch 从句。

这里有一个错误处理如何用来应对不同错误条件的例子。

func makeASandwich() throws {
    // ...
}
do {
    try makeASandwich()
    eatASandwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

在此例中,makeASandwich()(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为 makeASandwich() 抛出错误,函数调用被包裹在 try 表达式中。将函数包裹在一个 do 语句中,任何被抛出的错误会被传播到提供的 catch 从句中。

如果没有错误被抛出,eatASandwich() 函数会被调用。如果一个匹配 SandwichError.outOfCleanDishes 的错误被抛出,washDishes() 函数会被调用。如果一个匹配 SandwichError.missingIngredients 的错误被抛出,buyGroceries(_:) 函数会被调用,并且使用 catch 所捕捉到的关联值 String 作为参数。

抛出,捕捉,以及传播错误会在 错误处理 章节详细说明。


使用断言进行调试

你可以调用 Swift 标准库的 assert(::file:line:) 函数来写一个断言。向这个函数传入一个结果为 true 或者 false 的表达式以及一条信息,当表达式的结果为 false 的时候这条信息会被显示:

let Age = -3
assert(Age >= 0, "A person's age cannot be less than zero")

在这个例子中,只有 age >= 0 为 true 时,即 age 的值非负的时候,代码才会继续执行。如果 age 的值是负数,就像代码中那样,age >= 0 为 false,断言被触发,终止应用。

如果不需要断言信息,可以就像这样忽略掉:

assert(Age >= 0)

如果代码已经检查了条件,你可以使用 assertionFailure(_:file:line:) 函数来表明断言失败了,例如:

if Age > 10 {
    print("You can ride the roller-coaster or the ferris wheel.")
} else if Age > 0 {
    print("You can ride the ferris wheel.")
} else {
    assertionFailure("A person's age can't be less than zero.")
}

强制执行先决条件

当一个条件可能为假,但是继续执行代码要求条件必须为真的时候,需要使用先决条件。例如使用先决条件来检查是否下标越界,或者来检查是否将一个正确的参数传给函数。

你可以使用全局 precondition(_:_:file:line:) 函数来写一个先决条件。向这个函数传入一个结果为 true 或者 false 的表达式以及一条信息,当表达式的结果为 false 的时候这条信息会被显示:

precondition(index > 0, "Index must be greater than zero.")

你可以调用 preconditionFailure(_:file:line:) 方法来表明出现了一个错误,例如,switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。

如果你使用 unchecked 模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为 true(真),他将优化你的代码。然而,fatalError(_:file:line:) 函数总是中断执行,无论你怎么进行优化设定。

你能使用 fatalError(_:file:line:) 函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上 fatalError(“Unimplemented”)作为具体实现。因为 fatalError 不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。

Swift 初见


声明:

这里使用的Swift版本为 Swift 5.1 运行环境为Mac OS Catanina 10.15 developerbeta上运行Xcode 11.0版本

本文部分代码来自 gitbook


通常来说,学习一门编程语言第一件事就是学习输出“Hello world!”。在Swift上,可以用一行代码实现:

print(“Hello world!”)

如果你学习过python,你会很熟悉这种形式;但如果之前只学过C或者Java的,对这种形式可能不太熟悉—-在Swift中,这一行代码就是一个完整的程序。你不需要为了输入输出或者字符串处理导入一个单独的库。全局作用域的代码会被自动当作成程序的入口,所以你也不需要main()函数。你同样不需要在每个语句结尾写山分号。


我们先来简单接触一下Swift语言,知识点会在接下来的文章逐个讲解。


简单值

使用let来申明常量,var来申明变量。

一个常量的值,在编译时,并不需要有明确的值(不一定是一个值,可以是文件等其他东西),但是你只能为它赋一次值。这说明你可以用一个常量来命名一个值,一次赋值就可在多处使用。

var Variable=1231
Variable=2000
let Constant=20001231

常量和变量的类型必须和你赋给它的值一致。然而你不用明确地声明它的类型。当你通过一个值来声明变量和常量时,编译器会自动推断其类型。如:在上面的程序中 Variable会被自动推断为整型,因为它的初始值是整型。

如果初始值没有提供足够的信息(或者是没有初始值),那你需要在变量后面声明类型,用冒号分隔(注意格式,和其他的语言不太一样)。

let IsInteger = 70
let IsDouble = 70.0
let ISDouble: Double = 70

值永远不会隐式转换为其他类型。如果你需要吧其他值转为其他类型,必须显示转换。

let Str = "The width is "
let Width = 22
let StrForWidth = Str + string(Width)

还有一种更简单的办法:将值写入括号中,并在括号之前写一个反斜杠/

let Width = 22
let Str = "The width is /(Width)."

使用一对三个单引号(“””)来包含多行内容,字符串中的内容(包括引号、空格和换行符等)都会保留下来。

let ApplesNumber = 3
let OrangesNumber = 5
let Quotation = """
I said "I have \(Apples) apples."
I said "I have \(Apples + Oranges) pieces of fruit."
"""

使用方括号[]来创建数组和字典,并使用下标和键(key)来访问元素。

最后一个元素后面允许有逗号。

var ShoppingList = ["catfish", "water", "tulips", "blue paint"]
ShoppingList[1] = "bottle of water"
var Occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
Occupations["Jayne"] = "Public Relations"

使用初始化语言来创建一个空数组或者空字典。

var EmptyArray = [String]()
var EmptyDictionary = [String: String]()

如果类型信息可以被推断出来,你可以使用[]和[:]来创建空数组和空字典(就像你声明变量或者给函数传参的时候一样,编译器会自动推断类型)

var ShoppingList = []
var Occupations = [:]

控制流

使用 ifswitch 来进行条件操作,使用 for-inwhilerepeat-while 来进行循环。包裹条件和循环变量的括号可以省略,但是语句体的大括号是必须的。

let IndividualScores = [75, 43, 103, 87, 12]
var TeamScore = 0
for Score in IndividualScores {
    if Score > 50 {
        TeamScore += 3
    } else {
        TeamScore += 1
    }
}
print(TeamScore)

在 if 语句中,条件必须是一个布尔表达式——这意味着像 if score { ... }这样的代码将报错,而不会隐形地与 0 做对比。

你可以一起使用 iflet 一起来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是 nil 以表示值缺失。在类型后面加一个问号(?)来标记这个变量的值是可选的。

var OptionalString: String? = "Hello"
print(OptionalString == nil)
var OptionalName: String? = "John Appleseed"
var Greeting = "Hello!"
if let name = optionalName {
    Greeting = "Hello, \(name)"
}

如果变量的可选值是 nil,条件会判断为 false,大括号中的代码会被跳过。如果不是 nil,会将值解包并赋给 let 后面的常量,这样代码块中就可以使用这个值了。 另一种处理可选值的方法是通过使用 ?? 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。

var NickName: String?
var FullName: String = "John Appleseed"
let InformalGreeting = "Hi \(NickName ?? FullName)"

switch 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。

let Vegetable = "red pepper"
switch Vegetable {
	case "celery":
	    print("Add some raisins and make ants on a log.")
	case "cucumber", "watercress":
	    print("That would make a good tea sandwich.")
	case let x where x.hasSuffix("pepper"):
	    print("Is it a spicy \(x)?")
	default:
	    print("Everything tastes good in soup.")
}

注意 let 在上述例子的等式中是如何使用的,它将匹配等式的值赋给常量 x

运行 switch 中匹配到的 case 语句之后,程序会退出 switch 语句,并不会继续向下运行,所以不需要在每个子句结尾写 break

你可以使用 for-in 来遍历字典,需要一对儿变量来表示每个键值对。字典是一个无序的集合,所以他们的键和值以任意顺序迭代结束。

let InterestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var Largest = 0
for (Kind, Numbers) in InterestingNumbers {
    for Number in Numbers {
        if Number > Largest {
            Largest = Number
        }
    }
}
print(Largest)

使用 while 来重复运行一段代码直到条件改变。循环条件也可以在结尾,保证能至少循环一次。

var n = 2
while n < 100 {
    n *= 2
}
print(n)
var m = 2
repeat {
    m *= 2
} while m < 100
print(m)

你可以在循环中使用 ..< 来表示下标范围。

var Total = 0
for i in 0..<4 {
    Total += i
}
print(Total)

使用 ..< 创建的范围不包含上界,如果想包含的话需要使用 ...


函数和闭包

使用func来声明一个函数,使用名字和参数来调用函数。

使用->来指定函数返回类型。

func Greeting (Person :String ,Day: String) -> String{
	return "Hello \(Person), today is \(Day)."
}
print(Greeting(Person:"Bob", Day: "Tuesday”))

默认情况下,函数使用它们的参数名称作为参数标签,在参数名称前可自定义参数标签,或使用_表示不使用参数标签。

func Greeting (_ Person :String ,Tag Day: String) -> String{
	return "Hello \(Person), today is \(Day)."
}
print(Greeting("Bob", Tag: "Tuesday”))

使用元组来生成复合值,比如让一个函数来返回多个值。改元组的元素可以用名称和数字来获取。

func CalculateStatics(Sorces: [Int])->(Min:Int,Max:Int,Sum:Int){
	var Min = Sorces[0]
	var Max = Sorces[0]
	var Sum = 0
	for sorce in Sorces{
		if sorce > Max{
			Max = sorce
		}
		if sorce < Min{
			Min = sorce
		}
		sum += sorce
	}
	return (Min,Max,Sum)
}
let Ans = CalculatStatics(Sorces:[1,2,3,4,5,6,7,8,9,0])
print(Ans.Sum)
print(Ans.2)

函数可以嵌套。

被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。

func Fifteen()->Int{
	var Number = 10
	func AddFive(){
		y+=5
	}
	add()
	return y
}
print(Fifteen())

函数是第一等类型,这意味着函数可以作为另一个函数的返回值。

func MakeIncrementer() -> ((Int)->Int){
	func AddOne(Number: Int)->Int{
		return Number + 1
	}
	return AddOne
}
var Incrementer=MakeIncrementer()
print(Incrementer(4))

函数也可以当作参数传入另一个函数

func HasAnyMatches(List: [Int],Condition: (Int) ->Bool) -> Bool{
	for Itme in List {
		if Condition(Itme){
			return true
		}
	}
	return false
}
func IsLessTen(Number: Int)->bool{
	return Number < 10
}
var Numbers = [1,2,3,4,5,6,7,8,9,10]
print(HasAnyMatches(List: Numbers,Condition: IsLessTen))

函数实际上是一种特殊的闭包:它是一段之后能被调用的代码。闭包中的代码能访问闭包作用域中的变量和函数,即使闭包是在一个不同的作用域执行的 — 这一部分已经在嵌套函数中有了。你可以使用{}来创建一个匿名闭包。使用in将参数和返回值类型的声明与闭包函数体进行分离。

Numbers.map({
	(Number: Int)-> Int in
	let Result = 3*Number
	return Result
})

有很多种创建更简洁的闭包方法。

如果一个闭包的类型已知,比如作为一个代理的回调,你可以忽略参数,返回值,甚至两个都可以忽略。单个语句闭包会把它语句的值当作返回值。

let MappedNumbers = Number.map({Number in 3 * Number})
print(MappedNumbers)

你可以通过参数位置和参数名来引用参数—-这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在括号后面。当一个闭包是传给其函数的唯一参数,你可以完全忽略括号。

let SortedNumbers = Number.sorted{$0 > $1}
print (SortedNumbers)

对象和类

使用 class 和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

这个版本的 Shape 类缺少了一些重要的东西:一个构造函数来初始化类实例。使用 init 来创建一个构造器。

class NamedShape {
    var numberOfSides: Int = 0
    var name: String
    init(name: String) {
        self.name = name
    }
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

注意 self 被用来区别实例变量 name 和构造器的参数 name。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像 numberOfSides)还是通过构造器(就像 name)。

如果你需要在对象释放之前进行一些清理工作,使用 deinit创建一个析构函数。

子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以根据需要添加或者忽略父类。

子类如果要重写父类的方法的话,需要用 override 标记——如果没有添加 override 就重写父类方法的话编译器会报错。编译器同样会检测 override 标记的方法是否确实在父类中。

class Square: NamedShape {
    var sideLength: Double
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }
    func area() ->  Double {
        return sideLength * sideLength
    }
    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

除了储存简单的属性之外,属性可以有 gettersetter

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
    var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }
    override func simpleDescription() -> String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)

perimetersetter 中,新值的名字是 newValue。你可以在 set 之后显式的设置一个名字。

注意 EquilateralTriangle 类的构造器执行了三步:

1.设置子类声明的属性值

2.调用父类的构造器

3.改变父类定义的属性值。其他的工作比如调用方法、getterssetters 也可以在这个阶段完成。

如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用 willSetdidSet。写入的代码会在属性值发生改变时调用,但不包含构造器中发生值改变的情况。比如,下面的类确保三角形的边长总是和正方形的边长相同。

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)

处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 ?。如果 ? 之前的值是 nil? 后面的东西都会被忽略,并且整个表达式返回 nil。否则,? 之后的东西都会被运行。在这两种情况下,整个表达式的值也是一个可选值。

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

枚举和结构体

使用 enum 来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。

enum Rank: Int {
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king
    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
let aceRawValue = ace.rawValue

默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,Ace 被显式赋值为 1,并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用 rawValue 属性来访问一个枚举成员的原始值。

使用 init?(rawValue:) 初始化构造器来创建一个带有原始值的枚举成员。如果存在与原始值相应的枚举成员就返回该枚举成员,否则就返回 nil

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

枚举的关联值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。

enum Suit {
    case spades, hearts, diamonds, clubs
    func simpleDescription() -> String {
        switch self {
        case .spades:
            return "spades"
        case .hearts:
            return "hearts"
        case .diamonds:
            return "diamonds"
        case .clubs:
            return "clubs"
        }
    }
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()

注意在上面的例子中用了两种方式引用 hearts 枚举成员:给 hearts 常量赋值时,枚举成员 Suit.hearts 需要用全名来引用,因为常量没有显式指定类型。在 switch 里,枚举成员使用缩写 .hearts 来引用,因为 self 的值已经是一个 suit 类型,在已知变量类型的情况下可以使用缩写。

如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同枚举实例的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着不同的枚举成员的关联值都可以不同。你可以把关联值想象成枚举成员的寄存属性。例如,考虑从服务器获取日出和日落的时间的情况。服务器会返回正常结果或者错误信息。

enum ServerResponse {
    case result(String, String)
    case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
    print("Sunrise is at \(sunrise) and sunset is at \(sunset)")
case let .failure(message):
    print("Failure...  \(message)")
}

注意日升和日落时间是如何从 ServerResponse 中提取到并与 switchcase 相匹配的。

使用 struct 来创建一个结构体。结构体和类有很多相同的地方,包括方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

协议和拓展

使用protocol来声明一个协议

protocol ExamplePotocol{
	var SimpleDescription: String {get}
	mutating func adjust()
}

类、枚举和结构体都可以遵循协议。

class SimpleClass:ExamplePotocol{
	var SimpleDiscription: String = "A very simple class."
	var AnotherProperty: Int = 69105
	func adjust(){
		SimpleDiscription += "  Now 100% adjusted."
	}
}
var a = SimpleClass()
a.adjust()
let ADescription = a.SimpleDiscreption
struct SimpleStructure:ExamplePotocol{
	var SimpleDiscreption: String = "A very simple class."
	mutating func adjust(){
		SimpleDiscription += "  Now 100% adjusted."
	}
}
var b = SimpleStructure()
b.adjust()
let BDiscreption = b.SimpleDiscreption

注意:在声明SimpleSturcture时,mutating关键字用来标记一个会修改结构体的方法。而在声明SimpleClass时不需要标记任何办法,因为类中的方法通常可以修改类属性(类的性质)。

使用extension来为现有的类添加功能,比如新的方法和计算属性。

你可以使用拓展让某个在别处声明的类来遵守某个协议,这同样适用于从外部库或者框架引入的类型。

extension Int: ExampleProtocol {
	var SimpleDescription: String{
		return "The number \(self)"
	}
	mutating func adjust(){
		self += 42
	}
}
print (7.SimpleDiscreption)

你可以像使用其他命名类型一样使用协议名—例如,创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可以用。

let ProtocolValue: ExampleProtocol = a
print(PotocolValue.SimpleDescreption)

即使ProtocolValue变量运行时的类型是SimpleClass,编译器还是会把它的类型当作ExampleProtocol。这表示你不能调用在协议之外的方法或属性。


错误处理

使用采用Error协议的类型来表示错误。

enum PtinterError: Error{
	case OutOfPaper
	case NoToner
	case InFire
}

使用throw来抛出一个错误,使用throws表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并调用该函数的代码会进行错误处理。

func send(Job: Int , ToPrinter PrinterName: String)throws -> String{
	if PrinterName == "Never Has Toner" {
		throw PinterError.NoToner
	}
	return "Job sent"
}

有很多方式可以来进行错误处理。一种方式是使用do-catch。在do代码块中,使用try来标记可以抛出错误的代码。在catch代码块中,除非你另外命名,否则错误会自动命名为error

do{
	let PinterResponse = try send(job: 404,ToPrtinter:"Never Has Toner")
	print(PinterResponse)
}catch{
	print(error)
}

可以使用多个catch块来处理特定的错误。参照switch中的case风格来写catch

do {
    let PrinterResponse = try send(Job: 1440, ToPrinter: "Gutenberg")
    print(PrinterResponse)
} catch PrinterError.OnFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}

另一种处理错误的方式是使用try?将结果转化为可选的。如果函数抛出错误,该错误会被抛弃并且结果nil。否则,结果会是一个包含函数返回值的可选值。

let PrinterSuccess = try? send(Job: 1884, ToPrinter: "Mergenthaler")
let PrinterFailure = try? send(Job: 1885, ToPrinter: "Never Has Toner")

使用defer代码来表示在函数返回值前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用defer,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者执行时期截然不同。

var FridgeIsOpen = false
let FridgeContent = ["milk", "eggs", "leftovers"]
func FridgeContains(_ Food: String) -> Bool {
    FridgeIsOpen = true
    defer {
        FridgeIsOpen = false
    }
    let Result = FridgeContent.contains(Food)
    return result
}
FridgeContains("banana")
print(FridgeIsOpen)

范型

在尖括号<>里写一个名字来创建一个范型函数或者类型。

func MakeArray <Item> (Repeating AddItem: Item, NUmberOfTimes: Int) -> [Item]{
	var result = [Item]()
	for _ in 0..<NumberOfTime{
		result.append(AddItem)
	}
	return result
}

你也可以创建泛型函数、方法、类、枚举和结构体。

enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var p=PossibleInteger: OptionalValue<Int> = .none
PossibleInteger = .some(100)

在类型名后面使用 where 来指定对类型的一系列需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。

func AnyCommonElements<T: Sequence, U: Sequence>(_ LHS: T, _ RHS: U) -> Bool
    where T.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element {
        for LHSItem in LHS {
            for RHSItem in RHS {
                if LHSItem == RHSItem {
                    return true
                }
            }
        }
        return false
}
AnyCommonElements([1, 2, 3], [3])

<T: Equatable><T> ... where T: Equatable> 的写法是等价的。

python 视频压缩

应视频石大的要求,写了一段python代码,来对视频进行压缩,顺便写了一下图片压缩,但发现对.png格式的图片压缩效果不太好,其他的格式都没什么大问题。

先已经支持的格式:

  • 视频:.wmv,.asf,.asx,.rm,.rmvb,.mp4,.3gp,.mov,.m4v,.avi,.dat,.mkv,.fiv,.vob
  • 图片:.bmp,.gif,.jpeg,.jpg,.TIFF,.svg,.pcx,.wmf,.emf,.lic,.eps,.tga

所需要的环境

python3.7 ->python 环境配置 Windows&Mac

ffmpeg -> ffmpeg安装 Windows&Mac


使用方法

  1. 新建一个moriarty.py文件,将文章末尾的代码复制到文件中。
  2. 在python文件的文件夹中进入终端(或cmd)输入指令
python moriarty.py -address -input. -output.

其中-address 是所需要压缩的视频或文件的所在文件夹的绝对地址

-input.是所需要压缩的视频或图片的名称及格式名。一定要加格式名

-output. 是压缩后的视频或文件的名称和格式名(名称可以自己取)。

  1. 压缩后的文件会出现在同一文件夹中。

代码

#视频压缩
import sys
import os
import zlib
import threading
import platform
from PIL import Image

class Compress_Pic_or_Video(object):
    def __init__(self,filePath,inputName,outName=""):
        self.filePath = filePath
        self.inputName = inputName
        self.outName = outName
        self.system_ = platform.platform().split("-",1)[0]
        if  self.system_ ==  "Windows":
            self.filePath = (self.filePath + "\\") if self.filePath.rsplit("\\",1)[-1] else self.filePath
        elif self.system_ == "Linux":
            self.filePath = (self.filePath + "/") if self.filePath.rsplit("/",1)[-1] else self.filePath
        self.fileInputPath = self.filePath + inputName
        self.fileOutPath = self.filePath + outName

    @property
    def is_video(self):
        videoSuffixSet = {"WMV","ASF","ASX","RM","RMVB","MP4","3GP","MOV","M4V","AVI","DAT","MKV","FIV","VOB"}
        suffix = self.fileInputPath.rsplit(".",1)[-1].upper()
        if suffix in videoSuffixSet:
            return True
        else:
            return False

    def SaveVideo(self):
        fpsize = os.path.getsize(self.fileInputPath) / 1024
        if fpsize >= 150.0:
            if self.outName:
                compress = "ffmpeg -i {} -vcodec libx265 -crf 20 {}".format(self.fileInputPath,self.fileOutPath)
                isRun = os.system(compress)
            else:
                compress = "ffmpeg -i {} -vcodec libx265 -crf 20 {}".format(self.fileInputPath, self.fileInputPath)
                isRun = os.system(compress)
            if isRun != 0:
                return (isRun,"没有安装ffmpeg")
            return True
        else:
            return True

    def Compress_Video(self):
        thr = threading.Thread(target=self.SaveVideo)
        thr.start()

if __name__ == "__main__":
    tag = sys.argv[1:]
    savevid = Compress_Pic_or_Video(tag[0],tag[1],tag[2])
    print(savevid.Compress_Video())

#压缩图片
import sys
import os
import zlib
import threading
import platform
from PIL import Image

class Compress_Pic_or_Video(object):
    def __init__(self,filePath,inputName,outName=""):
        self.filePath = filePath
        self.inputName = inputName
        self.outName = outName
        self.system_ = platform.platform().split("-",1)[0]
        if  self.system_ ==  "Windows":
            self.filePath = (self.filePath + "\\") if self.filePath.rsplit("\\",1)[-1] else self.filePath
        elif self.system_ == "Linux":
            self.filePath = (self.filePath + "/") if self.filePath.rsplit("/",1)[-1] else self.filePath
        self.fileInputPath = self.filePath + inputName
        self.fileOutPath = self.filePath + outName

    @property
    def is_picture(self):
        picSuffixSet = {"BMP","GIF","JPEG","TIFF","PNG","SVG","PCX","WMF","EMF","LIC","EPS","TGA","JPG"}
        suffix = self.fileInputPath.rsplit(".",1)[-1].upper()
        if suffix in picSuffixSet:
            return True
        else:
            return False

    def SavePic(self):
        fpsize = os.path.getsize(self.fileInputPath) / 1024
        if fpsize >= 50.0:
            im = Image.open(self.fileInputPath)
            imBytes = im.tobytes()
            imBytes = zlib.compress(imBytes, 5)
            im2 = Image.frombytes('RGB', im.size, zlib.decompress(imBytes))
            if self.outName:
                im2.save(self.fileOutPath)
                return (self.fileOutPath,os.path.getsize(self.fileOutPath))
            else:
                im2.save(self.fileInputPath)
                return (self.fileInputPath,os.path.getsize(self.fileInputPath))
        else:
            return True

    def Compress_Picture(self):
        thr = threading.Thread(target=self.SavePic)
        thr.start()

if __name__ == "__main__":
    tag = sys.argv[1:]
    savepic = Compress_Pic_or_Video(tag[0],tag[1],tag[2])
    print(savepic.Compress_Picture())


如果出现了任何问题,请将问题出现的情景发送到邮箱 moriarty0305@icloud.com

ffmpeg 安装

Windows

下载地址

  1. 下载好后解压安装包
  2. 进入bin目录
  3. 复制bin目录的路径
  4. 在此电脑界面下右击选择属性
  5. 选择高级设置
  6. 选择环境变量
  7. 在上部分的用户环境变量中双击path
  8. 选择新建(不要改其他的环境变量)
  9. 将刚才复制下来的bin路径粘贴上去
  10. 确定保存
  11. 打开cmd 输入ffmpeg以确定环境变量已经成功配置

Mac

  1. 确定Xcode是最新版本
  2. 终端中输入这一段指令,安装Homebrew
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  1. Hpmebrew安装完成后输入以下指令来安装ffmpeg
brew install ffmpeg

Python环境配置


Windows

第一步:下载Python安装包

在Python的官网 http://www.python.org 中找到最新版本的Python安装包,点击进行下载,请注意,当你的电脑是32位的机器,请选择32位的安装包,如果是64位的,请选择64位的安装包;

python

第二步:安装

A.双击下载好的安装包,弹出如下界面:

这里要注意的是,将python加入到windows的环境变量中,如果忘记打勾,则需要手工加到环境变量中;在这里我选择的是自定义安装,点击“自定义安装”进行下一步操作;

B.进入到下一步后,选择需要安装的组件,然后点击下一步:

C.在这里可以自定义路径选择安装:

D.点击下一步后,就开始真正安装了:

第三步:测试

python安装好之后,我们要检测一下是否安装成功,用系统管理员打开命令行工具cmd,输入“python -V”,然后敲回车,如果出现如下界面,则表示我们安装成功了;(不要问我cmd怎么打开)

这句话的意思是显示python的版本信息;

第四步:写程序

安装成功之后,当然要写第一个python程序了,按照惯例,我们写一个“hello world”;

还是打开cmd,输入“python”后敲回车, 进入到python程序中,可以直接在里面输入,然后敲回车执行程序,我们打印一个“hello world”看看,在里面输入 print(“hello world”),敲回车,所有程序员都会遇到的第一个程序就出现啦;

第五步:配置python环境变量

如果在刚才安装的时候,忘记将加入到环境变量的勾打上,那么就需要手工配置环境变量之后,才能使用python,配置的方法如下:

A.右键点击“我的电脑”,点击“属性”;

B.在弹出的界面中点击“高级系统设置”(不同的windows系统版本,弹出的界面不完全相同,我用的是win8);

C.在弹出的界面中点击“环境变量”;

D.在弹出的页面中进行环境变量的配置;

找到系统变量中“Path”一项,选中后点击“编辑”;将之前安装的phtyon的完整路径加到最后面,注意要在完整的路径前加一个“;”,然后点击“确定”,保存所做的修改,这样,环境变量就设置好了;

设置完成后,可以按照上面的方法进行测试,以确保环境变量设置正确;

小结:

上面是python的安装方法,适合初学者的学习,安装完成后,通常我们还要安装pycharm,PyCharm是一种Python IDE,我们在编写python程序时,通常用该工具进行开发,调试和管理工程等。


Mac

目标版本python3.7

打开终端,输入python,你可以看到预装版本为python2.7

但是在开发时,我们多数使用的是python3,在终端中输入python3,按下enter键,查看电脑中是否装有python3

如果电脑中没有安装python3,可以去python官网下载。连接。下载后安装python3,一直点击下一步即可。

安装完成后,在终端中输入python3,查看是否安装成功。

python3安装完成后,我们下载python的一些扩展包的时候需要使用pip3 install命令,如需下载ipython扩展包,在终端命令行中输入pip3 install ipython,按下enter即可自动安装。输入pip3 list可以查看当前python环境中安装了哪些扩展包.

我们不能直接删掉2.7版本,2.7版本是Xcode会用到,而且brew intstall也会用到。

现在要删除mac自带的python,

sudo rm -R /System/Library/Frameworks/Python.framework/Versions/2.7 

把第一步里安装好的Python目录移到原本系统所持有的目录位置。

sudo mv /Library/Frameworks/Python.framework/Versions/3.7 /System/Library/Frameworks/Python.framework/Versions 

修改文件所属的Group设置Group为wheel,原来系统自带的就是这样的

sudo chown -R root:wheel /System/Library/Frameworks/Python.framework/Versions/3.7

更新一下Current的Link在Versions的目录里有一个Current的link,是指向当前的Python版本,原始是指向系统自带的Python2.7,我们把它删除后,link就失效了,所以需要重新链一下

sudo rm /System/Library/Frameworks/Python.framework/Versions/Current
sudo ln -s /System/Library/Frameworks/Python.framework/Versions/3.7 /System/Library/Frameworks/Python.framework/Versions/Current

重新链接可执行文件

先把系统原来的执行文件删掉

sudo rm /usr/bin/pydoc
sudo rm /usr/bin/python
sudo rm /usr/bin/pythonw
sudo rm /usr/bin/python-config

建立新的链接

sudo ln -s /System/Library/Frameworks/Python.framework/Versions/3.7/bin/pydoc3.7 /usr/bin/pydoc
sudo ln -s /System/Library/Frameworks/Python.framework/Versions/3.7/bin/python3.7 /usr/bin/python
sudo ln -s /System/Library/Frameworks/Python.framework/Versions/3.7/bin/pythonw3.7 /usr/bin/pythonw
sudo ln -s /System/Library/Frameworks/Python.framework/Versions/3.7/bin/python3.7m-config /usr/bin/python-config

最后,更新一下.bash_profile文件在终端输入vi ~/.bash_profile

按一下i进入编辑状态

# Setting PATH for Python 3.7
# The orginal version is saved in .bash_profile.pysavePATH="/System/Library/Frameworks/Python.framework/Versions/3.7/bin:${PATH}"export PATH

再按一下 “esc” 键 ,结束编辑并输入 “:wq” 存储离开

问题 F: 【哈希和哈希表】A Horrible Poem

时间限制: 2 Sec 内存限制: 128 MB

题面 提交 状态

题目描述

Bytie boy has to learn a fragment of a certain poem by heart. The poem, following the best lines of modern art, is a long string consisting of lowercase English alphabet letters only. Obviously, it sounds horrible, but that is the least of Bytie’s worries. First and foremost, he completely forgot which fragment is he supposed to learn. And they all look quite difficult to memorize…

There is hope, however: some parts of the poem exhibit certain regularity. In particular, every now and then a fragment, say A, is but a multiple repetition of another fragment, say B (in other words, A=BB…B , i.e.,A=Bk , where k≥1 is an integer). In such case we say that B is a full period of A (in particular, every string is its own full period). If a given fragment has a short full period, Bytie’s task will be easy. The question remains… which fragment was that?

Make a gift for Bytie – write a program that will read the whole poem as well as a list of fragments that Bytie suspects might be the one he is supposed to memorize, and for each of them determines its shortest full period.

输入

In the first line of the standard input there is a single integer n (1≤n≤500000). In the second line there is a string of length consisting of lowercase English alphabet letters-the poem. We number the positions of its successive characters from 1 to n.

The next line holds a single integer q (1≤q≤2000000) denoting the number of fragments. Then the following lines give queries, one per line. Each query is a pair of integers ai and bi (1≤ai≤bi≤n), separated by a single space, such that the answer to the query should be the length of the shortest full period of the poem’s fragment that begins at position ai and ends at position bi.

In tests worth in total 42% of the points n≤10000 holds in addition. In some of those, worth 30% of points in total, q≤10000 holds as well.

输出

Your program should print q lines on the standard output. The i-th of these lines should hold a single integer – the answer to the i-th query.

样例输入

8

aaabcabc

3

1 3

3 8

4 8

样例输出

1

3

5

#pragma GCC optimize(3,"Ofast","inline")
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
#include <vector>
#include <map>
#include <cmath>
#include <stack>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=6e5;
const int MAXN = 1e6+10;
const int Hash=131;
const ll mod = (ll)1000000007;
int prime[maxn],ss[maxn],p[maxn];
char str[maxn];
ull hasha[maxn],hashb[maxn],hashc[maxn],h[maxn];
inline void getPrime(){
    for(int i=2;i<=maxn;i++){
        if(!prime[i]) {
            prime[++prime[0]]=i;
            ss[i]=i;
        }
        for(int j=1;j<=prime[0]&&prime[j]<=maxn/i;j++){
            prime[prime[j]*i]=1;
            ss[prime[j]*i]=prime[j];
            if(i%prime[j]==0) break;
        }
    }
}
void base(int number){
    hasha[0]=1;
    for(int i=1;i<=number;i++){
        h[i]=h[i-1]*Hash+str[i]-'a'+1;
        //cout<<i<<"   "<<h[i]<<endl;
        hasha[i]=hasha[i-1]*Hash;
    }
}
bool check(int l,int r,int L,int R)
{
    //cout<<endl<<l<<" "<<r<<"   "<<L<<" "<<R<<endl;
    //cout<<h[r]-h[r-l+1]*hasha[l-1]<<"  "<<h[R]-h[L-R+1]*hasha[L-1]<<endl;
    return h[r]-h[l-1]*hasha[r-l+1]==h[R]-h[L-1]*hasha[R-L+1];

}
int n,l,r;
int main() {
    getPrime();
    scanf("%d", &n);
    getchar();
    scanf("%s", str + 1);
    base(n);
    int _;
    scanf("%d", &_);
    while (_--) {
        scanf("%d %d", &l, &r);
        int len = r-l+1,k=0;
        while(len>1){
            p[++k]=ss[len];
            len/=ss[len];
        }
        len=r-l+1;
        for(int i=1;i<=k;i++){
            int tmp=len/p[i];
            if(check(l,r-tmp,l+tmp,r)) len=tmp;
        }
        printf("%d\n",len );
    }
    return 0;
}

问题 J: Meeting

时间限制: 1 Sec 内存限制: 128 MB

提交 状态 命题人:admin

题目描述

You are the boss of company X and have N subordinates. Today, the i-th subordinate will come to the office Ai seconds later than you.

You will have a team meeting today. Due to the capacity of the meeting room, there must be exactly K people (excluding you) attending the team meeting. You can start the meeting S seconds after you come to the office. You can choose the value of S whatever you like, but it must be a positive real non-integer number. Everyone who is already present at the office at that start time will attend the meeting.

You can adjust the arrival time of your subordinates. By paying $1 (one dollar) and choosing a subordinate, you can change the subordinate’s arrival time by one second earlier or one second later.

However, a subordinate must not arrive at the office strictly before you—that would be shameful for you. Also, a subordinate must not arrive strictly later than T seconds after you—the subordinate could get fired. You can adjust the arrival time of as many subordinates as you want. You can also adjust the arrival time of the same subordinate more than once.

Determine the minimum amount of dollars needed such that you can have a meeting of exactly K people (excluding you). If it is impossible to do so, output -1.

输入

The first line contains three integers: N K T (1 ≤ K ≤ N ≤ 100,000; 0 ≤ T ≤ 1,000,000,000) in a line denoting the number of subordinates, the number of subordinates attending the meeting, and the maximum arrival time. The second line contains N integers: A1 A2… AN (0 ≤ Ai ≤ T ) in a line denoting the arrival time of each subordinate.

输出

The output contains the minimum amount of dollars needed such that you can have a meeting of exactly K people (excluding you), in a line. If it is impossible to do, the output contains -1 instead.

样例输入

4 2 4

1 2 3 4

样例输出

0

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=1e5+10;
const int inf = 0x3f3f3f3f;
int number[maxn];
int preson[maxn];
int main()
{
    int n,k,t;
    while(~scanf("%d %d %d",&n,&k,&t)){
        for(int i=0;i<n;i++)
            scanf("%d",&preson[i]);
        if(n==k){
            printf("0\n");
            continue;
        }
        sort(preson,preson+n);
        int ans=preson[k-1];
        int cost=0;
        for(int i=k;i<n;i++){
            if(preson[i]==ans){
                cost++;
            }
        }
        int cnt=0;
        for(int i=k-1;i>=0;i--){
            if(preson[i]==ans){
                cnt++;
            }
        }
        int aans=inf;
        if(ans!=0)
            aans=min(aans,cnt);
        if(ans!=t)
            aans=min(aans,cost);
        if(aans==inf)
            printf("-1\n");
        else
            printf("%d\n",aans);
    }
}
通过 WordPress.com 设计一个这样的站点
从这里开始