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
}
}
(타입 메소드로 구현한 사용자 정의 연산자는 각 타입의 익스텐션으로 구현해도 됨)
* 복잡한 연산을 하나의 특수문자로 구현하는 것은 일반 함수만으로 기능을 실행하는 것 보다 훨씬 강력

댓글
댓글 쓰기