juuuding

CH 14 포인터와 함수에 대한 이해 본문

C언어/윤성우의 열혈 C

CH 14 포인터와 함수에 대한 이해

jiuuu 2023. 3. 15. 21:59

 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 = &num;
*ptr= 30;	//컴파일 에러

 

2. 포인터 변수의 상수화

 포인터 변수가 상수라는 뜻은 한번 주소 값이 저장되면 그 값의 변경이 불가능하다는 뜻이며, 이는 한 번 가리키기 시작한 변수를 끝까지 가리켜야 한다는 뜻이다. 하지만 int * const ptr=&num;으로 선언하고 *ptr=40;과 같이 ptr이 가리키는 대상에 저장된 값을 변경하는 연산은 문제가 되지 않는다. 여기서 const int * const ptr = &num;과 같이 두가지 형태의 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