C언어 배열과 포인터 포인터와 함수

    2018-02-06 04:27:48 작성

    인자의 전달은 복사

    함수를 만들면서 인자값을 전달하면 인자값을 복사해서 전달하였습니다.
    다음과 같은 함수가 있다면

    #include <stdio.h>
    
    void add(int);
    
    int main(void) {
    	int num = 10;
    	add(num);
    	printf("main 함수 num : %d\n", num);
    	return 0;
    }
    
    void add(int a) {
    	a = a + 10;
    	printf("add 함수 a : %d\n", a);
    }


    main함수에서 add 함수를 호출하고 num을 인수로 전달하면 num에 저장된 값 10이 매개변수 a에 복사되어 전달됩니다.
    그렇기 때문에 add함수에서 a의 값을 변경해도 main함수의 num에는 영향을 주지 않습니다.

    그렇다면 배열을 인자값으로 전달한다면 어떻게 될까요?

    #include <stdio.h>
    
    void arrayShow(int*, int len); // 배열을 출력
    void arrayAdd(int[], int len, int add); // 배열의 각요소에 add 값을 더합니다
    
    int main(void) {
        int arr[] = { 10,20,30 };
        int len = sizeof(arr) / sizeof(arr[0]);
    
        printf("arrayAdd 함수 호출 전\n");
        arrayShow(arr, len);
        arrayAdd(arr, len, 5);
        printf("arrayAdd 함수 호출 후\n");
        arrayShow(arr, len);
        return 0;
    }
    
    void arrayShow(int * ptr, int len) {
        int i;
        for (i = 0; i < len; i++) {
            printf("%d ", *(ptr + i));
        }
        printf("\n");
    }
    
    void arrayAdd(int ptr[], int len, int add) {
        int i;
        for (i = 0; i < len; i++) {
            ptr[i] += add;
        }
    }


    배열을 인수로 전달하면 배열의 각 요소의 값 변경도 가능 한것을 알 수 있습니다.
    이것은 배열이 주소를 참조하는 참조자료형이기 때문에 가능한 것입니다.

    배열을 인수로 전달할때 포인터 변수로 받게 되므로 배열의 길이정보를 알수 없기 때문에 길이 같이 전달하였습니다.
    arrayShow 함수의 int * ptr과 arrayAdd 함수의 int ptr[]동일한 선언입니다.
    시각적인 차이로 int * ptr은 포인터의 성격이 강하고, int ptr[]은 배열의 성격이 강합니다.
    그렇다고 해서 다음과 같이 사용할 수는 없습니다.

    int arr[3] = {1,2,3};
    int ptr[] = arr; // 이렇게는 안됩니다.
    int *ptr = arr; // 이렇게 쓰셔야 합니다.


    Call-by-value VS Call-by-reference

    Call-by-value와 Call-by-reference는 함수 호출방식을 의미합니다.

    값을 전달하는 형태의 호출 : Call-by-value

    지금까지 정의한 함수는 대부분 call-by-value에 속합니다.
    main함수에서 두개 정수를 선언하고 함수를 호출하여 두개의 값을 바꾸어 저장하는 swap 함수를 선언하면 다음과 같습니다.

    #include <stdio.h>
    
    void swap(int, int);
    
    int main(void) {
    	int num1 = 10, num2 = 20;
    	printf("** swap함수 호출 전 **\n");
    	printf("%d, %d\n", num1, num2);
    	swap(num1, num2);
    	printf("** swap함수 호출 후 **\n");
    	printf("%d, %d\n", num1, num2);
    	return 0;
    }
    
    void swap(int num1, int num2) {
    	int temp = num1;
    	num1 = num2;
    	num2 = temp;
    	printf("** swap함수 내부 **\n");
    	printf("%d, %d\n", num1, num2);
    	return;
    }


    swap 함수 내부에서 num1과 num2의 값을 서로 바꾸지만 main함수에서는 바뀌지 않는다는 것을 알 수 있습니다.
    이것은 값을 복사하여 전달하기 때문입니다.
    주소를 출력해보면 명확합니다.
    직접해보세요.


    주소값을 전달하는 형태의 호출 : Call-by-reference

    위의 예제에서 함수의 매개변수로 주소값으로 전달하려면 포인터 변수로 인자를 받아야 합니다.
    물론 전달할때에 주소를 전달해야 합니다.

    #include <stdio.h>
    
    void swap(int*, int*);
    
    int main(void) {
    	int num1 = 10, num2 = 20;
    	printf("** swap함수 호출 전 **\n");
    	printf("%d, %d\n", num1, num2);
    	swap(&num1, &num2); // 주소를 전달
    	printf("** swap함수 호출 후 **\n");
    	printf("%d, %d\n", num1, num2);
    	return 0;
    }
    
    void swap(int * num1, int * num2) {
    	int temp = *num1;
    	*num1 = *num2;
    	*num2 = temp;
    	printf("** swap함수 내부 **\n");
    	printf("%d, %d\n", *num1, *num2);
    	return;
    }


    결과는 명확합니다. main함수에서 swap 함수를 호출 후 num1값과 num2값이 교환되었습니다.
    swap(int*, int*) 두 매개변수 모두 포인터 변수로 전달 받고, swap(&num1, &num2); 주소를 전달하였습니다.
    swap 함수 내부에서 포인터로 접근하여 값을 변경하기에 main함수의 num1과 num2에 참조할 수 있습니다.
    배열은 그 자체로 주소이기 때문에 배열을 전달했을때 배열의 각 요소의 값을 변경할 수 있었습니다.


    포인터 변수의 상수화

    포인터도 변수 이기때문에 가리키는 대상을 변경할 수 있습니다.
    포인터가 가리키는 값 또는 포인터변수의 주소값을 변경해서는 안될때에는 const 키워드를 사용하여 상수화 할수 있습니다.

    포인터가 가리키는 값을 변경해서는 안되는 경우

    자료형 앞에 const 선언합니다.

    int num = 20;
    int num2 = 30;
    const int * ptr = &num;
    *ptr = 30; // 이것은 에러입니다.
    ptr = &num2; // OK
    포인터의 주소값을 변경해서는 안되는 경우

    포인터 변수명 앞에 const 선언합니다.

    int num = 20;
    int num2 = 30;
    int * const ptr = &num;
    *ptr = 40; // OK
    ptr = &num2; // 이것은 에러입니다. 
    포인터의 주소값과 가리키는값 모두 변경해서는 안되는 경우

    두곳 다 const 선언합니다.

    int num = 20;
    int num2 = 30;
    const int * const ptr = &num;
    *ptr = 40; // 이것은 에러입니다.
    ptr = &num2; // 이것은 에러입니다. 


    연습문제

    1. 3개의 값을 서로 변경하는 swap 함수를 정의하세요.
    2. swap(&num1, &num2, &num3) 형태의 호출을 하면 num1은 num2의 값, num2는 num3의값, num3은 num1값이 저장되어야 합니다.


    1. 사용자로부터 5개의 정수를 입력받아 저장하는 배열을 선언하세요.
    2. 배열의 평균을 구하는 average 함수를 정의하세요.
    3. average(arr, len, &avr) 형태의 호출을 하면 avr 변수에 배열의 평균이 저장되어야 합니다.
    4. 평균은 소수점 2자리까지만 출력하세요.