Swift 문법 CH.3

김성환·2020년 8월 20일
0

swift문법

목록 보기
3/11

안녕하세요. 본 포스트에서는 Swift의 집단 자료형에 대해 설명하겠습니다.

※ 참고자료 : 꼼꼼한 재은 씨의 Swift:문법편

목차

  • 1. 배열
  • 2. 집합
  • 3. 튜플
  • 4. 딕셔너리

1. 배열

집단 자료형이란?

서로 관련이 있는 데이터끼리 모아서 관리할 수 있도록 도와주는 자료형
쉽게 말해, 하나의 변수에 여러가지 데이터를 담는 것

배열이란?

일련의 순서를 가지는 리스트 형식의 값을 저장하는 데에 사용되는 자료형
이때, 일련의 순서(일련번호)는 인덱스(index)라고 한다.
※ 인덱스는 0부터 시작하며 데이터가 추가될 때마다 차례로 증가한다.

배열을 정의하는 방법

  1. 정적 할당 방법
  2. 동적 할당 방법

정적? 동적? 무슨 의미일까?

정적, 영어로는 static 으로 고정된, 정지된 이라는 뜻을 갖는다. 그럼 정적 할당이란 정지된 상태에서 할당을 하는 것이다. 이때, 정지된 상태란 무슨 상태를 의미하는 것일까? 이는 프로그램이 움직이지 않는 상태, 즉, 프로그램이 실행 중이 아닌 상태를 뜻한다. 이 상태를 컴파일 타임이라고 부른다.
컴파일 타임에는 컴파일러가 프로그램이 실행되기 전에 해야되는 것들(메모리 할당 등등)을 한다. 이때 메모리가 할당 되는 것이 정적 할당인 것이다.
그렇다면 동적은 무엇일까?
동적, 영어로는 dynamic 으로 활발한, 역동적인 이라는 뜻을 갖는다. 즉, 프로그램이 실행 중인 상태를 뜻한다. 따라서 동적 할당이란 프로그램이 실행 중에 메모리가 할당되는 것이라고 할 수 있다.
※ 컴파일에 관한 것은 아직 공부가 필요하기 때문에 위의 설명이 틀릴 수 있고, 자세한건 다루지 않겠습니다.

정적 할당 방법

var 변수이름 = [저장할 값1, 저장할 값2....]

과정을 살펴보면 다음과 같다.
1. var 변수이름 -> 변수가 선언됨(아직 어떤 타입인지 모름)
2. = [저장할 값1, 저장할 값2....] -> 변수에 저장할 값들을 집어 넣음(이때, 타입 추론기에 의해 컴파일러가 배열임을 인식함)
※ 정적 할당은 미리 저장될 값들이 정해져야 하기 때문에 배열의 선언과 초기화동시에 일어나야 한다.

동적 할당 방법

동적 할당은 선언과 초기화를 분리할 수 있다.

var 변수이름 : Array<데이터 타입> // 배열 선언(어노테이션 이용)
var 변수이름 = Array<데이터 타입> () // 배열 선언과 동시에 빈 배열 초기화

위와 아래의 차이는 무엇일까?
위 = 배열이 선언만 되어있는 상태이기 때문에 메모리 할당이 안됨.
아래 = 배열이 선언도 되어있고 빈 배열로 초기화가 되었기 때문에 메모리 할당이 됨.
※ 메모리 할당이 안되면 그 배열을 프로그램상에서 사용 할 수 없다.
즉, 데이터를 저장할 수 없다는 뜻이다. 따라서 반드시 초기화를 해 메모리 할당을 해야한다.

다른방식

var 변수이름 : [데이터타입] // 배열 선언
var 변수이름 = [데이터타입] () // 배열 선언과 동시에 빈 배열 초기화
var 변수이름 : [데이터타입] = [] // 배열 선언과 동시에 빈 배열 초기화

배열이 갖는 메소드

배열은 구조체로 정의된 객체이다.(객체,구조체에 대해선 뒤에 배우게 된다.) 그렇기 때문에 속성과 메소드를 갖고 있다. 그 중 대표적인 메소드 3가지를 소개한다.
1. append(:) -> 데이터 1개를 마지막 인덱스에 추가
2. insert(:at:) -> 원하는 인덱스에 데이터 1개를 중간에 삽입
3. append(contentsOf:) -> 여러 데이터를 마지막 인덱스에 추가
4. remove(at:) -> 원하는 인덱스의 데이터 1개를 삭제

var a : [Int] = []
a.append(1)
a.append(contentsOf : [2,3,4,5])
a.insert( 666 , at : 3)
a.remove(at : 3)
for i in a{
    print("\(i)")
}
//실행결과
1 2 3 4 5

배열 안의 데이터 접근

배열이름[인덱스] // 해당 인덱스에 접근
배열이름[인덱스] = 변경할 데이터 // 기존 데이터 변경

주의!!

배열 안의 데이터를 접근할 경우 메모리가 할당된 부분만 접근이 가능하다.
즉, 할당되지 않은 부분을 접근할 경우 오류발생
그렇기 때문에 배열의 크기와 용량을 구분해야 한다.

배열의 크기(count)와 용량(capacity)

  • capacity(용량)는 배열에 포함될 수 있는 데이터의 총량(최대한도치)
  • count(크기)는 배열에 실제로 포함된 데이터의 총량
    Swift에서는 배열의 크기와 용량을 알 수 있는 속성이 있다.(타 언어도 마찬가지)
    그것들이 바로 capacity와 count이다.
var a : [Int] = []
print(a.count)
print(a.capacity)
//실행결과
0
0

용량은 어떻게 할당이 되는가?

배열에 요소를 추가할 때, 해당 배열이 예약된 용량을 초과하기 시작하면 배열은 더 큰 메모리 영역을 할당하고, 요소를 방금 할당한 새 메모리에 복사한다.
이때 새로운 저장소는 이전 저장소 크기의 2배이다.

예시를 통해 알아보자

var a : [Int] = []
print(a.count) // 0
print(a.capacity) // 0
a.append(1) // -> 크기가 1로 늘어남
print(a.count) // 1
print(a.capacity) // 2 이때, 0에서는 자동 2로 늘어남
a.append(2) // 크기가 2로 늘어남
print(a.capacity) // 2 왜 2이냐? 아직 최대치인 2를 넘지 않았기 때문
a.append(3) // 크기가 3으로 늘어남
print(a.capacity) // 4 왜 4냐? 최대치인 2를 넘었기 때문에 2의 2배인 4가 됨

※ capacity부분은 ZeddiOS님의 내용을 빌렸습니다.
출처: https://zeddios.tistory.com/117 [ZeddiOS]

배열의 크기를 미리 정하는 방법

var 변수이름 = Array(repeating : 초기화할 값 , count : 크기)
var 변수이름 = [데이터타입](repeating : 초기화할 값 , count : 크기)

2. 집합

집합이란?

같은 타입의 서로 다른 값을 중복 없이 저장하고자 할 때 사용하는 집단 자료형
※ 집합은 내부적으로 해시 연산의 결과값을 이용하여 데이터를 저장한다.
즉, 집합에 저장될 데이터타입은 해시 연산이 가능한 타입이여야 한다.

  • Swift에서 제공하는 기본 자료형은 모두 해시 연산이 가능
  • 사용자 정의 타입인 경우 Swift 표준 라이브러리에서 제공하는 Hashable 프로토콜을 구현해야함 (이렇게만 알고 넘어가자)

집합 선언과 초기화

Set 키워드를 사용

var 변수명 : Set<데이터타입> // 집합 선언
var 변수명 : Set<데이터타입> = [] // 집합 선언과 빈 집합으로 초기화
var 변수명 : Set<데이터타입> = [저장할 값1, 저장할 값2, ...] // 집합 선언과 초기화

집합이 갖는 메소드

집합도 배열과 마찬가지로 구조체이기 때문에 메소드를 갖는데 대표적인 메소드를 살펴보자
1. insert(추가할 값)
2. remove(삭제할 값)
3. removeAll() -> 전체 삭제
4. contains(검색할 값) -> 검색할 값이 집합에 있으면 true반환, 없으면 false반환

var a : Set<Int> = []
a.insert(1)
a.insert(2)
a.remove(1)
print(a.contains(1)) // false
a.removeAll()

집합의 연산 메소드

집합은 집합 자료형끼리 연산할 수 있는 메소드가 존재한다. 이는 수학에서 배운 집합과 동일하다.
1. 기준집합.union(비교할 집합) -> 합집합
2. 기준집합.intersection(비교할 집합) -> 교집합
3. 기준집합.symmetricDifference(비교할 집합) -> 공통된 부분을 제외한 양쪽 나머지 집합
4. 기준집합.subtract(비교할 집합) -> 차집합 (기준집합 - 비교할 집합)

  • 차집합을 뺀 나머지 연산들은 결과를 저장할 새로운 집합을 만들어 낸다.
  • 차집합의 경우 기준집합에서 비교할 집합과 공통된 부분을 뺀 것
    (새로운 집합 만들어지는 것 아님)
var a : Set<Int> = [1,2,3]
var b : Set<Int> = [1,2,4]
print(a.intersection(b))
print(a.symmetricDifference(b))
print(a.union(b))
// -> 위 집합들의 연산의 결과는 순서가 뒤죽박죽이다.
// 왜냐하면 집합은 비정렬(순서없다)의 특성을 갖기 때문이다.
--------------------------------
print(a.subtract(b)) 
// -> 이것의 실행결과는 ()이다. 왜냐하면 새로운 집합을 생성하지 않았기 때문이다.

집합의 포함관계 판단 연산 메소드

  1. a.isSubset(of: b) -> b는 a의 부분집합인지 판단
    (두 집합이 같은 집합인 경우 결과 true)
  2. a.isSuperset(of: b) -> b는 a의 상위집합인지 판단
    (두 집합이 같은 집합인 경우 결과 true)
  3. a.isStrictSubset(of: b) -> b는 a의 부분집합인지 판단
    (두 집합이 같은 집합인 경우 결과 false)
  4. a.isStrictSuperset(of: b) -> b는 a의 상위집합인지 판단
    (두 집합이 같은 집합인 경우 결과 false)
  5. a.isDisjoint(with:b) -> a와 b의 사이에 공통값이 있는지 판단
    (있으면 true, 없으면 false)

배열에 중복을 제거하는 방법

집합을 이용하면 된다.

var a = [1,1,2,3,5,4,5]
var b = Set(a)
a = Array(b)
  • Set(), Array() 메소드는 각각 집합,배열로 바꿔주는 메소드이다.

3. 튜플

튜플이란?

파이썬에서도 사용되는 자료형으로 한가지 데이터 타입만을 저장할 수 있는 배열,딕셔너리와는 달리 하나의 튜플에 여러 가지 타입의 데이터를 저장할 수 있다.

  • 단, 선언되고 나면 상수적 성격을 띠므로 더 이상 값을 추가하거나 삭제가 불가능하다.
  • 하지만 튜플을 변수로 선언했을 경우 값의 변경은 가능하다!!

튜플의 선언과 초기화

튜플은 배열과 다르게 소괄호()를 사용하여 초기화한다.

var 변수이름 : (데이터타입1,데이터타입2,...) // 변수로 선언된 튜플
let 변수이름 : : (데이터타입1,데이터타입2,...) // 상수로 선언된 튜플
//어노테이션을 이용하여 데이터타입을 명시해줌
-------------------------------------------------------
var a : (Int,String) = (1,"hello") // 초기화까지 된 모습
var b = (1,2) // 어노테이션 없이 선언과 초기화를 한경우
  • 이제 '선언되고 나면 상수적 성격을 띠므로 더 이상 값을 추가하거나 삭제가 불가능하다.' 위의 문장을 해석해보자.
    위의 예시에서 변수 a는 1과 "hello" 2개의 데이터가 들어간 튜플이다. 이때 우리가 a안에 들어가 있는 값들을 바꾼다고 한다면 아래 예시처럼 바꿀 수 있다.
a = (2,"안녕") // 값을 바꾼 모습
a = (1,"안녕",3) // 오류!!
  • 하지만 3개의 데이터를 넣고 싶다면 넣을 수 있을까?
    결론은 불가능하다. var로 선언이된 a는 분명 변수이지만 선언이 된 이후에는 상수적 성격을 띤다고 했다. 이말은 변수가 상수가 된다는 뜻이 아니라 튜플의 크기를 늘리거나 줄일 수 없다는 상수의 일부 성격을 갖는다는 뜻이다.

튜플의 원소참조

튜플은 배열과 같이 인덱스를 갖고 있다. 따라서 튜플의 원소들을 참조하고 싶다면 인덱스를 이용하면 된다.

  • 인덱스를 사용하려면 .(점)을 사용해야한다.
튜플명.참조할 인덱스
----------------------
var a = (1,2,3)
print(a.0)
//실행결과
1

바인딩구문

바인딩이란?

바인드 영어로는 bind, 뜻은 결속시키다, 묶다 라는 뜻을 가지고 있다.
그렇다면 프로그래밍에서 바인딩이란 특정 객체에서 실행되게끔 고정시킨다는 뜻이다.
즉, 둘 사이를 묶어준다는 뜻이다.

튜플의 바인딩구문

let tuple : (Int, String, Character) = (1, "hello", "h")
let (a,b,c) = tuple // a,b,c가 바인딩된 모습
  • 위의 예시를 살펴보면 tuple이라는 튜플은 1, hello, h 라는 데이터를 갖고 있다.
    이때 (a,b,c)는 상수로 선언된 튜플이며, 튜플 안에는 a,b,c라는 상수가 있다.
    let (a,b,c) = tuple 이 문장이 바로 바인딩구문으로 tuple과 (a,b,c)를 바인딩하고 있다.
    즉, (a,b,c) = (1, "hello", "h") 라는 뜻이다.
  • 위의 예시에서 만약 tuple안의 데이터 중 1, "hello"만 쓰고 싶을 경우는 어떻게 해야할까?
    답은 _(언더바)를 사용하면 된다.
let tuple : (Int, String, Character) = (1, "hello", "h")
let (a,b,_) = tuple // a,b가 바인딩된 모습
  • 바인딩구문을 사용시 주의사항은 바인딩하는 개수가 같아야 한다는 것이다.
    즉, tuple의 데이터 개수와 let (a,b,_)과 같은 변수,상수의 개수(언더바를 포함)가 같아야 한다는 뜻이다.

튜플의 특징

튜플의 특징은 인덱스속성이 지원된다. 와 그 이외의 속성은 지원되지 않는다. 이다.
즉, 크기계산(배열의 count), 순회(for in 구문) 등등 을 사용할 수 없다는 뜻이다.
그렇다면 튜플은 어디서 사용하는 것일까?
바로 함수다.

  • 함수에서 둘 이상의 결과 값을 반환하기 위해서는 별도로 배열이나, 딕셔너리같은 자료형에 담아야 한다.
    하지만 튜플을 이용한다면 바로 전달이 가능하다.
func tuple() -> (String, Int, Int){
	return ("hello", 1, 2) // 튜플을 반환
}
let (a,b,c) = tuple() // 바인딩을 이용하는 모습

4. 딕셔너리

딕셔너리란?

사전에서 고유 단어와 그 의미가 연결되어 있는 것처럼, 고유 키(key)와 그에 대응하는 값(value)을 연결하여 데이터를 저장하는 자료형

딕셔너리의 선언과 초기화

var 변수이름 : Dictionary<키의 데이터타입, 값의 데이터타입> // 어노테이션으로 선언
var 변수이름 : Dictionary<키의 데이터타입, 값의 데이터타입> = Dictionary() // 선언과 빈 딕셔너리 초기화
var 변수이름 : [키의 데이터타입 : 값의 데이터타입] // 어노테이션으로 선언
var 변수이름 : [키의 데이터타입 : 값의 데이터타입] = [키의 데이터타입 : 값의 데이터타입]() // 선언과 빈 딕셔너리 초기화
var 변수이름 = [키의 데이터타입 : 값의 데이터타입]() // 선언과 빈 딕셔너리 초기화

딕셔너리의 참조

딕셔너리는 키와 값으로 구성되어 있기 때문에 키(key)를 이용해 값(value)을 참조한다.

변수이름[] // 결과는 값

원소를 추가하기

  • 변수이름[키]=추가할 값
  • updateValue 사용
var a = [Int,Int]()
a[1] = 3 // key가 3이고 값이 1인 원소 대입
---------------------------------------
딕셔너리이름.updateValue(, forkey :)
a.updateValue(2, forkey : 4) // key가 4이고 값이 2인 원소 대입 + nil 반환
// 이때 해당 키에 값이 있는경우 기존 값을 반환하고 기존 값이 없을 경우 nil을 반환함.
a.updateValue(2, forkey : 1) // key가 1이고 값이 2인 원소 대입하는데 기존에 있는 값이 3이므로 3반환 

※ nil이란?
다음장에 설명하겠지만 값없음을 의미

딕셔너리의 특징

  • 하나의 키(key)는 하나의 값(value)에만 연결되어야 한다.
    값은 중복될 수 있어도 키는 중복될 수 없다.
    즉, 키가 중복될 경우 새로받은 값을 사용하고 기존 값은 없애버린다는 뜻이다.
profile
개발자가 되고 싶다

0개의 댓글