[Swift] 고급 연산자

임승섭·2023년 7월 15일
0

Swift

목록 보기
35/35

숫자 리터럴

/* 숫자 리터럴 표기방법 */
var num: Int = 25

// 2진법/8진법/16진법
num = 0b00011001
num = 0o31
num = 0x19

// 큰숫자는 읽기 쉽게하기위해 언더바를 붙이는 것도 가능
// (컴퓨터는 언더바를 읽지않음)
num = 1_000_000
num = 10_000_000
num = 10000_0000


/* 정수 타입과 범위 */
// Int, UInt(양수) : 최근 대부분 64비트(8바이트)

// 몇 바이트의 메모리 사이즈를 차지하는지 알려준다. 
MemoryLayout<Int8>.size		// 1 byte
Int8.max	// 127
Int8.min	// -128

MemoryLayout<UInt8>.size	// 1 byte
UInt8.max	// 255
UInt8.min	// 0

MemoryLayout<Int>.size		// 8 byte
Int.max		// 2^63 - 1	
Int.min		// -2^63

오버플로우

  • C에서는 오버플로우를 허용(숫자가 넘어가면 0으로 돌아감)하지만,
    스위프트에서는 오버플로우를 허용하지 않는다
  • 특정 경우, 특정 패턴을 구현하기 위해 오버플로우를 허용하는 경우가 필요한데,
    이를 위해 오버플로우 연산자를 마련해 두었다.

오버플로우 연산자

  • &+ : 오버플로우 더하기
  • &- : 오버플로우 빼기
  • &* : 오버플로우 곱하기
/* Unsigned의 Overflow */
var a = UInt8.max		// 255
a = a &+ 1		// 오버플로우 더하기 연산자로 1 더함 -> 0으로 이동

// a = 0b11111111 &+ 0b00000001

var b = UInt8.min		// 0
b = b &- 1		// 오버플로우 빼기 연산자로 1 빼기 -> 255로 이동

// b = 0b00000000 &- 0b00000001



/* Signed Overflow */
var c = Int8.max		// 127
c = c &+ 1		// 오버플로우 더하기 연산자로 1 더함 -> -128로 이동

// c = 0b11111111 &+ 0b00000001

var d = Int8.min		// -128
d = d &- 1		// 오버플로우 빼기 연산자로 1 빼기 -> 127로 이동

// d = 0b10000000 &- 0b00000001

var e = Int8.max		// 127
e = e &* 2		// 오버플로우 곱하기 연산자로 2 곱하기 -> 비트 한 칸씩 이동

논리 연산자와 단락 평가

/* 논리 연산자 */
// 1. Logical NOT
!true

// 2. Logical AND
true && false
false && true	// false만 확인하고 넘어감 (2번째 결과 상관x)

// 3. Logical OR
true || false	// true만 확인하고 넘어감 (2번째 결과 상관x)

// 논리연산자 우선순위
// 1. && 2. ||

단락 평가

  • 스위프트의 논리 평가식은 단락 평가 방식을 사용한다
    • 단락 평가 : 논리 평가식에서 결과도출에 필요한 최소한의 논리식만 평가한다
var num = 0

func checking() -> Bool {
	print(#function)	// 지금 실행되고 있는 함수를 출력
    num += 1			// 외부 변수 건드림 (side effect - 함수가 실행되는지 확인할 수 있다)
    // num을 가지고 함수가 몇 번 실행되는지 확인할 수 있다.
    return true
}

// 한번만 실행해도 true가 나오기 때문에 뒤에 checking()은 실행하지 않는다
// 만약 &&로 바꾸면, 두 번 실행된다
if checking() || checking() {
}

단락 평가에서 발생할 수 있는 문제

  • side effect 발생 시 반드시 주의해야 한다
  • 단락평가로 인해 함수의 실행횟수의 차이로 인해 의도치 않은 결과가 도출될 수 있다
var doorCheck = 0
var passwordCheck = 0

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

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


/* case 1 */
if doorCodeChecking() && passwordCodeChecking() && false || true 
	&& doorCodeChecking() && passwordCodeChecking() {
}
print(Door: \(doorCheck), Password: \(passwordCheck)")
// 예상 답 : 1, 1
// 정답 : 2, 2


/* case 2 */
doorCheck = 0
passwordCheck = 0
if doorCodeChecking() && false && passwordCodeChecking() || doorCodeChecking()
	|| passwordCodeChecking() {
}
print(Door: \(doorCheck), Password: \(passwordCheck)")
// 예상 답 : 2, 0 (정답)


/* case 3 */
doorCheck = 0
passwordCheck = 0
if doorCodeChecking() || passwordCodeChecking() && doorCodeChecking() 
	|| false && passwordCodeChecking() {
}
print(Door: \(doorCheck), Password: \(passwordCheck)")
// 예상 답 : 1, 0 (정답)


/* side effect를 아예 없애는 방법 */
doorCheck = 0
passwordCheck = 0

// 미리 함수를 다 실행시킨 후, 변수에 담는다. -> if문에 들어가기 전 이미 2번씩 실행
let doorResult1 = doorCodeChecking()
let passwordResult1 = passwordCodeChecking()
let doorResult2 = doorCodeChecking()
let passwordResult2 = passwordCodeChecking()

if doorResult1 || passwordResult1 && doorResult1 || false && passwordResult2 {
    
}
print("Door: \(doorCheck), Password: \(passwordCheck)")

비트 연산자

  • 실제 앱개발에서 사용되는 경우는 거의 없다

비트 연산자

  • ~ : Bitwise NOT
let a1: UInt8 = 0b0000_1111		// 15
let b1: ~a1		// 0b1111_0000	// 240
  • & : Bitwise AND
let a2: UInt8 = 0b1111_1100   // 252
let b2: UInt8 = 0b0011_1111   // 63
let c2 = a2 & b2  // 0b0011_1100      // 60
  • | : Bitwise OR
let a3: UInt8 = 0b1011_0010   // 178
let b3: UInt8 = 0b0101_1110   // 94
let c3 = a3 | b3  // 0b1111_1110      // 254
  • ^ : Bitwise XOR
let a4: UInt8 = 0b0001_0100    // 20
let b4: UInt8 = 0b0000_0101    // 5
let c4 = a4 ^ b4  // 0b0001_0001    // 17
  • << : Bitwise Left Shift
    • 빈 공간 0
  • >> : Bitwise Right Shift
    • Signed일 때, 만약 부호가 있으면(MSB가 1이면) 오른쪽으로 밀고 남은 공간에 1 넣어주기

연산자 메서드의 직접 구현

  • 연산자 : 타입.함수이름(파라미터: 타입) 형태로 실행하지 않는 특별한 형태의 타입 메서드
// String 구조체 내에 타입 메서드로 정의되어 있는 메서드
// 문자열 더하기 연산자
static func + (lhs: String, rhs: String) -> String
// 문자열 복합할당 연산자
static func += (lhs: inout String, rhs: String)



/*==================== 1. 연산자 메서드의 구현 ====================*/
struct Vector2D {
	var x = 0.0
    var y = 0.0
}

// 1. 산술 더하기 연산자의 구현 (infix 연산자)
// infix operator + : AdditionPrecedence
extension Vector2D {
	// infix는 생략할 수 있다
    // 원래는 static infix func ~~ 이렇게 써줘야 함
	static func + (lhs: Vector2D, rhs: Vector2D) -> Vector2D {
    	return Vector2D(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
}
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
// 두 좌표를 더하고 싶다!!
let combinedVector = vector + anotherVector


// 2. 단항 prefix 연산자의 구현 (전치연산자)
// 앞에 붙이는 - 구현
extension Vector2D {
	// prefix 반드시 써줘야 한다
	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


// 3. 복합할당 연산자의 구현
extension Vector2D {
	static func += (left: inout Vector2, right: Vector2D) {
    	left = left + right
    }
}



/*==================== 2. 비교 연산자 메서드의 구현 ====================*/
// 동일성 비교에 관한 프로토콜 : Equatable

// 크기, 순서 비교에 관한 프로토콜 : Comparable

let vector1 = Vector2D(x: 1.0, y: 2.0)
let vector2 = Vector2D(x: 2.0, y: 3.0)

vector1 == vector2
// 에러 발생 -> 연산자가 정의되어 있지 않다

// Equatable 프로토콜을 채택하여 비교 연산자를 정의하자
// Equatable 프로토콜을 채택하기만 하면, 컴파일러가 연산자 메서드 구현 내용을 자동으로 추가해준다
// 저장 속성의 타입(Double, Int)는 원래 비교가 가능하기 때문에 직접적으로 구현하지 않아도, 
// 컴파일러가 알아서 구현해준다!!
// == 구현하면 != 자동 구현

extension Vector2D: Equatable {

	// 사실상 이걸 직접 구현할 필요가 없다. <- 자동 구현
	static func ==(lhs: Vector2D, rhs: Vector2D) -> Bool {
    	return (lhs.x == rhs.x) && (lhs.y == rhs.y)
    }
}

vector1 == vector2
// 이제야 비교 가능

0개의 댓글