共计 8166 个字符,预计需要花费 21 分钟才能阅读完成。
Advanced Operators
Unlike arithmetic operators in C, arithmetic operators in Swift don’t overflow by default. If want to overflow by default, use the overflow operation begin with ampersand (&
). For example, the overflow addition operator (&+
).
It’s so free to define custom infix, prefix, postfix and assignment operators, precedence and associativity values.
Bitwise Operators
Here we use a function to pad 0 for the number’s print result.
func pad(num: UInt8, count: Int) -> String {var str = String(num, radix: 2) | |
var res: String = str | |
for _ in 0..<(count - str.count) {res = "0" + res} | |
return res | |
} |
-
~
NOT~1 is 0
~0 is 1
-
&
AND1 & 1 is 1
1 & 0 is 0
0 * 0 is 0
-
|
OR1 | 1 is 1
1 | 0 is 1
0 | 0 is 0
-
^
XOR1 ^ 1 is 0
1 ^ 0 is 1
0 ^ 0 is 0
Bitwise NOT Operator
Use ~
to do NOT operation.
var a: UInt8 = 0b11111100 | |
var b = ~a | |
print(pad(num: b, count: 8)) // 00000011 |
UInt8
has 8 bits, and can store 0 to 255.
Bitwise AND Operator
Use &
to do AND operation.
let a: UInt8 = 0b11111100 | |
let b: UInt8 = 0b00011010 | |
let c = a & b | |
print(pad(num: c, count: 8)) // 00011000 |
Bitwise OR Operator
Use |
to do OR operator.
let a: UInt8 = 0b11111100 | |
let b: UInt8 = 0b00011010 | |
let c = a | b | |
print(pad(num: c, count: 8)) // 11111110 |
Bitwise XOR Operator
Return a new number whose bits are set to 1 where the input bits are different and are set to 0 where the input bits are the same.
let a: UInt8 = 0b11111100 | |
let b: UInt8 = 0b00011010 | |
let c = a ^ b | |
print(pad(num: c, count: 8)) // 11100110 |
Bitwise Left and Right Shift Operators
Left shift (<<
) move all bits in a number to the left.
let a: UInt8 = 0b00111100 | |
let b = a << 1 | |
let c = a << 2 | |
print(pad(num: b, count: 8)) // 01111000 | |
print(pad(num: c, count: 8)) // 11110000 |
Shifting of the Unsigned Integer
It will place an zero at the left when right shift, and place an zero at the right when left shift.
Right shift (>>
) move all bits in a number to the right.
let a: UInt8 = 0b00111100 | |
let b = a >> 1 | |
let c = a >> 2 | |
print(pad(num: b, count: 8)) // 00011110 | |
print(pad(num: c, count: 8)) // 00001111 |
Use bit shifting to encode and decode values within other data types:
let pink: UInt32 = 0xCC6699 | |
let redComponent = (pink & 0xFF0000) >> 16 | |
let greenComponent = (pink & 0x00FF00) >> 8 | |
let blueComponent = pink & 0x0000FF | |
print(String(redComponent, radix: 16)) // cc | |
print(String(greenComponent, radix: 16)) // 66 | |
print(String(blueComponent, radix: 16)) // 99 | |
// By AND operation and shifting, we get the three part of the "pink" color: R G B |
The CSS color value #CC6699
is written as 0xCC6699
in Swift’s hexadecimal number representation.
Shifting Behavior for Signed Integer
In the 8-bit signed integer example:
Signed Integer use its first bit to indicate whether the integer is positive or negative. (0 is positive, 1 is negative)
00000100
is 4
11111100
is -4, the first bit 1 indicate that the integer is negative, and the remain 7 bits is 124 means that 128 – 124 = -4
So the signed 8-bits can represent -128(10000000) to 127(01111111).
Two’s Complement Representation
The way it’s encoded above is known as a two’s complement representation. In this way it has several advantage.
-
Add -4 and -1, it add in binary rule and discard anything that overflow.
(-4) + (-1) = 11
11111100 + 11111111 = 11111011
-
arithmetic shift
- left shift: an arithmetic shift = a logical shift. It will doubling the number.
-
right shift: when shift signed integers to the right, apply same rules as for unsigned integers, but fill any empty bits on the left with the sign bit, rather than with a zero.
11111110 >> 1 will be 11111111 01111110 >> 1 will be 00111111 In this way it ensures that the number will has the same sign after shifting. The shift will moves both positive and negative numbers closer to zero.
Overflow Operators
When the constant or variable can’t hold the value that too large or too small, Swift will raise an error.
// The num get the max value of Int8 | |
var num = Int8.max | |
num += 1 // Here will a runtime-error. |
If we specifically want an overflow condition to truncate the number of available bits, we can use the overflow operators that begin with an ampersand (&).
- Overflow addition (&+)
- Overflow subtraction (&-)
- Overflow multiplication (&*)
Value Overflow
If we allow the number to overflow, it may overflows in both positive and negative direction.
var num: UInt8 = UInt8.max // It's 11111111 (255) | |
num = num &+ 2 | |
print(pad(num: num, count: 8)) // 00000001 | |
var n: UInt8 = 1 | |
n = n &- 2 | |
print(pad(num: n, count: 8)) // 11111111 |
The overflow operation can be apply to signed integer.
var signedNum: Int8 = Int8.min // -128 | |
signedNum = signedNum &- 1 | |
print(String(signedNum, radix: 2)) // 127 |
Precedence and Associativity
From high to low:
- %
-
- /
-
- –
let a = 2 + 3 % 4 * 5 | |
print(a) // 17 |
Left associative: calculate from left to right.
- *
- /
- %
- &
- &*
- +
- –
- |
- ^
- &+
- &-
- is
- as
- as?
- as!
- &&
- .&
- ||
- .|
- .^
Right associative: calculate from right to left.
- ??
- :?
- =
- *=
- /=
- %=
- +=
- -=
- <<=
-
=
- &=
- |=
- ^=
- &*=
- &+=
- &-=
- &>>=
- &<<=
- .&=
- .|=
- .^=
Isn’t associative:
- <<
- &<<
- &>>
- <
- <=
-
=
- ==
- !=
- ===
- !==
- ~=
- .<
- .<=
- .>
- .>=
- .==
- .!=
- ..<
- …
Operator Methods
Classes and structures can overloading the existing operators.
struct Vector2D {var x = 0.0, y = 0.0} | |
extension Vector2D {static func + (left: Vector2D, right: Vector2D) -> Vector2D{return Vector2D(x: left.x + right.x, y: left.y + right.y) | |
} | |
} | |
var a = Vector2D(x: 1.0, y: 2.0) | |
var b = Vector2D(x: 3.0, y: 3.0) | |
var c = a + b | |
print(c) // Vector2D(x: 4.0, y: 5.0) |
Prefix and Postfix Operators
extension Vector2D {static prefix func - (vector: Vector2D) -> Vector2D {return Vector2D(x: -vector.x, y: -vector.y) | |
} | |
} | |
var x = Vector2D(x: 1.0, y: 2.0) | |
var xx = -x | |
print(xx) // Vector2D(x: -1.0, y: -2.0) |
Compound Assignment Operators
+=
extension Vector2D {static func += (left: inout Vector2D, right: Vector2D){left = left + right} | |
} | |
var v = Vector2D(x: 1.0, y: 2.0) | |
var w = Vector2D(x: 4.0, y: -3.0) | |
v += w | |
print(v) // Vector2D(x: 5.0, y: -1.0) |
Equivalence Operators
==
One way to make ==
be valid for enumeration or structure: Conform to the Equalble
, it has provided method to do that.
extension Vector2D: Equatable { } | |
var one = Vector2D(x: 1.0, y: 2.0) | |
var two = Vector2D(x: 1.0, y: 2.0) | |
print(one == two) // true |
Another way is to implement the method in the extension.
extension Vector2D: Equatable {static func == (left: Vector2D, right: Vector2D) -> Bool {return (left.x == right.x) && (left.y == right.y) | |
} | |
} | |
var one = Vector2D(x: 1.0, y: 2.0) | |
var two = Vector2D(x: 1.0, y: 2.0) | |
print(one == two) // true |
Custom Operators
Prefix operator
prefix operator +++ | |
extension Vector2D {static prefix func +++ (vector: inout Vector2D) -> Vector2D { | |
vector += vector | |
return vector | |
} | |
} | |
var some = Vector2D(x: 1.0, y: 2.0) | |
+++some | |
print(some) // Vector2D(x: 2.0, y: 4.0) |
Precedence for Custom Infix Operators
Here we define a new custom infix operator call +-, which belongs to the precedence group AdditionPrecedence
.
infix operator +-: AdditionPrecedence | |
exthe tension Vector2D {static func +- (left: Vector2D, right: Vector2D) -> Vector2D{return Vector2D(x: left.x + right.x, y: left.y - right.y) | |
} | |
} | |
var a = Vector2D(x: 1.0, y: 2.0) | |
var b = Vector2D(x: 2.0, y: 3.0) | |
var c = a +- b | |
print(c) // Vector2D(x: 3.0, y: -1.0) |
Don’t need to specify a precedence when defining a prefix or postfix operator. When apply both prefix and postfix to the same operand, the postfix operator is applied first.
Result Builders
If the code without result builder:
protocol Drawable {func draw() -> String | |
} | |
struct Line: Drawable {var elements: [Drawable] | |
func draw() -> String{return elements.map { $0.draw() }.joined(separator: "") | |
} | |
} | |
struct Text: Drawable { | |
var content: String | |
init(_ content: String) {self.content = content} | |
func draw() -> String {return content} | |
} | |
struct Space: Drawable {func draw() -> String {return " "} | |
} | |
struct Star: Drawable { | |
var length: Int | |
func draw() -> String {return String(repeating: "*", count: length) | |
} | |
} | |
struct AllCaps: Drawable { | |
var content: Drawable | |
func draw() -> String {return content.draw().uppercased()} | |
} | |
let name: String? = "Mike" | |
let drawing = Line(elements: [Star(length: 3), | |
Text("Hello"), | |
Space(), | |
AllCaps(content: Text((name ?? "World") + "!")), | |
Star(length: 3) | |
]) | |
print(drawing.draw()) // ***Hello MIKE!*** |
The content in the AllCaps
is awkward, if the content is complex, and have some extra conditions, it’s hard to write.
So we use the result builder to solve this problem.
@resultBuilder | |
struct DrawingBuilder { | |
// It combines several components into a line | |
static func buildBlock(_ components: Drawable...) -> Drawable{return Line(elements: components) | |
} | |
static func buildEither(first: Drawable) -> Drawable{return first} | |
static func buildEither(second: Drawable) -> Drawable {return second} | |
} | |
func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {return content() | |
} | |
func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {return AllCaps(content: content()) | |
} | |
func makeGreeting(for name: String? = nil) -> Drawable { | |
let greeting = draw {Star(length: 3) | |
Text("Hello") | |
Space() | |
caps { | |
if let name = name {Text(name + "!") | |
} else {Text("World!") | |
} | |
} | |
Star(length: 2) | |
} | |
return greeting | |
} | |
let p = makeGreeting() | |
print(p.draw()) // ***Hello WORLD!** | |
let s = makeGreeting(for: "Amy") | |
print(s.draw()) // ***Hello AMY!** |
The Swift will transform the call to caps(_:) into code like this:
let capsDrawing = caps { | |
let partialDrawing: Drawable | |
if let name = name {let text = Text(name + "!") | |
partialDrawing = DrawingBuilder.buildEither(first: text) | |
} else {let text = Text("World!") | |
partialDrawing = DrawingBuilder.buildEither(second: text) | |
} | |
return partialDrawing | |
} |
Add support for writing for
loops
extension DrawingBuilder {static func buildArray(_ components: [Drawable]) -> Drawable {return Line(elements: components) | |
} | |
} | |
let manyStars = draw {Text("Stars:") | |
for length in 1...3 {Space() | |
Stars(length: length) | |
} | |
} |
The result builder makes it easier and neater to organize the function blocks.