/* 본 게시물은 '독하게 시작하는 C 프로그래밍 | with 최호성' 의 내용을 토대로 작성되었습니다. */
참고 자료
[Kotlin 자료구조 - 배열] : https://kotlinworld.com/52
[BoostCourse - cs50 - 배열] : https://www.boostcourse.org/cs112
# 배열(Array)
1. 배열이란?
배열은 자료구조에서 가장 일반적인 구조로 메모리상에 형식이 같은 자료 여러 개가 모여 새로운 하나를 이룬 형식이다.
배열은 여러 값을 저장하기 위해 메모리의 연속적인 공간을 차지하고 있다.
코틀린으로 배열을 선언해보자.
// [1,2,3] 배열 생성
val arr1: Array<Int> = arrayOf(1,2,3)
위 코드는 [1,2,3] 의 배열을 만든다.
코틀린 공식 문서에 따르면 Kotlin의 배열은 Array 클래스로 표현된다.
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ...
}
Array<Int> 객체는 Heap 영역 메모리에 할당된다. arr1 변수에는 Array<Int> 객체의 주소 값이 저장된다.
cf> C / Java 에서의 배열
'nArray'는 배열을 이루고 있는 여러 요소를 대표하는 첫 번째 요소의 메모리 주소에 부여하는 식별자이다. 즉, 여기서 'nArray' 는 첫 번쨰 값인 1에 대한 메모리 주소를 가지고 있다.// C언어 int array[3]; int nArray[3] = {1,2,3}; // Java //배열 선언 int[] array; // new 연산자를 통해 초기화 array = new int[]{1,2,3}; // 선언과 초기화 동시에 int[] nArray = {1,2,3};
더 자세한 정보는 [Java] 배열 - 일차원/다차원 배열 선언, 메모리 할당을 확인하자.
2. 배열 특징
자료 구조에 따라 읽기, 검색, 추가, 삭제 하는 방법이 다르다. 배열에서의 방법을 알아보자.
2-1. 읽기
배열은 '배열 연산자'를 통해 데이터에 접근할 수 있다. 배열 연산자는 '배열이름[인덱스]' 형식을 갖는다. 각 배열요소의 인덱스는 0에서부터 전체 요소의 개수보다 1 작은 범위까지이다. 즉, 데이터가 있는 위치(인덱스)만 알고 있다면 바로 해당 메모리에 접근이 가능하다. 이러한 점 때문에 배열은 많은 데이터를 읽는데 최적화된 자료구조이다.
val arr: Array<Int> = arrayOf(1,2,3)
//읽기
for (i in 0 until arr.size){
// index 를 통해 데이터 읽기
println(arr[i])
}
for (data in arr){
println(data)
}
2-2. 검색
언듯 보면 읽기랑 비슷하다고 생각할 수 있지만 읽기랑 검색은 다르다. 읽기는 배열 안에 있는 데이터들을 말 그대로 읽기만 하면 되고, 검색은 내가 원하는 데이터가 있는지 없는지 여부를 확인해야한다. 기본적으로 배열은 특정 값이 어떤 배열에 들어있는지 모른다. (마치 사물함에 데이터가 들어가 있다고 생각하면 된다.) 그렇기 때문에 각각의 index 들어가 하나하나 데이터들을 비교해야한다. (선형 검색) 만약 데이터들이 정렬되어 있다고 하면 이진 검색을 이용하면 더 빠르게 검색할 수 있다. *배열에서 검색은 데이터 정렬했을 때 good..*
val value = 100 // 검색할 값
for (element in intArray){
if (value == element){
println("$value 값 존재")
}
}
2-3. 추가
배열을 처음 만들 때 메모리 공간을 정한다. 내가 100개의 메모리 공간을 할당했는데 101개를 저장해야한다면 101번 째 값을 유실할 수 있다. '배열의 100번째 메모리 옆에 101 번째 메모리 공간을 확보해서 추가하면 되는 거 아닌가? '라고 생각할 수 있다. 그러나 101 번째 메모리 공간에 값이 있는지 없는지 알 수 없기 때문에 함부로 추가할 수가 없다.
그래서 배열은 기존 공간을 사용하면서 크기를 늘리는 것이 불가능하다.
// 배열에서 마지막 요소 추가
arr2 = arr1.plus(4)
public actual operator fun <T> Array<T>.plus(element: T): Array<T> {
val index = size
val result = java.util.Arrays.copyOf(this, index + 1)
result[index] = element
return result
}
코틀린에서 제공하는 함수인 plus() 를 통해 데이터를 삽입할 수 있다.(엄밀히 말하면 데이터를 삽입하는 것이 아닌 새로운 배열을 만드는 것이다.) plus() 함수를 분석해보면 result 를 반환하는데 이는 기존 배열의 값을 복사해서 마지막 인덱스에 요소를 추가하는 방식이다.
fun main() {
val arr1: Array<Int> = arrayOf(1, 2, 3)
arr1.plus(5)
for (element in arr1){
println(element)
}
}
/**
1
2
3
*/
그래서 위의 코드를 실행해보면 arr1 배열은 그대로 라는 것을 확인할 수 있다. 이 처럼 데이터를 추가할 때
맨 마지막에 값을 추가한다면 그나마 코드를 쉽게 짤 수 있지만, 만약 중간이나, 맨 처음에 데이터를 삽입하고 싶은 경우도 있을 것이다. 배열에서 삽입은 피하는게 좋지만 구현이라도 한 번 해보겠다.
fun main() {
val arr1: Array<Int> = arrayOf(1, 2, 3)
val arr2 = arr1.plus(5, 3)
for (element in arr2){
println(element)
}
}
fun <T> Array<T>.plus(elementOfInsert: T, index: Int): Array<T> {
val arraySize = size
val result = java.util.Arrays.copyOf(this, size + 1)
if (index <= size){
for ((i, element) in this.withIndex()){
when {
i >= index -> result[i+1] = element
}
}
result[index] = elementOfInsert
} else {
throw ArrayIndexOutOfBoundsException()
}
return result
}
2-4. 삭제
삭제도 추가와 마찬가지로 기존 공간을 줄이는 것은 불가능하다. 중간에 값이 비게되면 배열의 특징인 메모리 연속성이 깨지기 때문이다.
따라서 추가처럼 새로운 array를 만들어야 한다. 구현은 pass..
3. 정리
배열의 특징
- 같은 자료형을 가진 값들을 하나로 나타낸 것
- 초기화와 동시에 크기가 정해짐
- 메모리 공간에 연속으로 저장함
- 인덱스를 통하여 요소(데이터)에 접근
- 정적이다.
배열 장점
- 인덱스로 요소에 바로 접근할 수 있어 읽기 빠름
- 연속된 메모리 공간을 사용하여 메모리 관리가 편리함 (다른 자료 구조보다 메모리를 적게 씀)
배열 단점
- 배열에서 데이터를 삭제하더라도 배열의 크기가 줄어들지 않아 메모리 낭비
- 배열은 정적이기 때문에 크기를 재조정할 수 없음.
- 기존 배열에 데이터를 추가/삭제가 불가능
배열에 대해서 공부하면서 헤맸던 점은 기존 C/Java의 자료로 공부했는데 코틀린에서의 배열과는 조금 차이가 있다는 점이었다. 내가 공부한 예제에는 기존 C / Java 에서 배열의 요소로 기본형 타입만 다뤘다. 그렇기 때문에 바로 첫 번째 요소의 메모리 주소로 접근한다. 하지만 Kotlin 에서는 Array를 객체로 취급하여 메모리 인스턴스에 접근한다. 그렇기 때문에 Array 객체 안에 Array / size / 함수 (get/set 등)을 포함한다.
Kotlin 에서는 IntArray, ByteArray, ShortArray 등 기본형 array도 제공한다. 다음 게시물에는 이에 관한 내용을 한 번 다뤄보겠다.
배열 힘들었다.. 오늘도 삽질 x 100