juuuding
CH 14 포인터와 함수에 대한 이해 본문
1. 함수의 인자로 배열 전달하기
*함수 호출 시 배열을 통째로 전달하는 방법은 없다. 배열을 통째로 넘겨받으려면 매개변수로 배열을 선언할 수 있어야 하는데 이는 불가능하다. 대신에 함수 내에서 배열에 접근할 수 있도록 배열의 주소 값을 전달하는 것은 가능하다.
1. 배열을 함수의 인자로 전달하는 방식
함수의 매개변수는 포인터 변수로 선언되어야 한다.
void ShowArrayElem(int* param, int len) {
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)); //arr1을 전달하는 것은 int* param= &arr[0] 와 같다.
ShowArrayElem(arr2, sizeof(arr2) / sizeof(int));
return 0;
}
이러한 원리로 배열의 주소 값만 안다면 어디서든 배열에 접근하여 저장된 값을 참조할 수 있고 변경도 가능하다.
2. 배열을 함수의 인자로 전달받는 함수의 또 다른 선언
void ShowArayElem (int param[], int len) { }
void AddArayElem (int param[], int len, int add) { }
즉, int param[]과 int *param은 완전히 동일한 선언이다. 전자가 배열이 인자로 전달된다는 느낌이 더 강하므로 주로 전자를 이용한다. 하지만 이 둘이 같은 선언으로 간주되는 경우는 매개 변수의 선언으로 제한된다.
따라서 "int *ptr=arr;" 을 "int ptr[] =arr"로 대체할 수 없다.
* 함수 내에서는 인자로 전달된 배열의 길이를 계산할 수 없다. 따라서 int *arr이나 int arr[]이 매개변수인 함수에서 sizeof(arr)을 한다면 배열의 크기가 반환되는 것이 아니라 포인터 변수의 크기가 반환된다. 이렇듯 배열의 길이를 계산할 수 없기 때문에 배열의 크기나 길이정보도 함께 인자로 전달해야한다.
2. Call-by-value vs Call-by-reference
* call-by-value와 call-by-reference를 구분하는 기준은 함수의 인자로 전달되는 대상이다. 함수 호출 시 단순히 값을 전달한다면 전자, 메모리 접근에 사용되는 주소 값을 전달한다면 후자이다.
1. 값을 전달하는 형태의 함수 호출: Call-by-value
위의 두가지를 구분하는 이유는 아래와 같은 실수를 막기 위함이다.
void Swap(int n1, int n2) {
int temp = n1;
n1 = n2;
n2 = temp;
printf("n1 n2: %d %d", n1, n2);
}
int main(void) {
int num1 = 10;
int num2 = 20;
printf("num1 num2: %d %d", num1, num2);
Swap(num1, num2);
printf("num1 num2: %d %d", num1, num2);
return 0;
}
위의 코드에서 n1과 n2는 값이 바뀌어 나오지만 num1과 num2는 값이 바뀌지 않는다.
2. 주소 값을 전달하는 형태의 함수 호출: call-by-reference
앞의 문제를 해결하기 위해서는 call-by-reference 방식을 사용하여 각 변수의 주소 값을 전달하여야 한다.
void Swap(int *ptr1, int *ptr2) {
int temp = *ptr1;
*ptr1 = *ptr2;
*ptr2 = temp;
}
int main(void) {
int num1 = 10;
int num2 = 20;
printf("num1 num2: %d %d", num1, num2);
Swap(&num1, &num2);
printf("num1 num2: %d %d", num1, num2);
return 0;
}
이와 같이 주소 값을 전달하면 num1과 num2가 바뀐 것을 확인할 수 있다.
3. 이제는 scanf 함수호츨 시 & 연산자를 붙이는 이유를 알 수 있다.
변수 num에 입력 받은 값을 채우기 위해서는 num의 주소 값을 알아야한다. 그래야 num에 접근을 해서 값을 채워 넣을 수 있다. 이것이 scanf 함수에서 &을 붙이는 이유이다. 그렇다면 왜 문자열을 입력 받을 때는 &연산자를 붙이지 않을까?
int main(void){
char str[30];
scanf("%s", str);
}
str은 그 자체로 배열의 주소 값이다. 그냥 str을 전달하면 배열의 주소 값이 전달되는 것이다. 따라서 & 연산자를 붙일 이유가 없다.
3. 포인터 대상의 const 선언
1. 포인터 변수가 참조하는 대상의 변경을 허용하지 않는 선언
const가 포인터 변수 맨 앞부분에 선언되면 "포인터 변수 ptr을 이용해서 ptr이 가리키는 변수에 저장된 값을 변경하는 것을 허용하지 않는다"는 뜻이다. 즉 아래와 같은 코드는 불가능하다.
int num =20;
const int *ptr = #
*ptr= 30; //컴파일 에러
2. 포인터 변수의 상수화
포인터 변수가 상수라는 뜻은 한번 주소 값이 저장되면 그 값의 변경이 불가능하다는 뜻이며, 이는 한 번 가리키기 시작한 변수를 끝까지 가리켜야 한다는 뜻이다. 하지만 int * const ptr=#으로 선언하고 *ptr=40;과 같이 ptr이 가리키는 대상에 저장된 값을 변경하는 연산은 문제가 되지 않는다. 여기서 const int * const ptr = #과 같이 두가지 형태의 const 선언을 한다면 *ptr=20;은 맨 앞의 const 선언으로 인해 불가능 하고, ptr=&age;는 뒤의 const 선언으로 인해 불가능해진다.
'C언어 > 윤성우의 열혈 C' 카테고리의 다른 글
CH 17 포인터의 포인터 (0) | 2023.03.17 |
---|---|
CH 16 다차원 배열 (0) | 2023.03.16 |
CH 15 도전! 프로그래밍 2 (0) | 2023.03.16 |
CH 13 포인터와 배열 함께 이해하기 (0) | 2023.03.15 |
CH 12 포인터의 이해 (0) | 2023.03.15 |