본문 바로가기

Swift/Swift Language

Swift 언어 가이드 - 기초 연산자

기초 연산자

연산자는 값을 검사하고 변경하고 합치는 특별한 기호와 구(phrase)입니다. 예를 들어, 더하기 연산자 (+)let i = 1 + 2처럼 2개의 숫자를 더합니다. 그리고 논리 AND 연산자인 (&&)if enteredDoorCode && passedRetinaScan처럼 두 개의 불리언 값을 합칩니다.

스위프트는 대부분의 C 연산자를 제공하며 코딩 에러를 제거할 수 있도록 여러 기능들을 향상하였습니다. 대입 연산자 (=)는 값을 반환하지 않습니다. 숫자를 다룰 때 숫자가 허용된 수치보다 크거나 작은 값을 저장하는 실수를 범할 수 있습니다. 이것을 방지하고자, 산술 연산자 (+, -, *, /, %, 등)는 값을 점검하는 기능이 있습니다. 스위프트의 오버플로우 연산자를 사용하면 값의 오버플로우 상황에 맞추어 코딩할 수 있습니다. 자세한 사항은 오버플로우 연산자 (Overflow Operators)를 확인하세요.

스위프트는 또한 C에서 제공하지 않는 범위 연산자를 제공합니다. 예를 들어, a..a...b처럼 값의 범위를 단축하여 표현할 수 있습니다.

본 챕터에서는 일반적인 스위프트의 연산자를 설명합니다. 심화 연산자 (Advanced Operators)는 스위프트의 심화된 연산자를 다룹니다. 심화 연산자 챕터에서는 어떻게 여러분이 연산자를 작성할지에 대한 내용과 기본 연산자를 여러분이 생성한 타입에 구현하는 방법을 설명합니다.

용어

연산자는 단항, 이항, 삼항이 있습니다.

  • 단항 (Unary) 연산자는 하나의 대상에 대해 연산합니다. 단항은 연산자의 접두사가 되어 연산하려는 대상 앞에 붙습니다. (예를 들어, !b) 또한, 단항 접미사가 있습니다. (예를 들어, c!)
  • 이항 (Binary) 연산자는 두 개의 대상에 대해 연산합니다. (예를 들어, 2 + 3) 이것은 두 개의 대상 사이에 위치합니다.
  • 삼항 (Ternary) 연산자는 세 개의 대상에 대해 연산합니다. C언어와 같이 스위프트는 오직 하나의 삼항 연산자만 가지고 있습니다. 삼항 조건 연산자인 ( a ? b : c )가 바로 그렇습니다.

연산자의 영향을 받는 값을 무엇이라고 할까요? 피연산자(operands)입니다. 1 + 2의 표현식에서 + 기호가 이항 연산자이고 12를 두 개의 피연산자라고 합니다.

할당 연산자

할당 연산자(a = b)는 a의 값을 b의 값으로 초기화하거나 수정합니다. 예를 들어,

let b = 10
var a = 5
a = b
// a는 10과 같음

할당 연산식에서 할당 연산자의 오른쪽이 2개 이상의 값(튜플)이라면 이 값은 두 개의 상수 혹은 변수로 나눌 수 있습니다.

let (x, y) = (1, 2)
// x 는 1, y는 2와 같음

C와 Objective-C의 할당 연산자와 다르게, 스위프트의 할당 연산자는 값을 반환하지 않습니다. 다음의 문(statement)은 허용되지 않습니다.

if x = y {
    // 이 문은 유효하지 않음
    // if의 조건절에는 표현식(expression)이 오지만, 할당 연산자는 반환 값이 없음

}

수식 연산자

스위프트는 모든 숫자 타입에 대 4가지의 수식 연산자를 제공합니다.

  • 덧셈 (+)
  • 뺄셈 (-)
  • 곱셈 (*)
  • 나눗셈 (/)
1 + 2 // 3
5 - 3 // 2
2 * 3 // 6
10.0 / 2.5 // 4.0

C와 Objective-C의 수치 연산자와 다르게, 스위프트의 수치 연산자는 값이 오버플로우되는 것을 허용하지 않습니다. 오버플로우 연산자를 사용하면 값의 오버플로우 동작에 대해 설정할 수 있습니다. 자세한 사항은 오버플로우 연산자 (Overflow Operators)를 참고하세요.

덧셈 연산자는 문자열 (String) 연결을 할 수 있습니다.

"hello, " + "world" // "hello, world"

나머지 연산자

나머지 연산자 (remainder operator)는 나머지(remainder)라고 불리는 나누어 떨어지는 값을 반환합니다. 예를 들어 a % b에서는 a안에 b가 얼마나 많이 들어갈 수 있을지에 대해 말해줍니다.

참고

나머지 연산자 (%)는 모듈로 연산자(modulo operator)라고도 불립니다. 하지만 스위프트는 음수에 대해서는 모듈로(modulo) 연산자보다는 나머지(remainder) 연산자라고 부릅니다.

여기에 어떻게 나머지 연산자가 동작하는지 볼 수 있습니다. 9 % 4를 계산하려면 먼저 9안에 4가 얼마나 많이 채워질 수 있는지 확인해야 합니다.

../_images/remainderInteger_2x.png

94를 2번 채워 넣었더니 1(주황색)이 남는 것을 확인할 수 있습니다.

스위프트에서는 이렇게 사용합니다.

9 % 4 // 1

a % b의 계산 방식을 보면 % 연산자가 다음의 방정식을 계산하고 나머지 (remainder)를 반환합니다.

a = (b x some multiplier) + remainder

이 식의 some mutiplier는 a에 얼만큼의 b가 채워질 수 있는지를 말합니다.

94a, b 각각에 넣으면 다음과 같습니다.

9 = (4 x 2) + 1

나머지 연산자는 음수 값에 대한 나머지를 계산할 수 있습니다.

-9 % 4   // -1

-94를 식에 넣으면 다음과 같습니다.

-9 = (4 x -2) + -1

나머지로 -1이 반환됩니다.

a가 음수일 때, b의 부호는 무시됩니다. 즉, a % ba % -b는 항상 똑같은 결과를 갖습니다.

단항 뺄셈 연산자

뺄셈기호(-)를 숫자의 앞에 붙여서 숫자 값의 부호를 반대로 뒤집을 수 있습니다. 이것을 단항 뺄셈 연산자(unary minus operator)라고 합니다.

let three = 3
let minusThree = -three // minusThree는 -3
let plusThree = -minusThree // plusThree는 3

단항 덧셈 연산자

단항 덧셈 연산자(unary plus operator)는 값을 변경없이 그대로 반환합니다.

let minusSix = -6
let alsoMinusSix = -minuSix // alsoMinusSix는 -6

단항 덧셈 연산자가 쓸모 없이 보일 수 있지만, 음수 값에 단항 뺄셈 연산자를 사용할 때 양수 값에 대한 코드의 대칭성을 나타낼 수 있습니다.

복합 할당 연산자

스위프트는 C언어처럼 복합 할당 연산자를 지원합니다. 복합 할당 연산자는 할당 연산자와 (=) 다른 연산자를 함께 사용하는 연산자입니다. 덧셈 연산자(+=)의 예를 보겠습니다.

var a = 1
a += 2
// a 는 3

a + 2a = a + 2을 줄여서 쓴 것과 같습니다. 덧셈 연산자와 할당 연산자를 하나의 연산자처럼 사용할 수 있습니다.

참고

복합 할당 연산자는 값을 반환하지 않습니다. 예를 들어, let b= a+ 2처럼 쓸 수 없습니다.

스위프트의 기본 라이브러리(Swift standard libary)가 제공하는 연산자에 대한 자세한 내용은 연산자 선언Operator Declarations을 참고하세요.

비교 연산자

스위프트는 모든 기본 C언어의 비교 연산자를 제공합니다.

  • 같다. (a == b)
  • 같지 않다. (a != b)
  • 보다 크다 (a > b)
  • 보다 작다 (a < b)
  • 크거나 같다 (a >= b)
  • 작거나 같다 (a <= b)

참고

스위프트는 두개의 일치(identity) 연산자(===!==)를 제공합니다. 일치 연산자는 두 개의 객체의 참조가 같은 객체의 인스턴스를 가리키는지를 확인할 수 있습니다. 더 자세한 사항 일치 연산자 (Identity Operators)를 참고하세요.

모든 비교 연산자는 문(statement)가 참인지 것인지를 말해주는 Bool 값을 반환합니다.

1 == 1 // true
2 != 1 // true
2 > 1 // true
1 < 2 // true
1 >= 1 // true
2 <= 1 // false

비교 연산자는 보통 조건문에 사용됩니다.

let name = "world"
if name == "world" {
  print("hello, world")
} else {
  print("I'm sorry \(name), but I don't recognize you")
}
// "hello, world가 출력됨

if 문에 대해 자세히 알고 싶다면 흐름 제어 (Control Flow)를 확인하세요.

만약 여러분이 같은 타입과 같은 개수의 숫자를 가진 두 개의 튜플을 가지고 있다면, 그 두 개의 튜플을 서로 비교할 수 있습니다. 튜플은 왼쪽에서 오른쪽으로 한번에 하나의 값씩 비교됩니다. 튜플 내의 모든 값들이 서로 같다면 두 개의 튜플은 같은 값입니다.

(1, "zebra") < (2, "apple") // true, 1보다 2가 작으므로 "zebra"와 "apple"은 비교되지 않음
(3, "apple") < (3, "bird") // true
(4, "dog") == (4, "dog") // true

위의 예제에서, 코드의 첫 번째 줄은 왼쪽에서 오른쪽으로 비교하는 방식을 보여줍니다. 1은 2보다 작습니다. 따라서 오른쪽의 다른 값과 상관없이 두 개의 튜플은 같다고 판정됩니다. 하지만, 첫 번째 요소가 같다면 두 번째 요소가 비교됩니다.

연산자가 각각의 튜플에 있는 값을 연산할 수 있다면, 튜플은 주어진 연산자를 이용해 비교됩니다. 예를 들어, 아래의 코드처럼, 타입이 (String, Int인 2개의 튜플을 비교할 수 있습니다. 왜냐하면 StringInt 값은 <연산자를 사용하여 비교할 수 있기 때문입니다. 반대로, (String, Bool)< 연산자로 비교할 수 없습니다. <연산자가 Bool 값에 적용할 수 없기 때문입니다.

("blue", -1) < ("purple", 1) // true
("blue", false) < ("purple", true) // 에러, Boolean 값을 < 연산자에 사용할 수 없음

주의

스위프트 기본 라이브러리는 7보다 작은 개수의 튜플을 비교할 수 있는 튜플 비교 연산자를 가지고 있습니다. 7개 이상의 값을 가진 튜플을 비교하려면, 여러분이 직접 비교 연산자를 구현해야 합니다.

삼항 조건 연산자

삼항 조건 연산자 (ternary conditional operator)question ? answer1 : answer2 형태를 갖는 연산자입니다. 이 연산자는 두 개의 표현식 중 하나를 구하기 위한 축약된 연산자입니다. 만약 question이 참이면 answer이 계산되어 값으로 반환됩니다. question이 거짓이면 answer가 반환됩니다.

삼항 조건 연산자는 다음의 코드를 줄여서 쓴 것과 동일합니다.

if question {
  answer1
} else {
  answer2
}

테이블 행의 높이를 구하는 예제를 살펴봅시다. 만약 행이 헤더를 가졌다면, 행의 높이는 내용의 높이보다 50 포인트 커야합니다. 만약 행이 헤더를 가지지않는다면 행의 높이는 내용의 높이보다 20포인트 작아야 합니다.

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight는 90이다.

위의 에제는 다음의 코드를 줄여서 쓴 것입니다.

let contentHeight = 40
let hasHeader = true
let rowHeight: Int
if hasHeader {
  rowHeight = contentHeight + 50
} else {
  rowHeight = contentHeigt + 20
}
// rowHeight는 90이다.

첫 번째 예제는 삼항 조건 연산자가 사용된 예제입니다. 두 번째 예제에서 사용된 코드보다 더 간결하게 한 줄의 코드 안에서 rowHeight가올바른 값을 가질 수 있습니다.

삼항 조건 연산자는 두 개의 표현식 중 하나를 결정할 때 효율적인 축약을 제공합니다. 하지만 삼항 조건 연산자를 신중히 사용해야 합니다. 만약 이 연산자가 남용된다면 가독성을 해치게 됩니다. 여러 삼항 조건 연산자를 조합하여 복합문을 만드는 것을 피하세요.

범위 연산자

스위프트는 값의 범위를 줄여서 표현하기 위한 범위 연산자 (range operators)를 제공합니다.

닫힌 범위 연산자

닫힌 범위 연산자 (closed range operator)의 (a...b)는 a와 b의 값을 포함하여 a부터 b까지의 범위를 표현합니다. a는 b보다 크면 안됩니다.

닫힌 범위 연산자는 for-in 반복문 처럼 범위를 순회하고 싶을 때 유용합니다.

for index in 1...5 {
  print("\(index) 곱하기 5는 \(index * 5)")
}
// 1 곱하기 5는 5
// 2 곱하기 5는 10
// 3 곱하기 5는 15
// 4 곱하기 5는 20
// 5 곱하기 5는 25

for-in 반복문은 흐름 제어(Control Flow)에서 자세히 알 수 있습니다.

한쪽만 열린 범위 연산자

한쪽만 열린 범위 연산자(half-open range operator)의 예제인 (a..<b)은 a는 포함되지만 b는 포함되지 않는 a부터 b까지의 범위를 포현합니다. 닫힌 범위 연산자처럼 a는 b보다 커선 안됩니다. a가 b와 같다면 범위는 존재하지 않습니다.

한쪽만 열린 범위는 배열과 같은 0부터 시작하는 리스트에 대해 작업할 때 유용합니다.

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
  print("사람 \(i + 1)은 \(names[i])라고 한다.")
}
// 사람 1은 Anna라고 한다.
// 사람 2는 Alex라고 한다.
// 사람 3은 Brian이라고 한다.
// 사람 4는 Jack이라고 한다.

배열이 4개의 아이템들을 포함한다는 것에 주목하세요. 하지만 0..<count는 배열의 마지막 아이템의 번호인 3까지 개수를 셉니다. 배열에 대해 자세한 사항은 배열 (Arrays)을 참고하세요.

단방향 범위

한 방향으로 범위가 계속될 때 닫힌 범위 연산자를 사용할 수 있습니다. 예를 들어, 배열의 2번째 요소 부터 모든 요소를 포함하는 범위를 표현할 때 유용합니다. 연산자가 오직 한쪽에만 값을 가지므로 이러한 범위를 단방향 범위라고 부릅니다.

for name in names[2...] {
  print(name)
}
// Brain
// Jack

절반만 열린 연산자 또한 단방향 형태를 취할 수 있습니다. 마지막 값은 범위에 포함되지 않습니다.

for name in names[..<2] {
  print(name)
}
// Anna
// Alex

단방향 범위는 이처럼 배열을 잘라낼 때 이외에도 다른 상황에서도 사용됩니다. 첫 번째 값을 제외하여 단방향 범위를 순회할 수 없습니다. 왜냐하면 순회가 어디서 시작되는지 명확하지 않기 때문입니다. 여러분은 마지막 값을 생략하여 단방향 범위를 순회할 수 있습니다. 하지만 범위는 무한정 계속됩니다. 반복문에 종료 조건을 표현하도록 하세요. 다음의 코드를 통해 단방향 범위가 특정 값을 포함하는지 확인할 수 있습니다.

let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true

논리 연산자

논리 연산자는 불리언 논리 값을 조합하거나 수정합니다. 스위프트는 C언어 기반의 3개의 일반 논리 연산자를 제공합니다.

  • 논리 부정, NOT (!a)
  • 논리 곱, AND (a && b)
  • 논리 합, OR (a || b)

논리 부정 연산자

논리 부정 연산자(!a)는 불리언 값을 반대로 바꾸어서 true가 false로 변경하거나 false를 true로 변경합니다.

논리 부정 연산자는 맨 앞에 붙는 연산자입니다. 이 연산자와 값 사이에는 공백이 있으면 안됩니다. 이 것을 "not a", "a가 아닌"으로 읽을 수 있습니다.

let allowedEntry = false
if !allowedEntry {
  print("ACCESS DENIED")
}
// "ACCESS DENIED"가 출력됨

if !allowedEntry만약 entry가 허용되지 않는다면 으로 읽을 수 있습니다. 이후의 줄은 "entry가 허용되지 않는다"가 참일 때만 수행됩니다.

이 예제에서 처럼 불리언 상수와 변수의 이름을 잘 선택하는 것은 코드의 가독성과 명료함에 도움을 줍니다. 이중 부정이나 복잡한 논리 문을 피하세요.

논리곱 연산자

논리곱 연산자(a && b)는 두 값이 반드시 참이여야 전체 표현식이 참이되는 논리적 표현을 생성합니다.

두 값 중 하나가 거짓이라면 모든 표현식은 거짓이됩니다. 더 자세하게는, 첫 번째 값이 거짓이라면 두 번째 값은 계산되지 않습니다. 이것을 short-circuit evaluation이라고 합니다.

let allowedEntry = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
  print("Welcome!")
} else {
    print("ACCESS DENIED")  
}
// "ACCEESS DENIED"가 출력됨

논리합 연산자

논리합 연산자(a || b)는 두 개의 파이프 문자를 사용한 사이에 붙는 연산자입니다. 두 값 중 하나가 참이라면 전체 표현이 참이되는 논리적 표현을 만들 수 있습니다.

논리곱 연산자처럼 논리합 연산자는 표현식이 참인지를 판별하기 위해 "short-circuit evalution"을 사용합니다. 왼쪽의 논리합 표현식이 참이라면 오른쪽은 계산되지 않습니다.

아래의 예제는 첫 번째 Bool 값인 hasDoorKey가 거짓이며 두 번째 값인 (knowsOverridePassword)가 참입니다. 둘 중하나가 참이므로 전체 표현식은 참이되며 if절 안의 접근이 허용됩니다.

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
  print("Welcome!")
} else {
  print("ACCESS DENIED")
}
// "Welcome!"이 출력됨

논리 연산자 조합하기

여러분은 여러 개의 논리 연산자를 조합하여 복합 표현식을 생성할 수 있습니다.

if enteredDoorCode && passedRetinaScan || hasDoorKey 
        || knowsOverridePassword {
             print("Welcome!")
} else {
      print("ACCESS DENIED")
}

// "Welcome!"이 출력됨

이 에제는 &&와 || 연산자를 여러번 사용하여 복합 표현식을 생성하였습니다. 하지만 &&와 || 연산자는 여전히 두 개의 값에 대해서만 동작합니다. 따라서 실제로는 3개의 작은 표현식이 서로 묶여있는 것입니다. 이 예제를 다음과 같이 해석할 수 있습니다.

만약 올바른 문 번호를 입력하고 홍채 검사를 통과하면 문에 들어갈 수 있다 또한, 유효한 문 열쇠를 가지고 있거나 응급 비밀번호를 알고 있다면 접근할 수 있다.

enteredDoorCode, passedRetinaScan, hasDoorKey의 값에 따라서 첫 번째 두개의 부 표현식이 거짓이 됩니다. 하지만, 응급 번호를 알고 있으므로 전체 복합 표현식은 참으로 계산됩니다.

참고

스위프트의 논리 연산자인 &&와 ||은 왼쪽 결합방식입니다. 즉, 여러 논리 연산자를 가진 복합 표현식은 가장 왼쪽의 부 표현식 먼저 계산합니다.

의도적 소괄호

복잡한 표현식을 쉽게 읽기 위해 소괄호를 사용하는 것이 좋습니다. 위의 예제를 통해, 첫번째 복합 표현식에 소괄호를 추가해 봅시다.

if (enteredDoorCode && passedRetinaScan) || hasDoorKey ||
        knowsOverridePassword {
      print("Welcome!")
    } else {
      print("ACCESS DENIED")
    }
// "Welcome!"가 출력됨

소괄호가 첫 번째 두개의 값을 묶어서 전체 논리표현식에 상태와 별개임을 표현하도록 했습니다. 이 복합 표현식의 결과는 바뀌지 않습니다. 하지만, 전체 표현식은 쉽게 읽혀집니다. 가독성은 간결함보다 중요합니다. 여러분의 의도를 명확하게 하기 위해 소괄호를 사용하세요.