Swift 6. 연산자

 Swift 6. 연산자



- 연산자

특정한 문자로 표현한 함수

특정 연산자의 역할을 프로그래머가 의도대로 변경할 수도 있음


연산자에 의해 연산되는 값의 수에 따른 분류 : 단항, 이항, 삼항

ex) !A    /    A + B    /    A ? B : C


연산자의 위치에 따른 분류 : 전위 중위 후위

ex) !A    /    A + B    /    A!



* 띄어쓰기

Swift에서는 띄어쓰기도 중요한 문법중 하나

연산자의 앞과 뒤 중 어디에 공백이 있는지도 중요


ex) A != B 와 A! = B 는 전혀 다른 의미

ex) A > B? A : B 는 잘못된 사용 - ?를 B에서 띄어서 써야 함 => A > B ? A : B





1. 연산자의 종류



- 할당 연산자 (대입 연산자)

값을 할당(대입)할 때 사용

서로 다른 데이터 타입이라면 오류 발생


* 예시

// 변수 A에 정수 10 할당

var A: Int = 10

// 변수 B에 변수 A의 값 할당

var B: Int = A  // 같은 타입 끼리만 가능





- 산술 연산자

수학에서 쓰이는 연산자와 같은 연산 수행

서로 다른 자료형 끼리의 연산을 엄격하게 제한 (Int 와 UInt 도 연산 불가)

다른 자료형 끼리 연산 하려면 값을 해당 타입으로 변환한 후 수행


* 예시

var A: Int = 4

var B: Int = 3


print(A + B)    // 더하기

print(A - B)    // 빼기

print(A * B)    // 곱하기

print(A / B)    // 나누기

print(A % B)    // 나머지

<실행결과>

7

1

12

1

1


나누기 연산은 나머지나 소수점을 제외한 정수만을 결과값으로 반환


Swift에서는 실수의 나머지 연산도 지원 (일반적인 언어들이 정수에만 나머지 연산을 지원하는 것과 구별됨) -> .truncationRemainder(devidingBy: ) 메소드 이용


* 예시

var A: Double = 5.0

var B: Double = 1.5


print(A.truncatingRemainder(dividingBy: B))

<실행결과>

0.5




- 비교 연산자

두 값을 비교할 때 사용

A == B : A와 B가 같은 값인지 비교

A >= B : A가 B보다 크거나 같은지 비교

A <= B : A가 B보다 작거나 같은지 비교

A > B : A가 B보다 큰지 비교

A < B : A가 B보다 작은지 비교

A != B : A와 B가 다른지 비교

A === B : A와 B가 참조(레퍼런스) 타입일 때 같은 인스턴스를 가리키는지 비교

A !== B : A와 B가 참조(레퍼런스) 타입일 때 서로 다른 인스턴스를 가리키는 지 비교

A ~= B : A와 B의 패턴이 매치되는지 비교



* 참조 비교 연산자

Swift의 유일한 참조 타입인 클래스의 인스턴스만 참조 비교 연산자 사용 가능

Swift의 기본 데이터 타입은 모두 구조체로 구현되어 있어서 값 타입

값의 비교 연산에는 '=='를 사용하고 클래스의 인스턴스인 경우에만 '==='를 사용


* 예시

class SomeClass{}   // 클래스 선언



let A: SomeClass = SomeClass()

let B: SomeClass = SomeClass()

let C: SomeClass = A


print(A === B)

print(A === C)

<실행결과>

false

true





- 삼항 조건 연산자

피연산자가 세 개인 삼항 조건 연산자

조건식을 간단히 한 줄에 표현

조건 안에 또 다른 삼항 연산자를 넣을 수도 있고, 여러번 중첩할 수도 있지만, 중첩을 너무 많이 사용하면 코드의 가독성이 떨어지므로 적절히 사용해야 함

[조건식] ? [참일 때] : [거짓일 때]


* 예시

let A: Int = 5

let B: Int = 7


var bigger = A > B ? A : B

var smaller = A < B ? A : B

print("큰 값 : \(bigger)")

print("작은 값 : \(smaller)")

<실행결과>

큰 값 : 7

작은 값 : 5




- 범위 연산자

값(수)의 범위를 나타내고자 할 때 사용

주로 반복문에서 사용


* 폐쇄 범위 연산자

A...B : A 부터 B 까지 (A와 B 포함)


* 반폐쇄 범위 연산자

A.. <B : A부터 B 미만 까지의 수 (A 포함 B 미포함)


* 단방향 범위 연산자

A... : A 이상의 수 (A 포함)

...A : A 이하의 수(A 포함)

..< A : A 미만의 수 (A 미포함)




- 부울 연산자

불리언 값의 논리 연산 수행

비트 논리 연산과 잘 구분해서 사용해야 함


!A : NOT - A의 참 or 거짓의 값을 반전 

A && B : AND - A와 B의 불리언 AND 논리연산 수행

A || B: OR - A와 B의 불리언 OR 논리연산 수행




- 비트 연산자

값의 비트 논리 연산을 수행


!A : NOT 비트 연산자 - A의 비트를 반전

A & B : AND 비트 연산자 - A와 B의 비트 AND 논리 연산 수행

A | B : OR 비트 연산자 - A와 B의 비트 OR 논리 연산 수행

A ^ B : A와 B의 비트 XOR 논리 연산 수행

시프트 연산자 (A >> B, A << B) : A의 비트를 B만큼 시프트(이동)


* 시프트 연산

지정하는 수 만큼 피연산자를 왼쪽이나 오른쪽으로 이동

시프트 한 후 빈자리는 0으로 채워지고 비트  범위를 벗어난 비트는 사라짐





- 복합 할당 연산자 (복합 대입 연산자)

할당(대입) 연산자와 다른 연한자가 하는 일을 한 번에 할 수 있도록 연산자를 결합


A += B :  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를 B만큼 왼쪽 비트 시프트 한 값을 A에 할당

A >>= B : A를 B만큼 오른쪽 비트 시프트 한 값을 A에 할당

A &= B : A와 B의 비트 AND 연산 결과를 A에 할당

A |= B : A와 B의 비트 OR 연산 결과를 A에 할당

A ^= B : A와 B의 비트 XOR 연산 결과를 A에 할당





- 오버플로우 연산자

오버플로우가 발생해도 정상적으로 값을 출력해 주는 연산자

오버플로우를 자동으로 처리

Swift는 기본 연산자를 통해 오버플로우에 대비

오버플로우에 대한 이해없이 사용하면 엉뚱한 값을 구할 수도 있음


오버플로우 더하기 (&+) : 오버플로우에 대비한 덧셈 연산

오버플로우 빼기 (&-) : 오버플로우에 대비한 뺄셈 연산

오버플로우 곱하기 (&*) : 오버플로우에 대비한 곱셈 연산


* 오버플로우 : 해당 자료형의 표현 범위를 벗어난 값을 처리하는 경우



* 예시 - UInt8 자료형에 대한 오버플로우 연산 (최소값 : 0, 최대값 : 255)

let a: UInt8 = 0    // UInt8의 최소값

let b: UInt8 = 255  // UInt8의 최대값


// 일반 산술 연산자로 인한 오버플로우

print(a - 1)

print(b + 1)

<실행결과>

오버플로우 발생으로 인한 런타임 에러


let a: UInt8 = 0    // UInt8의 최소값

let b: UInt8 = 255  // UInt8의 최대값


// 오버플로우 연산자를 이용해 오버플로우에 대비

print(a &- 1)

print(b &+ 1)

<실행결과>

255

0




- 기타 연산자

Swift 라이브러에 기본적으로 정의된 연산자


nil 병합 연산자 (A ?? B) : A가 nil이 아니면 A 반환, nil이면 B 반환

부호 변경 연산자 (-A) : A(수)의 부호를 변경

옵셔널 강제 추출 연산자 (A!) : A(옵셔널 개체)의 값을 강제로 추출

옵셔널 연산자 : (A?) : A(옵셔널 값)를 안전하게 추출하거나 A(데이터 타입)가 옴셔널임을 표현


* 옵셔널 강제 추출 연산자의 사용은 지양하는 것이 좋음

* nil 병합 연산자는 옵셔널을 사용할 때 아주 유용 -> 옵셔널을 안전하게 다룰 수 있음







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


Swift의 연산자 우선순위와 결합 방향은 스위프트 표준 라이브러리에 정리되어 있음

https://developer.apple.com/documentation/swift/operator-declarations





3. 사용자 정의 연산자

기존의 연산자에 추가로 새로운 역할을 부여하거나 없는 연산자를 선언해서 역할을 부여해 줄 수 있음

기존 연산자의 역할을 변경하거나 새로운 역할을 추가하려면 해당 연산자가 전위, 중위, 후위 연산자 중에 어떤 것인지 알아야 함


* 종류별 연산자 선언 키워드

infix : 중위 연산자

prefix : 전위 연산자

postfix : 후위 연산자

operator : 연산자

associativity : 연산자 결합 방향

precedence : 연산자 우선순위


사용자 정의 연산자는 아스키코드 문자 /, =, -, +, !, *, %, <, >, &, |, ^, ?, ~ 를 결합해서 사용

마침표(.)를 사용하려면 연산자를 표현하는 문자 중 맨 처음 문자가 마침표여야 가능 (마침표로 시작하지 않으면 인식할 수 없음)

물음표(?)와 느낌표(!) 자체만으로는 사용자 정의 연산자로 정의할 수 없음 (단 전위 연산자로는 사용할 수 없음

= , -> ,  // ,  /* , */ ,  . , < , & , ?(전, 중, 후위 연산자) , > , ! 는 이미 예약되어있기 때문에 사용자 정의 연산자로 사용할 수 없음




- 전위 연산자 정의와 구현

정의 : prefix  키워드 사용 -> prefix operator(연산자임을 의미) [연산자]


구현 : 어떤 데이터 타입에 연산자가 동작할 것인지 함수를 구현

prefix func [연산자] ([연산할 데이터 타입]) -> [반환값의 데이터 타입] {

    연산 내용

}


전위 연산자에는 결합방향이나 연산자 우선순위를 지정하지 않음



* 예시 - Int 타입의 제곱을 구하는 전위 연산자 '**' 정의

prefix operator **  // 연산자 정의


// 연산자 함수 구현

prefix func ** (value: Int) -> Int{

    return value * value

}


print(**4)


<실행결과>

16



기존에 존재하는 전위 연산자에 기능을 추가할 때는 연산자를 따로 정의하지 않고 함수만 중복 정의 

-> 연산자의 재정의는 불가능하기 때문에 기존 연산자의 우선순위나 결합방향 등은 변경할 수 없음



* 예시 - 불리언 타입에 사용되는 전위 연산자 '!' 를 문자열이 비어있는 지를 확인하는 연산자로 사용

prefix func ! (value: String) -> Bool{

    return value.isEmpty

}


var str: String = "Hello World"

print(!str)


str = ""

print(!str)

<실행결과>

false

true


* 예시 - 위에서 정의한 '**' 연산자에 문자열을 두 번 반복하는 연산자 기능 추가

prefix operator **  // 연산자 정의


// 연산자 함수 구현

prefix func ** (value: Int) -> Int{

    return value * value

}


// 연산자 기능 추가

prefix func ** (value: String) -> String{

    return value + " " + value

}


print(**5)

print(**"Swift")


<실행결과>

25

Swift Swift





- 후위 연산자 정의와 구현

정의 : postfix  키워드 사용 -> postfix operator(연산자임을 의미) [연산자]


구현 : 어떤 데이터 타입에 연산자가 동작할 것인지 함수를 구현

postfix func [연산자] ([연산할 데이터 타입]) -> [반환값의 데이터 타입] {

    연산 내용

}


후위 연산자에는 결합방향이나 연산자 우선순위를 지정하지 않음



* 예시 - Int 타입의 값을 2배 하는 후위 연산자 '**'

postfix operator **  // 연산자 정의


// 연산자 함수 구현

postfix func ** (value: Int) -> Int{

    return value * 2

}


print(7**)


<실행결과>

14



* 하나의 피연산자에 전위 연산자와 후위 연산자를 한 줄에 사용하면 후위 연산자 부터 수행



* 예시

prefix operator ** // 전위 연산자 정의

postfix operator **  // 후위 연산자 정의


// 전위 연산자 함수 구현

prefix func ** (value: Int) -> Int{

    return value * value

}


// 후위 연산자 함수 구현

postfix func ** (value: Int) -> Int{

    return value * 2

}


print(**3**) // (3*2) * (3*2) 

<실행결과>

36




- 중위 연산자 정의와 구현

중위 연산자는 전위, 후위 연산자와는 다르게 우선순위 그룹과 결합 방향, 할당 방향 사용 등을 명시해 줄 수 있음

연산자 우선순위 그룹은 중위 연산자에서만 사용

(전위, 후위 연산자는 우선순위를 지정하지 않고 동시에 사용될 경우 후위 연산을 먼저 수행)



* 연산자 우선순위 그룹 정의

-> precedencegroup: [그룹 이름]  {

        higherThan : 더 낮은 우선순위 그룹 이름

        lowerThan : 더 높은 우선순위 그룹 이름

        associativity : 결합 방향 (left, right, none)

        assignment : 할당 방향 사용(true, false)

}


* 우선순위 그룹 종류 (우선순위 높은 순)













higherThan과 lowerThan을 이용해서 기존의 우선순위 그룹과 새로 만들어줄 우선순위 그룹과의 상하관계를 설정 (lowerThan 속성에는 현재 모듈 밖에 정의된 그룹만 명시 가능)

중위 연산자를 정의할 때 우선순위 그룹을 명시해 주지 않으면 우선순위가 가장 높은 DefaultPrecedence그룹을 우선순위 그룹으로 갖게됨

결합 방향은 right, left, none을 설정해 줄 수 있고 default는 none

결합 방향이 없는 연산자는 연달아 사용할 수 없음

연산자가 옵셔널 체이닝을 포함한 연산에 포함되어 있을 경우엠나 assignment(할당 방향 사용)을 true 로 해줌 

(true로 하면 옵셔널 체이닝을 오른쪽에서 부터 하고 false(default)로 하면 왼쪽에서 부터 함)



중위 연산자 정의 : infix  키워드 -> infix operator(연산자임을 의미) [연산자] : [우선순위 그룹]


구현 : 어떤 데이터 타입에 연산자가 동작할 것인지 함수를 구현. func 키워드 앞에 따로 키워드를 추가해 주지 않음

func [연산자] ([연산할 데이터 타입]) -> [반환값의 데이터 타입] {

    연산 내용

}


* 예시 - 앞에 오는 문자열에 뒤에 오는 문자열이 포함되어 있는지 확인하는 중위 연산자 정의

(연산자 우선순위 그룹은 MultiplicationPrecedence)

import Swift


import Foundation // String의 contains() 함수를 사용하기위해 임포트



infix operator ** : MultiplicationPrecedence // 중위 연산자 정의


// 중위 연산자 함수 구현

func ** (str1: String, str2: String) -> Bool{

    return str1.contains(str2) // () 안의 문자열이 포함되어 있는지 확인하는 함수

}



let a = "Hello World"

let b = "Hello"


print(a ** b)


<실행결과>

true




- 사용자 정의 연산자는 사용자 정의 데이터 타입(클래스, 구조체 등)에서도 유용하게 사용할 수 있음



* 예시 클래스와 구조체의 인스턴스끼리 비교해서 같다면 true를 반환하는 중위 연산자 == 정의

import Swift


// 클래스 정의

class Car{

    var modelYear: Int? // 년식

    var modelName: String? // 모델명

}


// 구조체 정의

struct Phone{

    var company: String? // 제조사

    var model: String?   // 모델 명

}



// Car 클래스의 인스턴스 끼리 비교해서 모델명이 같으면 true 반환하는 연산자  == 정의

func == (cls1: Car, cls2: Car) -> Bool{

    return cls1.modelName == cls2.modelName

}


// Phone 구조체의 인스턴스끼리 비교해서 모델명이 같으면 true를 반환하는 연산자 == 정의

func == (strt1: Phone, strt2: Phone) -> Bool{

    return strt1.model == strt2.model

}


let car1 = Car()

car1.modelName = "S"


let car2 = Car()

car2.modelName = "S"


print(car1 == car2)


var phone1 = Phone()

phone1.model = "13"


var phone2 = Phone()

phone2.model = "13mini"


print(phone1 == phone2)


<실행결과>

true

false



* 예시 - 사용자 정의 연산자를 타입의 내부에 타입 메소드로 구현

// 클래스 정의

class Car{

    var modelYear: Int? // 년식

    var modelName: String? // 모델명

    

    // Car 클래스의 인스턴스 끼리 비교해서 모델명이 같으면 true 반환하는 연산자  == 정의

    static func == (cls1: Car, cls2: Car) -> Bool{

        return cls1.modelName == cls2.modelName

    }

}


// 구조체 정의

struct Phone{

    var company: String? // 제조사

    var model: String?   // 모델 명

    

    // Phone 구조체의 인스턴스끼리 비교해서 모델명이 같으면 true를 반환하는 연산자 == 정의

    static func == (strt1: Phone, strt2: Phone) -> Bool{

        return strt1.model == strt2.model

    }

}



(타입 메소드로 구현한 사용자 정의 연산자는 각 타입의 익스텐션으로 구현해도 됨)





* 복잡한 연산을 하나의 특수문자로 구현하는 것은 일반 함수만으로 기능을 실행하는 것 보다 훨씬 강력






댓글

이 블로그의 인기 게시물

Spring Boot 6. 계시판 프로젝트

Spring Boot 2. Maria 데이터베이스 & Spring Data JPA

Django 10. DB 연동