ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 포인터 - 바로 실행해보면서 배우는 C언어
    CS/C언어 2020. 1. 3. 21:22

    포인터

    포인터 : 변수의 주소값을 저장하는 변수. 포인터 변수라고도 부름.

     

     

    포인터 변수의 선언과 초기화

    포인터 변수를 선언할 때는 *(참조 연산자)를 붙여서 선언.

    int형 변수의 주소를 담고 싶다면

    int *p = null;

    과 같이 선언하면 됨.

     

    포인터 변수의 크기는 자료형이 달라도 모두 동일하다. (동일한 운영체제 시스템일 경우 주소값의 크기가 동일하기 때문)

     

    ※ 포인터 변수 선언 시 자료형의 역할

    : 가리킬 주소에 담긴 변수가 어떤 자료형을 갖는지 알려줌. (포인터 연산을 할 때, 해당 주소로 찾아가서 자료형에 따라 다른 크기(int-4바이트, double-8바이트 등)를 읽어와야 하기 때문)

     

    포인터 변수의 초기화 : null(0)로 초기화한다.

    초기화하지 않고 선언만 한 다음 나중에 주소값을 넣어도 되지만, 변수를 초기화하지 않을 경우 초기값이 null이 아닌 쓰레기값이 되므로 변수를 잘못 사용하여 에러가 발생할 수 있다.

     

    선언한 포인터 변수에 변수의 주소값 담기

    int num = 15;
    int *p = null;
    p = #

    변수 num의 주소값(&num)을 포인터 변수 p에 담았다.

    포인터 변수 p로 포인터 연산을 하면, (*연산자를 통해)p에 들어있는 주소값으로 찾아가 해당 주소에 담겨 있는 값(15)으로 연산을 하게 된다.

     

     

    참조 연산자 *

    포인터의 이름이나 주소 앞에 사용. 포인터가 가리키는 주소에 저장되어있는 값을 반환한다.

    int main() {
    	int *p = null;
    	int num = 15;
    
    	p = # 
    
    	printf("%d, ", &num);
    	printf("%d, ", p);
    	printf("%d", *p);
    
    	return 0;
    }
    
    > -363432804, -363432804, 15

     

    *p : p에 들어있는 주소(-363432804)로 가서 해당 주소에 담겨 있는 변수의 값(15)를 가져온다.

     

    포인터를 이용하면 일반 변수처럼 사칙연산을 할 수 있다.(곱셉/나눗셈 제외)

     

    그러나 증감 연산자의 경우, 참조 연산자보다 우선순위가 높아

    *p++;

    와 같이 처리할 경우

    p에 담겨있는 주소를 찾아가 해당 주소의 변수(15)의 값을 1증가시키는 것이 아니라,

    p에 담겨있는 주소값 자체를 증가시키게 된다.

     

    따라서 (*p)++; 처럼 작성해 참조 연산자가 우선처리되도록 한다.

     

     

    포인터를 사용하는 이유

    함수에서는 인자를 전달할 때, 복사본을 전달한다. 넘겨받은 함수에서 복사본을 수정해도 원래 변수의 값은 변경되지 않는다.

    그러나 포인터를 통해 메모리 주소를 넘겨주면(바로가기), 넘겨받은 함수에서 메모리에 직접 접근해 원래 변수의 값을 변경할 수 있다.

     

    Call by Value VS Call by Reference

    Call by value :

    값을 복사해 전달하는 방식. 인자로 전달되는 변수를 함수의 매개변수에 복사함.

    C언어에서 기본으로 지원하는 방식이다.

    원본 값을 바꿀 필요가 없는 경우에 사용한다.

    Call by reference :

    값 대신 주소값을 전달하는 방식.

    C언어에서 포인터를 사용해 주소값을 넘겨주는 것은 주소값 자체를 복사해서 전달하는 방식이므로 call by value, 혹은 call by adress(주소값을 복사해서 넘겨주는 것)라고 볼 수 있다.

    C언어에서는 call by reference를 공식적으로 지원하지 않는다.

    그러나 call by adress를 사실상 call by reference처럼 사용할 수있기 때문에, call by reference로 설명하는 곳도 많다.

     

    ⇒ C언어에서 함수 인자 전달은 원칙적으로 모두 call by value이지만, 포인터를 사용하여 call by reference를 구현할 수 있다.

     

    포인터 연산과 배열

    포인터로 버블 정렬 함수(오름차순) 만들기

    #include <stdio.h>
    
    void BubbleSort(int arr[]); // int arr[] 대신 int *arr을 써도 됨.
    
    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;
    }
    
    // int arr[] 대신 int *arr을 써도 됨.
    void BubbleSort(int arr[]){
    	int temp;
    	for(int j=0; j<9; j++) {
    		for(int i=0; i<9; i++) {
    			temp = arr[i];
    			if(arr[i] > arr[i+1]) {
    				arr[i] = arr[i+1];
    				arr[i+1] = temp;
    			}
    		}
    	}
    }

    1차원 배열은 int *arr과 같이 int형 포인터로 받을 수 있다.

    int arr[]도 마찬가지로 배열의 메모리주소를 담고 있는 포인터이다.

    포인터로는 배열의 크기를 알 수 없으므로, 다른 매개변수를 통해 배열의 크기를 받아야 한다.

    int arr[]의 경우, []안에 크기를 넣어도 무시된다.

    (위 코드는 배열의 크기가 정해진 간단한 코드이므로 매개변수로 배열의 크기를 받아 반복문 종결조건에서 사용하는 대신 종결조건에 직접 숫자를 입력해줬다.) 

     

    arr을 포인터로 받으면, 함수 안에서 배열의 요소를 변경했을 때 함수 밖에서도 배열의 요소가 변경된다.

     

    배열

    배열의 이름은 포인터 변수와 같은 기능을 한다.

    배열의 이름은 배열 첫번째 요소의 주소값을 나타내며, 배열의 주소는 연속되어 있다.

     

    예시 1

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

    ⇒ scanf로 입력받을 때 다른 자료형과 달리 & 연산자를 사용하지 않아도 된다. (문자열 배열도 마찬가지)

     

    예시 2

    int arr[5] = { 10, 20, 30, 40, 50 };
    
    int *p = arr;

    배열 이름 자체가 주소값이므로, & 연산자를 쓰지 않아도 바로 포인터에 대입이 가능하다.

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

    포인터 변수 p에는 배열 arr의 첫번째 원소의 주소값이 담긴다.

    따라서 p에 담긴 주소값이 가리키는 값은 첫번째 원소값인 10이다.

     

    포인터 연산

    포인터 변수도 일반 변수처럼 값을 담고 있기 때문에 증감 연산과 덧셈/뺄셈 연산을 할 수 있다.

    곱셈/나눗셈 연산은 할 수 없다.

    포인터 변수의 증감 연산은 일반 변수의 증감 연산과 다르다.

    일반 변수의 증감 연산은 값이 1씩 증가하거나 감소하지만,

    포인터 변수의 증감 연산은 자료형의 크기만큼 증가/감소한다.

     

    ⇒ 포인터 변수는 n만큼 더하거나 빼면 자료형의 크기 X n 만큼 증가하거나 감소한다.

     

    따라서 포인터를 다음과 같이 배열처럼 사용할 수도 있다.

     

    포인터의 이름 = 배열의 첫번째 원소의 주소이고, 

    포인터 + i = 배열의 i번째 원소의 주소이므로

    *(arr+i) == arr[i]

    가 성립된다.

     

     

    상수 포인터

    일반 변수 중 값을 바꿀 수 없는 상수가 있는 것처럼,

    포인터 변수 중에서도 주소값을 바꿀 수 없는 상수포인터가 있다.

    상수 포인터를 선언할 때도 상수처럼 const를 붙이는데, const의 위치에 따라 의미가 달라진다.

     

    const int *p

    포인터가 가리키는 변수를 상수화

    포인터를 이용해 변수의 값을 변경할 수 없다.

    그러나 변수 자체가 상수가 된 것은 아니므로, 변수의 값을 변경하는 것은 가능하다. (=변수의 값 자체는 변경할 수 있지만, 포인터를 이용해서 변경할 수는 없다.)

     

    int *const p

    포인터 상수화

    포인터 변수 자체가 상수화 된다. 주소값을 변경할 수 없다.

    포인터를 이용해서 변수의 값을 변경할 수는 있지만, 포인터가 가리키는 주소값은 변경할 수 없다. (=변수의 값을 변경할 수는 있지만 다른 변수를 가리킬 수는 없다.)

     

    ※ 원래 포인터를 선언할 때 * 연산자는 어디에 위치해도 상관없지만,

    포인터를 상수화시킬 때는 const 전에 * 연산자를 써주어야 한다.

    (int const *p 처럼 사용하면, const를 맨 앞에 써준 것과 같이 포인터가 가리키는 변수가 상수화된다.)

     

    const int* const p

    포인터를 통해 값을 변경할 수 없고, 포인터가 가리키는 주소값도 변경할 수 없다.(=변수의 값을 변경할 수도 없고, 다른 변수를 가리킬 수도 없다.)

     

     

    이중 포인터와 포인터 배열

     

    이중 포인터

    : 포인터의 주소값을 담는 변수. 포인터의 포인터

    포인터의 주소값을 담는 주소를 바꾸거나, 함수에서 문자열을 바꿀 때 사용한다.

     

    예시

    int num = 10;
    int *p;
    int **pp;
    
    p = &num;
    pp = &p;
    
    
    /*
    
    num == *p == **pp
    
    &num == p == *pp
    
    &p == pp
    
    */

     

    포인터 배열

    : 포인터를 담는 배열

    int *parr[3]; //참조 연산자를 붙이고, 일반 배열처럼 선언한다.
    
    parr[0] = &num1; //대입할 때는 변수의 주소값을 넣는다.

     

    댓글

Designed by Tistory.