애플사이다의 iOS 개발 일지

[Swift Language Guide 정독 시리즈] 2. Basic Operators 본문

Swift/영문 공식문서 뜯어보기-Swift

[Swift Language Guide 정독 시리즈] 2. Basic Operators

Applecider 2021. 9. 21. 01:48

안녕하세요. 애플 사이다입니다.

[Swift Language Guide 정독 시리즈]의 두 번째 파트 Basic Operators에 대해 정리해보겠습니다.

*Swift Language Guide를 읽어야 하는 이유는 시리즈 0. Language Guide란? 포스팅을 참고해주세요.

 


Basic Operators (기본 연산자)

연산자 (Operator)는 여러 값을 확인/변경/결합하기 위해 사용하는 특별한 기호 (symbol) 또는 구문 (phrase)이다.

Swift의 연산자는 "함수"로 정의되어 있다.

  • 더하기 연산자 (addition operator) + 는 2개의 숫자를 더한다. ex) let i = 1 + 2
  • AND 부울 연산자 (logical AND operator) && 는 2개의 불리언값 (boolean values)을 결합한다. ex) if enteredDoorCode && passedRetinaScan

Swift의 연산자는 C언어의 연산자와 유사하지만, 자주 발생하는 코딩 에러를 방지하도록 몇 가지 기능이 추가되었다. 

  • 할당 연산자 (assignment operator) = 는 값이 같음 연산자 (equal to operator) == 와 구분하기 위해 값을 반환하지 않도록 했다.
  • 산술 연산자 (arithmetic operator) +, -, *, /, % 등은 자동으로 오버플로우 (overflow)를 방지하여 해당 타입이 저장할 수 없는 값을 취급하지 않도록 막는다. (오버플로우 연산자 (overflow operator)를 사용하면 오버플로우를 자동으로 처리한다.)

또한, C언어에는 없는 기능이 추가되었다.

  • 범위 연산자 (range operator) a..<b, a...b 등을 통해 값의 범위를 쉽게 나타낸다.

*기본적인 연산자 외의 심화 내용은 Advanced Operators 챕터에서 다루겠습니다. Advanced Operators 챕터에서는 사용자 정의 연산자를 정의하고, 기본 연산자를 사용자 정의 타입에 구현하는 방법에 대해 설명합니다.

Terminology (용어)

연산자의 영향을 받는 값을 피연산자 (Operand)라고 한다. 

연산자는 피연산자의 개수에 따라 단항 (Unary) / 이항 (Binary) / 삼항 (Ternary)으로 구분한다.

(연산자의 위치에 따라 전위 (Prefix) / 중위 (Infix) / 후위 (Postfix)로 구분하기도 한다.)

  • 단항 연산자 : 1개의 대상에 대해 연산한다. 전위 연산자 (prefix operator) 또는 후위 연산자 (postfix operator)가 있다. ex) -a, !b, c!
  • 이항 연산자 : 2개의 대상에 대해 연산한다. 중위 연산자 (infix operator) 이다. ex) 2 + 3 
  • 삼항 연산자 : 3개의 대상에 대해 연산한다. 삼항조건 연산자 (ternary conditional operator) 한 가지밖에 없다. ex) a ? b : c

Assignment Operator (할당 연산자)

할당 연산자 a = b 는 좌측의 a 값을 우측의 b의 값으로 초기화 (initialize) 또는 변경 (update)한다. 

우측에 튜플이 있으면, 튜플의 요소 (element)는 여러 개의 상수/변수로 분해 (decomposed into multiple constants or variables) 된다.

C 및 Objective-C와 달리, 오류를 방지하기 위해 Swift의 할당 연산자는 값을 반환하지 않는다. 이를 통해 값이 같음 연산자 (equal to operator) == 와 기능이 구분된다.

let b = 10
var a = 5
a = b  // a is now equal to 10

let (x, y) = (1, 2)  // x is equal to 1, and y is equal to 2

if x = y {  // This isn't valid, because x = y doesn't return a value. -> x == y 로 수정해야 함
	// ...
}

Arithmetic Operator (산술 연산자)

모든 숫자 타입에 대해 네 가지 기본 산술 연산자를 사용 가능하다.

더하기 (Addition) +, 빼기 (Subtraction) -, 곱하기 (Multiple) *, 나누기 (Division) / 연산자가 있다.

 

더하기 연산자 + 는 문자열 이어붙이기 (String Concatenation)가 가능하다. (append 함수와 동일하게 작동한다.
(단, + 는 컴파일 속도를 늦추므로 append 함수 또는 문자열 보간법 (String Interpolation)을 사용하는 것이 좋다.) 

1 + 2       // equals 3
5 - 3       // equals 2
2 * 3       // equals 6
10.0 / 2.5  // equals 4.0

print("Hello, " + "AppleCider")  // Prints "Hello, AppleCider"

var greeting = "Hello, "
greeting.append("AppleCider")
print(greeting)			 // Prints "Hello, AppleCider"

//print("Hello, ".append("AppleCider"))  // 참고 - 컴파일 에러 발생 (Cannot use mutating member on immutable value: literals are not mutable) 
// 즉, String 리터럴인 "Hello, "는 default로 상수가 된다. 따라서 변수에 할당 후에 append 메서드를 사용할 수 있다.

C 및 Objective-C와 달리, Swift의 산술 연산자는 자동으로 오버플로우 (overflow)를 처리하지 않는다. 산술 연산 결과, 특정 타입이 저장할 수 없는 범위의 값을 할당하여 오버플로우가 되면, 런타임 오류가 발생한다. 

오버플로우를 자동으로 처리하려면 오버플로우 연산자 (overflow operator)를 사용한다. ex) a &+ b

Remainder Operator (나머지 연산자)

나머지 연산자 a % b 는 a를 b로 나눈 결과 발생한 나머지 (remainder)를 반환한다.

나머지 연산자는 아래의 수식을 계산하여 결과값을 반환한다.

a = (b x some multiplier) + remainder

  • some multiplier는 "a에 몇 개의 b가 채워질 수 있는지"를 의미한다.
    (some multiplier is the largest number of multiples of b that will fit inside a.)
  • some multiplier는 음수일 수 있다. (따라서 b가 음수인 경우, b의 부호는 무시된다. 즉 b = -b 로 취급된다.)
    따라서 a % b 및 a % -b 의 반환값은 동일하다.
  • a가 음수인 경우, 반환값도 음수이다.
9 % 4   // 9 = (4 x 2) + 1  =>  equals 1
-9 % 4  // -9 = (4 x -2) + -1  =>  equals -1

// Int 타입의 나누기 연산 및 나머지 연산
let Int5: Int = 5
print(5 / 3) // 1 출력 (나누기 연산자는 함수로 구현된다. 반환타입은 Self 이다.) 5 / 3 = 1.666... 이지만, Int 타입 (소수점을 버린 1)을 반환한다.
print(5 % 3) // 2 출력 (나머지 연산자가 5 = (3 x 1) + 2 를 계산하여, 2를 반환한다.)

// Double 타입의 나누기 연산 및 나머지 연산
let a: Double = 5.0
print(a / 3.0) // 1.6666666666666667 출력 (반환타입이 Self이므로 Double 타입을 반환한다.)
//a % 3.0 // 컴파일 에러 발생 - '%' is unavailable: For floating point numbers use truncatingRemainder instead
print(a.truncatingRemainder(dividingBy: 3.0)) // 2.0 출력 - Double 타입의 나머지 연산은 % 대신 truncatingRemainder 함수를 사용한다. (반환타입은 Self 이다.)

나누기 연산자 및 나머지 연산자를 구현한 함수를 보면, 반환타입이 Self 이다.

따라서 Int 타입의 나누기/나머지 연산 결과는 Int 타입 (소수점을 버림)이고, Double 타입의 연산 결과는 Double 타입이다.

단, Double 타입의 나머지 연산은 % 연산자를 사용하지 않고, truncatingRemainder 함수를 사용한다.

static func / (lhs: Self, rhs: Self) -> Self
// Returns the quotient of dividing the first value by the second.

static func % (lhs: Self, rhs: Self) -> Self
// Returns the remainder of dividing the first value by the second.

*lhs (Left Hand Side, 좌측)  : The value to divide.
*rhs (Right Hand Side, 우측) : The value to divide lhs by. rhs must not be zero.

 

Note: 다른 언어에서는 나머지 연산자를 modulo operator라고도 부른다. 하지만 Swift에서 음수에 대한 동작은 modulo operator가 아닌 나머지 연산에 해당한다.

Unary Minus Operator (단항 빼기 연산자, 부호변경 연산자)

단항 빼기 연산자 또는 부호변경 연산자라고 부르는 - 를 통해 숫자의 부호를 변경할 수 있다.

전위 (prefix) 형태이며, 공백 없이 값 앞에 붙인다.

방정식의 부호 처리와 동일하게, 연속하여 -(-someVariable) 형태로 사용하면 +(someVariable) 가 된다.

let three = 3
let minusThree = -three       // minusThree equals -3
let plusThree = -minusThree   // plusThree equals 3, or "minus minus three" (--3 => +3)

Unary Plus Operator (단항 더하기 연산자)

단항 더하기 연산자는 값을 변경 없이 그대로 반환한다.

단항 빼기 연산자 (부호변경 연산자)를 사용할 때, 코드를 대칭 (symmetry)적으로 보이게 해서 가독성을 개선한다.

let minusSix = -6
let alsoMinusSix = +minusSix  // alsoMinusSix equals -6

단항 빼기/더하기 연산자를 구현한 함수는 아래와 같다.

prefix static func - (operand: Self) -> Self
// Returns the additive inverse of the specified value.

prefix static func + (x: Self) -> Self
// Returns the given number unchanged.

Compound Assignment Operators (복합 할당 연산자)

할당 연산자 (=)와 다른 연산을 결합한 복합 할당 연산자가 있다.

예를 들어 더하기 및 할당 기능을 결합한 더하기 할당 연산자 += 가 있다. (그 외에도 -=, *=, /=, %= 가 있다.)

var a = 1
a += 2  // a = a + 2 와 동일하다.
// a is now equal to 3

Note: 복합 할당 연산자는 값을 반환하지 않는다. ex) let b = a += 2 는 불가하다.

Swift 표준 라이브러리에서 제공하는 다른 연산자는 Operator Declarations 페이지에서 확인 가능하다.

Comparison Operators (비교 연산자)

다음과 같은 비교 연산자가 있다.

  • 값이 같음 (Equal to) a == b, 값이 같지 않음 (Not equal to) a != b
  • 값이 큼 (Greater than) a > b, 값이 크거나 같음 (Greater than or equal to) a >= b
  • 값이 작음 (Less than) a < b, 값이 작거나 같음 (Less than or equal to) a <= b
  • 참조 비교 연산자 (Identitiy Operator, 식별 연산자) : 참조가 같음 (Identical to) ===, 참조가 같지 않음 (Not identical to) !==

*참조 비교 연산자 (식별 연산자)는 Structures and Classes 챕터에서 자세히 다루겠습니다.

(참조 타입인 클래스 인스턴스에서 사용한다. 2개의 참조 (object references)가 동일한 인스턴스를 가리키는지 (refer to the same object instance) 확인한다.)

 

비교 연산자는 구문 (statement)이 참인지, 거짓인지 나타내는 불리언값 (Bool value)을 반환한다.

조건문 (conditional statement)에도 사용할 수 있다.

1 == 1   // true because 1 is equal to 1
2 != 1   // true because 2 isn't equal to 1
2 > 1    // true because 2 is greater than 1
1 < 2    // true because 1 is less than 2
1 >= 1   // true because 1 is greater than or equal to 1
2 <= 1   // false because 2 isn't less than or equal to 1

let name = "world"
if name == "world" {  // 상수 name의 값 "world" 및 String 리터럴 "world"이 동일한지 비교한다. true를 반환한다.
    print("hello, world")
} else {
    print("I'm sorry \(name), but I don't recognize you")
}
// Prints "hello, world", because name is indeed equal to "world".

*if문 등 조건문은 Control Flow 챕터에서 자세히 다루겠습니다.

 

튜플의 1) 타입이 같고, 2) 튜플 요소의 개수가 같고, 3) 비교 연산자를 모든 튜플 요소에 적용 가능하면, 2개의 튜플을 비교할 수 있다.

이때, 튜플을 비교하는 방법은 특이하다.

Tuples are compared from left to right, one value at a time, until the comparison finds two values that aren’t equal. Those two values are compared, and the result of that comparison determines the overall result of the tuple comparison.

 

각 요소를 왼쪽부터 오른쪽으로 하나씩 비교하며, 2개의 값이 동일하지 않은 경우를 찾을 때까지 비교한다. 해당 2개의 값 (동일하지 않은 튜플 요소들)의 비교 결과가 최종적으로 두 튜플의 비교 결과를 결정한다. 

즉, someTupleA < someTupleB 일 때, 각 튜플의 모든 요소가 A < B 관계가 아니라는 것이다. 왼쪽부터 비교를 시작해서 한 요소라도 먼저 A < B 이면, someTupleA < someTupleB 이 참이 된다.

예를 들어 someTupleA = (10, 200, 300) 및 someTupleB = (100, 20, 30)가 있을 때, 두세 번째 요소가 200 > 20300 > 30 이더라도 첫 번째 요소인 10 < 100 가 참이므로 최종적으로 someTupleA < someTupleB 가 참이 된다.

 

튜플의 모든 요소가 동일하면, "동일한 튜플"이다.

(1, "zebra") < (2, "apple")  // true because 1 is less than 2; "zebra" and "apple" aren't compared
(3, "apple") < (3, "bird")   // true because 3 is equal to 3, and "apple" is less than "bird"
(4, "dog") == (4, "dog")     // true because 4 is equal to 4, and "dog" is equal to "dog"

("blue", false) < ("purple", true)  // 컴파일 에러 발생 - Binary operator '<' cannot be applied to two '(String, Bool)' operands
// Error because < can't compare Boolean values
  • 첫 번째 코드 : 튜플의 가장 왼쪽 요소인 요소 1 및 요소 2 가 동일하지 않다. 따라서 1 < 2 이 참이므로 최종적으로 튜플 비교 결과는 참이다. 이때, 다른 요소들 ("zebra" < "apple")의 비교는 무시한다.
  • 두 번째 코드 : 튜플의 가장 왼쪽 요소 (3)가 동일하다. 따라서 오른쪽의 요소를 확인하며, 요소 "apple" 및 요소 "bird"는 동일하지 않다. 따라서 "apple" < "bird"이 참이므로 (단어의 첫 번째 문자 "a" < "b"가 참이므로) 최종적으로 튜플 비교 결과는 참이다.
    *알파벳의 비교 : 사전 상의 순서 (lexicographical ordering, A-Z)에 따라 "a"가 "b" 앞에 있으므로 "a" < "b" 는 참이다.
  • 세 번째 코드 : 튜플의 모든 요소가 동일하므로 동일한 튜플이다.
  • 네 번째 코드 : 비교 연산자 < 를 불리언값인 true/false에 적용할 수 없으므로 컴파일 에러가 발생한다.
Note: The Swift standard library includes tuple comparison operators for tuples with fewer than seven elements. To compare tuples with seven or more elements, you must implement the comparison operators yourself.

 

Swift 표준 라이브러리의 튜플 비교 연산자는 튜플 요소가 7개 미만일 때만 사용 가능하다. 7개 이상의 튜플 요소가 있는 튜플을 비교할 때는 비교 연산자를 직접 구현해야 한다. 

Ternary Conditional Operator (삼항 조건 연산자)

The ternary conditional operator is a special operator with three parts, which takes the form question ? answer1 : answer2. It’s a shortcut for evaluating one of two expressions based on whether question is true or false. If question is true, it evaluates answer1 and returns its value; otherwise, it evaluates answer2 and returns its value.

 

question ? answer1 : answer2 형태의 연산자이다.

question의 불리언값에 따라 answer1 또는 answer2 중 하나를 평가 (evaluate)할 때 유용하다.

question이 참인 경우, 수식 (expression) answer1을 평가하여 결과값을 반환한다. question이 거짓인 경우, answer2를 평가하여 결과값을 반환한다.

*값 (value)은 수식 (expression)이 평가 (evaluate)되어 생성된 결과이다.

*평가 : 수식을 해석해서 값을 생성하거나 참조하는 것이다.

if question {
    answer1
} else {
    answer2
}

question ? answer1 : answer2  // 위 코드와 동일한 shorthand

아래 예시는 표의 행 높이 (the height for a table row)를 계산한다. 행의 헤더가 있으면 (true), 콘텐츠 높이에 50을 더한다. 행의 헤더가 없으면 (false), 콘텐츠 높이에 20을 더한다. 

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20) // rowHeight is equal to 90

// 위와 동일한 if-else문
let contentHeight = 40
let hasHeader = true
let rowHeight: Int
if hasHeader {
    rowHeight = contentHeight + 50
} else {
    rowHeight = contentHeight + 20
}
// rowHeight is equal to 90
Use the ternary conditional operator with care, however. Its conciseness can lead to hard-to-read code if overused. Avoid combining multiple instances of the ternary conditional operator into one compound statement.

 

❗️삼항 조건 연산자를 과용하면, 간결함 때문에 오히려 가독성이 떨어진다. 하나의 구문에 삼항 조건 연산자를 여러 개 결합하여 사용하지 않도록 한다.

Nil-Coalescing Operator (Nil 병합 연산자)

a ?? b 는 옵셔널인 a에 값이 있으면, 값을 추출 (unwrap)하여 반환한다. 옵셔널 a에 값이 없으면, b를 기본값으로 반환한다.

수식 (expression) a의 타입은 옵셔널 타입이어야 하고, 수식 b의 타입은 a에서 추출할 값의 타입과 동일해야 한다.

a != nil ? a! : b

a ?? b  // 위 코드와 동일한 shorthand

Nil 병합 연산자는 1) 조건 확인 및 2) 옵셔널 값 추출 기능을 묶어서 (encapsulate) 간결하고 가독성 있는 형태로 구현한다.

Note: If the value of a is non-nil, the value of b isn’t evaluated. This is known as short-circuit evaluation.

 

✅ 옵셔널 a에 값이 있으면, 수식 b는 평가되지 않는다. 이것을 단락 평가 (short-circuit evaluation)라고 한다.

연산을 최소화하는 방법이다. 2개 이상의 논리 연산을 수행할 때, 첫 번째 수식만으로 결과가 확실하면 두 번째 이후의 수식은 평가하지 않는다.

 

아래 예시는 Nil 병합 연산자를 사용하여 1) 기본 색상이름 또는 2) 옵셔널 타입의 사용자 정의 색상이름 중 하나를 선택한다.

let defaultColorName = "red"
var userDefinedColorName: String?  // defaults to nil (옵셔널 타입의 상수/변수에 초기값을 지정하지 않으면, 자동으로 nil이 할당된 상태가 된다.)

var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName is nil, so colorNameToUse is set to the default of "red"
// (변수 userDefinedColorName가 nil이므로 변수 colorNameToUse에 상수 defaultColorName의 값인 "red"가 할당된다.)

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName isn't nil, so colorNameToUse is set to "green"
// (변수 userDefinedColorName가 nil이 아니므로 변수 colorNameToUse에 변수 userDefinedColorName의 값인 "green"이 할당된다.)

Range Operators (범위 연산자)

범위 연산자를 통해 값의 범위를 쉽게 나타낼 수 있다.

Closed Range Operator (폐쇄 범위 연산자)

a...b 는 a 및 b를 포함하여 a부터 b까지의 값을 나타낸다. (a range that runs from a to b, and includes the values a and b.)

a보다 b가 커야 한다.

a와 b가 동일하면, 1개 값을 포함한 범위가 된다.

 

for-in 반복문 (for-in loop) 등에서 해당 범위의 수로 반복 (iterate)할 때 유용하다.

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

for index in 1...1 {  // a...b에서 a와 b가 동일하면, 1개 값을 포함한 범위가 된다.
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5

*For-in 반복문은 Control Flow 챕터에서 자세히 다루겠습니다.

Half-Open Range Operator (반폐쇄 범위 연산자)

a..<b 는 a는 포함하되 b는 포함하지 않는 a부터 b까지의 값을 나타낸다. (a range that runs from a to b, but doesn’t include b.)

첫 번째 값은 포함하고, 마지막 값은 포함하지 않기 때문에 반폐쇄 (half-open)라고 부른다.

a보다 b가 커야 한다.

a와 b가 동일하면, 빈 범위 (empty range)가 된다.

 

배열 (Array)과 같이 0에서부터 시작하는 리스트 (zero-based list)를 다룰 때, 리스트의 길이 (length)를 나타낼 경우 유용하다.
(배열의 index는 0부터 시작하기로 약속되어 있다.)

*배열은 Collection Types 챕터의 Arrays 섹션에서 자세히 다루겠습니다.

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count  // 배열에 4개 요소가 있으므로 count에 4가 할당된다.
for i in 0..<count {  // 0..<4 를 나타내므로 iterator i에 0, 1, 2, 3이 차례로 할당된다.
    print("Person \(i + 1) is called \(names[i])")
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack

for i in 0..<0 {  // a..<b 에서 a와 b가 같으면 빈 범위 (empty range)가 된다.
    print("Person \(i + 1) is called \(names[i])")
}
// 아무것도 출력하지 않는다.

One-Sided Ranges (단방향 범위 연산자)

폐쇄 및 반폐쇄 범위 연산자는 a 이상/이하 또는 a 초과/미만 등 한쪽 방향으로 커지거나 작아지는 범위의 값도 나타낼 수 있다.

예를 들어 배열에서 index 2 이상 (index 2부터 마지막까지)에 해당하는 요소를 나타낼 수 있다. (특정 범위의 배열 잘라내기)

이때, 범위 연산자의 한 쪽 끝을 생략 가능하므로 단방향 범위 연산자라고 부른다.

let names = ["Anna", "Alex", "Brian", "Jack"]  // 각 요소의 index는 0~3

for name in names[2...] {  // index 2 이상 (index 2부터 마지막까지)의 요소를 나타낸다.
    print(name)
}
// Brian
// Jack

for name in names[...2] { // index 2 이하 (index 처음부터 2까지)의 요소를 나타낸다.
    print(name)
}
// Anna
// Alex
// Brian

for name in names[..<2] {  // index 2 미만 (index 처음부터 1까지)의 요소를 나타낸다.
    print(name)
}
// Anna
// Alex

이러한 서브스크립트 (Subscript) 외에도 특정 값이 범위에 포함되었는지 여부를 확인하기 위해 단방향 범위 연산자를 사용할 수 있다.

*서브스크립트는 Subscripts 챕터에서 자세히 다루겠습니다.

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

for-in문 등 반복문에서 첫 번째 값이 생략된 단방향 범위 연산자를 사용할 수 없다. (반복이 어디에서 시작하는지 알 수 없으므로)

하지만 마지막 값이 생략된 단방향 범위 연산자는 사용 가능하다. 단, 반복문의 종료 조건을 명시해야 무한 루프가 되지 않는다.

Logical Operators (논리 연산자, 부울 연산자)

논리 연산자는 참/거짓의 불리언값을 수정하거나 결합할 때 사용한다. 즉, 연산자 좌우의 값을 비교하고, 그 결과로 불리언값을 만든다.

논리 연산자의 결과값은 참 또는 거짓이다.

Swift에는 C언어처럼 세 가지 논리 연산자가 있다.

  • NOT 논리 연산자 (Logical Not, 논리 부정 연산자) !a
  • AND 논리 연산자 (Logical AND, 논리 곱 연산자) a && b
  • OR 논리 연산자 (Logical OR, 논리 합 연산자) a || b

Logical NOT Operator (NOT 논리 연산자)

!a 는 불리언값을 반전 (invert)한다. 참은 거짓, 거짓은 참이 된다.

!a는 "not a"라고 읽을 수 있다.

let allowedEntry = false
if !allowedEntry {  // “if not allowed entry.” 라고 읽을 수 있다.
    print("ACCESS DENIED")
}
// Prints "ACCESS DENIED"

❗️위 예시는 Bool 타입의 상수 이름이 적절하며, 가독성이 좋은 코드이다. 이중 부정문 (double negatives)을 피해야 한다.

Logical AND (AND 논리 연산자, 논리 곱 연산자)

a && b 를 통해 논리적 수식 (logical expressions)을 만든다. a와 b 모두 참이면, 전체 수식이 참이 된다.

하나라도 거짓이면, 전체 수식은 거짓이 된다.

첫 번째 값인 a가 거짓이면, 두 번째 값인 b를 평가할 필요가 없으므로 위에서 언급한 단락 평가 (short-circuit evaluation)에 속한다.

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {  // 두 상수 모두 true이면, 조건이 true가 된다. (하나가 false 이므로 else문이 실행된다.)
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "ACCESS DENIED"

Logical OR (OR 논리 연산자, 논리 합 연산자)

a || b 를 통해 논리적 수식 (logical expressions)을 만든다. a와 b 중 하나라도 참이면, 전체 수식이 참이 된다.

첫 번째 값인 a가 참이면, 두 번째 값인 b를 평가할 필요가 없으므로 마찬가지로 단락 평가 (short-circuit evaluation)에 속한다.

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {  // 둘 중 하나라도 참이면, 조건이 참이 된다.
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

❗️Swift에서는 0/1이 false/true를 나타내지 않는다.

let i = 1
if i {
    // 컴파일 에러 발생 - Type 'Int' cannot be used as a boolean; test for '!= 0' instead
}

print(true || false) // true 출력
print(true && false) // false 출력

print(1 || 0) // 컴파일 에러 발생 - Type 'Int' cannot be used as a boolean; test for '!= 0' instead
print(1 && 0)

Combining Logical Operators (논리 연산자 결합)

여러 개의 논리 연산자를 결합하여 복합 수식 (compound expressions)을 만들 수 있다.

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

여러 개의 논리 연산자를 사용할 때도 논리 연산자는 2개의 값에 대해 동작한다.

따라서 위 예시는 3개의 수식이 연결되어 있는 것이다. 

If we’ve entered the correct door code and passed the retina scan, or if we have a valid door key, or if we know the emergency override password, then allow access.

 

위 예시는 "1) door code가 맞고, 망막 스캔이 통과되거나, 또는 2) 맞는 door key를 갖고 있거나, 또는 3) emergency override password를 알고 있다면, 접근을 허용한다." 라고 읽을 수 있다.

즉, A && B || C || D 는 1) A와 B 모두 참이다, 2) C는 참이다, 3) D는 참이다, 라는 세 가지 조건 중에서 하나라도 참이면, 최종적으로 조건은 참이 된다는 의미이다.

참고 - if A, B, C, D { ... } 형태와 같이 comma (,)로 연결된 것은 condition list (조건 리스트) 이다. 논리 연산자와 기능이 다르다.

Note: The Swift logical operators && and || are left-associative, meaning that compound expressions with multiple logical operators evaluate the leftmost subexpression first.

 

✅ Swift의 논리 연산자 && 및 || 는 left-associative 이므로 여러 개의 논리 연산자가 있는 수식은 맨 왼쪽 (leftmost)부터 먼저 평가한다.

 

*연산자는 우선순위 (Precedence) 및 결합방향 (Associativity)이 정해져 있다. 

여러 개의 연산자가 있을 때, 우선순위가 높은 연산자부터 먼저 실행된다. 또한 동일한 우선순위의 연산자가 여러 개 있을 때, 결합방향에 따라 연산을 실행한다. 예를 들어 수식 1 + 2 + 3 + 4 가 있으면, + 연산자의 결합방향은 왼쪽이므로 (((1 + 2) + 3) + 4) 순으로 왼쪽부터 그룹을 묶어 연산한다.

우선 순위 그룹 (Precedence Groups) 및 결합방향 설정 (associativity settings)은 Operator Declarations에서 확인할 수 있다.

*우선순위 및 결합방향은 Advanced Operators 챕터에서 자세히 다루겠습니다.

Explicit Parentheses (괄호를 통해 의도 명시하기)

수식이 복잡한 경우, (원칙적으로 필요한 상황이 아니더라도) 괄호 ()를 사용하여 가독성을 개선할 수 있다.

위 예시에서 첫 번째 수식에 괄호를 추가하면 의도가 명확해진다. 전체 논리에서 맨 앞의 2개 값을 별개의 구문으로 보이게 하기 때문이다.

if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"
Readability is always preferred over brevity; use parentheses where they help to make your intentions clear.

 

❗️가독성이 좋은 코드가 간결한 코드보다 더 중요하다. 의도를 명확히 할 수 있다면 괄호를 사용한다.

 

 

- Reference

 

🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.

Comments