[Swift] Advanced Operators


Advanced Operators


정수 타입 / 숫자 리터럴

스위프트 정수 타입과 범위

  • 플랫폼 사양에 따르는 타입 : Int, UInt (최근 대부분 64비트)
  • 8-bit : Int8, UInt8
  • 16-bit : Int16, UInt16
  • 32-bit : Int32, UInt32
  • 64-bit : Int64, UInt64
MemoryLayout<Int8>.size   // 1바이트
Int8.max      //  127    ( 2^7 -1)
Int8.min      // -128    (-2^7)

MemoryLayout<UInt8>.size     // 1바이트
UInt8.max     //  255     ( 2^8 -1)
UInt8.min     //    0

MemoryLayout<Int>.size     // 8바이트
// 64비트 컴퓨터 기준
// 63 = 2^8 - 1 (부호 비트)
Int.max       //  9223372036854775807   ( 2^63 -1)
Int.min       // -9223372036854775808   (-2^63 )

오버플로우

  • C언어나 Objective-C언어의 산술연산자에서는 값이 넘침(overflow)을 허락했음
    (예를 들어, 8비트 값을 담을 수 있는 숫자에서 255를 넘어가면 다시 0으로 순환)
  • 스위프트에서는 오버플로우를 기본적으로 허락하지 않음
    -> 에러발생 (크래시)
  • 오버플로우의 방향은 양(positive)의 방향, 음(nagative)의 방향을 모두 의미
  • 특정한 경우에, 특정패턴을 구현하기 위해 오버플로우를 허용하는 경우가 필요한데, 이런 경우 활용을 위해 오버플로우 연산자를 마련해 놓았음

오버플로우 연산자

  • 이항연산자: 연산자를 중심으로 왼쪽과 오른쪽에 표현식이 위치
    (스위프트에서 3가지 오버플로우 연산자 제공)
연산자 기호프로그래밍 용어설명
&+오버플로우 더하기 연산자
(Overflow Addtion Operator)
상위 범위에서 벗어나면 순환해서 가장 작은 수로 이동
&-오버플로우 빼기 연산자
(Overflow Subtraction Operator)
하위 범위에서 벗어나면 순환해서 가장 큰 수로 이동
&*오버플로우 곱하기 연산자
(Overflow Multiplication Operator)
곱해서 범위에서 벗어나는 것 허용

논리연산자와 단락 평가

  • 논리 연산자
    • 이항연산자 연산자를 중심으로 왼쪽과 오른쪽에 Boolean 표현식이 위치해야함. 결과값 또한 항상 참 또는 거짓으로 도출

단락 평가

  • 스위프트의 논리 평가식은 단락 평가 방식을 사용
  • 단락평가: 논리 평가식에서 결과도출에 필요한 최소한의 논리식만 평가
    • 참을 찾을때 까지만 실행하고, 참을 찾으면 나머지 표현식은 평가하지 않음
    • 최소한의 코드만 실행 - Short-circuit evaluation
  • 논리연산자 우선순위
    1. &&
    2. ||

단락 평가 시 유의 사항

논리평가식 내에 선언된 표현식의 평가에서 함수(메서드)의 실행이 일어날 수 있고, 사이드 이펙트(Side-Effect)를 발생시키는 경우, 단락평가로 인해 함수 등의 실행횟수의 차이로 인해 의도치 않은 결과가 도출될 수 있음을 주의
-> 논리적인 오류가 없도록 표현식을 미리 실행하도록 코드 수정

  • 사이드 이펙트(Side-Effect)
    • 함수 내부에서 일부 외부 변수의 값을 변경 시키는 것
    • ex) num += 1
var doorCheck = 0
var passwordCheck = 0

func doorCodeChecking() -> Bool {
    doorCheck += 1
    print(#function)
    return true
}

func passwordCodeChecking() -> Bool {
    passwordCheck += 1
    print(#function)
    return true
}

// ---------- 첫번째 케이스 ---------- 
if doorCodeChecking() && passwordCodeChecking() && false || true && doorCodeChecking() && passwordCodeChecking() {
    // 조건문에 대한 코드
}
print("Door: \(doorCheck), Password: \(passwordCheck)")

//-> Door: 2, Password: 2

// ---------- 두번째 케이스 ---------- 
doorCheck = 0
passwordCheck = 0

if doorCodeChecking() && false && passwordCodeChecking() || doorCodeChecking() || passwordCodeChecking() {
    // 조건문에 대한 코드
}
print("Door: \(doorCheck), Password: \(passwordCheck)")

// -> Door: 2, Password: 0

// ---------- 세번째 케이스 ---------- 
doorCheck = 0
passwordCheck = 0

if doorCodeChecking() || passwordCodeChecking() && doorCodeChecking() || false && passwordCodeChecking() {
    // 조건문에 대한 코드
}
print("Door: \(doorCheck), Password: \(passwordCheck)")

// -> Door: 1, Password: 0

// -------- 수정된 세번째 케이스 -------- 
doorCheck = 0
passwordCheck = 0

let doorResult1 = doorCodeChecking()
let passwordResult1 = passwordCodeChecking()
let doorResult2 = doorCodeChecking()
let passwordResult2 = passwordCodeChecking()

if doorResult1 || passwordResult1 && doorResult1 || false && passwordResult2 {
    // 조건문에 대한 코드
}
print("Door: \(doorCheck), Password: \(passwordCheck)")

// -> Door: 2, Password: 2

비트 연산자

비트 연산

  • 메모리 비트 단위로 직접적인 논리연산을 하거나, 비트 단위 이동시에 사용하는 연산

  • 주로, 어떤 하드웨어적인 처리(예, 장치 드라이버 생성)나 그래픽 프로그래밍과 임베디드 프로그래밍, 암호화처리, 게임 등 아주 한정적으로 쓰이는 이론적인 내용
    (프로그래밍을 배우고 있다는 목적아래, 이론적으로 듣고 지나치면 됨)

  • 장점

    • 연산속도가 빠름 (직접적으로 메모리의 실제 비트를 컨트롤)
    • 짧은 코드로 복잡한 로직을 구현 가능한 경우가 있음

스위프트 비트연산자 종류

  • 비트 논리 연산자
    • ~ : Bitwise NOT Operator(비트와이즈 낫 연산자)
      • 단항연산자로 사용. 기존 메모리 비트를 반전 시킴 (0 ➡ 1, 1 ➡ 0 반전)
          let a1: UInt8 = 0b0000_1111   // 15
          let b1 = ~a1  // 0b1111_0000  // 240
        
    • & : Bitwise AND Operator(비트와이즈 앤드 연산자)
      • 두개의 메모리 비트 중 모두가 1이면 1을 반환 (true && true의 논리)
          let a2: UInt8 = 0b1111_1100   // 252
          let b2: UInt8 = 0b0011_1111   // 63
          let c2 = a2 & b2  // 0b0011_1100   // 60
        
    • | : Bitwise OR Operator(비트와이즈 오어 연산자)
      • 두개의 메모리 비트 중 하나라도 1이면 1을 반환 (true || true의 논리)
          let a3: UInt8 = 0b1011_0010   // 178
          let b3: UInt8 = 0b0101_1110   // 94
          let c3 = a3 | b3  // 0b1111_1110   // 254
        
    • ^ : Bitwise XOR Operator(비트와이즈 엑스오어 연산자)
      • 두개의 메모리 비트 중 둘을 비교해서 서로 다르면 1을 반환 (서로 같으면 0반환)
          let a4: UInt8 = 0b0001_0100   // 20
          let b4: UInt8 = 0b0000_0101   // 5
          let c4 = a4 ^ b4  // 0b0001_0001   // 17
        
  • 비트 이동 연산자
    • << : Bitwise Left Shift Operator(비트와이즈 레프트 시프트 연산자)
      • 모든 비트를 원하는 값만큼 왼쪽으로 이동 (2를 곱하는 효과)
      • 부호가 있건 없건 상관없음
          let leftShiftBits: UInt8 = 4 // 0b0000_0100 //   4
          leftShiftBits << 1           // 0b0000_1000 //   8 (곱하기 2)
          leftShiftBits << 2           // 0b0001_0000 //  16 (곱하기 2^2승 => 곱하기 4)
          leftShiftBits << 5           // 0b1000_0000 // 128 (곱하기 2^5승 => 곱하기 32)
        
    • >> : Bitwise Right Shift Operator(비트와이즈 라이트 시프트 연산자)
      • 모든 비트를 원하는 값만큼 오른쪽으로 이동 (2를 나누는 효과)
      • 부호가 있을때만 주의 필요
      • 산술시프트 : 오른쪽으로 이동시에는 부호 유지를 위해, 부호가 있을땐 1 삽입됨 (부호가 없을땐 0 삽입)
          let rightShiftBits: UInt8 = 32 // 0b0010_0000 //  32
          rightShiftBits >> 1            // 0b0001_0000 //  16 (나누기 2)
          rightShiftBits >> 2            // 0b0000_1000 //   8 (나누기 4)
          rightShiftBits >> 5            // 0b0000_0001 //   1 (나누기 2^5승)
        

커스텀 연산자

사용자가 직접 정의해서(Custom) 연산자 메서드를 구현하는 방법

기본 연산자

// 1) 연산자 (위치) 선언
postfix operator <++  // 후치 연산자
prefix operator ++> // 전치 연산자

// 2) 연산자의 실제 정의
extension Int {
	// 후치 연산자
	static postfix func <++(number: inout Int) {
		number += 1
	}
	// 전치 연산자
	static prefix func ++>(number: inout Int) {
		number += 1
	}
}

// 커스텀 연산자의 사용
var a = 0
++>a      // +1
a<++      // +1
print(a)  //  2

중위 연산자

// 1) 우선순위 그룹의 선언 (우선순위, 결합성 설정)
precedencegroup MyPrecedence {
    higherThan: AdditionPrecedence
    lowerThan: MultiplicationPrecedence
    associativity: left  // 결합성 : left / right / none
}

// 2) (전역의 범위에서) 정의하려는 연산자를 선언하고, 우선순위 그룹을 지정
// 단항 -> 전치(prefix), 후치(postfix) / 이항 -> infix

// "우선 순위와 결합성"을 지정은 새로운 우선순위 그룹을 선언하거나, 이미 존재하는 우선 순위 그룹을 사용하는 것도 가능
// 우선순위 그룹을 지정하지 않으면 "DefaultPrecedence"라는 기본 그룹에 속하게 됨
// 삼항연산자보다 한단계 높은 우선순위가 되며, 결합성은 none설정되어 다른 연산자와 결합 사용은 불가능

infix operator +-: MyPrecedence

// 3) 연산자의 실제 정의
// 해당 연산자를 구현하려는 타입에서 타입메서드로 연산자 내용을 직접 구현
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

print(plusMinusVector)   // Vector2D(x: 4.0, y: -2.0)

참고

Ellen Swift Class






© 2021. by hminkim