[C언어] 포인터

0

C언어

목록 보기
4/7
post-thumbnail


포인터

  • 포인터는 변수의 주소값을 저장한다.
  • 선언할 때는 담고자 하는 자료형에 * (참조연산자) 를 붙여서 선언합니다.
#include <stdio.h>

int main()
{
	int *p = NULL;  // int* p == int * p 모두 같음
	int num = 15;

	p = &num;
    // 포인터에 주소값을 넣을때는 * 연산자를 떼고 주소값을 넣어줌

	printf("int 변수 num의 주소 : %d \n", &num);
	printf("포인터 p의 값 : %d \n", p);
	printf("포인터 p가 가리키는 값 : %d \n", *p);

	return 0;
    // 실행결과
    // int 변수 num의 주소 : 22115228 
	// 포인터 p의 값 : 22115228 
	// 포인터 p가 가리키는 값 : 15
}

포인터 + 증감연산자

#include <stdio.h>

int main()
{
	int *p = NULL; 
	int num = 15;

	p = &num;
	printf("포인터 p가 가리키는 값 : %d\n", *p);
	printf("num의 값 : %d\n\n", num);

	*p += 5;
	printf("포인터 p가 가리키는 값 : %d\n", *p);
	printf("num 값 : %d\n\n", num);

	(*p)++;
	printf("포인터 p가 가리키는 값 : %d\n", *p);
	printf("num 값 : %d\n\n", num);

	*p++;
	printf("포인터 p가 가리키는 값 : %d\n", *p);
	printf("num 값 : %d\n", num);

	return 0;
    // 실행 결과 
    
    //포인터 p가 가리키는 값 : 15
	//num의 값 : 15

	//포인터 p가 가리키는 값 : 20
	//num 값 : 20

	//포인터 p가 가리키는 값 : 21
	//num 값 : 21

	//포인터 p가 가리키는 값 : 572764048
	//num 값 : 21
}
  • 증감연산자는 참조연산자보다 우선순위가 높다.
    따라서 마지막 문단에 있는 *p++ 의 경우 주소를 먼저 찾아가지 않고 들어있는 변수 P 를 증가시키게 된다. 그러면 포인터 변수에 들어있는 주소값이 증가하는 것인데, 증가한 그 주소에는 아무것도 선언되어 있지 않으므로 쓰레기값이 들어있습니다. 따라서 포인터 P는 쓰레기값이 들어있는 주소를 가리키게 되는 것.

포인터 + 함수

  • 함수에서는 인자를 전달할 때 복사해서 사용합니다.
    즉, 전달해주는 원래 변수는 함수에서 수정할 수 없다는 뜻이죠.
    하지만 포인터로 메모리주소를 넘겨주면 함수에서도 메모리에 직접적으로 참조할 수 있기 때문에, 변수의 값을 바로 수정하는 것이 가능합니다.

예시

#include <stdio.h>

void pointerPlus(int *num)
{
	*num += 5;
}

int main()
{
	int num = 15;
	printf("num 값 : %d\n", num);

	pointerPlus(&num);
	printf("함수 사용 후 : %d\n", num);

	return 0;
}
// 실행결과

// num 값 : 15
//함수 사용 후 : 20

포인터와 배열

  • 배열의 주소는 연속되어있다.
  • 배열의 이름은 포인터 변수와 같은 기능을하며, 첫번째 요소의 주소값을 나타낸다.
#include <stdio.h>

int main()
{
	int arr[5] = {10, 20, 30, 40, 50};
	int *arrPtr = arr;

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

	return 0;
    // 실행결과
    // 10
    // 10
}

포인터 연산

  • 포인터 변수도 일반 변수 처럼 값이 들어가 있는 변수이기 때문에 증감 연산을 할 수 있습니다.
    ++,--, +,- 이 가능하며, 곱셈, 나눗셈 연산은 하지 못합니다.
  • 하지만 증감연산의 결과가 일반 변수와는 다르다.
#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;
    // 실행결과
    
    // 포인터 주소 : 1617438912 1617438944
    //증가 연산 후 : 1617438916 1617438952
	//변수 값 : 20 20.20
	//증가 연산 후 : 1617438924 1617438968
	//변수 값 : 40 40.40
}
  • 일반 변수에서 사용했던 연산은 1씩 증가하거나 감소하는 것이었는데, 포인터 변수는 연산을 했더니 증가하는 크기가 1이 아닙니다. 심지어 자료형에 따라 달라집니다. 숫자를 넣어서 덧셈연산을 해보아도 마찬가지 입니다. 숫자 2를 더했는데 증가한 주소값은 2가 아닙니다.
  • 실행해보면 아시겠지만 int형 포인터인 arrPtr는 1을 더할때마다 4씩 증가하고, double 형 포인터인 arrPtr2는 8씩 증가합니다.
  • 즉, 포인터변수가 N만큼 더하거나 뺄때 (자료형의 크기) * N만큼 증가한다는 것이죠. 감소 연산도 동일합니다.
  • 포인터를 배열처럼 사용할수 있는 방법은 *(arr+i) == arr[i]라는 것이다.
    포인터의 이름은 배열의 첫번째 원소의 주소를 가리키므로, i * (자료형의 크기) 만큼 더해지면 결국 배열 i 와 같아지는 것입니다.

포인터로 버블정렬 함수 만들기

  • 버블정렬 주어진 배열에서 첫번째 자료와 두번째 자료를, 두번째 자료와 세번째 자료, 세번째 자료와 네번째 자료.....(마지막-1) 번째 자료와 마지막 자료를 비교한 뒤 그 결과에 따라 값의 위치를 교환하며 정렬하는 방법을 말함.
#include <stdio.h>

void bubbleSort(int len, int *arr)
{
	int temp;	
	for(int i=0; i<len-1; i++)
	{
		for(int j=0; j<len-1-i; 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(10, arr);

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

상수 포인터

  • 일반 변수에는 값을 절대 바꿀 수 없는 상수가 있습니다. 포인터에도 마찬가지로, 주소값을 바꿀 수 없는 상수 포인터가 있습니다.
  • const 를 붙여서 선언함
#include <stdio.h>

int main()
{
	int num = 10;
	int *ptr1 = &num;
	const int *ptr2 = &num;

	*ptr1 = 20;
	num = 30; //num자체가 상수가 된 것이 아니기때문에 오류가 나지 않았음

	*ptr2 = 40; // 불가능

	return 0;
}
// 실행결과
// 오류남

포인터 상수화

  • int* const ptr과 같이 자료형 다음에 const를 선언하게 되면 포인터 변수 자체가 상수화 됩니다. 주소값을 변경할 수 없다는 뜻이죠.
  • 여기서 주의할 점은, 원래 포인터를 선언할 때 * 연산자는 어디에 붙여주어도 상관없었습니다. 하지만 포인터를 상수화 시킬 때에는 const전에 *연산자를 써주어야 합니다.
#include <stdio.h>

int main()
{
	int num1 = 10, num2 = 20;
	int *ptr1 = &num1;
	int* const ptr2 = &num1;
	
	ptr1 = &num2;
	
	*ptr2 = 30;
	ptr2 = &num2; //불가능
	
	return 0;
    //실행결과
    //오류
}

위와 같이 const를 자료형 다음에 써주면, 포인터를 이용해서 값을 변경하는 것은 가능하지만 포인터가 가리키고 있는 주소값을 변경하는 것은 불가능합니다. 따라서
"이 포인터가 오로지 num1변수만을 가리키며, 절대 다른 변수를 가리키지 않겠다"
라고 생각한다면 위와 같이 써주면 되는 것입니다.

또한 포인터를 통해 값을 변경하는 것도, 다른 변수를 가리키는 것도 불가능하게 하고 싶다면,
아래와 같이 써주면 됩니다.

const inst* const ptr2 = &num;

이중 포인터와 포인터 배열

  • 이중for문이 있듯이, 포인터에도 이중포인터가 있습니다. 이중 포인터는 포인터의 주소값을 담는 변수로, 포인터의 포인터라고 할 수 있습니다.
#include <stdio.h>
int main()
{
	int num = 10;
	int *ptr;
	int **pptr;

	ptr = &num;
	pptr = &ptr;

	printf("num : %d, *ptr : %d, **ptr : %d\n", num, *ptr, **pptr);
	printf("num 주소 : %d, ptr 값 : %d, **ptr 값 : %d\n", &num, ptr, *pptr);
	printf("ptr 주소 : %d, pptr 값 : %d", &ptr, pptr);

	return 0;
}

코드를 보시면 ptr에는 num의 주소값을 대입하고, pptr에는 ptr 의 주소값을 대입했습니다. 참조 연산자를 사용하면 포인터가 가리키고 있는 변수의 값을 말한다고 설명했었습니다. 이중 포인터는 그 포인터가 가리키고 있는 곳으로 가서, 또 그 포인터가 가리키는 주소로 찾아가서 그 변수의 값을 사용합니다. 따라서 첫번째 출력문에서 num의 주소인 &num, 이 주소를 담고있는 ptr의 값이 같습니다. 그리고 이중포인터는 참조 포인터를 두개 모두 쓰지 않고 하나만 쓸수도 있는데, 이렇게 되면 pptr 이 가리키고 있는 값을 의미합니다. 결국 ptr 변수에들어있는 값이 되겠죠. 그건 결국 num의 주소값이니까, 세개의 값이 모두 같습니다.

포인터 배열

  • 일차원 배열에 값들을 넣을 수 있듯이, 포인터를 담을 수 있는 배열이 있습니다. 이를 포인터 배열이라고 합니다. 어렵게 생각할 것은 없습니다. 그냥 일반 배열과 똑같은데, 포인터가 들어간다고 생각하면 됩니다. 어떻게 쓰는지 코드로 보도록 하겠습니다.
#include <stdio.h>

int main()
{
		int num1 = 10, num2 = 20, num3 = 30;
		int *parr[3];

		parr[0] = &num1;
		parr[1] = &num2;
		parr[2] = &num3;

		for(int i=0; i<3; i++)
		{
			printf("parr[%d] : %d\n", i, *parr[i]);
		}

		return 0;
        //실행결과

        //parr[0] : 10
		//parr[1] : 20
		//parr[2] : 30
}

참조 연산자를 붙인다는것만 빼면 일반 배열과 똑같이 선언하며, 대입할 때는 변수의 주소값을 넣습니다.


한 눈에 끝내는 c언어 기초 를 공부하고 작성한 글입니다.

0개의 댓글