ch14 포인터 함수에 대한 이해

암영·2022년 5월 30일
0

c언어

목록 보기
15/21

14-1 한수의 인자로 배열 전달하기

인자전달의 기본방식:복사

  • 함수 호출시 전달되는 인자의 값은 매개변수에 복사된다
  • point 복사
  • 복사가 되는 것이기 때문에 함수가 호출되고 나면, 전달되는 인자와 매개변수는 별개가 된다.

함수 호출시 매개변수에 배열을 통째로 넘겨주는 방법은 존재하지 않음
-> 이유: 매개변수로 배열을 선언할 수 없다->함수내에서 배열에 접근할 수 있도록 배열의 주소값을 전달 가능.

배열을 함수의 인자로 전달하는 방식

배열의 주소 값을 인자로 전달해서 접근하도록 유도.
매개변수로 포인터 변수를 선언

<예제1>

#include <stdio.h>

void showarayelem(int* param, int len)
{
	int i;
	for (i = 0; i < len; i++)
		printf("%d", param[i]);
	printf("\n");
}
int main()
{
	int arr1[3] = { 1,2,3 };
	int arr2[5] = { 4,5,6,7,8 };
	showarayelem(arr1, sizeof(arr1) / sizeof(int));
	showarayelem(arr2, sizeof(arr2) / sizeof(int));
	return 0;
}

예제 1에서는 함수 외부에 선언된 배열에 접근하여 값을 출력함.
출력뿐 아니라 값의 변경도 가능하다!

<예제2>

#include <stdio.h>

void showarayelem(int* param, int len)
{
	int i;
	for (i = 0; i < len; i++)
		printf("%d", param[i]);
	printf("\n");
}
void addarayelem(int* param, int len, int add)
{
	int i;
	for (i = 0; i < len; i++)
		param[i] += add;
}
int main()
{
	int arr1[3] = { 1,2,3 };
	
	addarayelem(arr1, sizeof(arr1) / sizeof(int),1);
	showarayelem(arr1, sizeof(arr1) / sizeof(int));
	addarayelem(arr1, sizeof(arr1) / sizeof(int), 2);
	showarayelem(arr1, sizeof(arr1) / sizeof(int));
	addarayelem(arr1, sizeof(arr1) / sizeof(int), 3);
	showarayelem(arr1, sizeof(arr1) / sizeof(int));
	return 0;
}

배열을 함수의 인자로 전달받는 함수의 또 다른 선언

void addarayelem(int* param, int len, int add)과
void addarayelem(param[], int len, int add) 완전히 동일한 선언이다.

일반적으로 배열의 주소값이 인자로 전달될 때에는 int param[] 형태의 선언을 주로 많이 사용한다. 하지만 이둘이 같은 선언으로 간주되는 경우는 매개변수의 선언으로 제한된다.

ex)

int main()
{
    int arr[3]={1,2,3};
    int *ptr=arr;// int ptr[]=arr;로 대체 불가능
   
}

+함수내에서는 인자로 전달된 배열의 길이를 계산할 수 없다.

따라서 배열의 크기나 길이정보도 함께 인자로 전달 해야한다.

14-2 call by values vs call by reference

call by values:값을 전달하는 형태의 함수 호출

  • call by values의 특징

예제1 을 보고 문제점 파악하기

#include <stdio.h>
void swap(int n1, int n2)
{
	int temp = n1;
	n1 = n2;
	n2 = temp;
	printf("n1,n2: %d %d \n", n1, n2);
	return 0;
}

int main()
{
	int num1 = 10;
	int num2 = 20;
	printf("num1과 num2: %d %d \n", num1, num2);

	swap(num1, num2); //num1과 num2에 저장된 값이 서로 바뀔 것을 기대
	printf("num1 num2: %d %d \n", num1, num2);
	return 0;
}

결과

num1과 num2: 10 20
n1,n2: 20 10
num1 num2: 10 20

swap의 함수를 통해 num1과 num2의 함수이 값이 바뀔 것이라 기대 했지만, 이는 매개 변수 n1,n2에 저장된 값을 변경 시킬뿐 num1,num2에 저장된 값을 변경 시키지 못한다.
즉, 값에 의한 호출은 원본과 매개 변수가 완전히 별개이다.

call by reference : 주소 값을 전달하는 형태의 함수 호출

앞서 보인 예제 1에서 swap 호출 결과로 num1과 num2의 값을 바꾸기 위해서는 num1의 주소 값과 num2의 주소값을 swap함수로 전달해서 swap함수 내에서 num1,num2에 직접 접근 가능하도록 해야한다.

예제2

#include <stdio.h>
void swap(int *ptr1, int* ptr2) //주소값을 인자로 받아 변수에 직접접근하는 형태
{
	int temp = *ptr1;
	*ptr1 = *ptr2;
	*ptr2 = temp;
	
	return 0;
}

int main()
{
	int num1 = 10;
	int num2 = 20;
	printf("num1과 num2: %d %d \n", num1, num2);

	swap(&num1, &num2);//*ptr1,*ptr2는 각각 num1과 num2를 의미한다.
	printf("num1 num2: %d %d \n", num1, num2);
	return 0;
}

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

int main()
{
	int num;
	scanf("%d", &num); //변수 num의 주소 값을 scanf 함수에 전달
}

scanf 함수 : 프로그램 사용자로 부터 값을 입력받아 변수 num에 그 값을 채우는 역할
scanf 함수가 이 역할을 수행하기 위해서는 num의 주소값을 알아야한다.
그래야 변수 num에 접근해서 값을 채워 넣을 수 있기 때문.

scanf 함수도 call by reference 형태의 함수 호출이다.

  • 문자열을 입력 받을 때 & 연산자를 붙이지 않는 이유
    :문자열 그 자체로 배열의 주소 값이기 때문이다.

13-1 참고

https://velog.io/@jsk2342/ch13-%ED%8F%AC%EC%9D%B8%ED%84%B0%EC%99%80-%EB%B0%B0%EC%97%B4

문제 14-1

문제1

변수 num에 저장된 값의 제곱을 계산하는 함수를 정의하고, 이를 호출하는 main함수를 작성해 보자 단, 여기서는 두가지 형태로 함수를 정의해야 한다.

  • call by value 기반의 squarebyvalue 함수
  • call by reference 기반의 squarebyreference 함수
#include <stdio.h>
int squarebyvalue (int n)
{
	return n * n; //돌려줄 값이 있으므로 int를 썼다
}

void squarebyreference(int* ptr)
{
	int num = *ptr;
	*ptr = num * num;
}

int main()
{
	int num = 10;
	
	printf("%d\n", squarebyvalue(num));
	squarebyreference(&num);
	printf("%d\n", num);

	return 0;
}

문제2

세 변수에 저장돈 값을 서로 뒤바꾸는 함수를 정의해보자. 예를 들어서 함수의 이름이 swap3이라 하면, 다음의 형태로 함수가 호출되어야 한다.
swap3(&num1,&num2,&num3);
그리고 함수호출의 결과로 num1에 저장된 값은 num2에 num2에 저장된 값은 num3에 그리고 num3에 저장된 값은 num1에 저장 되어야 한다.

#include <stdio.h>


void swap3 (int* ptr1, int* ptr2, int* ptr3 )
{
	int temp = *ptr1;
	*ptr1 = *ptr3;
	*ptr3 = *ptr2;
	*ptr2 = temp;

}

int main()
{
	int num1 = 10;
	int num2 = 20;
	int num3 = 30;

	printf("%d %d %d\n", num1, num2, num3);
	swap3(&num1, &num2, &num3);
	printf("%d %d %d\n", num1, num2, num3);

	return 0;
}

14-3:포인터 대상의 const 선언

chapter05 참고

point: const 선언은 포인터 변수를 대상으로 선언이 가능하다.

포인터 변수가 참조하는 대상의 변경을 허용하지 않는 const선언

const선언: 값을 변경하는 방법에 제한을 둔다

int main()
{
	int num = 20;
	const int* ptr = &num;
	*ptr = 30; //컴파일 에러
	num = 40;//컴파일 성공!
}

const int* ptr = #
: 포인터 변수 ptr을 이용해서 ptr이 가리키는 변수에 저장된 값을 변경하는 것을 허용하지 않는다!

int main()
{
	int num1 = 20;
	int num2 = 30;
	const int*ptr = &num1;
	ptr = &num2; //주소 값은 바꿀 수 있음!

	printf("%d", *ptr);
}

따라서 *ptr = 30; 은 컴파일 에러가 발생한다.

단 그렇다고 해서 포인터 변수 ptr이 가리키는 변수 num이 상수화 되는 것은 아니다. 따라서 num은 값을 변경하는 것은 허용된다.

포인터 변수의 상수화

const 선언은 포인터 변수의 이름 앞에 올수도 있다.

int * const ptr=#

이렇게 되면 포인터 변수 ptr은 상수가 되어 한번 저장된 주소값을 변경할 수 없다.

int main()
{
	int num1 = 20;
	int num2 = 30;
	int* const ptr = &num1;
	ptr = &num2; //컴파일에러
	*ptr = 40; //컴파일 성공
}

const int const ptr=# 같이 선언 할 수 있는데 이러면
ptr=20;과 ptr=&age; 연산이 동시에 불가능 해진다.

const 선언이 갖는 의미

const선언을 많이 하면 그만큼 프로그램의 안전성이 높아진다.


int main() 
{
	double pi = 3.14;
	double rad;
	pi = 3.07; //실수로 삽입된 문장, 컴파일시 발견 안됨
	scanf("lf", &rad);
	printf("circle area %f \n", rad * rad * pi);
	return 0;
}
int main()
{
	const double pi = 3.14;
	double rad;
	pi = 3.07; //컴파일시 발견되는 오류사항 
	scanf("lf", &rad);
	printf("circle area %f \n", rad * rad * pi);
	return 0;
}

문제 14-2

문제1

아래에 정의된 함수를 보자. 인자로 전달되는 정보를 참조하여 int형 배열 요소 전체를 출력하는 함수이다.

void showalldata(const int* arr, int len)
{
	int i;
	for (i = 0; i < len; i++)
		printf("%d ", arr[i]);
}

위 함수의 매개변수 선언에서 매개변수 arr을 대상으로 const 선언을 한 이유는?

이렇게 해야 변수를 함부로 변경할 수 없음!

문제2
아래의 예제는 한가지 지적할 만한 사항을 지니고 있다. 그것이 무엇인지 이야기해보자. 특히 이와 관련해서 showdata 함수를 유심하게 관찰해보자.

void showdata(const int* ptr)
{
	int* rptr = ptr;
	printf("&d \n", *rptr);
	*rptr = 20;
}
int main()
{
	int num = 10;
	int* ptr = &num;
	showdata(ptr);
	return 0;
}

int* rptr = ptr;

ptr에 저장된 값을 const로 선언되지 않응 포인터 변수로 대입하고있다.
따라서 ptr이 가리키는 변수에 저장된 값을 변경할 수 있는 상황이 되어버렸다. 즉 const 선언을 무의미하게 만드는 문장을 삽입하는 것은 잘못된 일이다

profile
just do! -얼레벌레 굴러가는 공대생

0개의 댓글