C#에서 배열은 매우 중요한 자료형이다.
배열을 이해하는 것은 자료구조를 파악하기 위한 첫걸음이다.
자료구조를 파악할 수 있으면 전체 프로그램의 구조와 원리를 꿰뚫어 볼 수 있다.
배열의 뜻에 대하여 네이버 사전에서는
1. 일정한 차례나 간격에 따라 벌여 놓음.
2. 정보·통신 동일한 성격의 데이터를 관리하기 쉽도록 하나로 묶는 일.
이라 한다.
배열이 성립하기 위해서는 같은 타입의 요소들이 모여야 하고 그 요소들의 순서를 가지고 정렬이 되야 한다.
다음과 같이 1byte의 요소들이 모여서 5번까지의 순서로 정렬하였다.
1: 0000 0000
2: 0000 0000
3: 0000 0000
4: 0000 0000
5: 0000 0000
이것이 배열의 모습이다. byte 형은 8비트, int 형은 32비트 등 같은 형태가 모인 것은 기본형의 배열이다.
16비트인 char 형과 32비트인 int 형을 번갈아 나열하면 이런 모습일 것이다.
1: 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
2: 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
3: 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
4: 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
5: 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
6: 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
7: 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
이것도 배열의 일종이다. 16비트와 32비트가 반복되는 형태를 하나의 타입으로 정의한 참조형 객체(클래스)도 배열이 될 수 있다. 객체의 배열이다. (메모리 상태는 기본형 배열과 다르다. 객체에는 메소드 등 더 많은 정보가 들어간다)
다음의 그림을 보자. (Fundamentals of Computer Programming with CSharp, Arrays chapter)
배열에 대한 일반 정의를 보여준다.
Arrays are collections of variables, which we call elements
배열은 변수들의 집합(collections)이다. 변수는 요소(elements) 라고 부른다.
* 위의 그림은 5개의 요소가 있는 배열이다.
* 변수(기본형 primitive, 참조형 reference) 하나하나는 배열의 요소를 구성한다.
* 요소에는 인덱스(색인)가 붙어있고 제일 처음은 0번 부터 N-1까지이다. (5개 요소의 배열에 인덱스 0~4)
배열은 또 다시 차원에 따라 달라진다. 1차원과 2차원이 가장 많이 사용되는 배열이다.
1. 배열의 선언
int[] myArray;
컴파일러에게 myArray 란 이름의 int 형 배열을 사용할 것이라고 알려주고 있다.
배열은 초기화되지 않았다. (null 이라 한다)
사용하려고 하면 아래와 같은 오류 메시지가 발생한다.
배열은 스스로 값을 가지고 있는 타입이 아니라 주소를 가지고 있는 참조변수이다. 아직 메모리를 할당하지 않았다. 모든 변수는 메모리를 할당하기 전까지 사용이 불가능하다. 컴파일 시점은 아직 메모리에 배열을 생성하는 단계는 아니지만 문맥상 실행시간(run time)에 이런 문제가 발생할 것을 미리 예측해서 사용자에게 오류 메시지를 출력하는 것이다.
아래 그림을 보면 메모리의 형태에 대하여 설명하고 있다.
배열을 선언할 때 컴파일러는 실행 타임의 메모리 구조를 만든다. C#에서 배열의 참조 변수는 지역변수를 할당하는 스택에 배치한다. 스택은 컴파일러가 예측할 수 있는 공간이다. 참조변수는 즉 실제 값들이 저장된 위치를 가지고 있는 값이다. 값이 저장된 위치를 메모리의 주소라 한다.
메모리의 주소는 HEAP 히프라 불리는 특정 메모리 안에 있다. 이렇게 복잡하게 메모리의 사용을 분리해야 하는 이유는 컴퓨터의 자원을 효율적으로 사용하기 위함이다.
스택과 히프의 구조는 다르다. 스택은 사이즈가 정해져 있고 컴파일러가 용량을 예측가능하다. 히프는 사이즈가 가변적이다. 스택은 정적이고, 히프는 동적이다. 컴파일러가 사전에 모든 것을 예측할 수 있으면 좋겠지만 실제 실행이 되는 시간에 메모리 상에서는 다이나믹한 일들이 일어난다.
예를들어 배열의 크기는 100개로 고정되 있는데 사용자가 200개의 데이터를 입력하려고 한다. 이런 경우 스택 영역에서는 대응이 안되고 전체 프로그램을 다시 짜야 한다. 그러나 히프 영역에서는 그럴 필요가 없다. 200개 크기의 새로운 메모리를 확보하고 기존의 100개의 자료를 새로운 메모리에 채운 뒤 중복된 기존 배열은 메모리에서 해제 시킴으로써 문제를 해결할 수 있다.
기존 C언어에서는 기본적으로 배열을 스택 영역에 저장한다. 필요할 때는 동적 메모리를 할당하여 히프영역에서 운영하게 한다. 그러나 C#에서는 처음부터 히프 영역을 사용한다. new 키워드는 메모리를 실제로 할당한다는 의미이다.
int[] myArray = new int[6];
이 문장은 아래와 같다.
new 키워드는 메모리에 할당을 시키며 값들을 0으로 초기화 시킨다. 소스코드로 확인해 본다.
using System;
namespace CSharpBasic
{
class Program
{
static void Main(string[] args)
{
int[] myArray = new int[6];
for(int i = 0; i < myArray.Length; i++)
{
Console.WriteLine($"myArray[{i}] : {myArray[i]}");
}
}
}
}
이 배열을 다룰 수 있는 참조 변수는 스택에 생성되고 실제 값들은 히프 영역에 저장된다. C#의 경우 실행시간에 히프영역을 실제 컨트롤 하는 것은 .NET 프레임워크 환경의 CLR(Common Language Runtime)과 해당 운영체제에게 달려있다.
CLR 은 자바가상머신(JVM) 과 유사한 구조이다.
사용자는 참조변수를 조작함으로써 실행환경에 영향을 미칠 수가 있다.
2. 배열의 초기화
배열을 사용하려면 초기화가 필요하다. 기본적으로 숫자형은 0, string은 공백상태인 null로 초기화가 되지만 그것은 의미가 없다. 배열을 만들었으니 자신의 값을 써야 할 것이다.
string[] myString = new string[3];
myString[1] = "C Sharp is great!";
for (int i = 0; i < myString.Length; i++)
{
Console.WriteLine($"myString[{i}] : {myString[i]}");
}
위의 예제는 인덱스 0,1,2 중에 가운데인 1에만 값을 넣어 줬다. 직접 대입하는 방법도 있고 for 루프를 활용할 수도 있다. 다. 보통은 프로그래머가 초기값들을 설정해주는 것은 좋은 방식이다. 프로그램의 사용자가 모르는 부분에 대하여는 일반적인 초기값을 넣어주고, 사용자의 학습을 유도하여 프로그램에 익숙해지도록 도와줄 수 있다.
3. 예외발생 (index out of range exception)
배열 사용의 오류는 인덱스 오류이다. index에는 어떤 값이라도 써넣을 수 있는데 배열의 크기는 한정되어 있다.
int[] newArray = { 2, 4, 6, 8, 9, 10 };
newArray[10] = 200;
이것은 실행 시간에 일어나는 runtime exception 이다. 6개의 배열을 만들었는데 프로그램에 인덱스를 10을 달라고 요청한다. 당연히 범위를 벗어났다. (OUT OF RANGE) 이 부분은 Exception 처리 루틴을 만들거나 하는 조치가 있어야 한다. 이 포스트에서 예외처리는 다루지 않는다.
* 거꾸로 복사하기
배열을 거꾸로 복사한다. 똑같은 크기의 배열을 생성한 후 for 루프로 끝에서 앞으로 값을 대입한다.
static void Main(string[] args)
{
int[] originalArray = { 1, 2, 3, 4, 5, 6, 7 };
int len = originalArray.Length;
int[] reversedArray = new int[len];
for (int i = 0; i < len; i++)
{
reversedArray[len - i - 1] = originalArray[i];
}
for(int i = 0; i < reversedArray.Length; i++)
{
Console.WriteLine($"original : {originalArray[i]} " +
$"reversed : {reversedArray[i]} ");
}
}
* foreach 문으로 반복하기
foreach 문은 python 의 iterable 과 비슷하다. 안에 들어있는 요소들을 처음부터 끝까지 하나씩 꺼내준다.
순서가 없어서 인덱스를 사용할 필요가 없을 때 사용한다.
static void Main(string[] args)
{
string[] capitals ={ "Seoul", "Tokyo", "Beijing", "Bangkok", "HongKong" };
foreach (string item in capitals)
{
Console.WriteLine(item);
}
}
여기까지 배열에 대한 내용이다. 이차원 다차원 배열에 대한 내용은 이번 포스팅 시리즈에서는 제외하도록 한다. 상급적이고 시간이 많이 걸리는 내용은 제외하여 최대한 빠르게 전 챕터를 한바퀴 도는 것이 목표이다. 부족하거나 심화학습의 내용은 추후에 추가할 것이다.
* 불가리아 C# 자료를 참고했다. (Free License 저자 링크)
https://introprogramming.info/
* 배열의 자료구조에 대한 더 상세한 내용은 아래 문서를 참고한다.