포인터는 다른 변수의 주소를 값으로 가지는 변수입니다. 포인터의 연산자 *(asterisk)는 포인터가 가리키는 값을 의미합니다. 즉, int *a = &b 로 선언했을 때 포인터 변수a는 변수b의 주소를 값으로 가지며 *a는 변수 b가 가진 값을 가리키는 것입니다. 이것을 다른 말로 'dereferncing' 한다고 말합니다.
포인터를 이야기하면 항상 나오는 개념이 배열입니다. 포인터는 배열과 매우 비슷합니다. 배열 int a[4] 를 선언했다고 합시다. 우리가 배열에 들어있는 특정 인덱스의 값에 접근하려면 a[0] = 2, a[2] = 30 등 이러한 방식으로 사용합니다. 이것은 포인터에서 *a = 2, *a = 30 과 유사합니다. 지정 영역에는 차이가 있지만 둘 다 값을 할당 해주는 개념인 것이죠.
#include <stdio.h>
void swap(int *px, int *py)
{
int tmp;
tmp = *px;
*px = *py;
*py = tmp;
}
int main(void)
{
int a = 1, b = 2;
printf("swap을 호출하기 전: a=%d, b=%d\n", a, b);
swap(&a, &b);
printf("swap을 호출한 다음: a=%d, b=%d\n", a, b);
return 0;
}
그렇다면 포인터를 함수의 매개변수로 보내봅시다. swap() 함수의 매개변수를 살펴보면 포인터 변수 2개를 받고 있습니다. 즉, 이 함수로는 각각 주소를 보내야 한다는 의미입니다. 정수 a와 b를 1과 2로 선언하고 a와 b의 주소를 함수로 보냅니다. 이것을 call by address라 지칭하는데 이것에 대해선 다른 글에서 다시 살펴보겠습니다.
변수의 주소를 함수로 보냈기 때문에 함수에서 값을 변경한 내용이 함수가 종료된 후에도 보존됩니다.
#include <stdio.h>
#define SIZE 6
void get_integers(int list[]){
printf(“%d개의 정수를 입력하시오: “, SIZE);
for (int i = 0; i < SIZE; ++i) {
scanf("%d", &list[i]);
}
}
int cal_sum(int list[]){
int sum = 0;
for (int i = 0; i < SIZE; ++i) {
sum += *(list + i);
}
return sum;
}
int main(void){
int list[SIZE];
get_integers(list);
printf("합 = %d \n", cal_sum(list));
return 0;
}
이번에는 포인터와 유사한 개념인 배열을 매개변수로 보내봅시다. 주의할 점은 배열을 매개변수로 보낸다고 하여 배열 자체를 보내는 것이 아니라 포인터로 받는다는 것입니다.
함수 호출부를 보시면 get_intagers(list[SIZE]) 가 아니라 get_intagers(list) 인 것을 볼 수 있습니다. 즉, 배열 list를 보낸다는 것은 배열의 주소를 보낸다는 의미인 것입니다. 주소를 보냈으니 그것을 받는 매개변수는 포인터인 것이죠.
cal_sum() 함수에서도 확인해봅시다. list 배열에 들어있는 모든 요소들의 합을 구하는 함수입니다. for 반복문의 내용을 보시면 변수 sum에 list + i의 dereferencing(*) 한 값을 더하고 있습니다. 배열 list가 포인터이기에 포인터 연산자 *을 사용하는 것이죠.
이제 문제를 통해 정리해봅시다. 문제를 먼저 풀어보신 후 풀이를 확인해보시기 바랍니다.
[1-1] Point가 2차원 공간에서의 점을 나타내는 구조체라고 했을 때 다음 두 가지의 차이를 쓰시오.
>> 매개변수는 값을 전달하는 역할만 하기 때문에 함수의 매개변수로 값을 보내서 함수에서 그 값을 변경하여도 함수 스코프 바깥의 공간에서는 아무런 영향이 없다. 그런데 매개변수로 포인터를 전달하면 함수 안에서 메모리 주소에 접근하여 값을 저장할 수 있기 때문에 함수 바깥 공간에서도 해당 주소에는 여전히 값이 저장되어 있다는 차이점이 있다.
[1-2] 크기가 SIZE인 배열 array에서 임의의 위치 loc에 정수 value를 삽입하는 함수 insert()를 작성하라. 정수가 삽입되면 그 뒤에 있는 정수들을 한 칸씩 뒤로 밀어야 한다. 현재 배열에 들어있는 원소의 개수는 변수 items에 할당한다.
void insert(int list[], int value, int loc) {
for (int i = items; i >= loc; i--) {
list[i + 1] = list[i];
}
list[loc] = value;
}
>> 매개변수로 list의 포인터, int형 변수 value, loc을 넘겨 받고 자릿값을 한 칸씩 옮겨주기 위해 i를 현재의 원소 개수(items)부터 지정한 위치(loc)까지 반복하면서 i의 다음 인덱스의 값을 그 전 인덱스의 값으로 넣는 형식으로 할당한다. 이후 list의 loc 인덱스 자리에 넘겨받은 value값을 넣어주면 된다.
[1-3] 크기가 SIZE인 배열 array에서 임의의 위치 loc에 있는 정수를 삭제하여 반환하는 함수 delete()를 작성하라. 정수가 삭제되면 그 뒤에 있는 정수들은 한 칸씩 앞으로 이동하여야 한다. 현재 배열에 들어있는 원소의 개수는 변수items에 할당한다.
* 이 문제는 정수를 삭제하여 그 정수만 반환하는 방식과 정수를 삭제하고난 배열을 반환하는 방식 두가지를 작성할 수 있습니다.
정수를 삭제하고난 배열을 반환하는 경우
int* delete(int list[], int loc) {
for (int i = loc; i < items; i++) {
list[i] = list[i + 1];
}
items -= 1;
return list;
}
>> 매개변수로 list의 포인터, int형 변수 loc을 넘겨받고 list의 loc 인덱스를 그 다음 인덱스에 있던 값으로 덮어씌우면 loc 인덱스에 있던 값이 없어지기 때문에 loc 인덱스부터 현재 원소의 개수 이전까지 반복하면 자릿값들이 한 칸씩 앞으로 이동한다. 이동시킨 list포인터를 다시 반환하여 함수 바깥에서 값이 계속 유지되도록 한다
삭제한 정수를 반환하는 경우
int delete(int list[], int loc) {
int tmp = list[loc];
for (int i = loc; i < items; i++)
list[i] = list[i + 1];
items -= 1;
return tmp;
}
>> 매개변수로 list의 포인터, int형 변수 loc을 넘겨받고 값을 넣어둘 변수 tmp에 list의 loc인덱스에 위치한 값을 넣어준다. list의 loc 인덱스를 그 다음 인덱스에 있던 값으로 덮어씌우면 loc 인덱스에 있던 값이 없어지기 때문에 loc 인덱스부터 현재 원소의 개수 이전까지 반복하면 자릿값들이 한 칸씩 앞으로 이동한다. 이후 값을 넣어두었던 tmp를 반환한다
실행결과
#include <stdio.h>
#define SIZE 100
int items = 6;
void get_integers(int list[]) {
printf("%d개의 정수를 입력하시오\n", 6);
for (int i = 0; i < 6; ++i) {
scanf_s("%d", &list[i]);
}
}
void insert(int list[], int value, int loc) {
for (int i = items; i >= loc; i--) {
list[i + 1] = list[i];
}
list[loc] = value;
}
int* delete(int list[], int loc) {
for (int i = loc; i < items; i++) {
list[i] = list[i + 1];
}
items -= 1;
return list;
}
int main(void) {
int list[SIZE];
get_integers(list);
insert(list, 99, 4);
printf("insert() 실행\n");
printf("%d\n", list[4]);
delete(list, 4);
printf("delete() 실행\n");
printf("%d\n", list[4]);
return 0;
}