[Swift] 기초 문법 정리 - 연산자

Posted by WhiteHyun on February 12, 2022

Goal

  • Swift의 연산자를 이해한다.

들어가기에 앞서

  1. 본 글은 Swift의 언어가이드를 따릅니다.[1]
  2. 문법작성규칙은 아래와 같이 작성합니다.[2]
    • lower camel case: method, variable, constant
    • upper camel case: enum, struct, class, extension

연산자의 종류

할당 연산자

  • 대입연산자라고도 하며 = 을 사용해 값을 할당합니다. 서로 다른 데이터 타입이라면 오류가 발생합니다.
    1
    2
    
    let name: String = "SeungHyeon Hong"
    let age: Int = "27" // 오류
    

산술 연산자

  • 대체로 수학에서 쓰이는 연산자와 같은 역할을 수행합니다.

    1
    2
    3
    4
    5
    
    let numberPlus: Int = 2 + 3 // 더하기 연산자
    let numberMinus: Int = 2 - 3  // 빼기 연산자
    let numberMul: Int = 2 * 3    // 곱하기 연산자
    let numberDiv: Int = 2 / 3  // 나누기 연산자
    let numberRem: Int = 2 % 3    // 나머지 연산자
    

나머지 연산자와 나누기 연산자

  • swift에서는 truncatingRemainder(dividingBy:)를 사용하여 부동소수점 타입의 나머지 연산을 지원합니다.

    1
    2
    3
    
    let number: Double = 5.0
    var result: Double = number.truncatingRemainder(dividingBy: 1.5) // 0.5
    result = 12.truncatingRemainder(dividingBy: 2.5)  // 2.0
    
  • 나누기 연산은 기존의 프로그래밍 언어처럼 나머지나 소수점을 제외한 정수만을 결괏값으로 반환합니다.

    1
    2
    
    var result: Int = 5 / 3
    result = 10 / 3
    
  • 나머지 연산을 할 때 유의해야할 것이 있습니다. -9 % 2와 같이 음수에 대한 나머지를 계산하면 음수가 나옵니다.
    이유는 swift의 나머지 연산 방식이 a % b = a - (a/b) * b이기 때문입니다. 따라서 양수든 음수이든 부호만 다르면서 동일한 나머지 숫자를 반환합니다. 저의 식견이 짧아 왜 이렇게 구현되었는지는 잘 모르겠습니다. 사용에 유의해 주세요.

    1
    2
    3
    
    let number: Int = -9
    let result: Int = number % 2
    print(result) // -1
    

비교연산자

  • 두 값을 비교할 때 사용합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    print(A == B)  // 값이 같다.
    print(A >= B)  // 값이 크거나 같다.
    print(A <= B)  // 값이 작거나 같다.
    print(A > B)   // 값이 크다.
    print(A < B)   // 값이 작다.
    print(A != B)  // 값이 같지 않다.
    print(A === B) // 참조가 같다. class 전용
    print(A !== B) // 참조가 같지 않다. class 전용
    print(A ~= B)  // 패턴 매치
    
  • 비교연산자는 다음에 살펴볼 if-else 흐름제어 구문에서 자주 사용됩니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    let a: Int = 10
    let b: Int = 20
    if a > b {
      print("a가 b보다 크다.")
    } else if a < b {
      print("a가 b보다 작다.")
    } else {
      print("a와 b는 같다.")
    }
    
  • 앞서 다뤘던 튜플을 이용하여 비교연산자를 사용할 수 있습니다. 튜플로 비교할 때 각 요소의 자료형은 일치해야하며, 비교연산자를 사용할 수 있는 자료형을 삽입해야합니다.

    1
    2
    3
    
    (1, "zebra") < (2, "apple")   // true because 1 is less than 2; "zebra" and "apple" aren't 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"
    
  • 예를 들어, 튜플을 (String, Bool) 형태로 비교하게 된다면 오류가 발생할 것입니다. Bool타입은 비교연산자를 사용할 수 없기 때문입니다.

    1
    
    ("apple", true) < ("banana", false) // error!
    

삼항 조건 연산자

  • 피연산자가 세 개인 삼항 조건 연산자입니다.

    1
    2
    
    // 삼항 조건 연산자, condition 이 참이면 A, 아니면 B 반환
    print(condition ? A : B)
    
  • 삼항 조건 연산자를 사용하여 조건식을 간단히 한 줄에 표현 가능합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    let number: Int = 5
    let anotherNumber: Int = 10
    var maxNumber: Int = anotherNumber > number ? anotherNumber : number
    
    // 위 삼항연산자와 같은 의미
    if anotherNumber > number {
      maxNumber = anotherNumber
    } else {
      maxNumber = number
    }
    
  • 삼항연산자는 용도에 맞게 사용하면 코드가 심플해지고 가독성이 높아질 수 있으나, 남용하게 된다면 오히려 복잡해질 수 있으니 상황에 맞게 사용하는 것을 추천드립니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    var value: String = "None"
    var number: Int = 5
    
    if number < 3 {
        value = "number is less than 3"
    } else if number < 5 {
        value = "number is greater than 3, less than 5"
    } else {
        value = "number is greater than 4"
    }
    
    // 💩
    value = number < 3 ? "number is less than 3" : number < 5 ? "number is greater than 3, less than 5" : "number is greater than 4"
    

범위 연산자

Closed Range Operator

  • 폐쇄 범위 연산자는 a...b 형태로 사용합니다. 이 때 a에서 b까지의 범위에서 b를 포함하며 a는 b보다 커서는 안됩니다.

  • 폐쇄 범위 연산자는 모든 값을 사용할 범위에서 반복할 때 유용합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    
    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 형태로 사용합니다. 이 때 a에서 b까지의 범위에서 b를 포함하지 않습니다. 폐쇄 범위 연산자와 동일하게 a는 b보다 커서는 안됩니다.

  • 배열과 같은 0-based lists를 사용할 때 유용합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    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

  • 단방향 범위 연산자는 3가지로 나뉘어 사용됩니다.

    부호 설명
    a… a부터 끝까지의 범위
    …b 시작부터 b까지의 범위
    ..<b 시작부터 b보다 작은 모든 값
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    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
    

논리 연산자

  • 불리언 값의 논리 연산을 할 때 사용합니다.
    1
    2
    3
    
    !B      // NOT 연산자
    A && B  // AND 연산자
    A || B  // OR 연산자
    

비트 연산자

  • 값의 비트 논리 연산을 위한 연산자입니다.
    1
    2
    3
    4
    5
    6
    
    ~A      // NOT 비트 연산자
    A & B   // AND 비트 연산자
    A | B   // OR 비트 연산자
    A ^ B   // XOR 비트 연산자
    A << B  // 왼쪽 시프트 연산자
    A >> B  // 오른쪽 시프트 연산자
    

복합 할당 연산자

  • 할당 연산자와 다른 연산자가 하는 일을 한 번에 할 수 있도록 연산자를 결합할 수 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    A += B  // A = A + B
    A -= B  // A = A - B
    A *= B  // A = A * B
    A /= B  // A = A / B
    A %= B  // A = A % B
    A <<= N // A = A << N
    A >>= N // A = A >> N
    A &= B  // A = A & B
    A |= B  // A = A | B
    A ^= B  // A = A ^ B
    

오버플로 연산자

  • 오버플로에 대비한 연산을 합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    /*
     * &+ 오버플로에 대비한 덧셈 연산
     * &- 오버플로에 대비한 뺄셈 연산
     * &* 오버플로에 대비한 곱셈 연산
     */
    
    var unsignedInteger: UInt8 = 0
    let errorUnderflowResult: UInt8 = unsignedInteger - 1  // 런타임 오류
    let underflowedValue: UInt8 = unsignedInteger &- 1     // 255
    
    unsignedInteger = UInt8.max                          // 255
    let errorOverflowResult: UInt8 = unsignedInteger + 1 // 런타임 오류
    let overflowedValue: UInt8 = unsignedInteger &+ 1    // 0
    

연산자 우선순위와 결합방향

  • swift의 연산자는 연산자 우선순위 그룹(precedencegroup)으로 지정되어있으며 결합방향과 할당 방향도 함께 지정되어있습니다. 이를 우선순위로 나열하면 다음과 같습니다.

    연산자 우선순위 그룹 이름 결합 방향 할당 방향 사용
    DefaultPrecedence none false
    BitwiseShiftPrecedence none false
    MultiplicationPrecedence left false
    AdditionPrecedence left false
    RangeFormationPrecedence none false
    CastingPrecedence none false
    NilCoalescingPrecedence right false
    ComparisonPrecedence none false
    LogicalConjunctionPrecedence left false
    LogicalDisjunctionPrecedence left false
    TernaryPrecedence right false
    AssignmentPrecedence right true
    FunctionArrowPrecedence right false

사용자 정의 연산자

  • 전위, 후위, 중위 연산자를 직접 정의하고 구현하여 사용할 수 있습니다.
  • 특히 중위 연산자는 우선순위 그룹(precedencegoup)을 정의 또는 지정해줄 수 있으며, 명시하지 않을 경우 DefaultPrecedence로 자동지정됩니다.
    1
    2
    3
    4
    5
    6
    
    precedencegroup 우선순위 그룹 이름 {
      higherThan: 더 낮은 우선순위 그룹 이름
      lowerThan: 더 높은 우선순위 그룹 이름
      associativity: 결합방향(left / right / none)
      assignment: 할당방향 사용(true / false)
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
prefix operator ++  //  전위연산자 정의
postfix operator ++ //  후위연산자 정의
infix operator ** : MultiplicationPrecedence   //  중위연산자 정의

prefix func ++ (value: inout Int) -> Int {
  value += 1
  return value
}

postfix func ++ (value: inout Int) -> Int {
  let tempValue: Int = value
  value += 1
  return tempValue
}

func ** (lhs: Int, rhs: Int) -> Int {
  return Int(pow(Double(lhs), Double(rhs)))
}
  • 또한 표준 라이브러리에 존재하는 전위 연산자를 중복 정의하여 기능을 새롭게 추가할 수 있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    prefix func ! (value: String) -> Bool {
      return value.isEmpty
    }
    
    var stringValue: String = "SeungHyeon"
    var isEmptyString: Bool = !stringValue
    
    print(isEmptyString)  // false
    
    stringValue = ""
    isEmptyString = !stringValue
    
    print(isEmptyString)   // true
    

Reference

  1. The Basics — The Swift Programming Language (Swift 5.6)
  2. Camel Case - 위키피디아