프로그래밍 언어/C언어

C언어 12. 배열과 포인터

닉네임못짓는사람 2020. 7. 15. 17:27
반응형

저번 글에서 C언어의 가장 중요한 특징이라고 할 수 있는 포인터에 대해서 알아보았습니다.

이번엔 포인터와 이전에 알아보았던 배열의 관계에 대해서 알아보도록 하겠습니다.

 

먼저 기억을 한번 더듬어봅시다.

프로그램에서 배열의 모든 요소를 출력해야 한다면 어떻게 해야 할까요?

맞습니다. 반복문을 사용해서 모든 요소를 출력할 수 있죠.

 

하지만 이 작업을 한 번만 하는 것이 아닌 프로그램 전체에서 여러번 빈번하게 출력한다면 어떨까요?

이에 대한 함수를 새로 정의해서 사용하면 매우 간단하게 사용할 수 있겠죠.

배열을 함수의 인자로


그런데 한번 생각해봅시다.

이 배열을 어떻게 함수에게 인자로 넘겨줘야 할까요?

배열의 모든 요소를 각각 인자로 넘겨주면 이 문제를 해결할 수 있겠죠.

#include<stdio.h>
void printArr(int a1, int a2, int a3, int a4, int a5);

int main() {
	int a[5] = { 0, 1, 2, 3, 4 };
	printArr(a[0], a[1], a[2], a[3], a[4]);
    	return 0;
}

void printArr(int a1, int a2, int a3, int a4, int a5) {
	printf("%d ", a1);
	printf("%d ", a2);
	printf("%d ", a3);
	printf("%d ", a4);
	printf("%d ", a5);
}

하지만 이 배열의 크기가 100, 또는 1000이라면 어떻게 할까요?

이런 방법으로 인자를 넘겨주기란 매우 어려운 일이 될 것입니다.

그래서 우리는 함수의 인자로 배열을 넘겨줄 때 포인터를 사용해야 합니다.

그럼 이제 그 방법에 대해서 알아보도록 합시다.

 

배열은 메모리상에서 연속적인 기억공간을 가지게 됩니다.

또한 이 기억공간을 얼마만큼 차지하고 있는지도 이미 알고 있을 것입니다.

그렇다면 배열의 첫 번째 요소의 주소값만 알게된다면 배열의 모든 요소에 접근할 수 있겠죠.

만약 int형 배열의 첫번째 요소의 주소값이 100번지라면,

여기에 4를 더한 104번지가 2번째 요소의 주소값이 되겠죠?

코드로 한번 확인해봅시다.

#include<stdio.h>

int main() {
	int i = 0;
	int a[5] = { 0, 1, 2, 3, 4 };
	int* a_P = &a[0];
	*(a_P + 4) = 5;
    	int a_Size = sizeof(a) / sizeof(int);

	printf("배열의 첫번째 요소의 포인터 : %u\n", a_P);
	printf("배열의 두번째 요소의 포인터 : %u\n", a_P + 4);

	for (i = 0; i < a_Size; i++) {
		printf("배열의 %d번째 요소의 값 : %d\n", i+1, a[i]);
	}
    	return 0;
}

위의 코드를 한번 실행해봅시다.

그럼 우리가 생각했던 결과와는 뭔가 다른 것을 알 수 있을 것입니다.

본래 생각했던 대로라면 배열의 두 번째 요소의 값이 5로 변경되었어야 하는데,

어째서인지 5번째 요소의 값이 변경된 걸 볼 수 있습니다.

또한 포인터의 값을 확인해보아도 ...756번지가 첫 번째 요소의 포인터라면,

4를 더한 두 번째 요소의 포인터는 ...760번지가 되어야 되는데... 772번지가 출력되었습니다.

 

그 이유는 바로 포인터에 대한 연산이 일반 정수형에 대한 연산과는 다르게 적용되기 때문입니다.

포인터는 자신의 자료형에 대한 정보를 가지고 있다고 저번 글에서 설명했었습니다.

그래서 포인터에 정수 연산을 하게 되면 이를 자신의 자료형의 크기에 맞춰서 계산합니다.

 

예를 들어... 756번지를 가진 int형 포인터 변수에 1을 더하게 되면,

이는 ...757번지가 아닌 int형의 크기인 4만큼 증가하게 되어... 760번지가 되는 것입니다.

따라서 위에서 우리가 작성한 코드는 배열의 다섯 번째 요소의 값을 변경하게 된 것입니다.

 

그러면 이제 이를 토대로 위의 코드를 올바르게 변경해보도록 합시다.

#include<stdio.h>

int main() {
	int i = 0;
	int a[5] = { 0, 1, 2, 3, 4 };
	int* a_P = &a[0];
	*(a_P + 1) = 5;
    	int a_Size = sizeof(a) / sizeof(int);

	printf("배열의 첫번째 요소의 포인터 : %u\n", a_P);
	printf("배열의 두번째 요소의 포인터 : %u\n", a_P + 1);

	for (i = 0; i < a_Size; i++) {
		printf("배열의 %d번째 요소의 값 : %d\n", i+1, a[i]);
	}
    	return 0;
}

위와 같이 하면 정확히 우리가 원했던 결과를 얻을 수 있습니다.

이렇듯 배열의 첫 번째 주소값만 알고 있으면 배열의 모든 요소에 접근할 수 있습니다.

그럼 이를 통해 배열의 모든 값을 출력하는 코드를 작성해보도록 합시다.

#include<stdio.h>

int main() {
	int i = 0;
	int a[5] = { 0, 1, 2, 3, 4 };
	int* a_P = &a[0];
    	int a_Size = sizeof(a) / sizeof(int);

	for (i = 0; i < a_Size; i++) {
		printf("배열의 %d번째 요소의 값 : %d\n", i+1, *(a_P+i));
	}
    	return 0;
}

이처럼 사용하면 배열의 인덱스 값을 사용해 출력한 코드와 동일한 결과를 출력할 수 있습니다.

배열명은 포인터다.


C언어에선 배열의 첫 번째 요소의 주소값을 쉽게 알 수 있는 방법이 있습니다.

그것은 바로 배열의 배열명을 통해 주소값을 구하는 것인데,

배열명은 그 배열의 첫 번째 요소의 주소값을 이름으로 설정해놓은 것입니다.

그럼 한번 확인해보도록 할까요?

#include<stdio.h>

int main() {
	int a[5] = { 0, 1, 2, 3, 4 };
	int* a_P = &a[0];

	printf("배열의 첫번째 요소의 주소값 : %u\n", &a[0]);
	printf("배열의 첫번째 요소의 주소값 : %u\n", a);
    	return 0;
}

결과를 확인해보면 &a[0]과 배열명 a의 값이 정확하게 같다는 것을 확인할 수 있습니다.

그럼 이제 배열명을 사용해서 배열의 모든 값을 출력해보도록 합시다.

#include<stdio.h>

int main() {
	int i = 0;
	int a[5] = { 0, 1, 2, 3, 4 };
	int* a_P = &a[0];
    	int a_Size = sizeof(a) / sizeof(int);

	for (i = 0; i < a_Size; i++) {
		printf("배열의 %d번째 요소의 값 : %d\n", i+1, *(a + i));
	}
    	return 0;
}

사실 컴파일러는 배열 요소를 사용할 때 항상 배열명으로부터 배열 요소의 주소값을 계산하고,

참조를 통해 이 값을 사용하게 됩니다. (*(a + 0) -> a[0])

 

또한 위와 관련해서 주의해야 할 점이 있습니다.

그것은 바로 배열명은 포인터변수가 아니라 상수값이라는 것입니다.

따라서 배열명은 자신의 값을 절대 변경할 수 없습니다.(a = a+1 : 불가능)

하지만 포인터 변수는 확실한 기억공간을 가지고 있기 때문에 이것이 가능합니다.(a_P = a_P+1 : 가능)

이를 통해 자신의 주소값을 계속 변경하면서 연산을 실행할 수 있습니다.

 

그럼 이제 맨 처음으로 돌아가서 포인터를 사용해 다른 함수에서 배열을 사용할 수 있도록 해봅시다.

#include<stdio.h>
void printArr(int* arr, int size);

int main() {
	int arr[5] = { 0, 1, 2, 3, 4 };
	int arr_Size = sizeof(arr) / sizeof(int);
	printArr(arr, arr_Size);
	return 0;
}

void printArr(int* arr, int size) {
	int i = 0;
	for (i = 0; i < size; i++) {
		printf("arr배열의 %d번째 요소값 : %d\n", i+1, *(arr+i));
	}
	
}

위의 코드는 printArr함수에 배열명과 배열요소의 개수를 인자로 넘겨준 뒤,

이를 사용해 배열의 모든 값을 printArr함수에서 출력하는 코드입니다.

주의해야 할 점은 배열명 또한 포인터이기 때문에 매개변수를 포인터 변수로 선언해주셔야 합니다.

 

이제 배열과 포인터의 관계에 대해서 충분히 이해가 되셨나요?

그렇다면 이 방법을 사용해서 배열의 값들을 오름차순, 내림차순 정렬하는 함수를 만들어봅시다.

#include<stdio.h>
void myOrder(int* arr, int size);
void printArr(int* arr, int size);

int main() {
	int i = 0;
	int arr[5];
	int arr_Size = sizeof(arr) / sizeof(int);
	
	printf("배열에 들어갈 정수 5개를 입력해주세요. : ");
	for (i = 0; i < arr_Size; i++) {
		scanf("%d", arr + i);
	}
	printArr(arr, arr_Size);
	myOrder(arr, arr_Size);
	printArr(arr, arr_Size);
	return 0;
}

void myOrder(int* arr, int size) {
	char or;
	int tmp, i, j;
	getchar();
	printf("어떤 순서로 정렬하시겠습니까?\n(오름차순 : a, 내림차순 : d) : ");
	scanf("%c", &or );
	if (or == 'a') {
		printf("오름차순 정렬\n");
		for (i = 0; i < size-1; i++) {
			for (j = i+1; j < size; j++) {
				if (*(arr + i) > * (arr + j)) {
					tmp = *(arr + i);
					*(arr + i) = *(arr + j);
					*(arr + j) = tmp;
				}
			}
		}
	}
	else if (or == 'd') {
		printf("내림차순 정렬\n");
		for (i = 0; i < size-1; i++) {
			for (j = i+1; j < size; j++) {
				if (*(arr + i) < *(arr + j)) {
					tmp = *(arr + i);
					*(arr + i) = *(arr + j);
					*(arr + j) = tmp;
				}
			}
		}
	}
	else {
		printf("잘못 입력하셨습니다.\n");
	}
}

void printArr(int* arr, int size) {
	int i = 0;
	for (i = 0; i < size; i++) {
		printf("배열의 %d번째 요소값 : %d\n", i + 1, *(arr + i));
	}
}

 

코드를 변경하면서 사용법에 대해서 충분히 이해하시길 바랍니다.

배열과 포인터에 대해선 이 정도로 마치도록 하고, 다음 글에선 문자열에 대해서 이야기하겠습니다.

감사합니다.

반응형

'프로그래밍 언어 > C언어' 카테고리의 다른 글

C언어 14. 포인터 배열, 다중 포인터  (0) 2020.07.17
C언어 13. 문자열  (0) 2020.07.16
C언어 11. 포인터  (0) 2020.07.14
C언어 10. 배열  (0) 2020.07.13
C언어 9. 사용자 정의 함수  (0) 2020.07.12