List는 연속된 메모리에 요소가 놓이게 되기 때문에 , 메모리 공간을 떨어뜨려 놓을 수 없다.
이럴 때 흔하게 LinkedList를 사용하여 요소별로 메모리를 분리시킬 수 있다.
하지만 인덱서를 제공하지 않아 출력을 못한다는 문제가 있다.
인덱서를 제공하고 List -> LinkedList만 바꾸면 된다면, 바꿔가면서 성능을 비교해보면 좋을텐데 안된다는게 문제이다.
Collection과 인덱서(indexer)
=> IList<T> 인터페이스를 구현한 컬렉션은 인덱서를 제공하지만, 메모리공간의 분리 불가능.
=> LinkedList<T>는 메모리공간의 분리가 가능하지만, 인덱서를 제공하지 않아 출력을 못함.
//List
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
for(int i=0; i < c1.Count; i++) {
Console.WriteLine(c1[i]);
}
}
}
//-----------------------------------------------------//
//LinkedList
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
LinkedList<int> c1 = new LinkedList<int>(arr);
for(int i=0; i < c1.Count; i++) {
Console.WriteLine(c1[i]); // error
}
}
}
반복자(iterator) 패턴
=> 복합객체의 내부 구조에 상관 없이 동일한 방식의 요소를 열거하는 디자인 패턴
=> Collection 의 내부 구조에 상관없이 동일한 방법으로 요소를 열거
=> C#에서는 반복자(iterator) 대신 열거자(enumerator)라는 용어를 사용
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
LinkedList<int> c2 = new LinkedList<int>(arr);
}
}
//------------------------------------------------------------------------------------------//
// IEnumerator객체 생성
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
LinkedList<int> c2 = new LinkedList<int>(arr);
IEnumerator<int> e1 = c1.GetEnumerator();
IEnumerator<int> e2 = c2.GetEnumerator();
// var e1 = c1.GetEnumerator();
}
}
C#에서 모든 컬렉션은 IEnumerable<T> 인터페이스를 구현한다. 이 얘기는 공통의 어떠한 메소드가 있을 것이란 이야기다.
이 메소드는 GetEnumerator()이다.
메소드가 반환하는 것을 열거자라고 부르는데 이것은 객체이다. 객체는 요소를 가리키며 다음 요소로 이동하거나 값을 꺼내올 수 있다.
또, 모든 열거자의 사용법은 동일하다.
왜냐하면 IEnumerator<T> 인터페이스를 구현하기 때문이다.
여기서 인터페이스 이름을 헷갈릴 수 있는데
IEnumerable<T>은 컬렉션의 인터페이스이고
IEnumerator<T>는 열거자의 인터페이스이다.
따라서 반환값을 받을 때 IEnumerator<T>로 받으면 된다.
요즘은 var를 많이 사용한다. (여기서는 IEnumerator<T>를 사용한다.)
// IEnumerator객체를 만들고 초기화를 하여, 요소를 꺼내오는 코드.
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
LinkedList<int> c2 = new LinkedList<int>(arr);
IEnumerator<int> e1 = c1.GetEnumerator();
IEnumerator<int> e2 = c2.GetEnumerator();
// var e1 = c1.GetEnumerator();
e1.MoveNext(); // 최초 호출 - 초기화
Console.WriteLine(e1.Current); // 첫 번째 요소 꺼내오기 - 1
e1.MoveNext(); // 다음으로 이동
Console.WriteLine(e1.Current); // 두 번째 요소 꺼내오기 - 2
}
}
//------------------------------------------------------------------------------------//
// 모든 요소를 꺼내오기 위한 효율적 방법.
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
LinkedList<int> c2 = new LinkedList<int>(arr);
IEnumerator<int> e1 = c1.GetEnumerator();
IEnumerator<int> e2 = c2.GetEnumerator();
// var e1 = c1.GetEnumerator();
while(e1.MoveNext()) // 더 이상 Next못 할 시 false 반환
Console.WriteLine(e1.Current);
while(e2.MoveNext())
Console.WriteLine(e2.Current);
e1.Reset(); //다시 처음 위치로 보내는 초기화
While(e1.MoveNext())
Console.WriteLine(e1.Current);
}
}
① 모든 컬렉션은 IEnumerable<T> 인터페이스를 구현한다.
=> 열거자를 꺼내는 GetEnumerator() 라는 메소드를 제공한다.
② 열거자
=> 컬렉션의 요소를 가리키는 객체
=> MoveNext(), Current, Reset() 멤버로 모든 요소에 접근 가능
=> 모든 열거자는 사용법이 동일하다.
=> 모든 열거자는 IEnumerator<T> 인터페이스를 구현하고 있다.
=> 모든 컬렉션은 foreach를 사용해서 열거 할 수 있다.
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
foreach(int n in c1) {
Console.WriteLine(n);
}
}
}
//==============================================================//
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = {1, 2, 3, 4, 5};
List<int> c1 = new List<int>(arr);
foreach(int n in c1) {
Console.WriteLine(n);
}
for(IEnumerator<int> p = c1.GetEnumerator(); p.MoveNext(); ) {
int n = p.Current;
Console.WriteLine(n);
}
}
}
모두 알다시피 foreach문이 있어 모든 컬렉션의 요소를 편하게 뽑아볼 수 있다.
그런데 왜 위와같이 IEnumerator로 받아서 열거하는 방법을 설명했을까?
사실 foreach문을 사용하면 내부적으로 열거자를 사용하는 코드로 바뀌게 된다. 따라서 역for문을 사용하게된다.
열거자 개념을 통해 foreach가 동작하는 원리를 알아두면 되겠다.
출처
[C++ STL] list 개요 (0) | 2022.02.25 |
---|---|
[C++ STL] deque (0) | 2022.02.25 |
[C++ STL] map (0) | 2022.02.25 |
[C++ STL] std::vector 주요 멤버 함수 목록 (0) | 2022.02.25 |
[C#] Collection과 Interface (0) | 2021.11.15 |