Swift/Swift 공식 문서

[Swift] 기본연산자 (Basic Operators)

hyunjuntyler 2023. 5. 18. 00:11

기본 연산자는 코딩을 하면서 거의 기본 중에 기본이라고 볼 수 있다. 과연 Swift는 다른 언어와 어떠한 점이 같고, 어떠한 점이 다를까?

Swift에서 기본 연산자란 대입, 연산, 비교를 수행한다. 값을 변경, 확인, 결합하기 위해서 사용한다. 예를 들면 + 는 두 숫자를 더하게 되고 AND 연산자 (&&)는 두 Boolean값을 결합하게 된다.

용어 (Terminology)

연산자는 단항, 이항, 삼항으로 나눌 수 있다.

  • 단항의 경우 -a 처럼 단일 항목에 적용된다. !b, c! 처럼 말이다.
  • 이항의 경우 2 + 3 처럼 2개의 항목에 동작하고 2개의 항목 사이에 위치한다.
  • 삼항의 경우 Swift는 하나의 삼항 연산자인 조건 연산자만 있고 a ? b : c 형태로 이루어진다.

연산자 (operators) 는 피연산자 (operands) 값에 영향을 준다고 볼 수 있다.

대입 연산자 (Assignment Operator)

대입연산자 a = b 에서 ab 의 값으로 초기화 되거나 업데이트 된다.

let b = 10
var a = 5
a = b
// a is now equal to 10
let (x, y) = (1, 2)
// x is equal to 1, and y is equal to 2

Swift에서는 CObjective-C에서의 대입연산자와 다르게 값을 반환하지 않는다. 따라서 아래 예문에서 === 로 바꿔줘야 한다.

if x = y {
    // This is not valid, because x = y does not return a value.
}

산술 연산자 (Arithmetic Operators)

Swift에서는 모든 숫자 타입에 대해서 4가지의 기본 연산자인 덧셈 + 뺄셈 - 곱셈 * 나눗셈 / 를 제공한다.

1 + 2       // equals 3
5 - 3       // equals 2
2 * 3       // equals 6
10.0 / 2.5  // equals 4.0

나머지 연산자 (Remainder Operator)

나머지 연산자 a % bba 안에 몇 번 들어갈지를 계산하고 남은 값을 반환한다.

Note: 나머지 연산자는 다른 언어에서는 모듈로 연산자 (modulo operator)라고 한다.

나머지 연산자는 아래와 같이 동작한다 9 % 4 를 했을 때, 9 안에는 아래와 같이 4가 두 번 들어가고 1이 남는다. 

9 % 4    // equals 1

a % b 의 결과를 구하기 위해 % 연산자는 a = (b * some multiplier) + remainder 를 계산하고 remainder 를 반환한다. 따라서 a 에 음수를 넣더라도 같은 방법으로 계산된다. -9 = (4 * -2) + -1 이므로 -1의 나머지를 얻는다.

-9 % 4   // equals -1

단항 연산자 (Unary Operator)

솔직히 단항연산자를 설명하는 것보다는 아래의 예시를 보는 것이 편하다. 단항 빼기 연산자는 공백없이 값 바로 앞에 붙이게 된다. 

let three = 3
let minusThree = -three       // minusThree equals -3
let plusThree = -minusThree   // plusThree equals 3, or "minus minus three"

let minusSix = -6
let alsoMinusSix = +minusSix  // alsoMinusSix equals -6

복합 대입 연산자 (Compound Assignment Operators)

C처럼 Swift는 대입 = 과 다른 연산자를 결합한 복합 대입 연산자를 쓸 수 있다!

var a = 1
a += 2
// a is now equal to 3

위의 a += 2 는 a = a + 2 의 짧은 표현이다. 하지만 복합 대입 연산자는 값을 반환하지 않아 let b = a += 2 로 작성은 할 수 없다. 당연하다 a 의 값을 업데이트 하는 것이지 3이라는 값을 반환하지는 않는다.

비교 연산자 (Comparison Operators)

비교 연산자의 종류는 아래와 같다.
  • 같음 ( a == b ), 다름 ( a != b )
  • 보다 큼 ( a > b ) 보다 작음 ( a < b )
  • 보다 크거나 같음 ( a >= b ) 보다 작거나 같음 ( a <= b )

각 비교 연산자는 아래와 같이 Bool 값을 반환한다.

1 == 1   // true because 1 is equal to 1
2 != 1   // true because 2 is not equal to 1
2 > 1    // true because 2 is greater than 1
1 < 2    // true because 1 is less than 2
1 >= 1   // true because 1 is greater than or equal to 1
2 <= 1   // false because 2 is not less than or equal to 1
Note : Swift는 2개의 객체 참조가 동일한지 판별하기 위해 ( === ) 나 ( !== ) 의 연산자도 있다. 이게 뭔가 싶을 것이다. 자세한 내용은 뒤에서 다룰 예정이다.

비교연산자는 아래와 같이 조건 구문에서 활용된다. name 이라는 값이 "world" 라면 "hello, world" 를 출력해주세요! 라는 의미이다. if 구문에 대한 자세한 흐름은 뒤에 Control Flow 파트에서 다룰 예정이다.

let name = "world"
if name == "world" {
    print("hello, world")
} else {
    print("I'm sorry \(name), but I don't recognize you")
}
// Prints "hello, world", because name is indeed equal to "world".

아래의 내용부터 약간 복잡해진다. 같은 타입과 같은 개수의 값을 가지고 있는 튜플도 비교가 가능하다! 튜플은 두 개의 값이 다를 때까지 왼쪽에서 오른쪽으로 하나씩 비교한다.

(1, "zebra") < (2, "apple")   // true because 1 is less than 2; "zebra" and "apple" are not compared
(3, "apple") < (3, "bird")    // true because 3 is equal to 3, and "apple" is less than "bird"
(4, "dog") == (4, "dog")      // true because 4 is equal to 4, and "dog" is equal to "dog"

1보다 2가 크기 때문에 첫 번째 줄은 true 값을 반환한다. 두 번째 줄은 3으로 첫 번째 값이 같기 때문에 두 번째 값을 비교하게 된다. applebird 보다 작은 값이기 때문에 true 값을 반환한다.. 왜냐하면 ab 보다 앞에 있기 때문이다. 세 번째 줄은 모든 값이 같기 때문에 true 이다.

("blue", -1) < ("purple", 1)        // OK, evaluates to true
("blue", false) < ("purple", true)  // Error because < can't compare Boolean values

하지만 위와 같이 Bool 값은 비교가 불가능하기 때문에 비교연산자로 비교가 불가능한 값이 포함된다면 비교가 불가능하다. 또한 Swift 는 기본적으로 7개 미만의 요소를 가지고 있는 튜플만 비교해 준다. 그 이상은 비교 연산자를 직접 구현해야 한다. 

삼항 조건 연산자 (Ternary Conditional Operator)

삼항 조건 연산자의 question ? answer1 : answer2 형태의 3가지 부분으로 이루어져 있다. 나는 SwiftUI 를 사용하면서 이 삼항 조건 연산자를 아주 즐겨 쓴다. 삼항 조건 연산자는 아래의 코드를 줄여서 표현한 것과 같다.

if question {
    answer1
} else {
    answer2
}

questiontrue 라면 첫 번째 값인 answer1 값을 반환하고, false 라면 answer2 값을 반환한다.

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight is equal to 90

위의 코드를 분석해 보면 hasHeadertrue 이기 때문에 결국 rowHeight40 + 50 인 셈이다. 만약 삼항 조건 연산자를 쓰지 않고 위의 코드를 표현해 본다면...

let contentHeight = 40
let hasHeader = true
let rowHeight: Int
if hasHeader {
    rowHeight = contentHeight + 50
} else {
    rowHeight = contentHeight + 20
}
// rowHeight is equal to 90

이렇게 될 것이다. 그러면 무조건 삼항 조건자로 코드를 간결하게 나타내는 게 좋은 것 아닌가? 라고 생각할 수도 있지만 삼항 조건 연산자를 너무 남용한다면 읽기 어려운 코드가 될 수 있다!

Nil-결합 연산자 (Nil-Coalescing Operator)

nil 결합 연산자 ( a ?? b ) 는 옵셔널인 a 에 값이 있다면 a 의 값을 풀거나 (unwrap) 값이 없다면 기본값인 b 를 반환한다. a 는 항상 옵셔널 값이여야만 하고 ba 에 저장된 타입과 같아야 한다. 아래와 같은 코드를 간결하게 나타낸 것이 a ?? b 라고 할 수 있다.

a != nil ? a! : b

공식 문서와 다르게 약간 변형해 봤다.

let a = 1
var b: Int?   // defaults to nil

var c = b ?? a
// b is nil, so c is set to the default of 1

a1 으로 두고 bnil 값일 경우 ca 의 값인 1 값을 가지게 된다.

b = 2
c = b ?? a
// b is not nil, so c is set to 2

만약 bnil 값이 아닌 2 라는 값이 있을 때, c2 라는 값으로 설정된다.

범위 연산자 (Range Operators)

값의 범위를 표현할 때 범위연산자를 사용한다.

닫힌 범위 연산자 (Closed Range Operator)

닫힌 범위 연산자인 ( a...b ) 는 값 ab 가 포함된 a 부터 b 까지의 범위 실행을 정의한다. a 의 값은 b 보다 클 수 없다.

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

반-열림 범위 연산자 (Half-Open Range Operator)

이 연산자 ( a..<b ) 는 b 가 포함되지 않은 a 부터 b 까지의 범위를 정의한다. 이와 같은 범위 연산자는 배열과 함께 사용하면 좋다.

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

단-방향 범위 (One-Sided Ranges)

아래와 같이 쓸 수 있다.

let names = ["Anna", "Alex", "Brian", "Jack"]

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

위와 같은 상황 말고도 아래와 같이 사용 될 수 있다. range 자체를 5 이하인 수들로 정해줄 수 있다.

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

논리 연산자 (Logical Operators)

Swift에서는 3개의 논리 연산자가 있다.

  • 논리적 NOT ( !a )
  • 논리적 AND ( a && b )
  • 논리적 OR ( a || b )

논리적 NOT 연산자 (Logical NOT Operator)

논리적 NOT 연산자 ( !a ) 는 Boolean 값을 true 인 값을 false 로, false 인 값을 true 로 변환한다. 아래처럼 falsea 의 앞에 NOT 연산자를 붙여주면 true 로 변하는 것을 볼 수 있다.

let a = false
if !a {
    print("a is true!")
}
// Prints "a is true!"​

논리적 AND 연산자 (Logical AND Operator)

논리적 AND 연산자. ( a && b ) 는 두 값이 모두 true 여야 true 의 값이 반환된다. 두 값중 하나라도 false 라면 false 값이 반환된다. 첫번째 값이 false 라면 두번째 값은 살펴보지 않는다. 이 것을 연산 생략 (short-circuit evaluation) 이라 한다.

논리적 OR 연산자 (Logical OR Operator)

논리적 OR 연산자 ( a || b ) 는 두 값중 하나라도 true 이면 true 값을 반환시킨다.

논리적 연산자 결합 (Combining Logical Operators)

이제 위 두가지 논리적 연산자를 결합하여 표현식을 생성할 수 있다.

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

이런식으로 enteredDoorCodetrue 여야 하고, 나머지 세 개중 하나라도 true 라면 true 의 값을 반환한다.

Note: Swift 의 논리적 연산자 &&|| 는 왼쪽 우선결합 (Left-associative) 이다. 여러 개의 논리적 연산자로 이루어진 복합 표현식은  가장 왼쪽부터 판단한다.

명시적 소괄호 (Explicit Parentheses)

복합 표현식을 읽기 쉽게 하기 위해 아래와 같은 소괄호를 포함할 수 있다. 가독성은 항상 간결성보다 선호되기 때문에 코드 작성자의 의도를 표현하기 위해서 소괄호를 사용해 주는 것이 좋다고 한다.

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