Chapter14. 포인터와 함수에 대한 이해
14-1 함수의 인자로 배열 전달하기
함수는 인자를 전달받도록 정의할 수 있다. 사실 함수라는 이름이 붙은 이유도 인자의 전달과 값의 반환이 가능하기 떄문이다.
인자전달의 기본방식은 값의 복사이다!
함수호출 시 전달되는 인자의 값은 매개변수에 복사가 된다.
복사가 되는 것 뿐이기 때문에 함수가 호출되고 나면, 전달되는 인자와 매개변수는 별개가 된다.
SimpleFunc(age); 이 문장은 함수의 호출을 통해서 인자로 age를 전달하고 있다. 그러나 실제로 전달되는 것은 age가 아닌, age에 저장된 값이다! 그리고 그 값이 매개변수에 복사되는 것이다. 따라서 age랑 매개변수는 값을 주고받은 사이일 뿐 그 이상은 아무런 관계도 아니다.
SimpleFucn 함수 내에서 매개변수에 저장된 값을 1 증가시킬 경우, 변수 age의 값의 변화는 전혀 없다. 왜냐하면 매개변수와 age는 별개의 변수이기 때문이다.
매개변수로 배열을 선언할 수 없어서 배열을 통째로 전달하는 방법은 없다. 대신에 함수 내에서 배열에 접근할 수 있도록 배열의 주소값을 전달하는 것은 가능하다.
C언어는 매개변수로 배열의 선언을 허용하지 않는다.
배열을 함수의 인자로 전달하는 방식
아파트를 보고 싶어 하는 사람 앞에 아파트를 통째로 복사해다 놓을 수 없다며느 아파트의 주소를 가르쳐줘서 직접 찾아가게 하면 된다. 이와 유사하게 배열을 통째로 전달하는 것이 불가능하다면, 배열의 주소값을 인자로 전달해서 일ㄹ 통해서 접근하도록 유도하는 방법을 생각해볼 수 있다.
int arr[3] = {1, 2, 3};
SimpleFunc(arr);
void SimpleFunc(int* param)
이런 식으로 배열의 주소값을 인자로 전달받으려면 함수 선언시 매개변수를 자료형이 같은 포인터 변수로 선언해야 한다.
이 매개변수 param을 이용해서 배열에 접근하려면:
printf("%d %d \n", param[0], param[1]);
이런식으로 접근이 가능하다 포인터 변수를 이용해도 배열의 형태로 접근이 가능하다고 앞서 학습하였다.
그 함수 내에서 선언된 변수/배열 이 아니고 외부에서 선언된 변수/배열일지라도 주소 값을 알면 접근/변경 할수 있다. 변수/배열의 주소값만 안다면 어디서든 변수/배열에 접근하여 저장된 값을 참조하고 변경할 수 있다.
변수의 주소를 함수에 전달할 때는 &(앤퍼센트)를 붙여햐 한다.
scanf 함수 호출시 & 연산자를 붙이는 이유는?
scnaf는 우리가 마련해놓은 메모리공간에 값을 저장해주는 함수임. scnaf 함수 외부에 선언된 배열/변수에 값을 저장해주는 함수임. 그러긴 위해선 주소값을 이용할수 밖에 없음.
&는 변수에다가 붙히는 것임. 상수엔 안붙힌다.
C언어에서는 지역변수는 선언된 그 지역에서만 접근이 가능하지만 주소 값만 안다면 접근이 가능하다.
배열을 함수의 인자로 전달받는 함수의 또 다른 선언
앞에선 우리가 함수가 배열의 주소 값을 인자로 전달받을려면:
int func(int* param, int len)
이런식으로 가능하다고 했다. 허나 한가지 더 방법이 있다.
int fucn(int param[], int len)
이 둘은 동일한 선언이다. 그런데 후자의 선언이, 배열이 인자로 전달된다는 느낌을 더 강하게 주는 선언이다. 따라서 일반적으로 배열의 주소 값이 인자로 전다될 때에는 int param[]형태의 선언을 주로 많이 사용한다. 하지만 이 둘이 같은 선언으로 간주되는 경우는 매개변수의 선언으로 제한된다. :
int main(void)
{
int arr[3] = {1, 2, 3}
int* ptr = arr; / / int ptr[]=arr; 로 대체 불가능
. . . . . .
}
함수 내에서는 인자로 전달된 배열의 길이를 계산할수 없다. 배열의 주소 값을 인자로 전달받는 매개변수는 포인터 변수 이기 때문에(배열을 가지고 있느게 아닌 주소 값을 가진 포인터 변수) 이를 대상으로 함수 내에서 sizeof() 연산을 할 경우 배열의 크기가 반환되는 것이 아닌 포인터 변수의 크기가 반환이 된다. 그래서 배열의 길이를 계산해서 같이 인자로 전달해서 사용한다.
함수의 매개변수 선언 시 이렇게 선언도 가능하다(매개변수 선언시만 int param[] 이 가능하다. 다를땐 사용이 불가능하다.):
int* param == int param[]
동일한 선언이나 int* param 은 변수의 주소를 저장할때 주로 사용하고 int param[]은 배열의 주소를 저장할때 주로 사용한다.
----------------------------------------------------------------------------------------------------------------------------------
14-2; Call-by value vs. Call-by-reference
이 두가지는 함수의 호출방식을 의미한다.
Call-by-reference ==> 함수를 호출할때 외부에 선언된 변수의 주소 값을 전달하는 형태의 함수를 가르켜 말함.
Call-by-value ==> 함수를 호출하면서 값을 전달하는 것.
값을 전달하는지 주소 값을 전달하는지 나누는것.
----------------------------------------------------------------------------------------------------------------------------------
14-3; 포인터 대상의 const 선언
const 선언은 변수를 상수화 시킬때 사용하는 선언이다.:
const int NUM = 20 // num은 상수가 되어버려서 값의 변경이 불가능해짐
그런데 포인터 변수를 대상으로도 선언이 가능하다.
cosnst int* ptr = # int* const ptr = #
이렇게 두개의 선언이 있고 두개를 합쳐서 const int* const ptr = # 이렇게 선언도 가능하다.
우선 const가 int 뒤에 오게 되면 -> const int* ptr = #
ptr이 가르키는 num이 상수화가 된다. 여기서 상수화가 된다는것은 num이 진짜로 상수화가 되는게 아니라 포인터 변수 ptr의 관점으로 num을 볼때 상수화가 되었다는 것이다. 쉽게말해서 ptr로 접근을해서 num의 값을 변경하려고 하면 변경이 되지않는다. ptr입장에선 num은 상수인것이다. 그러나 ptr을 통한 접근이 아닌 num을 직접 통해서 값을 변경하면 변경이 가능하다. 그러나 ptr에 저장된 주소 값은 변경이 가능하다. 변경을 하더라도 ptr을 통한 값 변경은 불가능하다. 그리고 ptr로 num의 참조는 가능하다.
두번째로 const가 포인터 변수 뒤(ptr)에 올경우 -> int* const ptr = #
이 경우는 첫번째와 반대로 ptr이 상수가 된다. 즉, ptr에 저장된 주소 값을 변경할수 없게 된다는 것이다.
그러나 *ptr의 값은 변경이 가능하다.
이 두가지를 합친경우 -> const int* const ptr = #
이 선언은 ptr의 저장된 주소 값도 변경이 불가능하고 ptr을 통한 num의 값 변경도 불가능하게 된다.
즉, const int* 의 경우 *ptr 의 값의 변경이 불가능하고
int8 const ptr 의 경우는 ptr에 저장된 주소 값의 변경이 불가능하다.
const 선언은 기능적인 측면은 없지만 코드의 안정성을 높이기 위해 사용된다. const 사용을 습관화 하는것이 좋다.
바뀌면 안되는 값을 실수로 코딩할때 변경을 할 경우가 생기는데 이럴땐 컴파일러가 걸러주지 못하나 const선언을 사용하면 컴파일 오류가 뜨면서 확인이 가능하다.
좋은 프로그래밍 습관은 이렇듯 사소한 것들이 모여서 완성되는 것이다.