C언어로 복습하는 자료구조 (3) 포인터
c의 가장 큰 특징이라고도 볼 수 있는 포인터
포인터란? 변수의 주소를 가지고 있는 변수
모든 변수는 메모리에 저장되고, 포인터는 해당 메모리 주소를 의미한다
int a = 100;
int *p;
p = &a;
이렇게 선언한다 치면 int형 변수 a는 100의 값을 갖고, 포인터 변수 p는 a의 주소를 갖는다. (&a)
&는 주소 연산자로, &a와 같이 쓰면 변수 a의 주소를 추출할 수 있다.
*는 간접참조 연산자로, '포인터가 가리키는 장소'에 값을 저장한다. 예를 들어 변수 p가 가리키는 '장소'에 200이라는 값을 저장하고 싶다면 *p = 200과 같이 사용한다.
동일한 메모리 위치를 참조하는 변수는 실제로 동일한 객체를 가리키므로 한쪽이 변경되면 다른 쪽도 변경된다.
포인터 사용에 있어 대표적인 예제는 두 변수의 값을 바꾸는 swap이다.
void swap(int* px, int* py) {
int tmp;
tmp = *px;
*px = *py;
*py = tmp;
}
int main() {
int a = 1, b = 2;
swap(&a, &b);
printf("%d %d\n", a, b);
return 0;
}
값이 되는 변수 자체를 넣는 것이 아닌 값을 가리키는 포인터를 매개변수로 받아주고 두 포인터가 가리키는 값을 바꿔주면 해당 변수값이 바뀌게 된다.
매개변수로 포인터 변수를 받으므로 주소연산자를 붙여주어야 한다.
포인터는 배열과 밀접한 관련이 있는데, c에서는 '배열의 이름 = 배열의 시작위치를 가리키는 포인터' 이기 때문이다.
지난 글에서 이야기했던 것 같은데 배열은 시작 주소가 있고 이후 인덱스는 시작 주소에서 자료형의 크기만큼 떨어진 주소를 갖게 된다.
배열의 이름이 곧 포인터이므로 배열이 함수의 매개변수로 전달될 때 사실은 포인터가 전달되게 된다.
함수 호출 시에 배열을 복사하지 않아도 되게 하여 자원을 절약해준다는 장점이 있다.
정수 여러 개를 배열로 입력받아 합을 구하는 프로그램을 작성해보자
int sum(int list[], int size) {
int sum = 0;
for (int i = 0; i < size; i++)
sum += *(list + i);
return sum;
}
int main() {
int list[6];
printf("정수 6개 입력: ");
for (int i = 0; i < 6; i++)
scanf("%d", &list[i]);
printf("sum: %d\n", sum(list, 6));
return 0;
}
scanf로 값을 받아 배열에 저장할 때는 주소연산자를 붙여줘야하는건 당연한거긴 하지만...
여튼 배열 이름=배열의 시작점을 가리키는 포인터라고 했으니 *(list + i) 는 결국 list[i]의 값과 같은 걸 의미하게 된다
+ 동적 메모리 할당
배열은 선언 시 고정된 크기를 정해줘야 하는데 입력이 몇 개나 들어올지 모른다면.. 사용하는 것이 동적메모리
동적 메모리가 할당되는 공간을 히프(heap)라고 한다. 히프는 운영체제가 사용되지 않는 메모리 공간을 모아놓은 곳을 의미한다.
int* p;
p = (int*)malloc(sizeof(int)); //할당
*p = 1000; //사용
free(p); //반납
malloc()으로 할당하고 free()로 반납한다.
malloc()은 size 바이트만큼의 메모리를 할당한다. 반환되는 주소의 타입은 void* 이므로 위 코드에 보이는 것처럼 적절한 형변환이 필요하다. 또한 동적 메모리는 포인터로만 사용할 수 있다.
예를 들어 크기가 SIZE인 int형 배열에 동적 메모리를 할당하려면 우선 포인터 int *p를 선언하고
p = (int*)malloc(SIZE * sizeof(int)); 이런 식으로 할당해주면 되는 것이다
더는 동적메모리 공간을 사용하지 않을 때는 free()를 사용해 메모리를 반환한다.
만약 시스템 메모리가 부족해 할당할 공간이 없으면 NULL을 반환한다. 동적메모리할당을 사용할 때는 반환값이 NULL인지 확인해주는 작업이 필요하다.
+ 구조체 포인터
구조체에 대한 포인터를 선언하여 구조체 멤버에 보다 편리하게 접근할 수 있다.
동적으로 생성된 구조체는 포인터를 통해서만 접근할 수 있다.
typedef struct {
char name[10];
int age;
double gpa;
} student;
int main() {
student* s;
s = (student*)malloc(sizeof(student));
if (s == NULL) {
fprintf(stderr, "메모리 부족\n");
exit(1);
}
strcpy(s->name, "Cado");
s->age = 5;
s->gpa = 10.0;
printf("%s %d %.1f\n", s->name, s->age, s->gpa);
return 0;
}
->를 사용하여 구조체 멤버에 접근할 수 있다. s->name은 (*s).name 과 같은 의미
연습문제
8. 크기가 n인 배열 array에서 임의의 위치 loc에 정수 value를 삽입하는 함수 insert() 작성
정수 삽입 시 그 뒤에 있는 정수들은 한칸씩 뒤로 밀린다
void insert(int array[], int loc, int value, int items) {
for (int i = items; i >= loc;i--) {
array[i + 1] = array[i];
}
array[loc] = value;
}
int main() {
int array[100] = { 1,2,3,4,5,6 };
insert(array, 3, 10, 6);
for (int i = 0; i < 7; i++)
printf("%d ", array[i]);
return 0;
}
insert()의 시간복잡도는 O(n)이다.
10. 배열 array에서 임의의 위치 loc에 있는 정수를 삭제하는 함수 delete()
정수가 샂게되면 그 뒤에 있는 정수들은 한칸씩 앞으로 이동
int delete(int array[], int loc, int items) {
for (int i = loc + 1; i < items; i++)
array[i - 1] = array[i];
}
얘도... O(n)
뭔가 또 무지성코드로 마무리된 것 같지만 쨌든 끝