2023.01.12 - 안드로이드 앱개발자 과정

CHA·2023년 1월 12일
0

C언어



포인터

Data 가 있는 곳의 위치(주소) 를 통해 제어하는 문법


포인터 변수 : 주소값을 가진 변수

  • 선언 방식
    int* P;

  • & , * 연산자

    • & 연산자 : 주소값을 의미하는 연산자
    • .* 연산자 : 값을 의미하는 연산자
  • 포인터의 선언 및 활용

    		int a = 10;
    		int* p; // 포인터 변수 - 주소를 저장하는 변수
    		p = &a;
    
    		printf("%p\n", &a);
    		printf("%p\n", p);
    
    		//p가 가진 주소안에 있는 데이터 출력
    		printf("%d\n", *p);
    
    		//double 형과 char도 마찬가지
    		double b = 3.14;
    		double* p2 = &b;
    
    		printf("%.2lf\n", *p2);
    
    		char c = 'A';
    		char* p3 = &c;
    
    		printf("%c", *p3);

    포인터 변수의 타입은 위 코드에서 볼 수 있듯, 가르킬 변수의 데이터 타입입니다. int 형 변수를 가르킬 때는 int* 으로, double 형 변수를 가르킬 때에는 double* 로 포인터 변수를 선언합니다. 그리고 가르키고자 하는 변수의 주솟값을 포인터 변수의 값으로 가집니다. 그것을 통해 포인터 변수는 변수의 데이터의 조작이 가능해집니다.

  • 포인터 변수의 활용

    	// 포인터 변수를 활용하여 값의 변경도 가능!
    	int num = 100;
    	int* pp = #
    	*pp = 200;
    
    	printf("%d", num);
    
    	// 포인터 변수에 값 입력하기
    	printf("input : ");
    	scanf_s("%d", pp);

    포인터 변수를 활용하면 기존 변수의 값 또한 변경이 가능합니다. 포인터는 주소를 받아오는 변수이고, 그 주솟값에 들어가 있는 데이터에 접근이 가능하기 때문입니다. 또한, 다른 변수들과 마찬가지로 값을 사용자 입력 받는것 또한 가능합니다. 다만, 기존에는 변수 앞에 & 기호를 붙여 변수의 주솟값에 입력값을 집어넣었습니다. 하지만 포인터의 경우에는 주솟값을 가지고 있는 변수이기 때문에 & 기호 없이 사용 가능합니다.

    	//포인터 변수 하나로 두개의 변수 값 제어가능!
    	int n1 = 100;
    	int n2 = 200;
    	int* ppp;
    
    	ppp = &n1;
    	printf("%d\n", *ppp);
    
    	ppp = &n2;
    	printf("%d\n", *ppp);

    위 코드는 한번 참고삼아 보면 될것 같습니다.


포인터가 필요한 이유


  • 다른 지역(함수) 지역변수를 제어
void increase(int* a) {
	(*a)++;
}
void main() {
	int a = 10;
	printf("a : %d\n", a);

	increase(&a); // a변수의 주소를 전달해주기 
	printf("a : %d\n", a);
}

일반적으로 값을 전달해주면 위와 같은 코드에서는 원본 데이터는 바뀌지 않기 때문에 결과값은 똑같이 나올겁니다. 그때 사용하는것이 포인터 이며, 위와같은 코드를 통해 데이터를 변경할 수 있습니다.


  • 문자열 저장(참조)
void main() {
	char aaa[10] = "Hello";
	printf("%s\n", aaa);// %s 는 주소를 달라고 하는 서식자

	char* p;
	p = "andro"; // 문자열 상수를 대입하면 문자열의 시작주소가 전달.
	printf("%s\n", p);
}

포인터를 활용하면 문자열을 마치 저장하듯 사용할 수 있습니다. 표면적으로는 저장이라는 표현이 어울리겠지만, 내부적으로 보면 결국에는 포인터 변수가 문자열의 주소를 받아오게 됩니다. 자바의 String 이 이러한 방식으로 문자열을 표현하니까 잘 알아둬야겠습니다.


  • 지역 변수로 만든 배열을 다른 함수에 전달
void output(int* aaa) { ---- (2)

	printf("%d\n", aaa[0]); ---- (3)
	printf("%d\n", aaa[1]);
	printf("%d\n", aaa[2]);

	printf("%d\n", *(aaa+0)); 
	printf("%d\n", *(aaa+1));
	printf("%d\n", *(aaa+2));
}
void main() {
	int aaa[3] = { 10,20,30 };

	output(aaa); ---- (1)
}

포인터 변수를 이용하여 다른 지역에 있는, 즉, 다른 함수에 있는 변수의 값을 참조하는것은 알고 있습니다. 다른 지역의 배열 또한 마찬가지로 포인터 변수를 이용하여 위 코드 처럼 주소값을 전달 하게 됩니다.

(1). output(aaa) 를 통해 output 함수를 호출하였고, 파라미터로 aaa 배열의 주솟값을 전달하였습니다.
(2). 그리고 output 함수 내부에서는 전달받은 aaa 배열의 주솟값을 int* aaa 로 전달받습니다. 주솟값을 전달했으니까요.
(3). 배열의 데이터를 출력하는 코드가 output 함수 내부에 짜여져 있습니다.
일단, 배열처럼 사용하는것도 가능합니다. 다만, 본질적으로 본다면 파라미터로 받은 int* aaa 는 결국 주솟값 이기에 printf("%d\n", *(aaa+0)); 처럼 작성하는것도 가능합니다. 그리고 주솟값도 결국 16진수로 표현된 숫자이기에, 연산도 가능합니다. 다만, 포인터에서 주솟값을 출력할 때에는 +1 을 하게 되면 1이 더해지는 것이 아닌 포인터의 데이터 타입의 크기만큼 늘어나게 됩니다. 즉, int형 포인터를 1 더하면 4만큼 증가합니다. 이 부분은 잘 알아둡시다.


  • 동적 메모리 영역(Heap) 사용

    RAM 에는 메모리 영역이 총 4개로 나뉘어 집니다. 4개는 다음과 같습니다. Code,Data,Stack,Heap 나머지들은 위 그림을 참조하면 될것같고, Heap 영역에서 메모리 사용하는 방법에 대해 알아보겠습니다. malloc() 함수로 사용하고자 하는 크기의 메모리를 할당 받고, 그 메모리를 프로그램 내에서 다 사용했다면, free() 함수로 메모리를 해제 해주면 됩니다. 그런데, 메모리를 할당을 받았으면 그 메모리를 활용을 해주어야 합니다. 그런데 안타깝게도 Heap 영역에 할당된 메모리 공간에는 따로 이름을 지어줄 수 없습니다. 그러면 이름이 없이 어떻게 그 메모리 공간을 활용해야 할까요? 다음 코드를 보겠습니다.

    #include <stdio.h>
    #include <stdlib.h>
    
    void main() {
        //Heap 메모리에 정수형 숫자를 저장해보기 위해 4byte 공간을 받아 보기
        int* p = malloc(4);  ---- (1)
        *p = 10; ---- (2)
    
        printf("%d\n", *p);
        
        free(p); ---- (3)
    }

    그림에서 볼 수 있듯,
    (1). malloc() 함수를 이용하면 Heap 영역에 메모리가 할당됩니다. 코드에서는 4byte 의 메모리를 할당 받았습니다. 그리고 malloc() 함수는 할당된 메모리의 "주솟값" 을 리턴해줍니다. 리턴된 값을 포인터 변수 int* p 에 넣어줍니다. 그러면, 포인터 변수 p 에는 할당받은 메모리의 주솟값이 들어가게 됩니다.
    (2). 그리고 *p 에 데이터 10을 넣어주었습니다. 그리고 화면에 출력해주었습니다.
    (3). 포인터 변수 p의 사용이 끝나, free() 함수를 이용하여 메모리를 해제 해주었습니다.


포인터 배열


앞서서 배열의 데이터를 활용하고 싶을 때, 포인터를 통해서 조작이 가능하다고 배웠습니다. 그런데, 만약 조작해야 하는 배열이 10개, 혹은 100개라면 어떻게 해야할까요? 포인터를 100개를 만들기는 귀찮습니다. 그런데 우리는 포인터도 변수라는것을 알고 있습니다. 그렇다면 혹시 포인터를 배열로 만들 수도 있지 않을까요? 맞습니다. 포인터에도 배열을 적용시킬 수 있습니다. 다음 코드를 봅시다.

	int aaa[3] = { 1,2,3 };
	int bbb[3] = { 4,5,6 };
	int ccc[3] = { 7,8,9 };

	int* pArray[3]; ---- (1)

	pArray[0] = aaa; ---- (2)
	pArray[1] = bbb;
	pArray[2] = ccc;

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

(1) 크기가 3인 포인터 배열을 만들어 주었습니다.
(2) 그리고 각 포인터 배열의 요소마다 일반 배열의 시작주소값을 넣어주었습니다.
(3) 포인터 배열을 출력합니다.

위에서 봤던 코드를 그림을 도식화 하였습니다.


  • 포인터 배열을 통한 문자열 조작
    포인터 배열을 활용하면, 문자열 또한 손쉽게 제어할 수 있습니다. 예를 들어, 학생이 10명이 있고, 10명의 이름을 저장하는 배열을 만들고자 합니다. 기존의 배열을 활용한다면 2차원 배열을 만들어서 저장을 했어야 합니다. 하지만 포인터의 경우에는 다음과 같이 만들 수 있습니다.
	char* names[3];

	names[0] = "sam";
	names[1] = "robin";
	names[2] = "tom";

위에서 봤던 코드를 그림을 도식화 하였습니다.

profile
Developer

0개의 댓글