week05.TIL(Today I learned) C언어 포인터

Baedonguri·2022년 4월 29일
0
post-thumbnail

1. 포인터 개념

포인터란?

  • C언어의 포인터란 주소를 가리키는 것을 의미한다. 즉 주소값을 저장하는 것이다.
    포인터를 사용하는 이유에는 크게 3가지가 존재한다.
    1) 함수의 매개변수로 이용할 수 있음
    2) 배열을 쉽게 접근할 수 있음
    3) 동적할당
    -> 예를 들어, 큰 프로그램을 만들고, 많은 양의 데이터를 활용할 때
    많은 양의 데이터를 복사해서 여기저기 두는 것은 낭비일 것이다.
    이때 포인터를 이용하여 데이터가 저장된 주소를 가리켜서 필요할 때 가져다 쓰게 하는 것이다.
    이를 통해 복사시간 낭비 및 메모리 낭비를 줄일 수 있다.

참조 연산자 *

  • 참조 연산자는 포인터의 이름이나 주소 앞에 사용하며, 포인터가 가리키는 주소에 저장된 값을 반환한다.
    예를 들어 *ptr을 이용하면 ptr에 들어있는 주소로 가서 그 변수의 값을 가져오게 된다.

함수에 인자를 전달하는 방식에는 크게 두가지가 존재한다.

1.Call by Value

-> 기본적으로 C언어에서 지원하는 방식으로, 함수에서 값을 복사해서 전달하는 방식이다.
-> 인자로 전달되는 변수를 함수의 매개변수로 복사하여 사용하는데, 이렇게 되면 인자로 전달한 변수와는 별개의 변수가 되며, 매개변수를 변경해도 원래 변수에는 영향을 미치지 않는다.

다음은 Call by value를 활용한 예시이다.

#include <stdio.h>

void swap(int a, int b){
    int tmp;

    tmp = a;
    a = b;
    b = tmp;
}
int main()
{
    int a,b;
    a = 10;
    b = 20;

    printf("swap 전 : %d %d\n", a,b);
    swap(a,b);
    printf("swap 후 : %d %d\n", a,b);

    return 0;
}

// swap 전 : 10 20
// swap 후 : 10 20

실행결과를 살펴보면 swap을 진행했음에도 불구하고 값이 swap되지 않은 것을 볼 수 있다.

- 2.Call by Reference

-> Call by Reference 방식은 함수에서 값을 전달하는 대신 주소값을 전달하는 방식이고,
주소값 자체를 복사해서 넘겨주는 방식을 Call by address 방식이라고 한다.

#include <stdio.h>

void swap(int *a, int *b){
    int tmp;

    tmp = *a;
    *a = *b;
    *b = tmp;
}

int main(){
    int a,b;

    a = 10;
    b = 20;

    printf("swap 전 : %d %d\n", a,b);
    swap(&a,&b);
    printf("swap 후 : %d %d\n", a,b);
    
    return 0;
}
// swap 전 : 10 20
// swap 후 : 20 10

실행결과를 살펴보면 그냥 변수를 넘겨줄 때와는 다르게 원래 변수의 값도 바뀐다.

2. 포인터와 배열

1. 배열(Array)은 포인터 변수와 같은 기능을 하며, 첫번째 요소의 주소값을 나타낸다.

#include <stdio.h>

int main(void){
    int arr[5] = {10,20,30,40,50};
    int *arrPtr = arr; // 배열의 첫번째 원소의 위치가 주소가 된다.

    printf("%d\n", *arrPtr); // 10
    printf("%d\n", arr[0]); // 10

    return 0;
}

위와 같이 & 연산자를 쓰지 않아도 arr 자체가 주소값이기 때문에, 바로 포인터에 대입이 가능하다.
scanf로 문자열을 입력 받을 때, & 연산자를 쓰지 않아도 되는 것도 이와 같은 이유에서이다.

2. 포인터 변수도 일반 변수처럼 증감 연산이 가능하다.

#include <stdio.h>

int main()
{
	int arr[5] = {10, 20, 30, 40, 50};
	double arr2[5] = {10.1, 20.2, 30.3, 40.4, 50.5};
	int *arrPtr = arr;
	double *arrPtr2 = arr2;

	printf("포인터 주소 : %d %d\n", arrPtr++, arrPtr2++);
	printf("증가 연산 후 : %d %d\n", arrPtr, arrPtr2);
	printf("변수 값 : %d %.2f\n", *arrPtr, *arrPtr2);

	arrPtr += 2; 
	arrPtr2 += 2;

	printf("증가 연산 후 : %d %d\n", arrPtr, arrPtr2);
	printf("변수 값 : %d %.2f\n", *arrPtr, *arrPtr2);

	return 0;
}

// 포인터 주소 : -8144 -8112
// 증가 연산 후 : -8140 -8104
// 변수 값 : 20 20.20
// 증가 연산 후 : -8132 -8088
// 변수 값 : 40 40.40

실행결과에서 주목해야 할 점은
int형 포인터는 +1 할 때마다, 4씩 증가한다. 그래서 다음 배열 원소의 위치를 가리킬 수 있다.
그래서 arrPtr += 2에서 index 1번을 가리키고 있던 것을 index 3번을 pointing 하게 되고,
더 나아가 double형 포인터는 +1 할 때마다 8씩 증가한다.

즉, 포인터변수가 n만큼 더하거나 뺄 때 자료형의 크기 * n 만큼 증가하거나 감소한다는 것을 알 수 있다.

예제) 포인터를 이용하여 버블 정렬 함수 만들기

#include <stdio.h>

void bubbleSort(int arr[]) # 배열을 파라미터로 넘겨줌, 이때 배열은 주소 그 자체이다.
{
	int temp;
    // bubble sort
	for(int i=0; i < 9; i++)
	{
		for(int j=0; j < 9; j++)
		{
			if(arr[j] > arr[j+1])
			{
				temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}		
	}
}

int main()
{
	int arr[10];
	for(int i=0; i<10; i++)
	{
		scanf("%d", &arr[i]);
	}

	bubbleSort(arr); # 주소를 가리키는 &를 적어주지 않아도 된다.

	for(int i=0; i<10; i++)
	{
		printf("%d ", arr[i]);
	}
	
	return 0;
}

이중포인터

이중포인터는 포인터 변수의 주소값을 저장할 수 있다.

포인터 변수도 내부적으로 메모리 공간을 가지고 있고, 주소를 가지고 있다.

#include <stdio.h>

int main(void){

    int a = 7;
    int* ptr = &a; 
   
    *ptr = 8 ;// 간접접근(역참조) indirection

    int **pptr = &ptr;

    **pptr = 9; // double indirection
    
    return 0;
}

차례대로 천천히 살펴보자.
1) int형 변수 a에 7을 대입한다.
2) ptr 포인터 변수에 변수 a의 주소값을 저장한다.
3)
를 사용하여 ptr에 들어있는 주소로 가서 8을 저장한다.
4) ptr변수의 주소를 ** pptr에 저장한다.
5) pptr에 저장된 주소를 역참조하여 9를 저장한다.
즉, pptr에 저장된 ptr의 주소 -> ptr에 저장된 a의 주소 -> a의 주소 도착(이곳에 값 대입)

따라서, 최종적으로 변수 a에 저장되는 값은 9가 저장된다.

profile
Software Engineer

2개의 댓글

🤍

1개의 답글