6-3-다.출력용 인수

다소 어려운 참조 호출에 대해 알았으므로 지금까지 설명을 보류해 두었던 scanf 함수에 대해서도 설명이 가능해졌다. 이 함수는 사용자로부터 값을 입력받아 그 값을 변수에 대입하기 때문에(리턴하는 것이 아니라) 참조 호출을 해야 한다. 변수의 번지를 전달받아야 scanf 함수 내부에서 입력된 값을 변수에 대입해 줄 수 있다. 그래서 정수값을 입력받아 i에 대입하고 싶다면 다음과 같이 호출한다.

 

scanf("%d",&i);

 

&i, 즉 i 변수가 저장된 번지를 인수로 받았으므로 이 함수 내부에서 *i=x; 식으로 i 변수에 값을 대입할 수 있다. 만약 scanf("%d",i)로 값 호출 방식을 사용한다면 scanf는 사용자로부터 값을 입력받을 수는 있지만 이 값을 i에 대입할 수는 없다. 앞에서 얘기했다시피 값 호출은 어떤 방법을 쓰더라도 실인수의 값을 바꿀 수 없기 때문이다. 문자열을 입력받을 때는 & 연산자를 사용하지 않는데 문자 배열은 그 자체가 포인터이기 때문이다.

 

char name[32];

scanf("%s",name);

 

name이라는 배열 이름은 배열의 선두 번지를 나타내는 포인터 상수이다. 그래서 &연산자를 사용할 필요가 없으며 사용해서도 안된다. scanf("%s",&name); 이렇게 쓰더라도 에러는 발생하지 않으며 동작도 정상적이지만 절대로 정상적인 문법이 아니다. &name과 name은 타입이 완전히 틀린데 상세한 이유에 대해서는 다음에 포인터를 공부한 후에 생각해 보도록 하자. 인수는 언제 초기화되는지, 상수를 쓸 수 있는지에 따라 다음 세가지 형태로 분류할 수 있다.

 입력용 인수 : 가장 일반적인 의미의 인수이며 호출원에서 함수에게 작업 대상을 지정하기 위해 전달된다. plusone 함수의 a인수, printf 함수의 모든 인수들, gotoxy 함수의 인수들이 모두 입력용 인수이다. 함수의 입장에서 볼 때 이 인수들은 호출원으로부터 입력되는 값이므로 입력용 인수라고 한다.

 

gotoxy(10,20);

plusone(i);

printf("The Result is %d",k);

 

이 호출문들에서 사용된 모든 인수가 바로 입력용 인수들이다. 함수로 입력되는 실체는 값이므로 상수를 바로 쓸 수도 있다. 하지만 초기화되지 않은 변수를 쓰는 것은 일반적으로 경고 처리되는데 다음 호출문을 보자.

 

int i,j;

j=plusone(i);

 

i를 선언만 하고 바로 plusone 함수에게 이 값을 전달했다. i를 선언만하고 초기화하지 않았으므로 이때 plusone이 받게 되는 값은 쓰레기값이며 컴파일러에 의해 경고로 처리된다. 알지도 못하는 값에 대해 어떤 동작을 하려고 했으므로 정상적인 코드가 아니라는 뜻이며 이대로 내버려 두면 골치아픈 버그의 원인이 되기도 한다. 입력용 인수는 함수로 전달되기 전에 반드시 원하는 값으로 초기화되어야 한다.

 출력용 인수 : 참조 호출로 전달되는 인수들의 대부분이 출력용 인수이다. 함수에게 작업거리를 주기 위해 전달되는 것이 아니라 함수가 작업한 결과를 돌려 받기 위해서 사용된다. 함수의 입장에서 볼 때 호출원에서 입력되는 값이 아니라 호출원으로 출력되는 값이므로 출력용 인수라고 한다. 대표적인 예는 scanf 함수의 인수이다.

 

int i;

scanf("%d",&i);

 

정수값 하나를 입력받기 위해 i변수를 선언하고 이 변수의 번지를 scanf 함수에게 전달했다. 참조 호출이기 때문에 상수는 사용할 수 없고 반드시 변수만 사용할 수 있다. 또한 입력되는 값이 아니므로 초기화할 필요가 없으며 위 코드는 경고없이 정상적으로 컴파일된다. scanf는 i의 값을 입력받아 어떤 동작을 하는 것이 아니라 먼저 어떤 동작을 한 후 그 결과를 i에 대입하기 위해 이 변수의 번지를 전달받았다. 그래서 scanf의 인수는 출력용이다.

함수가 호출원으로 작업 결과를 돌려주는 일반적인 방법은 리턴값을 사용하는 것이지만 이런 식으로 출력용 인수를 사용하여 리턴값을 돌려줄 수도 있다. 리턴값은 단 하나밖에 돌려줄 수가 없지만 출력용 인수를 사용하면 여러 개의 값을 동시에 돌려줄 수 있다는 이점이 있다. 예를 들어 게임의 캐릭터 좌표를 조사하는 함수를 만든다면 다음과 같이 만들어야 한다.

 

void GetCharacterPos(int *x, int *y);

 

평면상의 좌표는 (x,y) 두 개의 정수값으로 표현되기 때문에 리턴값으로는 이 좌표를 돌려줄 수 없으며 참조 호출로 두 개의 정수값을 돌려 주도록 해야 한다. 함수의 리턴값이 하나밖에 없는 이유는 유일한 값을 리턴해야만 함수를 수식내에서 바로 사용할 수 있기 때문이다. 이 예에서처럼 여러 개의 값을 동시에 조사해야 하는 경우라면 출력용 인수를 사용하거나 아니면 구조체를 리턴하도록 해야 한다. 이 함수를 호출하려면 반드시 두 개의 변수를 선언해야 하며 수식내에서 바로 사용할 수도 없다.

 입출력용 인수 : 이 인수는 입력과 출력을 겸하는 인수이다. 즉, 호출원에서 함수에게 작업 대상을 전달하기 위해서도 사용하며 함수가 호출원에게 작업 결과를 전달하기 위해서도 사용한다. 출력을 해야 하므로 이 인수는 참조 호출 방식으로 전달되어야 하며 형태상으로는 출력용 인수와 동일하다.

출력용 인수와 다른 점은 입력을 겸하고 있기 때문에 반드시 초기화를 해야 한다는 점이다. 그러나 초기화하지 않고 전달할 경우 컴파일러가 이를 에러나 경고로 처리하지는 않는데 왜냐하면 이 인수가 출력용인지 입출력용인지는 형태상으로 구분되지 않기 때문이다. 하지만 초기화되지 않은 쓰레기값을 사용하면 당연히 원하는 결과는 나오지 않는다.

참조 호출 방식이므로 상수는 물론 사용할 수 없으며 변수만 사용할 수 있다. 입출력용 인수의 예는 그리 흔하지는 않지만 바로 앞에서 만들어 보았던 plusref 함수의 a 인수가 바로 입출력을 겸하는 인수이다. 이 함수는 a의 값을 입력으로 받아 들이기도 하며 이 값을 1 증가시켜 다시 a로 돌려 주기도 한다. 입력, 출력을 모두 겸하고 있으니 입출력용 인수라고 하는 것이다.