SturdyCobble's Study Note

[프로그래밍] 2.6 자료형[3] - 배열과 리스트(1) 본문

휴지통/['19.06-'20.07]프로그래밍101

[프로그래밍] 2.6 자료형[3] - 배열과 리스트(1)

StudyingCobble 2019. 7. 28. 18:56

NOTICE : 독학인 만큼, 잘못된 내용이 있을 수 있습니다. 내용 지적은 언제나 환영합니다.

더 이상 이 블로그에는 글을 올리지는 않을 예정입니다. 그렇지만 댓글을 달아주시면 최대한 답변드리고자 노력하겠습니다.


※이 글은 프로그래밍 언어에 대한 기초적인 이해를 가정하고 있습니다. 최소 프로그래밍 언어 하나 정도를 약간이라도 접해보시는 것을 추천합니다. 또한, 이 글은 심화 내용은 되도록 피하여 서술했습니다.

 

아래 표는 이번 글의 범위입니다. 

  C/C++ Java Python
배열 O O  
리스트(다음 글)   O O

 

(프로그래밍 언어를 하나 이상 접해보신 분들이라면 지금부터 순서가 섞이는 느낌을 경험할 수 있습니다. C언어를 배워보신 분이라면 리스트는 책에 소개되지 않은 경우도 많고, 배열도 이보다 한참 뒤에 설명합니다. Python을 배우신 분이라면, 배열같은 건 없고 지금쯤 리스트와 함께 딕셔너리 자료형이 같이 나오지 않아 당혹스러우실 수도 있습니다. 이는 이후 한번에 묶어서 다시 소개하겠습니다.)


이번 글에서는 리스트와 배열에 대해서 다뤄보겠습니다.

일단 아직 다루지 않은 특징적인 자료형들을 소개해보자면 다음과 같습니다.(물론 더 존재하지만, 기본적으로 Python에서 자주 사용되는 개념 위주로 다루었습니다.)

개념 C/C++ Python Java 비고
배열(Array) O X
(외부 패키지 필요)
O

C,C++에서는  '배열'은 자료형이 아님
Java에선 배열이 객체로 표현됨.

리스트(List)
list.h(C++)
O O  
튜플(Tuple)
tuple.h(C++11)
O X  
맵(Map)
map.h(C++)
X O

서로 비슷한 개념
(거의 동일하다 봐도 무관함)

Java에서 Map자체는 인터페이스로 세부적인 자료형들로 나뉘어짐

딕셔너리(Dictionary) X O
(거의 사용X)
집합(Set)
set.h(C++)
O O Java에서 Set자체는 인터페이스로 세부적인 자료형들로 나뉘어짐

이 모든 것을 한 글에서 다루기에는 여러모로 무리가 있어서 C/C++, Java에서 이용되는 배열(Array)의 개념과 Python과 Java에서 사용되는 리스트의 개념만 소개하겠습니다.(나머지는 지금 상황에서는 자주 이용되지 않기 때문에, 추후 같이 설명하도록 하겠습니다.)

 

 

 일단, 배열과 리스트의 공통점부터 알아보겠습니다. 둘 다 기본적인 형태의 자료들을 순서대로 모아둔 것이라는 특징이 있고, 수정가능하다는 특징이 있습니다.

1부터 8까지의 숫자들을 순서대로 나열한 모습

 하지만, 배열의 경우 길이가 정해져있고(포함된 자료들의 갯수), 리스트는 길이가 정해져 있지 않고, 데이터의 삽입과 제거가 배열보다 쉬운 특징이 있습니다. 이는 메모리에 저장된 형태를 통해 이해할 수 있습니다.

 

데이터가 메모리에 저장되는 형태의 모식도

 배열은 그 크기가 정해져있어 크기를 변화시킬 수 없습니다. 대신 자리가 고정되어 있기 때문에 간편하게 배열 내의 데이터를 읽고 수정할 수 있습니다.

 

 반면에 Linked List, 즉 연결 리스트는 메모리의 임의의 위치에 저장되더라도 둘 사이에 연결에 대한 정보를 저장하고 있기 때문에, 자유롭게 데이터를 추가할 수 있고, 특히 데이터 중간에 새로운 값을 삽입하거나 특정 값만을 지우고 뒤의 목록을 당겨와야 할때 간편하게 이용할 수 있습니다.(그림에는 그래도 순서는 유지해놓았지만, 메모리 상 순서도 의미없습니다.)

대신, 중간에 있는 어떤 값을 찾기 위해 앞에서 부터 순차적으로 찾아야 하는 불편함이 있습니다.(우리는 못 느끼지만, 매우 큰 리스트를 생각한다면 이를 컴퓨터가 찾아 연산하는 시간이 매우 커질 것이라 생각할 수 있습니다.)

 

 ArrayList는 배열처럼 저장되지만, List처럼 자유로운 삽입과 길이 변경이 가능한 특징이 있습니다. (대신 아무래도 그렇게 연산하는 속도는 연결 리스트보다 떨어지겠죠? 그러나 우리가 일반적으로 다룰만한 상황에서는 큰 차이가 없을 겁니다.)

 

 일단, C/C++과 Java에서 배열이 어떻게 이용되는 지 아래 예시를 통해 확인해봅시다.(기본적인 main함수 또는 메소드 부분 등은 생략하였습니다.)

(//뒤의 부분은 '주석(Comment)'라 부르며 프로그램에 포함되지 않습니다. 부가 설명을 위해 자주 이용됩니다.(C,C++,Java))

 

int array1[10] = {1,2,3,4,5,6,7,8,9,10};    //C,C++
 
int array2[]= {1,2,3,4,5,6,7,8,9,10};       //C,C++,Java

첫번째 줄에서는 int형의 길이가 10인(크기는 10×4=40 Byte가 되겠죠?) 배열 array1을 생성했습니다. 두번째 줄에서는 같은 내용인데 뒤의 주어진 값을 보고 알아서 크기를 알아맞추라고 크기칸을 비워둔 것을 볼 수 있습니다. 다만, Java에서는 첫번째 식이 사용 불가능합니다. 대신 Java에서 배열의 크기를 지정하기 위해선 다음의 식을 사용합니다.

//JAVA
int arr1 [] = new int[10];   //크기를 10으로 지정
int arr2 [] = new int[10] {1,2,3,4,5,6,7,8,9,10} //크기를 10으로 하고 1,2,3,4,...,10을 값으로
int arr3 [] = new int[] {1,2,3,4,5,6,7,8,9,10} //1,2,3,4,...,10을 값으로, 자동으로 크기는 10

그리고 Java에선 아래 식이 가능합니다.

 

int [] arr1 = {1,2,3,4,5};

Java를 처음 접했을 때 가장 당황했던 것이 위 코드의 int []와 같은 식이 가능하다는 점입니다. 이는 C언어의 배열과 Java의 배열에 약간의 차이가 있기 때문인데, 여기서는 넘어가겠습니다. 그리고 Java는 배열 길이를 변수를 가지고 선언할 수 있습니다.(물론 그렇다고 크기가 계속 변하는 것은 아닙니다.)

 

 

이제 우리가 C언어 코드에서 문자열을 저장하기 위해 char str1[]="Hello, world"와 같은 문장을 사용했던 이유를 알 수 있습니다. C언어에서는 문자열을 배열로 저장하기 때문입니다. 그렇기 때문에 처음에 최대 사용 가능 문자 수를 지정했어야 했습니다. (char* str1;과 같이 문자열을 나타내는 방식은 이후 포인터를 다루며 알아보겠습니다.)

 

위 예시에서 특정 위치에 있는 값을 가져오거나 수정하려면 아래와 같이 하면 됩니다. 이때 배열 안에서 데이터의 위치를 인덱스(index)라고 하는데, 이 값이 대부분의 언어의 경우 0부터 시작함에 주의해야 합니다.

array1[]={1,2,3,4,5,6,7,8,9,10};

int a1 = array1[1];
array1[1] = array[3] - array[0];

 위 예시의 경우 a1에는 2가 대입되며, array1의 2번째 데이터(인덱스가 1인 데이터)는 4-1=3이 저장될 것을 예상할 수 있습니다.

 

 또한 값을 참조하기 위한 인덱스 자리에는 변수가 올 수 있습니다.(단, int arr1[10];과 같이 [ ] 안에 인덱스가 아닌 크기를 주고 선언을 할 때는 변수가 올 수 없습니다.)

int ind1 = 3;
int arr1[]={1,2,3,4,5};
int b = arr1[ind1];

 

 그렇다면 아래의 예시는 어떠한 결과를 나타낼까요?

int array1[] = {1,2,3};
int a = array1;

아마 오류를 나타낼지도 모르고, 아니면 이상한 숫자가 a에 대입될 것입니다. 이는 배열의 이름 자체는 그 배열의 시작 주소(메모리상 위치)를 나타내기 때문입니다. 이에 관한 다양한 활용은 이후 다시 알아보겠습니다.

 참고로 C언어에서 문자열을 입력받을 때 scanf함수에서 다른 자료형과는 달리 &을 사용하지 않은 이유도 여기에 있습니다. 이름 자체가 주소이므로 주소를 가져오는 & 연산자를 사용할 수 없었던 것입니다.(이는 char *형태로 사용할 때도 같습니다)

 

 결국 배열 두 개가 같은 값을 가지게 하려면 이후 다룰 반복문을 이용하던가, 수동으로 일일히 작업해야 합니다.

int arr1[]={1,2,3,4,5};
int arr2[]={11,12,13,14,15};
int i;
for(i=0;i<5;i++){
	arr2[i] = arr1[i];
}

간단하게 위 식의 의미를 살펴보자면, i=0일때부터 i<5인 범위 내에서 i를 1씩 증가시켜가며 arr2[i]=arr1[i]식을 수행하라는 의미입니다.

 

배열을 초기화하는 식을 조금 더 알아보겠습니다. 일단, 아래 식은 C/C++이든 Java든 불가능합니다. 배열 이름이 주소를 저장하므로 어쩌면 당연한 결과입니다.(물론 C/C++은 두번째 식에, Java는 첫번째와 두번째 식에 에러를 표시하겠죠?)

int array[10];
array = {1,2,3,4,5,6,7,8,9,10};

 

C/C++의 경우 다음 식은 가능합니다. 대신 나머지 공간은 0으로 채워집니다.

int arr1[10]={1,2,3};
int arr1[10]={1,2,3,};

두번째 식의 경우 1,2,3을 차례로 대입하고 나머지를 0으로 채움을 강조하기 위해 컴마(Comma)를 삽입했을 뿐 의미는 첫번째 식과 같습니다. 만약 두번째 식으로 하고 길이를 지정하지 않으면, 길이가 3인 배열이 됩니다.

 

나중에 코드를 작성하다보면, 배열의 크기를 알고 싶을 때가 생깁니다. 이때 C/C++과 Java에서는 아래와 같은 방식을 이용합니다. 이때 C/C++의 sizeof 연산자는 배열또는 자료형의 크기(Byte)를 구하므로, 배열의 크기를 자료형의 크기로 나눠 길이를 구할 수 있습니다.

 

(참고로 Java에서 배열이 객체이므로 .length와 같이 배열의 길이를 저장하고 있는 속성(Property)를 이용할 수 있는 것입니다.(자세한 내용은 생략하겠습니다))

int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int sizeOfArr1 = sizeof(arr1)/sizeof(int);

int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int sizeOfArr1 = arr1.length;

이제 나중에 반복문을 배우고 배열의 값을 하나씩 출력하는 예시를 작성할 때, 배열의 크기를 반복횟수에 수동으로 넣어주어야 할 필요가 사라지게 될 겁니다.

 

배열의 또다른 특징은 다차원 배열이 가능하다는 점입니다. 이 경우 마치 xy좌표계처럼 인덱스를 여러개 필요합니다. 그렇다고 메모리에 2차원적으로 저장되지는 못하지만, 이해를 돕기 위해 아래와 같이 표시할 수 있습니다.

3×4 2차원 배열

일단 3×4배열(3행 4열)을 예시로 들어 이를 선언하는 식을 알아보겠습니다. 아래와 같은 경우들이 가능합니다.

int arr1[3][4];

int [][] arr1 = new int[3][4];
int arr2 [][] = new int[3][4];

 

이때, 이차원 배열 등 다차원 배열을 초기화하기 위해서는 다음과 같이 나타내야 합니다.

int arr1[][3] = {
	{1,2,3},
	{4,5,6}
};

int arr1[][] = {
	{1,2,3},
	{4,5,6}
};

한줄로 써도 상관은 없지만, 이렇게 하는게 가독성이 더 좋다는 특징이 있습니다. 위 두 예제 모두 int arr1[2][3];과 같이 C/C++에서 선언한 것과 동일한 크기를 나타냅니다. 참고로 C/C++에서 2차원 배열의 길이를 지정할 때는 적어도 첫번째 인덱스는 채워주어야 합니다. 이는 중괄호가 강제 사항이 아니기 때문입니다.

 

 일부 요소를 생략하는 경우 나머지는 0으로 채워집니다. 단, 만약 크기도 생략하고, 일부 요소를 생략했는데, 주어진 값으로는 배열의 크기가 얼마인지 정확히 나타낼 수 없다면 에러가 발생합니다.

 

각 요소에 접근하는 방식은 1차원처럼 인덱스를 가지고 접근하돼, 0부터 시작함에 유의하면 됩니다.

 

 

글이 생각보다 길어져 리스트는 다음 글에서 다루겠습니다. 

 

그 다음다음 글은 주석(Comment)에 관한 내용이고 그 다음은 제어문이 될 것같습니다.

 

Comments