본문 바로가기

공부/C언어

C언어 - (12) : 포인터와 함수

함수에 대해 복습해보면, 함수를 선언할 때 'return type', 'function name', 'input(type)' 등의 요소가 필요했다.

int Func(int num) // int : return type / Func : name / int num : input type, input
{
    실행문
    return res;
}

함수는 input으로 입력된 인자를 활용해 함수를 실행시키게 되는데, 인자를 전달받을 때

변수 그 자체를 받는 것이 아니라 변수에 저장된 값을 복사해 전달받게 된다.

 

만약 어떤 함수에서 배열을 인자로 사용해야되는 경우가 있다면, 배열을 인자로 주고 함수를 실행시키면 될 것 같지만 

C언어는 매개변수로 배열의 선언을 허용하지 않기 때문에 그렇게 직접적으로 전달할 수 없다.

 

따라서, 포인터를 활용해 (편법으로) 배열의 주소값을 함수의 인자로 전달해, 해당 주소값을 참조해 배열에 접근할 수 있도록 해야한다.

// 포인터를 인자로 사용한 함수 (1)

#include <stdio.h>
void ShowArrayElem(int * param, int len) // len = sizeof(arr)/sizeof(type) = 배열의 길이
{
    int i;
    for (i=0; i<len; i++)
        printf("%d ", param[i]);

    printf("\n");
}

int main(void)
{
    int arr1[3]={1,2,3};
    int arr2[5]={4,5,6,7,8};
    ShowArrayElem(arr1, sizeof(arr1) / sizeof(int)); // 1 2 3
    ShowArrayElem(arr2, sizeof(arr2) / sizeof(int)); // 4 5 6 7 8
    return 0;
}
// 포인터를 인자로 사용한 함수 (2)
#include <stdio.h>

void ShowArrayElem(int * param, int len);
void AddArrayElem(int * param, int len, int add);

int main(void)
{
    int arr[3] = {1,2,3};
    AddArrayElem(arr, sizeof(arr)/sizeof(int), 1);
    ShowArrayElem(arr, sizeof(arr)/sizeof(int));
    AddArrayElem(arr, sizeof(arr)/sizeof(int), 2);
    ShowArrayElem(arr, sizeof(arr)/sizeof(int));
    AddArrayElem(arr, sizeof(arr)/sizeof(int), 3);
    ShowArrayElem(arr, sizeof(arr)/sizeof(int));

    return 0;

}

void ShowArrayElem(int * param, int len)
{
    int i;
    for (i=0; i<len; i++)
        printf("%d ", param[i]);

    printf("\n");
}

void AddArrayElem(int * param, int len, int add)
{
    int i;
    for (i=0; i<len; i++)
        param[i]+=add;
}

추가적으로, 매개변수로 배열을 전달하는 경우에 위의 예시와 같이 `int * param`으로 사용할수도 있지만,

`int param[]`으로 사용하는 것도 가능하며, 후자가 조금 더 직관적이기 때문에 후자를 사용하는 것을 권장한다.

Call-by-value vs Call-by-reference

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

void Call_by_value(int num)
{
    if (num<0)
    	return ;
}

'Call-by-value'는 함수를 호출할 때 단순히 값을 전달하는 형태의 함수 호출을 의미하며,

단순히 인자로 사용된 값만을 함수에서 전달받기 때문에 함수 외부에서 선언된 변수에 대한 접근이 불가능하다.

Call-by-reference

void Call_by_reference(int * param, int len)
{
    int i;
    for (i=0; i<len; i++)
    	printf("%d ", param[i]);
    printf("\n");
}

'Call-by-reference'는 함수를 호출할 때 메모리 접근에 사용되는 주소값을 전달하는 형태의 함수 호출을 의미하며,

주소값을 함수에서 전달받기 때문에 메모리 주소를 사용해 함수 외부에서 선언된 변수에 대한 접근이 가능하다.

예시

위 두 유형의 함수의 차이를 변수간의 값을 교환하는 함수의 선언을 통해서 확인할 수 있다.

#include <stdio.h>

// call-by-value
void Swap_value(int n1, int n2)
{
    int temp=n1;
    n1=n2;
    n2=temp;
    printf("In Swap_value... num1, num2: %d, %d \n", n1, n2);
}

void Swap_reference(int * param1, int * param2)
{
    int temp = *param1;
    *param1 = *param2;
    *param2 = temp;
    printf("In Swap_reference... num1, num2: %d, %d \n", *param1, *param2);
}

int main(void)
{
    int num1=10;
    int num2=20;
    printf("num1 num2: %d %d \n", num1, num2); // 10 20
    
    Swap_value(num1, num2);
    printf("num1 num2: %d %d \n", num1, num2); // 10 20 (swap X)
    Swap_reference(&num1, &num2);
    printf("num1 num2: %d %d \n", num1, num2); // 20 10 (swap O)
    
    return 0;
}

위의 코드에서 'Call-by-value'유형의 함수인 `Swap_value`함수를 사용했을때 함수 안에서는 값의 교체가 이뤄졌지만,

함수 밖에서는 `num1`, `num2`의 값이 그대로인 것을 확인할 수 있고

 

'Call-by-reference'유형의 함수인 `Swap_reference`함수를 사용했을때 함수 안에서도 값의 교체가 이뤄지고,

함수 밖에서도 `num1`, `num2`의 값이 교체된것을 확인할 수 있다.

scanf 호출 시 '&' 연산자를 붙이는 이유

앞서 살펴봤듯이, 함수 내에서 함수 외부에 있는 변수에 접근하기 위해서는 변수의 주소값을 통해 접근해야한다.

int num;
scanf("%d", &num);

그렇기 때문에 `scanf`함수를 통해 외부에 선언된 변수에 값을 대입해주기 위해서는 값을 대입할 변수의 주소값이 필요하기 때문에

`&`연산자를 필요로 하는 것이다.

char arr[3];
scanf("%s", str);

더 나아가 배열에 대해 입력을 받을 때에는 이전에 살펴봤듯이(2024.10.15 - [공부] - C언어 - (11) : 포인터와 배열),

배열의 이름은 포인터 그 자체의 역할을 하기 때문에 `&`연산자를 필요로 하지 않는 것이다.

포인터 변수의 참조대상에 대한 const 선언

상수 선언(`const`)는 값의 변경을 불가능하게 하기 때문에 코드의 안정성을 높이는데 있어서 유용하다.

참고 : 2024.10.03 - [공부] - C언어 - (4) : 자료형/형 변환

 

C언어 - (4) : 자료형/형 변환

C언어의 자료형자료형크기값의 표현범위정수형char1Byte$-2^7 \leq \text{char} \leq 2^7-1$short2Byte$-2^{15} \leq \text{short} \leq 2^{15}-1$int4Byte$-2^{31} \leq \text{int} \leq 2^{31}-1$long4Byte$-2^{31} \leq \text{long} \leq 2^{31}-1$lon

sdsf1225.tistory.com

포인터 변수를 통해 일반 변수의 값을 변경하지 못하도록 하는 것이 다음과 같은 상수화 방식의 주 목적이라고 할 수 있다.

#include <stdio.h>
int main(void)
{
    int num=20;
    const int * ptr=&num; // 포인터 변수의 상수화
    // *ptr=30; // ERROR
    num=40; // OK
    printf("%d \n", num);
    
    return 0;
}

위 예시 코드처럼 포인터 변수를 이용해 일반 변수의 값을 변경하는 것이 불가능한 것이지, 변수의 값 변경 그 자체가 불가능한 것은 아니다.

포인터 변수의 상수화

방금 살펴본 것과 유사하지만, 다른 방식으로 `const`를 붙여 포인터 변수 그 자체를 상수화하는 것도 존재한다.

#include <stdio.h>
int main(void)
{
    int num1=20;
    int num2=30;
    int * const ptr = &num1;
    // ptr = &num2; // ERROR
    *ptr = 40; // OK

    return 0;
}

직전에 살펴본 `const int * ptr = &num`과 같은 선언 방식과 달리, 위 방식은 포인터 변수(`ptr`)에 저장되는

메모리 주소값에 대한 변경을 허용하지 않는 상수 선언이다.

 

따라서 `*ptr = 40;`과 같이 `ptr`가 가리키는 일반 변수의 값을 변경하는 것은 가능하지만,

`ptr = &num2`와 같이 `ptr`에 직접 저장되는 메모리 주소값의 변경은 불가능하다.

 

예제

// 예제 1
#include <stdio.h>
#define MAX_LEN 49
int main(void)
{
    char ch;
    printf("Input One Character: ");
    scanf("%c", &ch);
    char str[MAX_LEN];
    printf("Input English String: ");
    scanf("%s", str);

    int i=0;
    int cnt=0;

    while (str[i])
    {
        if (str[i]==ch)
            cnt++;
        i++;
    }
    printf("%d \n", cnt);

    return 0;
}
// 예제 2
#include <stdio.h>

void sortNumber(int arr[], int len)
{
    int temp=0;
    for (int i=0; i<len; i++)
    {
        for (int j=i; j<len; j++)
        {
            if (arr[i]<arr[j])
            {
                temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
    
}

int main(void)
{
    int num_arr[3];
    for (int i=1; i<4; i++)
    {
        printf("Integer Number %d? ", i);
        scanf("%d", &num_arr[i-1]);
    }

    sortNumber(num_arr, sizeof(num_arr)/sizeof(int));
    printf("num1=%d num2=%d num3=%d \n", num_arr[0], num_arr[1], num_arr[2]);

    return 0;
    
}
// 예제 3(예제 2와 거의 동일)
#include <stdio.h>

void sortNumber(int arr[])
{
    int temp=0;
    for (int i=0; i<3; i++)
    {
        for (int j=i; j<3; j++)
        {
            if (arr[i]<arr[j])
            {
                temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
    
}

int main(void)
{
    int num_arr[3];
    for (int i=1; i<4; i++)
    {
        printf("Integer Number %d? ", i);
        scanf("%d", &num_arr[i-1]);
    }

    sortNumber(num_arr);
    printf("num1=%d num2=%d num3=%d \n", num_arr[0], num_arr[1], num_arr[2]);

    return 0;
    
}

'공부 > C언어' 카테고리의 다른 글

C언어 - (14) : 포인터의 포인터  (0) 2024.10.16
C언어 - (13) : 다차원 배열  (3) 2024.10.15
C언어 - (11) : 포인터와 배열  (0) 2024.10.15
C언어 - (10) : 포인터  (0) 2024.10.15
C언어 - (9) : 1차원 배열  (0) 2024.10.15