Swift - 26. 고급 연산자 Advanced Operators

지우개·2022년 4월 29일
0

Swift study

목록 보기
15/15
post-thumbnail

고급 연산자 Advanced Operators

비트 연산자 Bitwise Operators

비트 연산자를 사용해 데이터 구조 내에서 개별 원시 데이터 비트를 조작할 수 있음

  • 비트 NOT 연산자 (~) : 숫자의 모든 비트 반전
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits  // equals 11110000
// 접두사, 공백x
  • 비트 AND 연산자 (&) : 두 숫자의 비트를 결합, 둘 다 1이면 1
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8  = 0b00111111
let middleFourBits = firstSixBits & lastSixBits  // equals 00111100
// 교집합. 둘 다 1이면 1
  • 비트 OR 연산자 (|) : 두 숫자의 비트를 비교, 하나라도 1이면 1
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits  // equals 11111110
// 합집합. 하나라도 1이면 1
  • 비트 XOR 연산자 (^) : 두 숫자의 비트를 비교, 비트가 같으면 0, 다르면 1
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits  // equals 00010001
  • 비트 왼쪽과 오른쪽 이동 연산자 (<<, >>) : 특정 숫자만큼 왼쪽/오른쪽으로 숫자의 모든 비트 이동
    • 정수를 2배로 곱하거나(왼쪽) 나누는(오른쪽) 효과

부호없는 정수에 대한 이동 동작

  1. 기존 비트는 요청된 숫자만큼 왼쪽/오른쪽으로 이동됨
  2. 정수의 저장소 범위를 넘어 이동된 모든 비트는 삭제됨
  3. 원래 비트가 왼쪽 또는 오른쪽으로 이동한 후 뒤에 남겨진 공백에 0 삽입

→ 이 접근 방식을 논리적 이동 logical shift 라고 함

let shiftBits: UInt8 = 4   // 00000100 in binary
shiftBits << 1             // 00001000 왼쪽으로 1
shiftBits << 2             // 00010000 
shiftBits << 5             // 10000000
shiftBits << 6             // 00000000
shiftBits >> 2             // 00000001 오른쪽으로 2
// 다른 데이터 타입 내에서 값을 인코딩/디코딩 하기 위해 사용하기도 함
let pink: UInt32 = 0xCC6699
let redComponent = (pink & 0xFF0000) >> 16    // redComponent is 0xCC, or 204
let greenComponent = (pink & 0x00FF00) >> 8   // greenComponent is 0x66, or 102
let blueComponent = pink & 0x0000FF           // blueComponent is 0x99, or 153

부호있는 정수에 대한 이동 동작

  • 첫번째 비트 : 정수가 양수인지 음수인지 나타내는 부호 비트 sign bit

    • 0은 양수, 1은 음수
  • 나머지 비트 : 값 비트 value bit

    • 양수는 이진 표기법으로 작성

    • 음수는 2의 보수 표현 two’s complement : 절대값을 2에서 값 비트의 수인 n의 제곱으로 빼서 저장

      • 예) Int8 내부의 비트에서 숫자 -4

        11111100 = -4 : 부호 비트 1, 값 비트 7개 128 - 4124

      • 장점

        1. 부호 비트를 포함하여 모든 8비트에 표준 이진 덧셈 수행이 간단함

        2. 음수 비트를 양수처럼 왼쪽/오른쪽으로 이동할 수 있음 (2배 곱하거나 나누는 효과)


오버플로우 연산자 Overflow Operators

값이 너무 크거자 너무 작을 때 에러 처리를 제공해야 함

사용 가능한 비트 수를 자르기 위해 특별히 오버플로우 조건을 원하는 경우

→ 3가지 산술 오버플로우 연산자 overflow operators

  • 오버플로우 덧셈 &+
  • 오버플로우 뺄셈 &-
  • 오버플로우 곱셈 &*

값 오버플로우 Value Overflow

// 부호 없는 정수
var unsignedOverflow = UInt8.max
// unsignedOverflow equals 255, which is the maximum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &+ 1 // 오버플로우됨
// unsignedOverflow is now equal to 0

var unsignedOverflow = UInt8.min
// unsignedOverflow equals 0, which is the minimum value a UInt8 can hold
unsignedOverflow = unsignedOverflow &- 1
// unsignedOverflow is now equal to 255

// 부호 있는 정수
var signedOverflow = Int8.min // 10000000
// signedOverflow equals -128, which is the minimum value an Int8 can hold
signedOverflow = signedOverflow &- 1 // 01111111
// signedOverflow is now equal to 127

부호가 있는 정수와 부호가 없는 정수에 대해
양의 방향의 오버플로우는 최대 → 최소값으로 돌아가고 음의 방향의 오버플로우는 최소 → 최대값으로 순환


우선순위와 연관성 Precedence and Associativity

우선순위 %, * > +


연산자 메소드 Operator Methods

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)
    }
}

let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector is a Vector2D instance with values of (5.0, 5.0)

접두사와 접미사 연산자 Prefix and Postfix Operators

extension Vector2D {
    static prefix func - (vector: Vector2D) -> Vector2D {
        return Vector2D(x: -vector.x, y: -vector.y)
    }
}

let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative is a Vector2D instance with values of (-3.0, -4.0)
let alsoPositive = -negative
// alsoPositive is a Vector2D instance with values of (3.0, 4.0)

복합 할당 연산자 Compound Assignment Operators

extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) {
        left = left + right
    }
}

var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original now has values of (4.0, 6.0)

등가 연산자 Equivalence Operators

extension Vector2D: Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
}

let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
    print("These two vectors are equivalent.")
}
// Prints "These two vectors are equivalent."

사용자 정의 연산자 Custom Operators

// 새로운 연산자는 operator 키워드를 사용하여 전역으로 선언됨
prefix operator +++

extension Vector2D {
    static prefix func +++ (vector: inout Vector2D) -> Vector2D {
        vector += vector
        return vector
    }
}

var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled now has values of (2.0, 8.0)
// afterDoubling also has values of (2.0, 8.0)

사용자 정의 중위 연산자의 우선순위 Precedence for Custom Infix Operators

infix operator +-: AdditionPrecedence
extension Vector2D {
    static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y - right.y)
    }
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector is a Vector2D instance with values of (4.0, -2.0)

결과 빌더 Result Builders

리스트나 트리와 같은 중첩된 데이터를 자연스럽게 생성하기 위한 구문을 추가하는 타입

protocol Drawable {
    func draw() -> String // draw 메소드 구현해야 함
}
struct Line: Drawable { // 각 선의 구성요소에서 draw()를 호출하고 하나의 문자열로 연결
    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 Stars: 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? = "Ravi Patel"
let manualDrawing = Line(elements: [
    Stars(length: 3),
    Text("Hello"),
    Space(),
    AllCaps(content: Text((name ?? "World") + "!")), // 중첩된 괄호 읽기 어려움
    Stars(length: 2),
    ])
print(manualDrawing.draw())
// Prints "***Hello RAVI PATEL!**"

// 결과 빌더 정의
@resultBuilder
struct DrawingBuilder {
		// 코드 블럭에 선을 작성하기 위한 지원 추가
    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
    }
}

// @DrawingBuilding 을 함수의 파라미터로 적용하여 함수에 전달된 클로저를
// 결과 빌더가 해당 클로저에서 생성하는 값으로 바꿀 수 있음
func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {
    return content()
}
func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {
    return AllCaps(content: content())
}

// makeGreeting(for:) 함수는 name 파라미터를 가져와 개인화 인사말을 그리는데 사용
func makeGreeting(for name: String? = nil) -> Drawable {
    let greeting = draw {
        Stars(length: 3)
        Text("Hello")
        Space()
        caps {
            if let name = name {
                Text(name + "!")
            } else {
                Text("World!")
            }
        }
        Stars(length: 2)
    }
    return greeting
}
let genericGreeting = makeGreeting()
print(genericGreeting.draw())
// Prints "***Hello WORLD!**"

let personalGreeting = makeGreeting(for: "Ravi Patel")
print(personalGreeting.draw())
// Prints "***Hello RAVI PATEL!**"

0개의 댓글