Swift 자료형과 연산자
Swift
- 애플에서 개발한 프로그래밍 언어
- 오픈소스 언어
- 컴파일 언어
- REPL(레플) 사이트가 있다 - swiftfiddle.com
- 안전하고 신속하다. 포인터 쓰는 데 안 쓰는 것처럼 한댄다.
- 실행 속도 최적화와 컴파일러 개선을 통한 신속한 실행이 가능하다.
- 객체 지향 언어이기 때문에 class에 대해 숙지하고 있는 게 좋다.
- 함수형 언어, 프로토콜 지향 언어
Swift 실행 환경 - Xcode
애플 공식 통합 개발 환경이다.
Playground로 프로젝트 생성 없이 소스코드를 실행할 수 있다.
실행 버튼 눌러도 아무 일도 안 일어나는데 그냥 느려서 그런 거다. Xcode 위에 지금 뭐하고 있는지 상태가 나온다.
Quick look 기능으로 변수 값의 변화를 확인할 수 있다.
도움말은 Control 키를 누른 상태에서 해당 부분 클릭 - Show Quick Help 혹은 Jump to Definition
실행했을 때 속도는 레플 사이트가 좀 더 빠른 것 같다.
명명법
유니코드에서 지원하는 문자는 사용 가능하지만 주석까지도 전부 영어로 작성하는 게 좋다.
*사용 불가능한 것들
- 스위프트에서 미리 정한 예약어 및 키워드
- 연사자 기호
- 숫자로 시작하는 이름
- 공백이 포함된 이름
세미콜론(;) 안 붙여도 된다.
자료형, 열거형, 클래스, 구조체, 익스텐션, 프로토콜의 이름은 첫 글자를 대문자로 시작하는 카멜 케이스를 사용한다. (예: UpperCamelCase)
변수, 상수 함수, 메서드, 객체(인스턴스)의 이름은 첫 글자를 소문자로 시작하고 다음 단어의 첫 글자는 대문자로 표기하는 소문자 카멜 케이스 사용한다. (예: lowerCamelCase)
코딩 환경에 따라 스네이크 표기법, 파스칼 표기법 등을 사용할 수 있다.
관련 내용은 Swift API Design Guidelines를 참고한다.
주석
// 한줄 주석
/* 여러 줄 주석 */
/* 주석 내용
//중첩 주석
주석 내용 */
변수와 상수
- 변수(variable): 선언 후 데이터 값 변경 가능
- 상수(constant): 선언 후 데이터 값 변경 불가능
var 변수명: 자료형 = 값
var 변수명 = 값
let 상수명: 자료형 = 값
let 상수명 = 값
선언할 때 자료형을 명시하는 것을 권장한다. 자료형을 명시하지 않으면 컴파일할 때 컴파일러가 선언에 사용된 값을 통해 자료형이 추론될 수 있어 컴파일 시간이 오래 걸릴 수 있다. 값에 따라 변수 또는 상수의 타입 추론이 불가능할 수 있다.
변수 이름은 소문자로, 자료형은 대문자로 시작하니까 구분해서 유추할 수 있다.
var intVar: Int = 3
var dblVar: Double = 3.1
let strLet: String = "my name is"
자료형
타입 추론(type inference)
Swift는 컴파일 언어이므로 변수 또는 상수를 선언할 때 자료형을 명시해야 한다. 이를 명시하지 않으면 컴파일할 때 컴파일러가 선언된 값을 통해 자료형을 추론한다. 값을 통해 타입 추론이 불가능할 경우 에러가 발생한다.
배열이나 딕셔너리를 만들 때 값을 비운 상태에서 자료형을 명시하지 않으면 에러가 발생하므로 반드시 값을 넣거나 자료형을 명시한다.
변수의 자료형을 확인하는 함수: type
print(type(of: 변수이름))타입 별칭(type alias)
자료형에 대해 별칭을 생성할 수 있다. C/C++의 typedef와 유사하다.
typealias MyInt = Int
typealias TupIntDbl = (String, Int, Double) typealias ArrStr = Array<String>
var int: MyInt = 3
var personG: PersonTuple = ("kang", 11, 160.25)
정수형(Int, UInt)
- Int: -, + 부호를 포함하는 정수
- UInt(unsigned Int): -부호를 포함하지 않는 정수 표현. 큰 수치의 데이터를 사용해야 할 때 활용할 수 있다.
| 자료형 | 설명 | 표현 범위 | |
|---|---|---|---|
| 최소값 | 최대값 | ||
| Int | 32 bit CPU(32 bit 아키텍처)에서는 Int32와 동일, 4 byte | −231 | 231 − 1 |
| 64 bit CPU(64 bit 아키텍처)에서는 Int64와 동일, 8 byte | −263 | 263 − 1 | |
| Int8 | 1 byte (8 bit)로 정수표현 (28개의 정수 표현) | −27 | 27 − 1 |
| Int16 | 2 byte (16 bit)로 정수표현 (216개의 정수 표현) | −215 | 215 − 1 |
| Int32 | 4 byte (32 bit)로 정수표현 (232개의 정수 표현) | −231 | 231-1 |
| Int64 | 8 byte (64 bit)로 정수표현 (264개의 정수 표현) | −263 | 263 − 1 |
| UInt | 32 bit CPU(32 bit 아키텍처)에서는 UInt32와 동일 | 0 | 232 − 1 |
| 64 bit CPU(64 bit 아키텍처)에서는 UInt64와 동일 | 0 | 264 − 1 | |
| UInt8 | 1 byte (8 bit)로 부호 없는 정수표현 (28개의 부호 없는 정수표현) | 0 | 28 − 1 |
| UInt16 | 2 byte (16 bit)로 부호 없는 정수표현 (216개의 부호 없는 정수표현) | 0 | 216 − 1 |
| UInt32 | 4 byte (32 bit)로 부호 없는 정수표현 (232개의 부호 없는 정수표현) | 0 | 232 − 1 |
| UInt64 | 8 byte (64 bit)로 부호 없는 정수표현 (264개의 부호 없는 정수표현) | 0 | 264 − 1 |
//자료형의 최소값과 최대값 확인
print("Int.min: \(Int.min), Int.max: \(Int.max)")
//형 변환
var intNum: Int = -100
var uintNum: UInt = 50
intNum = Int(uintNum)
컴파일러가 자동으로 해주지만 좋은 성능을 위해 형 변환을 확실하게 적어주자!!
*진수에 따른 정수 표현
- 2진수(binary): 접두어 0b
- 8진수(octal): 접두어 0o
- 16진수(hexadecimal): 접두어 0x
실수형(Float, Double) 또는 부동소수점형(floating-point type)
- Float: 4 byte 실수 표현. e로 표현된 실수에서 6자리까지 정확히 표현한다. 표현 범위가 넓지 않아 빠른 계산이 필요할 때 사용한다. 속도가 빠르다.
- Double: 8 byte 실수 표현. e로 표현된 실수에 15자리까지 정확히 표현한다. 표현 범위가 넓어서 float보다 속도가 2~4배 정도 느릴 수 있다.
자료형을 명시하지 않으면 컴파일러에 의해 Double형으로 타입 추론이 된다.
실수형만으로 1시간 동안 강의하실 수 있다는데 대체 무슨 이야기를 할 수 있는 걸까.
*e(exponent)를 이용한 실수 표현
e는 10이라고 생각하면 된다. 실수를 간단하게 표현할 수 있다.
1230 = 1.23e3 = 1.23e+3 = 1.23 × 103
문자열(String)
- String: 하나 이상의 문자로 구성된 문자열을 큰따옴표(")를 이용해서 표현한다.
var str1: String = "aaa"
var str2: String
str2 = "bbb"
//빈 문자열 생성
var str3: String = String() // ""
//문자열 뒤에 문자열 추가
str2.append("ccc") // str2 == "bbbccc"
//문자열 결합
str3 = str1 + " " + str2 // str3 == "aaa bbbccc"
//빈 문자열 여부, 문자열 내 문자 수 확인
print(str1.count) // 3
print(str1.isEmpty) // false
//문자열 접두어와 접미어 확인
var result: Bool
result = str3.hasPrefix("aa") //문자열 str3이 "aa"로 시작하는가?
result = str3.hasSuffix("cc") //문자열 str3이 "cc"로 끝나는가?
//대소문자 변환
str4: String
str4 = str1.uppercased()
str4 = str1.lowercased()
//문자열 보간법(String Interpolation): 문자열 내에 변수 또는 상수 값을 넣고 싶을 때
str4 = "Hello \(str1)"
//여러 줄 표현: 큰따옴표 3개 사용. 실제로 줄바꿈을 하지 않지만 에디터에서 줄바꿈을 하게 될 경우 백슬래시(\) 사용
var str5: String = """
Hello \
Goodbye
"""
print(str5) //Hello Goodbye
문자(Character)
Character: 하나의 문자를 큰따옴표(")를 이용해서 표현한다.
*탈출문자(escape character): C랑 똑같다.
| 탈출 문자 | 설명 |
|---|---|
| \n | 줄 바꿈(개행) |
| \\ | 백슬래시(\) |
| \" | 큰따옴표(") |
| \t | 탭(tab) |
| \0 | 문자열의 끝(null 문자) |
Any, AnyObject
- Any: 모든 자료형을 표현한다.
- AnyObject: 모든 클래스를 표현한다. 어떤 클래스의 객체도 할당 받을 수 있다.
- nil: 어떠한 메모리 주소도 가리키지 않는다는 의미이다. C/C++의 NULL과 유사하다. 변수 초기화를 위해 사용한다.
C에서는 변수를 초기화하지 않으면 쓰레기값이 들어가는데 Swift에서는 에러가 발생하므로 반드시 초기화를 해주어야 한다.
var value1: Int print(value1) // value1을 초기화하지 않아 에러 발생 var value2: Int? // 옵셔널(optional) 변수 value2는, 값을 가지거나 갖지 않음 var value3: Int? = nil print(value2) // 실행결과: nil print(value3)
*와일드 카드 식별자(_)는 어떠한 수도 허용한다는 의미.
튜플(Tuple)
- C/C++의 구조체처럼 여러 자료형으로 구성된다.
- 파이썬의 튜플과 달리 튜플 내의 각 element를 수정할 수 있다.
- 자료형의 이름이 따로 없는데 이름을 붙여도 된다. 변수의 이름을 명시적으로 확실하게 표현해주는게 좋다.
var personA: (String, Int, Double) // unnamed
var personB: (name: String, age: Int, height: Double) // named
personA = ("joe", 10, 150.23)
personB = (name: "joe", age: 10, height: 150.23)
personB = ("joe", 10, 150.23)
print("이름: \(personA.0), 나이: \(personA.1), 키: \(personA.2)")
print("이름: \(personB.name), 나이: \(personB.age), 키: \(personB.height)")
personA.0 = "jane"
personB.name = "lee"
배열(Array)
- element가 순서대로 저장된다.
- index는 0부터 시작된다. 점(.) 찍지 말고 C처럼 [index]으로 접근하면 된다.
var arrayA: Array<String>
var arrayB: [String]
arrayA = ["aa", "bb", "cc", "dd"]
arrayB = ["aa", "bb", "cc", "bb"]
//여러 자료형일 경우 타입 추론이 안 되므로 반드시 Any형을 명시해야 한다.
var arrayAnyA: Array<Any> = [1, 2.3, "c", "string"]
//빈 배열을 생성하려면 반드시 자료형을 명시해야 한다.
var emptyArrayA: Array<String> = Array<String>()
var emptyArrayB: [String] = [String]()
var emptyArrayC: [String] = [] // element 접근 및 수정 arrayA[0] = "ee" print(arrayA.first) //첫 번째 값 print(arrayA.last) //마지막 값 print(arrayB.firstIndex(of: "bb")) //해당 원소가 앞에서 몇 번째 인덱스인지 탐색 print(arrayB.lastIndex(of: "bb")) //해당 원소가 뒤에서 몇 번째 인덱스인지 탐색 print(arrayB) //배열 전체 출력 print(arrayB[1...3]) //인덱스 1부터 3까지 출력 //element 추가 arrayC.append("ee") arrayC.append(contensOf: ["ff", "gg"]) arrayC.insert("hh", at: 2) //인덱스 2에 삽입
딕셔너리(dictionary)
- element가 키(key)와 값(value)으로 구성된다. 같은 키를 중복해서 사용할 수 없다. 키 값은 Any를 사용할 수 없다.
- element가 순서없이 저장된다.
- 여러 자료형으로 구성된 딕셔너리를 생성할 때는 반드시 Any형을 명시해야 한다.
var dictA: Dictionary<String, Int>
var dictB: [String: Int]
var emptyDictC: [String: Int] = [:]
dictA = ["aa": 1, "bb": 2, "cc": 3]
dictB = ["aa": 1, "bb": 2, "cc": 3]
typealias DicStrInt = Dictionary<String, Int>
dictA["aa"] = dictA["bb"]! + dictA["cc"]! //키에 대한 값은 없을 수도 있으므로 키에 대한 값은 옵셔널 변수이다. 따라서 강제 추출 연산자(!)를 사용하여 옵셔널 변수의 값을 추출한다.
print(dictA["aa"]!)
//element 추가 삭제
dictA["dd"] = 4
dictA.removeValue(forKey: "aa")
집합(Set)
배열처럼 같은 자료형(Any를 허용하지 않음)의 element들로 구성되어 있지만 순서 없이 저장되며 같은 element가 존재하지 않는다.
var setA: Set<String>
setA = ["aa", "bb", "cc"]
var setB: Set<String> = ["aa", "bb", "cc"]
//빈 집합 생성
var emptySetA: Set<String> = Set<String>()
var emptySetB: Set<String> = []
typealias SetStr = Set<String>
var emptySetC: SetStr = SetStr()
//빈 집합인지 확인 및 element 수 확인
print(setA.isEmpty)
print(setA.count)
//element 추가 삭제
setA.insert("dd")
setA.remove("aa")
//집합 연산
let classA: Set<String> = ["aa", "bb", "cc"]
let classB: Set<String> = ["bb", "cc", "dd", "ee", "ff"]
var result: Set<String>
result = classA.union(classB) //합집합
result = classA.subtracting(classB) //차집합
result = classA.intersection(classB) //교집합
result = classA.symmetricDifference(classB) //여집합의 합(배타적 논리합)
//정렬 - 배열을 반환한다.
var sortedResult: Array<String>
sortedResult = result.sorted()
//포함관계 연산
print(A.isDisjoint(with: B)) //서로 배타적인지
print(A.isSubset(of: Bl)) //A가 B의 부분집합인지
print(A.isSuperset(of: B)) //A가 B의 전체집합인지
열거형(enum)
연관된 항목을 하나로 묶어서 표현한다. 열거형으로 선언된 변수는 열거형 내의 항목만 값으로 가질 수 있다. 각 항목 자체가 고유의 값이다.
enum Week: String {
case sunday = "Sun."
case monday //원시값이 monday가 된다.
case tuesday = "Tue."
case wednesday = "Wed."
case thursday = "Thu."
case friday = "Fri."
case saturday = "Sat."
}
//원시값 얻는 법
var day: Week = Week.wednesday
print("\(day.rawValue)")
var dayA: Week = Week.sunday
var dayB: Week = .monday
C와 달리 switch case문처럼 case가 있어 각 항목은 원시값을 가질 수 있다. 원시값을 넣으려면 원시값에 대한 자료형을 넣어주어야 한다. 원시값이 없을 경우 원시값의 자료형이 String이면 항목 이름이 원시값이 된다. 자료형이 Int이면 원시값은 첫 항목을 기준으로 0부터 1씩 증가한다.
연관값(튜플)을 가진 항목들로 구성시킬 수 있다. named 튜플이면 named 튜플로 사용해야 한다. 연관값을 상수로 받아서 사용할 수도 있다.
enum Device {
case iPhone(model: String, storage: Int)
case iMac(size: Int, model: String, price: Int)
case macBook(Int, String, Int)
}
var giftA: Device = Device.iPhone(model: "Y", storage: 256)
var giftB: Device = Device.iPhone("Y", 256) //에러 발생
var giftC: Device = .macBook(12, "X", 200)
switch giftA {
case .iPhone(model: "X", storage: 256):
print("iPhone X and 256GB")
case .iPhone(model: "Y", _): //와일드 카드
print("iPhone Y")
case .iPhone:
print("iPhone")
default:
print("default")
}
//출력 결과: iPhone Y
//연관값을 상수로 받아 사용할 때
switch giftA {
case.iPhone(let mdl, let stg):
print("iPhone \(mdl) and \(stg)GB")
case let .iMac(sz, mdl, prc):
print("iMac \(sz), \(mdl), \(prc)")
default:
print("default")
}
//출력 결과: iPhone Y and 256GB
switch giftB {
case .iPhone(let mdl, let stg):
print("iPhone \(mdl) and \(stg)GB")
case let .iMac(sz, mdl, prc):
print("iMac \(sz), \(mdl), \(prc)")
default:
print("default")
}
// 출력 결과: iMac 12, X, 100
연산자
C와 매우 유사하다. 거의 똑같은 듯??
연산자 분류
- 피연산자 수에 따라: 단항, 이항, 삼항
- 피연산자 앞/사이/뒤에 있는 연산자: 전위, 중위, 후위
산술 연산자
- 더하기 A+B
- 빼기 A-B
- 곱하기 A*B
- 나누기 A/B
- 나머지 A%B (둘 다 정수일 경우에만 가능)
*실수형 나누기, 나머지 연산
var c: Double = 5.0
var d: Double = 3.0
var dblQ: Double = c / d
var dblR: Double = c.truncatingRemainder(dividingBy: d)
print("dblQ: \(dblQ), dblR: \(dblR)") // dblQ: 1.6666666666666667, dblR: 2.0
비교 연산자
| 연산자 | 설명 |
|---|---|
| A == B | 같은 지 확인 |
| A != B | 다른 지 확인 |
| A >= B | 크거나 같은 지 비교 |
| A <= B | 작거나 같은 지 비교 |
| A > B | 큰 지 비교 |
| A < B | 작은 지 비교 |
| A === B | 같은 클래스의 인스턴스를 가리키는지 확인 |
| A !== B | 다른 클래스의 인스턴스를 가리키는지 확인 |
| A ~= | 패턴이 매치되는지 확인 |
===와 !==는 참조가 같은지 같지 않은 지를 확인하는 것이다.
삼항 연산자(ternary operator)
Qustion ? A:B
→ Question이 참이면 A를, 거짓이면 B를 반환한다.
범위 연산자(range operator)
| 분류 | 연산자 | 설명 |
|---|---|---|
| 폐쇄 범위 연산자 | A...B | A이상 B이하의 수 |
| 반폐쇄 범위 연산자 | A..<B | A이상 B미만의 수 |
| 단방향 범위 연산자 | A... | A이상의 수 |
| ...A | A이하의 수 | |
| ..<A | A미만의 수 |
논리 연산자
| 논리 연산자 | 표현 |
|---|---|
| NOT | !A |
| AND | A && B |
| OR | A || B |
비트 연산자
| 비트 연산자 | 표현 | 설명 |
|---|---|---|
| NOT | ~A | A의 비트를 반전 |
| AND | A & B | A와 B 비트 AND 논리 연산 |
| OR | A | B | A와 B 비트 OR 논리 연산 |
| XOR | A ^ B | A와 B 비트 XOR 논리 연산 |
| 비트 이동(shift) 연산자 | A >> B | A의 비트를 B만큼 비트를 오른쪽으로 이동 |
| A << B | A의 비트를 B만큼 비트를 왼쪽으로 이동 |
복합 할당 연산자
A += B 처럼 연산자를 간소하게 표현할 수 있다. C랑 똑같음.
오버플로 연산자
값의 범위를 넘어가서 오버플로가 발생했을 때 자동으로 대처하는 연산자. 예외 처리를 어떻게 할 것인가.
| 연산자 | 표현 | 설명 |
|---|---|---|
| 오버플로 더하기 연산 | &+ | 오버플로에 대비한 덧셈 연산 |
| 오버플로 빼기 연산 | &- | 오버플로에 대비한 뺄셈 연산 |
| 오버플로 곱하기 연산 | &* | 오버플로에 대비한 곱셈 연산 |
기타 연산자
| 연산자 | 표현 | 설명 |
|---|---|---|
| nil 병합 연산자 | A ?? B | A가 nil이 아니면 A를, nil이면 B를 반환 |
| 부호 변경 연산자 | -A | A의 부호 변경 |
| 옵셔널 강제 추출 연산자 | O! | O(옵셔널 개체)의 값을 강제로 추출(가능한 사용하지 않는 것이 좋음) |
| 옵셔널 연산자 | V? | V(옵셔널 값)을 안전하게 추출하거나 V(데이터 타입)가 옵셔널임을 표현 |
연산자 우선순위
| 연산자 우선순위 그룹 | 연산자 |
|---|---|
| Bitwise shift | >>, << |
| Multiplicative | %, *, / |
| Additive | |, -, +, ^ |
| Range | ..<, ... |
| Casting | is as |
| Nil-Coalescing | ?? |
| Comparison | !=, >, <, >=, <=, ===, == |
| Logical AND | && |
| Logical OR | || |
| Ternary Operator | ? : |
| Assignment Precedence | |=, %=, /=, *=, >>=, <<=, ^=, +=, -= |
참고
2023-2 모바일 프로그래밍 성광제 교수님 수업

0 댓글