나만의 작은 도서관
[C#] 문법 정리 #3. 배열과 컬렉션(+제너릭) 본문
배열(Array)
배열은 동일한 자료형의 값들이 연속적으로 저장되어 있는 자료구조이다. 정적인 메모리이기 때문에 이미 크기가 정해진 배열은 크기를 변경할 수 없다.
선언
// 선언: 데이터_타입[] 배열_이름;
int[] arr;
다양한 초기화 방법
// 선언 + 초기화(값 지정이 없을 시, 기본값으로 초기화. int는 0)
int[] arr1 = new int[5];
// 선언 + 값 지정 초기화
int[] arr2 = new int[5] { 1, 2, 3, 4, 5 };
// 선언 + 값 지정 초기화(중괄호 초기화 시, new int[5] 생략가능)
int[] arr3 = { 1, 2, 3, 4, 5 };
// Array클래스의 메서드를 사용한 초기화
int[] arr4 = new int[5];
Array.Fill(arr4, 42); // 모든 요소를 42로 초기화
2차원 배열
2차원 배열은 대괄호 안에 ','을 하나 추가하면 된다. 참고로 ','을 추가할수록 차원이 하나씩 올라간다.
// 2차원 배열 선언 + 초기화
int[,] arr4 = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
// 2차원 배열 선언 + 값 지정 초기화(중괄호 초기화 시, new int[2, 3] 생략가능)
int[,] arr5 = { { 1, 2, 3 }, { 4, 5, 6 } };
가변 배열(Jagged Array)
가변 배열은 각 생이 서로 다른 길이를 가질 수 있는 배열을 의미한다.(이빨빠진 배열이라 부르기도 한다.) 가변 배열을 초기화하는 방법은 다음과 같다.
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[] { 1, 2 };
jaggedArray[1] = new int[] { 3, 4, 5 };
jaggedArray[2] = new int[] { 6 };
컬렉션(Collection)
컬렉션(Collection)은 여러 개의 데이터를 저장하고 관리할 수 있는 자료 구조로, 배열과 유사하지만 배열과는 다르게 크기가 가변적이다. 컬렉션은 List, Dictionary, Stack등이 있다.
네임스페이스 추가
컬렉션은 System.Collections.Generic 네임스페이스에 존재하므로 해당 네임스페이스를 추가해야한다. (Non-Generic Cooections도 있지만 따로 다루진 않겠다.)
using System.Collections.Generic;
컬렉션 종류 및 메서드
- List<T>
크기가 동적으로 조정되는 배열이다. 동적으로 크기가 변하는 배열이 필요할 때 사용한다.
List<int> numbers = new List<int>(); // 빈 리스트 생성
numbers.Add(1); // 리스트에 데이터 추가
numbers.Remove(2); // 리스트에서 데이터 삭제
Console.WriteLine(numbers[0]); // 0번째 인덱스에 접근
Console.WriteLine(numbers[^1]); // -1번째 인덱스에 접근(C# 8.0이상)
foreach(int number in numbers) // iterator방식으로 접근
{
Console.WriteLine(number);
}
var subList = numbers[1..4]; // 인덱스 1~4인 범위의 배열 저장(C# 8.0이상)
foreach (int num in subList)
{
Console.WriteLine(num); // 출력: 20, 30, 40
}
배열 vs 리스트
리스트는 동적으로 크기를 조정할 수 있어 배열보다 유연하다. 이러한 점 때문에 배열보다 리스트를 선호하는 경우가 많은데, 리스트를 남발하면 아래와 같은 문제가 발생할 수 있다.
1. 메모리 사용량 증가
- 리스트는 배열과는 다르게 메모리 공간을 미리 예약해놓는다. 이 과정으로 인해 리스트는 사용하지 않는 메모리를 계속 들고 있을 수 있다. 이는 메모리 사용량을 높이며 높은 메모리 사용량은 성능 저하로 연결될 수 있다.
2. 데이터 접근 시간 증가
- 리스트는 연결 리스트(Lincked List)로 구현되어 있다. 연결 리스트는 삽입과 삭제가 O(1)으로 매우 빠르지만, 데이터 접근이 O(N)으로 O(1)인 배열보다 접근이 느리다. 만약, 빈번하게 특정 위치의 데이터에 접근하는 경우라면 접근 시간으로 인해 성능 저하로 연결될 수 있다.
3. 코드 복잡도 증가
- 데이터의 삽입과 삭제로 인한 메모리 크기가 동적으로 변경될 수 있다는 유연함때문에 메모리 크기에 대한 추적이 다소 복잡해진다. 이는 코드 복잡도를 증가시킴을 의미하며 코드 복잡도가 증가함에 따라 코드의 가독성과 유지보수성이 떨어질 수 있다.
이렇듯 리스트는 배열에 없는 단점들이 존재하므로, 상황에 맞게 배열과 리스트를 적절하게 섞어 쓰는 것이 좋다.
- Dictionary<TKey, TValue>
딕셔너리(Dictionary)는 <key, value>쌍으로 이루어진 자료구조이다. 당연하게도 중복된 키를 가질 수 없다. 키-값 쌍을 빠르게 검색하거나 저장해야 할 때 사용한다.
Dictionary<string, int> scores = new Dictionary<string, int>(); // 빈 딕셔너리 생성
scores.Add("Alice", 100); // 데이터 추가
scores.Remove("Bob"); // 데이터 삭제
Console.WriteLine(scores["Alice"]); // key값으로 value에 접근
foreach(KeyValuePair<string, int> pair in scores) // iterator방식으로 접근
{
Console.WriteLine(pair.Key + ": " + pair.Value);
}
- Stack<T>
후입선출(LIFO) 구조를 갖는 자료구조이다. 함수 호출 스택 등에 사용한다.
Stack<int> stack1 = new Stack<int>(); // int형 Stack 선언
// Stack에 요소 추가
stack1.Push(1);
stack1.Push(2);
stack1.Push(3);
// Stack에서 요소 가져오기
int value = stack1.Pop(); // value = 3 (마지막에 추가된 요소)
- Queue<T>
선입선출(FIFO) 구조를 갖는 자료구조이다. 작업 대기열 등에 사용한다.
Queue<int> queue1 = new Queue<int>(); // int형 Queue 선언
// Queue에 요소 추가
queue1.Enqueue(1);
queue1.Enqueue(2);
queue1.Enqueue(3);
// Queue에서 요소 가져오기
int value = queue1.Dequeue(); // value = 1 (가장 먼저 추가된 요소)
- HashSet<T>
중복되지 않은 요소들로 이루어진 집합이다. 중복되지 않는 고유한 데이터를 관리해야 할 때 사용한다.
HashSet<int> set1 = new HashSet<int>(); // int형 HashSet 선언
// HashSet에 요소 추가
set1.Add(1);
set1.Add(2);
set1.Add(3);
// iterator방식으로 데이터 접근
foreach (int element in set1)
{
Console.WriteLine(element);
}
//[출력 결과]
// 1
// 2
// 3
참고 링크
각 컬렉션에서 제공하는 메서드들은 굉장히 많다. 더 많은 메서드들은 아래의 링크에서 확인할 수 있다.
https://learn.microsoft.com/ko-kr/dotnet/api/system.collections.generic?view=net-8.0
제너릭(Generic)
제너릭(Generic)은 클래스나 메서드를 일반화하여 다양한 자료형에 대응할 수 있는 기능이다. C#에서는 <T> 형태의 키워드를 통해 제너릭을 선언한다.
제너릭을 사용하는 이유
- 타입 안정성(Type Safety): 제너릭 클래스나 메서드에서 사용할 자료형은 선언 시점이 아닌 사용 시점에 결정된다. 따라서, 컴파일 시점에 타입 검사가 이루어져 런타임 오류를 방지할 수 있다.
- 코드 재사용성(Reusability): 데이터 타입에 상관없이 재사용할 수 있는 코드 작성 가능하다.
- 성능 향상(Performance): 박싱/언박싱이 제거되므로 성능이 향상된다.
예시
using System;
class GenericClass<T>
{
private T data;
public void SetData(T value)
{
data = value;
}
public T GetData()
{
return data;
}
}
class Program
{
static void Main()
{
GenericClass<int> intInstance = new GenericClass<int>();
intInstance.SetData(10);
Console.WriteLine(intInstance.GetData()); // 출력: 10
GenericClass<string> stringInstance = new GenericClass<string>();
stringInstance.SetData("Hello");
Console.WriteLine(stringInstance.GetData()); // 출력: Hello
}
}
'Unity > 문법 및 메소드(System)' 카테고리의 다른 글
[C#] 문법 정리 #6. 인터페이스 (0) | 2024.11.21 |
---|---|
[C#] 문법 정리 #5. 상속과 다형성(virtual, abstract 키워드) (0) | 2024.11.21 |
[C#] 문법 정리 #4. 클래스 (0) | 2024.11.21 |
[C#] 문법 정리 #2. 문자열 (0) | 2024.11.20 |
[C#] 문법 정리 #1. 기본 구조과 입출력(+리터럴) (0) | 2024.11.20 |