Swift Learning (3) - Basic Operators (Fully Ver.)

Swift Learning (3) - Basic Operators (Fully Ver.)

August 10, 2021·Jingyao Zhang
Jingyao Zhang

image

Operators are special symbols or phrases used to check, change, or combine values. For example, the plus sign + adds two numbers (like let i = 1 + 2). More complex examples include the logical AND operator && (such as if enteredDoorCode && passedRetinaScan).

The operators supported by Swift may already be familiar to you from other languages like C. However, Swift has made some improvements to reduce common coding errors. For example, the assignment operator = no longer returns a value, which eliminates the common mistake of writing = instead of == in conditional statements.

Arithmetic operators (+, -, *, /, %, etc.) in Swift are checked for overflow and will not allow it, preventing unexpected results when a variable exceeds the range of its type. Of course, Swift also provides overflow operators if you need to allow overflow.

Swift also provides range operators not found in C, such as a..<b or a...b, which make it easier to express a range of values.


Terminology

Operators are divided into unary, binary, and ternary operators:

  • Unary operators operate on a single operand (such as -a). Unary operators can be prefix or postfix. Prefix operators appear before the operand (like !b), postfix operators appear after the operand (like c!).
  • Binary operators operate on two operands (2 + 3) and are infix, as they appear between the two operands.
  • Ternary operators operate on three operands. As in C, Swift has only one ternary operator, the conditional operator (a ? b : c).

The values affected by operators are called operands. In the expression 1 + 2, the plus sign + is a binary operator, and its operands are the values 1 and 2.


Assignment Operator

The assignment operator (a = b) means initializing or updating the value of a with the value of b.

let b = 10
var a = 5
a = b  // a now equals the value of b, which is 10
print("Assigned the value of b to a, now a is: \(a)")
---
output: Assigned the value of b to a, now a is: 10

If the right side of the assignment is a tuple, its elements can be decomposed into multiple constants or variables immediately.

var (x, y) = (2, 5)  // Now x is 2, y is 5
print("Tuple (2, 5) decomposed, now x is: \(x), y is: \(y)")
---
output: Tuple (2, 5) decomposed, now x is: 2, y is: 5

Unlike C and Objective-C, the assignment operation in Swift does not return a value, so the following statement is invalid:

// if x = y {
         // This is invalid because x = y does not return a value. Error: Use of '=' in a boolean context, did you mean '=='?
// }

if x == y {
        // This is valid
}

By marking if x = y as invalid, Swift helps developers avoid mistakes like writing = instead of ==.


Arithmetic Operators

All numeric types in Swift support the basic four arithmetic operators:

  • Addition (+)
  • Subtraction (-)
  • Multiplication (*)
  • Division (/)
print("1 + 2 equals:", 1 + 2)  // equals 3
print("5 - 3 equals:", 5 - 3)  // equals 2
print("2 * 3 equals:", 2 * 3)  // equals 6
print("10.0 / 2.5 equals:", 10.0 / 2.5)  // equals 4.0
---
output: 1 + 2 equals: 3
5 - 3 equals: 2
2 * 3 equals: 6
10.0 / 2.5 equals: 4.0

Unlike C and Objective-C, Swift does not allow overflow in numeric operations by default. However, you can use Swift’s overflow operators (like a &+ b) to allow overflow.

The addition operator can also be used for string concatenation.

print("Hello " + "World!")  // equals "Hello World!"
---
output: Hello World!

Remainder Operator

The remainder operator (a % b) calculates how many times b fits into a and returns the leftover part (the remainder).

Note

The remainder operator (%) is also called the modulo operator in other languages. However, strictly speaking, “remainder” is more accurate than “modulo” when considering its behavior with negative numbers.

Let’s see how the remainder operation works. Calculating 9 % 4, first determine how many times 4 fits into 9:

remainder_operator

You can fit two 4s into 9, so the remainder is 1.

In Swift, this can be expressed as:

print("9 % 4 is: \(9 % 4)")  // equals 1
---
output: 9 % 4 is: 1

Similarly, for -9 % 4:

print("-9 % 4 is: \(-9 % 4)")  // equals -1
---
output: -9 % 4 is: -1

When taking the remainder with a negative b, the sign of b is ignored. This means a % b and a % -b yield the same result.

print("9 % -4 is: \(9 % -4), same as 9 % 4.")
---
output: 9 % -4 is: 1, same as 9 % 4.

Unary Minus Operator

The sign of a number can be toggled using the prefix - (the unary minus operator).

let three = 3
let minusThree = -three  // minusThree is -3
let plusThree = -minusThree  // plusThree is 3, "double negative is positive"
print("Double negative result: \(plusThree)")
---
output: Double negative result: 3

Unary Plus Operator

The unary plus operator + returns the value of its operand unchanged.

let minusSix = -6
let alsoMinusSix = +minusSix
print("Unary plus does nothing, so alsoMinusSix is: \(alsoMinusSix)")
---
output: Unary plus does nothing, so alsoMinusSix is: -6

Although the unary plus operator does nothing, you can use it for symmetry when expressing positive numbers, especially when using the unary minus operator for negatives.


Compound Assignment Operators

Like C, Swift provides compound assignment operators that combine other operators with the assignment operator =. The compound addition operator += is one example.

var c = 2
c += 2
print("Value of c after compound addition: \(c)")  // c is now 4
---
output: Value of c after compound addition: 4

The expression a += 2 is shorthand for a = a + 2. A compound assignment operator combines the arithmetic and assignment into one operator, performing both tasks at once.

Note

Compound assignment operators do not return a value. Code like let b = a += 2 is invalid, which is different from the increment and decrement operators mentioned earlier.


Comparison Operators

Swift supports the following comparison operators:

  • Equal to (a == b)
  • Not equal to (a != b)
  • Greater than (a > b)
  • Less than (a < b)
  • Greater than or equal to (a >= b)
  • Less than or equal to (a <= b)

Note

Swift also provides identity operators (=== and !==) to check if two object references point to the same instance.

Each comparison operator returns a Boolean value.

print("1 equals 1: \(1 == 1)")  // true
print("2 not equal to 1: \(2 != 1)")  // true
print("2 greater than 1: \(2 > 1)")  // true
print("1 less than 2: \(1 < 2)")  // true
print("1 greater than or equal to 1: \(1 >= 1)")  // true
print("2 less than or equal to 1: \(2 <= 1)")  // false
---
output: 1 equals 1: true
2 not equal to 1: true
2 greater than 1: true
1 less than 2: true
1 greater than or equal to 1: true
2 less than or equal to 1: false

Comparison operators are often used in conditional statements, such as if statements.

let name = "Jensen"
if name == "world" {
        print("Hello, World!")
} else {
        print("I'm sorry \(name), but I don't recognize you.")
}
---
output: I'm sorry Jensen, but I don't recognize you.

If two tuples have the same types and number of elements, they can be compared. Tuple comparison proceeds from left to right, comparing each value until a difference is found. If all values are equal, the tuples are considered equal.

print("Tuple comparison 1: \((1, "zebra") < (2, "apple"))")  // true 1 < 2, so (1, "zebra") < (2, "apple"), even though "zebra" > "apple"
print("Tuple comparison 2: \((3, "apple") < (3, "bird"))")  // true, when the first element is equal, the second is compared
print("Tuple comparison 3: \((4, "dog") == (4, "dog"))")  // true
---
output: Tuple comparison 1: true
Tuple comparison 2: true
Tuple comparison 3: true

Therefore, as long as the tuple’s elements can be compared, you can use these operators to compare them. For example, you can compare two tuples of type (String, Int) because both Int and String are comparable. In contrast, Bool cannot be compared, so tuples containing Bool cannot be compared.

print("String and Int tuple elements can be compared: \(("blue", -1) < ("purple", 1))")  // valid, result is true
//("blue", false) < ("purple", true)  // invalid, '<' cannot compare Bool values; uncommenting this line will cause an error: Binary operator '<' cannot be applied to two '(String, Bool)' operands
---
output: String and Int tuple elements can be compared: true

Note

The Swift standard library only supports comparison for tuples with up to six elements. If a tuple has more than six elements, you need to implement comparison operators yourself.


Ternary Conditional Operator

The ternary operator is unique in that it operates on three operands. Its form is condition ? value1 : value2. It succinctly expresses a two-way choice based on whether condition is true. If condition is true, it returns value1; otherwise, it returns value2.

The ternary operator is a shorthand for the following code:

let question = 3 > 2
let answer1 = 3, answer2 = 2
print("Result of ternary operator: \(question ? answer1 : answer2)")  // ternary operator

if question {
        print("answer1 is: \(answer1)")
} else {
        print("answer2 is: \(answer2)")
}
---
output: Result of ternary operator: 3
answer1 is: 3

Here’s an example for calculating row height in a table. If there is a header, the row height should be 50 points higher than the content; if not, only 20 points higher.

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)  // rowHeight is now 90
print("rowHeight is now: \(rowHeight)")
---
output: rowHeight is now: 90

The above is more concise than the following code:

let contentHeights = 40
let hasHeaders = true
var rowHeights = contentHeights
if hasHeaders {
        rowHeights += 50
        print("hasHeaders = True, rowHeights: \(rowHeights)")
} else {
        rowHeights += 20
        print("hasHeaders = False, rowHeights: \(rowHeights)")
}
---
output: hasHeaders = True, rowHeights: 90

The first example uses the ternary operator, so you can get the correct answer in a single line. This is much more concise than the second example, and you don’t need to define rowHeight as a variable since its value doesn’t need to change in an if statement.

The ternary operator provides a very convenient way to express two-way choices. However, to improve code readability, avoid using multiple ternary operators in a single complex statement.


Nil-Coalescing Operator

The nil-coalescing operator a ?? b checks if the optional a contains a value. If it does, it unwraps and returns it; otherwise, it returns the default value b. The expression a must be of an optional type, and the default value b must be of the same type as the value stored in a.

The nil-coalescing operator is a shorthand for the following code:

var d: Int? = 3
let e: Int? = nil
print("Original expression with nil-coalescing: \((d != nil ? d! : e)!)")  // d != nil ? d! : e
---
output: Original expression with nil-coalescing: 3

The above code uses the ternary operator. When the optional a is not nil, it force unwraps a! to access its value; otherwise, it returns the default value b. The nil-coalescing operator ?? provides a more elegant way to combine conditional checking and unwrapping, making the code more concise and readable.

Note

If a is non-nil, b will not be evaluated. This is called short-circuit evaluation.

The following example uses the nil-coalescing operator to choose between a default color name and an optional user-defined color name.

let defaultColorName = "red"
var userDefinedColorName: String?
var colorNameToUse = userDefinedColorName ?? defaultColorName  // userDefinedColorName is nil, so colorNameToUse is "red"
print("colorNameToUse is: \(colorNameToUse)")
---
output: colorNameToUse is: red

Since userDefinedColorName is nil, the expression userDefinedColorName ?? defaultColorName returns the value of defaultColorName, which is red.

If you assign a non-nil value to userDefinedColorName and use the nil-coalescing operator again, the result will be the value in userDefinedColorName, not the default.

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
print("colorNameToUse is: \(colorNameToUse)")  // userDefinedColorName is non-nil, so colorNameToUse is "green"
---
output: colorNameToUse is: green

Range Operators

Swift provides several range operators for conveniently expressing a range of values.

Closed Range Operator

The closed range operator a...b defines a range that includes all values from a to b, including both a and b. The value of a cannot be greater than b.

The closed range operator is very useful for iterating over all values in a range, such as in a for-in loop.

for index in 1...5 {
        print("\(index) * 5 = \(index * 5)")
}
---
output: 1 * 5 = 5
2 * 5 = 10
3 * 5 = 15
4 * 5 = 20
5 * 5 = 25

Half-Open Range Operator

The half-open range operator a..<b defines a range from a up to, but not including, b. It’s called half-open because it includes the first value but not the last.

Half-open ranges are useful when working with zero-based lists (like arrays), allowing you to count from 0 up to the length of the list.

let names = ["Jensen", "Alex", "Davis", "Jerry"]
let count = names.count
for i in 0..<count {
        print("Person \(i + 1) is \(names[i])")
}
---
output: Person 1 is Jensen
Person 2 is Alex
Person 3 is Davis
Person 4 is Jerry

The array has 4 elements, but 0..<count only goes up to 3 (the last index), since it’s a half-open range.

One-Sided Ranges

Closed range operators have another form that allows you to express ranges that extend infinitely in one direction. For example, a range that includes all values from index 2 to the end of an array. In these cases, you can omit one side of the range operator, creating a one-sided range.

for name in names[2...] {
        print("names[2...], ", name)
}

for name in names[...2] {
        print("names[...2], \(name)")
}
---
output: names[2...],  Davis
names[2...],  Jerry
names[...2], Jensen
names[...2], Alex
names[...2], Davis

The half-open range operator also has a one-sided form, including its final value. When using a range to include values, the final value is not included.

for name in names[..<2] {
        print("names[..<2], \(name)")
}
---
output: names[..<2], Jensen
names[..<2], Alex

One-sided ranges can be used not only in subscripts but also in other contexts. You can also check if a one-sided range contains a specific value.

let range = ...5
print("range.contains(7): \(range.contains(7))")  // false
print("range.contains(4): \(range.contains(4))")  // true
print("range.contains(-1): \(range.contains(-1))")  // true
---
output: range.contains(7): false
range.contains(4): true
range.contains(-1): true

Note

You cannot iterate over a one-sided range that omits its start value, because the starting point is unclear. However, you can iterate over a one-sided range that omits its end value. Due to the potentially infinite nature of such ranges, make sure to include a condition to end the loop.


Logical Operators

Logical operators operate on Boolean values. Swift supports three standard logical operators based on C:

  • Logical NOT (!a)
  • Logical AND (a && b)
  • Logical OR (a || b)

Logical NOT Operator

The logical NOT operator !a inverts a Boolean value, turning true into false and vice versa.

It is a prefix operator, appearing immediately before its operand with no space. Read as “not a”, for example:

let allowedEntry = false
if !allowedEntry {  // When allowedEntry is false, !allowedEntry is true, so the code block runs
        print("ACCESS DENIED")  // Outputs "ACCESS DENIED"
}
---
output: ACCESS DENIED

Carefully choosing Boolean constants or variables improves code readability and helps avoid double negatives or confusing logic.

Logical AND Operator

The logical AND operator a && b means the entire expression is true only if both a and b are true.

If either expression is false, the entire expression is false. In fact, if the first expression is false, the second is not evaluated, since it cannot affect the result. This is called short-circuit evaluation.

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {  // passedRetinaScan is false, so the whole expression is false
        print("Welcome!")
} else {
        print("ACCESS DENIED")  // Outputs "ACCESS DENIED"
}
---
output: ACCESS DENIED

Logical OR Operator

The logical OR operator a || b is an infix operator made of two consecutive | symbols. It means the entire expression is true if either a or b is true.

Like logical AND, logical OR is also short-circuited: if the left expression is true, the right is not evaluated, since it cannot change the result.

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {  // knowsOverridePassword is true, so the whole expression is true
        print("Welcome!")  // Outputs "Welcome!"
} else {
        print("ACCESS DENIED")
}
---
output: Welcome!

Combining Logical Operators

You can combine multiple logical operators to express complex logic.

if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
        print("Welcome!")  // Outputs "Welcome!"
} else {
        print("ACCESS DENIED")
}
---
output: Welcome!

The above example uses a compound logic expression with multiple && and || operators. Regardless, && and || always operate on two values at a time, so this is actually a sequence of simple logical operations.

Note

Swift’s logical operators && and || are left-associative, meaning that in a compound expression, the leftmost subexpression is evaluated first.

Using Parentheses to Clarify Precedence

To make complex expressions easier to read, it’s effective to use parentheses to clarify precedence, even though it’s not strictly necessary. In the previous example, adding parentheses to the first part makes it clearer.

if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
        print("Welcome!")  // Outputs "Welcome!"
} else {
        print("ACCESS DENIED")
}
---
output: Welcome!

The parentheses make the first two values an independent part of the overall logic. Although the output is the same with or without parentheses, code with parentheses is clearer to readers. Readability is as important as conciseness.

Last updated on