함수에 대해 복습해보면, 함수를 선언할 때 '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=# // 포인터 변수의 상수화
// *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 |