[C언어 강좌] #12-1 [Array] 1차원 배열


안녕하세요 파일입니다. C언어 강좌는 굉장히 오랜만이네요..

제가 학업에 치이기도 했고 귀찮음이 앞서기에 강좌를 너무 늦게 작성했네요 ㅠㅠ

 

사실 50% 정도 글을 작성하고 임시저장을 해뒀는데 이어서 작성하려고 보니 글이 통째로 날아가서 멘붕을 조금 

했습니다;; 티스토리 임시저장은 네이버에 비하면 너무 부족한거같아요 ㅠㅠ

 

어쨌든 제 강의를 읽어주시는 분들이 있기에 다시 강의를 작성하게 됬습니다.

 

오늘 배워볼 것은 배열입니다~ 여기까지 오신 분들은 사실 C언어를 60% 정도는 배웠다고 해도 괜찮죠 ㅎㅎ

참고로 배열은 시각적으로 이해하는 것이 좋기 때문에 최대한 그림을 추가해서 설명해보겠습니다.

또한 난이도가 올라가는 부분이기 때문에 편한 마음으로 시청하시길 권장드립니다. (물론 대충 보라는 뜻은 아니고요..)

 

 

100%를 향해서 달려봅시다!

 

배열

 

#include <stdio.h>

int main(){
	int car1, car2, car3, car4, ... car30;
	return 0;
}

 

같은 종류의 차가 30대 있고 이를 각각 구분하기 위해서 7자리의 일련번호를 할당한 상황이 있다고 생각해봅니다.

이를 프로그래밍으로 구현하기 위해서 다음과 같은 코드를 작성했습니다. 

 

일단 선언만 했는데도 한숨이 나옵니다. 변수를 30개 만들어 줘야 하니깐요..

앞에서 배운 대로라면 우선 이 방법이 최선일 것입니다.

 

하지만 배열을 배우게 된다면 이게 최선은 아니죠.

우선 선언만이라도 배열을 통해 간단하게 만들어보겠습니다.

 

#include <stdio.h>

int main(){
	int car1[30];
	return 0;
}

오.. 코드가 굉장히 간단해졌습니다. 배열에 대해 감이 오시나요?

 

배열은 같은 자료형으로 선언된 연속적인 메모리 공간으로 값을 할당할 수 있습니다.

한마디로 같은 변수를 여러 개 만든다는 거죠. 이름 그대로 역시 배열입니다 ^^.

 

그러면 배열의 선언 방법을 보겠습니다.

 

선언방법

배열은 다음과 같은 방법으로 선언합니다. 

동일한 자료형의 변수가 연속된 메모리 공간에 할당됩니다.

기억해두세요 ^^

 

배열의 메모리 구조

#include <stdio.h>

int main(){
	char array1[5];
	int array2[5];
	return 0;
}

위 예제에선 배열 두 개를 선언했습니다.

크기도 5로 같습니다. 그런데 자료형이 다르네요.

 

#4 자료 형편에서 각 자료형마다 메모리가 할당되는 크기가 다르다고 했습니다. 기억나시나요?

int형은 참고로 컴퓨터 레지스터에서 계산이 가장 빠른 메모리 크기로 할당됩니다.

 

어찌 됐건 배운 바에 따르면 int형은 메모리 공간이 4바이트, char은 1바이트로써 할당됩니다. 

그림으로 보면 다음과 같이 됩니다.

 

 

참고로 배열은 zero-based index입니다. 0부터 샌다는 거죠. 프로그래밍을 하다 보면 인덱싱이 0부터 시작되는 경우가

많습니다. 알아두시면 좋겠네요. 배열은 아까도 말했듯이 동일한 자료형의 변수를 연속적으로 만들게 됩니다.

 

그러므로 array1은 char형으로 메모리 공간이 1바이트씩 동일한 char형이 총 5개이므로 5바이트 할당되었고, array2는 int형으로 메모리 공간이 4바이트씩 총 5개이므로 20바이트 할당되었습니다.

 

왜 0부터 시작하는지에 대해 (궁금하신분만 열어보세요)

 

#include <stdio.h>

int main(){
	char array1[5];
	int array2[5];
    
    printf("%d \n", sizeof(array1));
    printf("%d", sizeof(array2));
	return 0;
}

5
20
--------------------------------
Process exited after 0.04694 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

앞에서 배운 sizeof는 변수의 데이터 크기를 확인 가능했습니다. 배열 또한 가능합니다. 

sizeof로 배열의 크기를 확인하면 배열 전체의 데이터 크기가 나옵니다.

 

예제를 실행해보면 앞에서 이론적으로 계산해본 값과 정확히 일치합니다.

 

만약 배열의 개수를 데이터 크기로 알아내고 싶다면?

배열의 데이터 크기를 그 배열 항목 한 개가 할당된 자료형의 크기로 나눠주면 될 것입니다.

 

말이 어려워서 그렇지 위에서 위에서 20바이트가 나왔고 int형 자료형이니 한 개당 4바이트

20/4  = 5(개)로 계산할 수 있겠네요.

 

이를 프로그래밍으로 일반화시켜보면..

#include <stdio.h>

int main(){
	char array1[5];
	int array2[5];
    
    printf("%d \n", sizeof(array1) / sizeof(array1[0]));
    printf("%d", sizeof(array2) / sizeof(array2[0]));
	return 0;
}

5
5
--------------------------------
Process exited after 0.0539 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

이렇게 되겠네요. 첫째 인덱스의 배열 크기를 sizeof로 알아내고 (자료형 크기)  전체 배열 크기 / 자료형 크기를 시행했습니다. 사실 C언어만 아니어도 C#, 파이썬 등등 많은 언어들이 배열 길이(크기)를 알려주는 Length와 같은 함수가

이미 구현되어 있습니다.

 

뭐든지 자가 구현해야 하는 C언어의 현실.. 하지만 처음 말한 대로 컴퓨터의 본질적인 이해가 자연스럽게 되는 언어도

C언어 밖에 없을 겁니다.

배열 사용하기

변수를 만들고 데이터를 저장하지 않으면 의미가 없죠?

배열도 마찬가지입니다. 배열에 데이터를 저장해봅시다.

 

 

#include <stdio.h>

int main(){
	int array1[5];
	
	array1[0] = 10;
	array1[1] = 20;
	array1[2] = 30;
	array1[3] = 40;
	//array1[4] = 50;
	
	printf("%d %d %d %d %d", array1[0], array1[1], array1[2], array1[3], array1[4]);
	
	return 0;
}

10 20 30 40 36
--------------------------------
Process exited after 0.005757 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

배열에 값을 할당할 때는 배열 이름[인덱스 번호]으로 값을 할당해주시면 됩니다.

배열 4번에 값을 주석 처리해서 할당 안 하고 출력해봤는데 36이라는 이상한 숫자가 나왔습니다.

 

초기화를 하지 않아서 쓰레기 값이 저장됐습니다. 변수의 쓰레기 값에 대해서는 #3 변수 편에서 설명했습니다.

 

#include <stdio.h>

int main(){
	int array1[5] = {10,20,30,40,50};
	printf("%d %d %d %d %d", array1[0], array1[1], array1[2], array1[3], array1[4]);
	
	return 0;
}

요런 식으로도 선언이 가능합니다. 중괄호 { }를 사용합니다.

만약 이런 식으로 선언을 했다면 크기를 정해주지 않아도 크기가 자동으로 정해집니다.

#include <stdio.h>

int main(){
	int array1[] = {10,20,30,40,50};
	int array2[5] = {10,20,30};
	printf("%d %d %d %d %d \n", array1[0], array1[1], array1[2], array1[3], array1[4]);
	printf("%d %d %d %d %d", array2[0], array2[1], array2[2], array2[3], array2[4]);
	
	return 0;
}

int array1 [] = {10,20,30,40,50}; 이렇게 적어주면 값이 들어가서 크기를 정해주지 않아도 자동으로 5로 들어갑니다.

int array2 [5] = {10,20,30}; 또한 이런 식으로 크기를 명시해주고 3번과 4번의 배열 값을 지정해주지 않았는데

특이하게 0으로 자동 초기화가 되네요 알아둡시다.

 

#include <stdio.h>

int main(){
	int score[5] = {90,80,70,85,60};
	int total = 0;
	
	for (int i=0; i<5; i++){
		total += score[i];
	}
	
	printf("총점수 : %d점이고 평균 %.2f점 입니다.", total, (float) total/5);
	return 0;
}

총점수 : 385점이고 평균 77.00점 입니다.
--------------------------------
Process exited after 0.05513 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

앞에서 배운 for문을 이용해 시험 점수 배열의 합을 구하고 평균을 계산하는 코드입니다.

for문은 1부터 시작할 때 반복 조건에 1을 더해주던가 등호를 붙여야 하는 불편함(?)이 있었는데 0부터 시작하니 한결

편해졌네요. 

 

배열 선언 주의점

#include <stdio.h>

int main(){
	int score[5] = {90,80,70,85,60};

	printf("%d", score[5]); //출력 불가 
	return 0;
}

항상 주의할 점은 0부터 시작이므로 배열 크기가 5라면 0~4까지 할당이 돼서 score [5] 따위 값을 출력할 수 없습니다.

초보자분들이 0부터 새는 것에 많은 혼란을 가질 수 있는데 꼭 유의합시다.

 

원래 저부분에서 인덱스가 배열을 벗어났습니다 등의 exception이 발생하는데 왜인진 모르겠지만 출력이 되네요 ^^;;

어쨌든 유의합니다.

 

#include <stdio.h>

int main(){
	int score[5]; 
	score = {90,80,70,85,60};
	return 0;
}

 

그리고 중괄호로 배열을 선언할 때 개별 선언할 수 없습니다.

한 번에 초기화해줘야 합니다. *에러 발생

 

#include <stdio.h>
#define max 100
int main(){
	int length = 30;
	const int size = 50;
	int score[max]; //심볼릭 상수 o.k
	int array[size]; //심볼릭 상수 o.k
	int array2[length]; //변수로 배열길이 선언 x 
	return 0;
}

배열 길이를 되도록 변수로 설정하면 안 된다.

 

여기서 되도록이 붙은 이유는 변수 편에서 변수 선언은 항상 맨 위에 한다. 와 비슷한 맥락입니다.

C99 표준에서 배열의 크기를 변수로 선언하는 게 가능해졌습니다. (C99 표준에서만 동작)

이런 걸 VLA라고 하는데요. Variable Length of Array의 약자일 겁니다 아마도...

 

오류가 나신다면 사용하신 컴파일러가 C99이전 규칙일 가능성이 높습니다 ^^.

항상 C언어 하다 보면 책하고 실행결과가 다른 경우가 많이 나오는데 이렇게 이유를 알아가시면 좋겠습니다.

 

1차원 배열 주소 참조하기

& 연산자(주소 연산자)를 사용하면 메모리 공간의 주소를 표현 가능합니다. 

(이 부분을 앞에서 설명한 거 같기도 하고 아닌 거 같기도 하고.. 아무튼 잘 기억 안 나네요.)

앞서 & 를 비트 연산자 AND로도 배웠었죠.

 

그런데 어라. 두 개의 기호가 같네요? 차이점은 아래와 같습니다.

주소 값을 가리키는 &연산자는 단항 연산자입니다. 변수 a의 주소를 알고 싶다면 &a와 같이 이용하죠.

그런데 비트의 AND 연산을 할 땐 두 개의 변수가 필요합니다 a & b 같이요.

 

차이점이 보이시죠?

scanf를 사용할 때도 scanf("%d", &a);와 같이 항상 써왔던 &기호. 

요게 바로 주소 연산자 되겠습니다.

 

int a = 10; 에서 a가 어디 메모리 주소에 저장되었는지 알고 싶다면 printf("%d", &a); 이렇게 출력 가능합니다.

배열도 같은 논리를 적용시키면

 

&배열 요소를 하면 배열 요소의 주소를 알 수 있습니다.

 

#include <stdio.h>

int main(){
	int array[5] = {50,40,60,30,20};
	
	printf("%x \n", array);
	printf("%x \n", &array[0]);
	printf("%x \n", &array[1]);
	printf("%x \n", &array[2]);
	printf("%x \n", &array[3]);
	printf("%x", &array[4]);
	
	return 0;
}

62fe00
62fe00
62fe04
62fe08
62fe0c
62fe10
--------------------------------
Process exited after 0.03194 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

이렇게 출력해보면 앞에서 이론적으로 배운 많은 사실들이 값으로 증명됩니다.

 

일단 알 수 있는 사실.

printf("% x \n", array);
printf("%x \n", &array [0]);

요 두 개는 1차원 배열에서 용래가 같다는 걸 알게 됩니다. (2차원 배열에선 살짝 다릅니다)

 

배열 이름 = 배열의 시작 주소라는 것이죠.

 

그리고 

 

62fe00
62fe04
62fe08
62fe0c

 

이렇게 int형으로 4바이트씩 연속적으로 메모리 공간이 할당되어 있는 게 확인됩니다.

(62 fe08 - 62 fe12가 아닌 건 16진수 표기법에 의함)

 

char형의 배열의 메모리 크기를 보면 1바이트씩 연속적으로 되어 있음도 확인할 수 있습니다.

위의 예제를 바꿔서 직접 확인해보시길 바랍니다.

 

#include <stdio.h>

int main(){
	int array[5] = {50,40,60,30,20};
	
	printf("%x \n", &array[0]);
	printf("%x \n", &array[1]);
	printf("%x \n", &array[2]);
	printf("%x \n", &array[3]);
	printf("%x \n", &array[4]);
	printf("------------ \n");
	printf("%x \n", array + 0);
	printf("%x \n", array + 1);
	printf("%x \n", array + 2);
	printf("%x \n", array + 3);
	printf("%x \n", array + 4);
	
	
	return 0;
}

아까 배열 이름 = 배열의 시작 주소라는 것에 착안하여 +1, +2 연산을 해주면

array + 1 = &array [1] , array + 2 = &array [2] 

완전히 동일한 표현이 됩니다.  (앞의 주소 연산자에 유의해주세요)

+1씩 하면 배열을 한 칸씩 (위는 int형 배열이므로 4바이트씩)

건너뛴다고 생각해주면 되겠습니다.

 

array + 0 = array와 동일한데 어떤 수에 0을 더해도 의미가 없기 때문입니다.

 

위 코드를 실행해보면 서로 값이 동일하게 나옵니다.

 

1차원 배열 값 참조하기

이제부터 슬슬 어려워집니다.

*는 메모리 공간에 저장된 값을 참조하는 연산자입니다. (곱셈 아닙니다)

 

int a = 10; 에서 a의 메모리 공간이 &a = 84ed라고 가정해본다면

84ed 란 값에 * 연산을 시행해주면 *84ed = 그 메모리 공간에 저장된 값 즉, 10이 나오게 됩니다.

 

int a = 10;

printf("%d", a);

printf("%d , *&a); 

 

두 결과 값이 동일하다 이거죠.

*&a를 해석해보면 연산자 두 개가 나왔으므로 연산자 우선순위를 따라가는데,

 

결합성이 오른쪽에서 왼쪽이므로 &a가 수행되어 a의 주소 값이 나오고 *을 통해 주소값이

그 변숫값으로 참조되므로 출력 시 *&a = a 가 되는 거죠.

 

이 논리 역시 배열에 똑같이 적용됩니다.

 

#include <stdio.h>

int main(){
	int array[5] = {50,40,60,30,20};
	
	printf("%d \n", *&array[0]); //50 
	printf("%d \n", *&array[1]); //40
	printf("%d \n", *&array[2]);
	printf("%d \n", *&array[3]);
	printf("%d \n", *&array[4]);
	printf("------------ \n");
	printf("%x \n", array + 0);
	printf("%x \n", array + 1);
	printf("%x \n", array + 2);
	printf("%x \n", array + 3);
	printf("%x \n", array + 4);
	
	
	return 0;
}

아까 예제에서 메모리 참조한 곳에 전부 *연산을 붙여줬습니다.

그러니깐 배열 값으로 돌아왔네요.

 

*&array [0] = array [0]과 동일한 표현이므로 *&는 생략 가능합니다.

 

#include <stdio.h>

int main(){
	int array[5] = {50,40,60,30,20};

	printf("%d \n", *array + 0);
	printf("%d \n", *array + 1);
	printf("%d \n", *array + 2);
	printf("%d \n", *array + 3);
	printf("%d \n", *array + 4);
	printf("---------------- \n");
	printf("%d \n", *(array + 0));
	printf("%d \n", *(array + 1));
	printf("%d \n", *(array + 2));
	printf("%d \n", *(array + 3));
	printf("%d \n", *(array + 4));
	
	return 0;
}

50
51
52
53
54
----------------
50
40
60
30
20

--------------------------------
Process exited after 0.05408 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .


아까 배열 이름이 곧 그 배열의 시작 주소라고 했죠? 

이 예제를 실행해보면 괄호를 붙인 것과 안 붙인 것에 값이 차이가 나타나게 되는데요.

 

위에 연산자 우선순위를 보면 *연산자가 덧셈 연산자보다 위에 있어서 *연산을 먼저 수행합니다.

즉, *(array + 1)는 &array [1]의 값을 참조하는 반면

*array + 1 은 *array로 &array [0]의 값을 참조하고 1을 더하는 겁니다.

 

그래서 50 , 51 , 52 , 53 , 54  같은 결과가 나온 겁니다.

 

슬슬 머리 아프죠? 별로 걱정하실 거 없습니다. 1차원 배열은 여기까지 거든요 ㅋㅋㅋ..

 

& 주소 연산자, * 연산자 요게 오늘 강의의 핵심입니다. 

2차원 배열에서도 사용할 것이니 어렵다고 넘기셨으면 다시 봐주세요!!

 


어떤가요. 조금 힘드시죠? 고통을 이겨내면 c언어의 꽃이자 핵심이라고 할 수 있는 포인터가 기다리고 있을 겁니다!!

물론 2차원 배열하고 포인터 막 섞이면 더 어려울 수 있어요. 하지만 기왕 여기까지 온 거 꼭 제 강의가 아니더라도 끝을

봅시다.

 

사실 배열 부분을 어떻게 작성하면 쉽게 이해할 수 있을지 고민을 많이 했어요.

C언어에서 막히는 게 비트 연산, 배열, 포인터 부분이죠. 한마디로 어려운 부분이란 겁니다.

 

쉽게 설명해보려고 노력했는데 저도 부족한 부분이 많기에

오류가 있을 수 있으니 이해가 안 되거나 오류가 있는 부분은 댓글로 정정 부탁드리겠습니다!

 

2편에서 계속됩니다...

COMMENT WRITE

  1. 비밀댓글입니다

  2. 비밀댓글입니다