배열과 리스트, 딕셔너리

배열

int[] arr = new int[5]
// int[5]로 저장공간을 확정해줄수도 있지만 비워놓고 뒤에 따로 선언해도 무관

int[] arr = new int[]{0,1,2,3,4}
// 심지어 new int[]도 쓰기 귀찮아서 바로 element 해도 괜찮음

int[] arr = {0,1,2,3,4}

 

보통 배열이라는 것을 더 가독성있게 표현하려고 new int[ ]를 많이 사용한다고 한다.

 

배열은 참조형식이라서 해당 변수 이름에 주소를 넣어서 스택에 저장하고,

주소에 있는 데이터들 (본체?)를 힙에 저장한다.

 

 

위 배열의 선언은 1차원 배열이고, 다차원 배열 또한 이와 같은 방식으로 선언한다.

 

int[ , ] map = new int [2, 3];


int [][] a = new int[3][];
a[0] = new int[3];
a[1] = new int[6];
a[2] = new int[2];

a[0][0] = 1;

 

위에서 선언한 [ , ] 방식은 각 차원이 고정되어 있어서 모두 같은 크기의 방을 할당할 수 밖에 없지만

 

int [ ][ ] 버전은 각 차원마다 공간을 따로따로 할당할 수 있다.

 

 

추가로 Length는 모든 element의 숫자를 가져오고, GetLength()는 해당 차원의 element만 가져온다.

for(int y = 0; y< tiles.GetLength(1); y++)
{
	
    for(int x=0; x< tiles.GetLength(0); x++)
    {
    	
        if(tiles[y,x] == 1)
        	// console
        else
        	// console
    }
}

 

 

 

 

 

리스트

 

배열을 선언할 때 처음부터 큰 사이즈는 메모리를 낭비하는 것이니까

처음에는 작은 사이즈 배열로 시작하고, 데이터를 넣다가 부족해지면 더 큰 공간의 새로운 배열을 만들어서 기존 값들을 복사한다.

 

여기에서 크기를 유동적으로 늘릴 수 있는 새로운 배열형태가 리스트다.

그냥 동적배열 형태다. 그럼 왜 동적인가.

 

 

처음에 선언한 배열의 크기가 부족하면, 더 큰 배열을 만들어서 원래 있던 데이터에 연결해준다.

 

리스트는 처음에 생성하면 비어있는 상태이기 때문에, 이 상태에서 인덱스로 접근하면 에러가 나는 것을 조심하자.

 

 

List<int> list = new List<int>();

 

 

리스트 추가

list.Add(숫자) 는 리스트의 끝에 새로운 숫자를 밀어넣어준다.

 

 

리스트 인덱스에 삽입

list.Insert(index, number);

해당 인덱스의 뒤에 있는 element들은 한칸씩 뒤로 밀린다.

 

 

리스트 삭제 (값 입력)

list.Remove(number)

0번 index부터 순차적으로 검색해서 입력한 값을 처음 만난 숫자만 삭제하고, 나머지 동일한 숫자는 건들지 않는다.

그리고 bool 값을 반환한다. 삭제에 성공하면  true를 반환한다.

 

리스트 삭제 (index 입력)

list.RemoveAt(index)

 

list.Clear()는 전체삭제

 

 

참고로 배열의 length 개념이 list에서는 Count 에 대응한다.

 

 

 

 

 

딕셔너리

 

리스트의 단점은 데이터 많아지면 바로 찾기에 비효율적이다는 것이다.

 

정렬된 것도 아닌데 100만개의 element 중에서 103번째 값을 찾기 위해 해당 배열을 전부 for문 돌리는 것은 비효율이다.

 

이런 단점을 보완하기 위해 딕셔너리라는 개념이 나왔다.

딕셔너리는 키값을 입력하면 해당 value를 출력한다.

 

 

딕셔너리와 리스트의 가장 큰 차이는

키값을 알면 value를 빨리찾을 수 있다는 것이다.

(하지만 반대로 value를 안다고 해서 앞에있는 키를 찾을 수는 없다.)

 

 

class Monster
{
	public int id;
}

///

List<int> list = new List<int>();


Dictionary<int, Monster> dic = new Dictionary<int, Monster()>;

 

어째 배열과 리스트, 딕셔너리 모두 선언이 비슷하다.

위 코드에서 딕셔너리는 키값으로 int 타입을 받고, value로 Monster 타입을 받았다.

 

 

 

딕셔너리의 문법은 add로 추가한다.

 

dic.Add(1, new Monster());

dic[5] = new Monster(5); // 어떤 방식으로 선언해도 무방

 

이전 코드에서 몬스터 객체를 딕셔너리에 value값으로 할당했으니 new Monster()를 통해 객체를 생성하여 추가한다.

 

 

값을 꺼내올 때는 해당 타입으로 받아서 

Monster mon = dic[5000];

 

 

그런데 만약 해당 키를 가진 정보가 없다면 프로그램이 바로 터진다.

그래서 바로 dic[ ]로 인덱스를 통한 접근은 위험하다.

 

 

이런 문제를 해결하기 위한 함수가 있다.

 

bool found = dic.TryGetValue(20000, out mon);

 

 

입력한 값을 제대로 찾았다면 out 변수에 할당하고 true를 반환한다.

만약 해당 value를 못찾은 경우 false를 반환하고 mon은 null값을 갖게 된다.

 

 

 

dic.Remove()는 해당하는 키의 정보가 삭제되고

dic.Clear()는 딕셔너리를 전부 삭제한다.

 

 

 

딕셔너리의 이런 기능은 해시 테이블을 사용하여 구현됐다.

 

 

예를 들어 하나의 박스에 1만개의 공이 들어있다고 하자. 이곳에서 원하는 공을 찾기는 어려울 것이다.

하지만 1~100 까지의 공, 101~201 까지의 공으로 분류하여 여러개의 박스를 준비한다면 훨씬 찾기 쉬울 것이다.

 

 

 

이런 방식은 문제가 있는데,

박스를 많이 준비하니까 빨리 찾을수는 있지만 메모리상에서는 굉장히 손해를 보게 된다.

 

딕셔너리를 사용하는 것은 결국 메모리를 내주고 성능을 취하는 것이다.

'언어 > C#' 카테고리의 다른 글

property 소개  (0) 2024.07.08
추상 클래스와 인터페이스  (0) 2024.07.08
generic 타입 소개와 object, var 대신 사용하는 이유  (0) 2024.07.08
객체지향의 원칙 상속성, 은닉성, 다형성  (0) 2024.07.03
C# 기본 지식  (0) 2024.07.02