Chapter 11 함수

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/Chapter-11-%ED%95%A8%EC%88%98.gif

함수 (function)

제, 앞으로 다룰 내용은 C 언어에서

중요하면서도 쉬운 부분

이니 큰 부담 없이 편히

읽으셨으면 한다..

또한, 앞에서 배운 포인터를

이제 본격적으로 활용하는

단계에 접어들기 때문에

혹여라도 잊은 것이

있는지 없는지 매일 한 번씩

다시 정독하시면 좋다. 또는,

다른 C 언어

강좌로 한 번 더

공부해 보도록하자.

다른 방식으로

공부하다 보면

이해가 더 잘될 수 도 있다

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/1.png

음.. 아래 물음표에서 어떤 값이 출력될까?

아마, 여러분 대부분은

’40’ 이라고 짐작하실 것다.

맞습니다. 40 입니다. 위 마술 상자는 입력

받은 값에 4 를 더해서

출력하는 상자 이다. 만일

우리가 36 이 아니라

10 을 집어넣었다면 14 가 나왔을 것

수학에서 함수는 마술 상자와 비슷하다.

특정한 값을 입력 받아,

이 값을 가지고 상자 내부에서

결과를 내보낸 마술 상자 처럼,

수학에서는 특별한 값 x 를 입력 받아낸뒤

(유식한 말로 연산을 취하여)

에 결과를 출력하는 것을 함수 라고 한다.

(참고적으로, 수학에서 보통 입력값은 x,)

(출력값은 y 라고 하니, 아래에선 아무런)

(이야기 없이 사용하도록 하겠습니다.)
수학에서 마술 상자를 글로 표현하기

조금 그러니 보통 다음의 표현을 사용한다.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/2.png

이는, ’x 라는 값을 f 라는 마술 상자

(함수) 를 통과시켰더니 y 라는

값이 되었다’ 라는 의미와 일맥

상통한다. 위의 마술 상자의 경우

입력값에 4 를 더한 값을 반환하였다.

그렇다면, 위의 마술

상자는 아래와 같은 식으로 나타낼 수 있다.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/3.png

예를 들어 x 에 36 이 들어간다면 f(x) 의 값,

즉 y 의 값은 36 + 4 인 40 이 된다.

따라서, 40 이출력된다는 사실을 볼 수 있다.

그렇다면, 아래의 예를 보고 어떠한 값이

출력되는지 맞추어 보도록하자.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/4.png

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/5.png

(이례적으로 답을 올면 https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/6.png)

그리고 함수를 전혀 들어보지 못한 분들이

있을 까봐 짤막하게 함수에 대해 설명하였다.

C 언어의 함수도 비슷한 개념으로 사용된다.

함수의 시작

우리가 프로그래밍을 하면서

여러가지 작업들을 반복적으로

해야되는 경우가 종종 있다.

예를들어서 변수 a 와 b 중 최대값을

구하는 것을 생각해보도록하자.

우리가 이를 프로그래밍 시에

필요로하게 된다면 다음과

같이 해야 될 것이다.

int max;
if (a >= b) {
  max = a;
} else {
  max = b;
}

위 코드는 쉬운 코드임으로

설명은 하지 않겠다.

그런데, 실제로 프로그래밍을 하다보면

어떠한 두 변수의 최대값을 구하는 경우가

자주 생긴다는 것이다.

현재 까지 배운바로는

이러한 상황에서는 코드 복사

붙여넣기를 통해 소스를

채워나가면 된다고 생각했다.

그렇다면 이러한 방법이

합리적인 것일까?

만일 최대값을 구하는 것이 프로그램에서

100 번 정도 필요하다면 그 때 마다

위 코드를 복사해서
변수 이름만 살짝 바꿔주면 된다.

하지만, 소스가 얼마나

지저분해질까? 소스가 수천줄이

넘어가면 위 코드가 무슨 작업을 하는지

눈에 팍 들어오기 힘듧다.

그렇다면 여러분은 이렇게 생각해 볼 수 있다.

“최대값을 출력하는 함수를 만들어버리자!!”

응? 도대체 위 말이 무슨뜻일까..

아마도 여러분은 갈피를 잡기 힘들 것이다.

하지만 이렇게 생각하면 편하다.

아까 위에서 설명한 마술 상자 처럼

우리가 만들게 될 마술상자는 두 개의 값이

입력된다면 큰 놈을 출력하는 것이야!

오오. 괜찮은 아이디어 아닌가.

우리는 그 긴 코드

(사실 그렇게 긴 것은 아니지만;;)를 매번 쓰는

대신에 두 값을 입력받아서 큰 것을 출력하는

마술 상자 (함수) 를 제작하여,

최대값을 구하는 것이 필요할 때 마다

그 마술 상자에 두 변수를 넣어 버리면

되지 않는가?

그러면 우리는 그 마술 상자가

뱉어내는 값을 받아

먹기만 하면 되는 것이니까.

자, 그럼 마술 상자를 만들어 보도록하자

일단, 최대값을 구하는 함수를 만들어

보기 전에 아주 아주 간단한

함수를 먼저 만들어보도록하자.

/* 보통 C 언어에서, 
좋은 함수의 이름은 그 함수가
무슨 작업을 하는지 명확히 하는 것이다. 
수학에서는 f(x), g(x) 로 막 정하지만, 
C 언어에서는 그 함수가 하는작업을 설명해주는 
이름을 정하는 것이 좋다.
 */
#include <stdio.h>
int print_hello() {
   printf("Hello!! \n");
   return 0;
}
int main() {
  printf("함수를 불러보자 : ");
  print_hello();

  printf("또 부를까? ");
  print_hello();
  return 0;
}

성공적으로 컴파일 하였다면

 오후 11:00:48에 2021. 12. 22.에서 복원된 세션 콘텐츠 

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.  

새로운 크로스 플랫폼 PowerShell 사용 https://aka.ms/pscore6

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
함수를 불러보자 : Hello!! 
또 부를까? Hello!!        
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

음.. 일단 우리가

여태까지 보아왔던 것과

매우 다른 모습이다.

하지만 걱정하지 말자.

금세 이해하게 될 것이다.

int print_hello() {
  // 잠시 생략
}

일단, 함수의 정의(definition)

부분을 살펴 보도록하자.

위와 같이 int print_hello() 라고 써있는 부분을

함수의 ’정의’ 부분이라 부른다.

우리는 함수의 정의 부분에서

3 가지 사실을 알 수 있는데,

일단은 2 가지만 먼저 설명하고

나머지 하나는 아래에서 설명하겠다.

먼저, 우리에게 친근한 키워드가 하나 있다.

바로 int ! 우리는 여태까지 int를

변수나배열을 정의하는데만

사용하였다. 그런데, int 가 놀랍게도

함수를 정의하는데도 사용되고있다.

여기서의 int 는 다음과 같은 사실을 알려준다.

’이 함수는 int 형의 정보를 반환한단다∼

’. 반환? 그렇다면 반환은 또 뭐야.

먼저, 우리에게 친근한 키워드가 하나 있다.

바로 int ! 우리는 여태까지 int 를 변수나

배열을 정의하는데만 사용하였다.

그런데, int 가 놀랍게도 함수를

정의하는데도 사용되고 있다.

여기서의 int 는 다음과 같은 사실을

알려준다. ’이 함수는 int 형의 정보를

반환한단다

∼’. 반환? 그렇다면 반환은 또 뭘까.

int print_hello() {
  printf("Hello!! \n");
  return 0;
}

위 함수 정의 부분에서, 아래에서

두번째 줄에 return 0;

라고 써있는 부분을 볼 수 있다.

이 함수는 0 을 반환한다는 뜻이다.

즉, 우리가 위와 같은 마술 상자를

이용한다면 언제나 0 이 출력된다.

이 때, 함수의 반환형이 int 이므로,

0 은 int 의 형태로 저장되어 나간다.

여기서 int 의 형태로 저장된다는

말의 의미는 0 이라는 데이터가

메모리 상의 4 바이트를 차지하여
반환된다는 뜻이다.

(통상적으로 정수를 반환하는)

(함수들은 모두 int 를 사용합니다. )

함수의 정의 부분에서 알 수 있는 두 번째

사실은 바로 함수의 이름이다.

대충 짐작이 가듯이, 위함수의 이름은

print_hello 이다. 끝에 붙는 () 는 함수의

이름에 포함되는 것이 아니다.

끝에 붙는 괄호 두 개는 이것이 함수라는

사실을 의미한다. 만일 우리가 끝에 () 를

붙이지 않는다면 int print_hello 라는 문장은

단순히 끝에 ; 를 제대로 붙이지 않았구나 라고

해석되어오류를 출력하게 된다.

꼭 () 를 붙여주도록하자∼

주석에서도 잘 설명 하였듯이 좋은 함수

이름의 조건은 함수가 무슨 일을 하는지에

대해서 잘 설명 하는 것 이다.

만일 우리가 함수를 int asdfasd() 라고

만들었다면 우리가 asdfasd 라는함수를

보고 무슨 일을 하는지 잘 알 수 없다.

하지만 우리의 예제 처럼 print_hello 라고

하게 된다면 이 함수가 대략

’hello 를 출력하는구나’라는

사실을 알 수 있겠지.

다만, 함수의 이름이 너무 길어지면

함수를 사용시 너무 불편하므로

20 자가 넘어가게 하지는 맙시다.

또한, 함수의 이름 역시 변수의 이름

조건과 동일하므로 기억나지않는

분들은 3강 변수가 뭐지? 의

맨 마지막 부분을 보로록하자.

int print_hello() {
  printf("Hello!! \n");
  return 0;
}

함수의 정의부분은 그만 살펴보고,

이제 함수가 무슨 일을 하는지

알 수 있는 부분을 살펴 보도록하자.

이 부분은 보통 함수의 몸체(body)라고

부른다. 이번 예제 함수의 몸체는

설명을 안해도 잘 알수 있다.

이 함수는 printf(“Hello!! \n”);을

실행한 후, 0 을 반환한다 이지요?

printf("함수를 불러보자 : ");
print_hello();

printf("또 부를까? ");
print_hello();

마지막으로 실제로 함수를 호출하는

부분을 살펴 보도록하자.

함수를 불러내는 방법

(보통 호출한다(call)라는표현을)

(사용하므로 앞으로)

(호출한다고 표현하겠다)

은 단순히 함수의 이름을

써주시기만 하면 된다.

물론 그 뒤에 () 도 붙여주어야 겠지.

다시 말하지만 () 는 함수의 이름에

포함되는 것이 아니다.

하지만, () 를 써줌으로써

컴파일러에게 ’내가 지금

쓴 것이 함수 이니라∼’ 라는 사실을

말해주게 되는 것이다.

만일 함수를 호출한답시고

print_hello; 라고 쓴다면 컴파일러는

’어딘가에 print_hello 라는

변수에 접근하였네’

라고 생각하는데,

print_hello 라는 변수가 없으므로

오류를 출력하게 된다.

함수를 호출하면 프로그램은 함수의

내용을 실행하게 된다. 그리고 다시,

원래 실행되려는 부분으로 돌아오게 된다.

위의 경우, “함수를 불러보자” 가 출력된 후

print_hello() 를 통해 함수를 호출하였다.

그러면 프로그램은 print_hello() 라는 함수로

넘어가서, 이 함수의 내용을 다실행한 뒤에

다시 원래 있던 곳으로 돌아와 넘어가게 된다.

이와 같은 현상은 실생활에서도 볼 수 있다.

밥을 먹고 있다가 ’엄마가 부르신다’ 라는 함수가

호출되면 엄마한테로 달려갑니다.

그리고 ’엄마가 부르신다’

라는 함수가 종료되면

다시 밥 먹던식탁으로 와서 밥을 먹게 된다.

이 때, 함수의 종료는 두 가지 형태로 있을 수 있다.

하나는 반환이 되어 종료를 하게 되는 것이고

다른 하나는 함수의 끝 부분 까지

실행하여 종료되는 것이다.

함수는 반환을 하여 종료되는 것이 안전하다.

한 가지 중요한 사실은 return을 실행하면 함수는

무조건 종료되어 함수를 호출하였던

부분을 돌아간다는 점이다.

/* 함수의 리턴 */
#include <stdio.h>
int return_func() {
  printf("난 실행된다 \n");
  return 0;
  printf("난 안돼 ㅠㅠ \n");
}
int main() {
  return_func();
  return 0;
}

성공적으로 컴파일 한다면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
난 실행된다 
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

물론 앞에서 이야기 하였듯이 짐작은 하고

있으셨겠지만 확실히 보여드리기

위해 예제를 작성하였다.

int return_func()

연습 삼아 위 부분이 무슨 의미인지

다시 한 번 살펴 보도록하자.

일단, int 를 보아 이 함수는

int 형을리턴한다는 의미이고,

return_func 을 보아서 이 함수의

이름이 return_func라는

사실을 알수 있다.

{
  printf("난 실행된다 \n");
  return 0;
  printf("난 안돼 ㅠㅠ \n");
}

다음은 함수의 몸체 이다.

앞에서 이야기 하였듯이 return이

실행되면 프로그램은 바로 함수를

호출하였던 부분으로 넘어가 버려

그 다음에 오는 모든 것들

(위 예제에선 printf(“난 안돼 ㅠㅠ
\n”);) 이 실행되지 않게 된다.

/* 반환값 */
#include <stdio.h>
int ret() { return 1000; }
int main() {
  int a = ret();
  printf("ret() 함수의 반환값 : %d \n", a);

  return 0;
}

성공적으로 컴파일 한다면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test    
ret() 함수의 반환값 : 1000 
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\>

마지막으로 한 번더,

함수의 정의 부분을

분석해보도록하자.

int ret()

아마 이쯤 되면 여러분은 위 것만

보고도 이 함수는 이름이 ret 이고,

int 형을 반환한다 라는

사실을 알 수 있을 것 이다.

그리고 ret 함수의 몸체를 살펴 보자면

상당히 간단하다라는 것을 알 수 있다.

{ return 1000; }

그리고 위 코드는

“이 함수를 호출하면 1000 을 리턴한다”

정도 되겠다

int main() {
  int a = ret();
  printf("ret() 함수의 반환값 : %d \n", a);

  return 0;
}

위는 ret() 함수를 호출하여 그 값을 a에

대입하는 문장 이다.

그런데 ret() 가 가지는 값이있는가?

물론, ret() 는 함수이기 때문에 위와 같이

이용하면 안될것 같습니다만,

ret() 를 코드에쓰게 된다면 이 말은

“ret() 함수의 반환값” 라는 의미를 가집게 된다.

즉, 컴퓨터가 위 코드를 실행한다면 a 에는

ret 함수의 반환값인 1000 이라는

값이 들어가게 된다.

아무튼 아래의 유명한

격언을 기억하시기 바란다.

호랑이는 죽어서 가죽을 남기고,

함수는 죽어서 리턴값을 남긴다!

메인(main) 함수
아마 꼼꼼하신 여러분들은 이미 int main()이란

부분도 main 이라는 함수를 정의하고 있다는

사실을 눈치 채고 있을 것이다.

맞다. 여러분은 main 이라는

이름의 함수를 정의하고 있는 것이였다.

그런데 왜 하필이면 main 일까?

왜냐하면 프로그램을 실행할 때 컴퓨터가

main 함수 부터 찾기 때문이다

(물론 모든 경우가 그런것은 아니고)

(적어도 우리가 앞으로 만들게)

(될 C 프로그램들의 경우).

즉, 컴퓨터는 프로그램을실행할 때

프로그램의 main 함수를 호출함으로써

시작한다. 만일 main 함수가 없다면

컴퓨터는 프로그램의 어디서 부터

실행할 지 모르게 되어 오류가 나게 된다.

보통 메인 함수를 아래와 같은 형태로 정의이다.

int main()

위에서 배운 내용을 살짝 활용하면

“이 함수는 리턴형이 int 이고 이름은 main 이네!”

정도 알수 있겠지. 그런데,

메인 함수가 리턴을 하면 누가 받을까?

메인 함수가 프로그램 맨 처음에

실행되는 함수라면,

맨 마지막으로 종료되는

함수도 메인 함수 이기 때문에

리턴값을 받을 수 있는 함수가 없을 듯이다.

사실, 그렇지 않는다.

메인 함수가 리턴하는 데이터는

바로 운영체제가 받아들인다.

운영체제.즉 여러분이 아마도 쓰고 계실

Windows 10 나 Linux 에서 받는다는

이야기 이지요.

1) 보통 메인
함수가 정상적으로 종료되면 0 을 리턴하고,

비정상적으로 종료되면 1 을

리턴한다고 규정되어
있습니다. 우리가 여태까지

만들어왔던 모든 메인

함수들은 정상적으로 종료되므로

마지막에 0 을 리턴하였죠.

사실, 1 을 리턴한다고 해서 큰 문제는

없습니다. 이 정보를 활용하는

경우는 매우 드물기 때문이죠.

아무튼, 여기서 알아야 할 사실은

“main 도 함수다!” 정도만 알아 두셨으면 합니다.

이번에는 맨 위에서 구상하였던 마술 상자

( 4 를 더한값을 출력하는..)를

제작해보기로 하였다. 일단 여러분은

아래와 같이 구현할 수 있지 않을까라는

것을 머리속에 떠올릴 것이다.

/* 마술 상자 */
#include <stdio.h>
int magicbox() {
  i += 4;
  return 0;
}
int main() {
  int i;
  printf("마술 상자에 집어넣을 값 : ");
  scanf("%d", &i);

  magicbox();
  printf("마술 상자를 지나면 : %d \n", i);
  return 0;
}

컴파일 하면 아래와 같이 달콤한 오류를 볼 수 있다.

 컴파일 오류
 error C2065: ‘i’ : 선언되지 않은 식별자입니다.

아니, 왜? 이런 오류가 뜨는 것이지..

분명히 우리는 main 함수 내에서

i 라는 이름의 int 형변수를 선언하였고

다른 함수(여기선 magicbox)에서

사용할 수 있어야 되는 것 아닌가? 하지만

안타깝게도 아니다. 사실, 이 마술상자는

우리가 생각했던 것 보다도 훨씬 멋진 개념이다.

어떠한 함수를 호출할 때, 호출된 함수는 함수를

호출한 놈에 대해서 어떠한 것도 알고 있지 않는다.

즉, 내가 magicbox 라는 함수를 호출하였을 때,

이 magicbox 는 내가 얘를 호출하였는지,

다른애가 (즉, 다른 코드를 말하는 것이겠죠;;)

얘를 호출하였는지 ’전혀 알 수 없다’ 라는 것이다.

int magicbox() {
  i += 4;
  return 0;
}

따라서 이 함수는 i라는

변수에 대해서 아무런 정보도 가지지 않고 있다.

왜냐하면 이 함수를 호출한 것이 무엇인지에

대한 정보가 하나도 없기 때문이다.

결과적으로 main 함수에서 정의된i 라는 변수는

magicbox 의 입장에서 본다면 듣도 보도 못한 것이

되는 것이다. 결과적으로 위에서 보았던 오류와

같이 i 라는 변수가 선언되어있지

않다는 오류를 내게 된다.

아직도 위 코드가 왜 작동이 되지 않는지

이해가 되지 않으신 분들은 아래의

옛날 이야기(?) 를 보시면 된다.

옛날 옛날 이집트 시대에

어떤 부유한 귀족이 있었습니다.

이 귀족은 하루에 10000 달러씩 장사를

해서 벌었습니다. 그런데 공교롭게도 수학을

매우매우 못했죠. 따라서,

이 귀족은 노예를 한 명 사서,

이 노예에게 자신의 현재 재산에 10000 을

더해서 알려 달라고 하였다.

그리고 시간이 흘러 10시간 뒤,

귀족의 일과가 끝났다.

이제, 그는 오늘 자신의 재산

현황을 파악하기 위해서 노예를

호출했습니다. 야 말해그런데

노예는 아무 말도 하지 못했다.

야 말하라고, 내 재산에 10000 을 더해서 말하라니까

역시 아무말도 없었습니다.

왜일까요? 그야, 당연히 노예는

귀족의 재산에 대한 정보가 없었기

때문입니다. 귀족이 방금 노예를

호출함으로써 한 일은,

“자신의 재산 += 10000” 이였습니다.

그런데, ’자신의 재산’ 이란

변수는 노예의 머리에서

정의된 것이 아니므로

알 노릇이 없습니다.

그렇다면, 이제 아무 쓸모

없게된 불쌍한 노예를

악랄한 귀족이 죽이게 내버려

두어야 하나요? 물론,

그리하면 안되겠죠. 일단,

여기서 문제점을 해결하기

위해선 노예가

“현재 귀족의 재산” 이라는

데이터만 머리에 넣고 있으면 됩니다.

(노예가 계산을 충분히 잘한다는 가정 하에..)

이 말을, C언어 적으로 이야기 하면

노예라는 함수에

“주인의 현재 재산” 이라는

변수를 정의하고 이 변수에

“자신(주인)의 재산” 의 값을 넣은 뒤에,

“주인의 현재 재산+=10000″을

계산한 후, “주인의 현재재산”

을 반환(입으로 말함)

하면 되는 것입니다.

이제, 문제는 노예 머리속에

“주인의 현재 재산”

이라는 변수에

“자신(주인) 의 재산”

값을 어떻게 넣느냐가 문제 입니다.

바로 아래에서 보도록 하죠.

함수의 인자

#include <stdio.h>
int slave(int master_money) {
  master_money += 10000;
  return master_money;
}
int main() {
  int my_money = 100000;
  printf("2009.12.12 재산 : $%d \n", slave(my_money));

  return 0;
}

성공적으로 컴파일 하면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
2009.12.12 재산 : $110000 
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

일단, 함수의 정의 부분이

바뀐 것을 볼 수 있다.

int slave(int master_money)

slave 가 함수 임을 알려주는 소괄호 안에

int master_money 가 써 있다.

이는 다음과 같은 의미를 가진다.

“나를 호출하는 코드로 부터 어떤 값을

mater_money 라는 int 형 변수에 인자

(혹은 매개변수라고도 부름)로 받아들이겠다!”

허걱.. 정말 뭔소린지 알 수 없다.

먼저 ’인자’가 무엇인지 살펴 보도록 하자.

아까 전에 우리는 노예의 머리속에

’현재 주인이 가지고 있는 재산’ 이라는

값을 어떻게 입력해야 할지가 문제라고 하였다.

그런데, slave 함수와 main 함수는 전혀 별개의

함수 이기 때문에 slave

함수는 main 함수 안의 변수를

사용할 수 없을 뿐더러 main 함수에서도

slave 함수의 변수들이 무엇인지

전혀 알 길이 없다.

하지만, 인자

(argument, 혹은)

(매개변수(parameter) 라고 부른다)를

이용하면 이러한 일을 가능하게 한다.

일단, 인자는 직관적으로 봐도

알 수 있듯이 slave 함수 내에

선언이 되어 있는 변수 이다.

이 때, 인자는 함수 정의할 때의

소괄호 안에 나타나게 된다.

위의 경우 slave 함수는

int 형의 master_money라는

변수를 인자로 가지고 있다.

이제, 이 함수를 어떠한

함수에서 호출을 한다고 한다.

그렇다면, 이 함수를 호출 할 때,

인자에 적당한 값을 넣어 주어야 한다.

마치 아래와 같이.

slave(500);

이 말은 slave 함수를 호출할 때,

slave 함수 안에서

정의된 master_money라는

변수에 500이라는값을 전달하겠다!

라는 의미이다. 따라서, slave 함수 내부에

정의된 master_money라는 변수에는 500

이라는 값이 들어가게 된다.

그렇다면 아래는 어떨까?

slave(my_money);

이 것도 마찬가지다.

이렇게 이용한다면 “slave 함수를 호출할 때,

slave 함수 안에서 정의된 master_money라는

변수에 my_money 의값을 전달하겠다!”가 된다.

만일 my_money 에10000 이

있었더라면 slave 함수를

호출 시에 master_money 에는

10000 이 들어가게 된다.

결론적으로 말하자면 함수의 인자는

’함수를 호출한 것과,

함수를 서로 연결해

주는 통신 수단’ 이라

고 말할 수 있다.

이러한 연유에서 수학적인 용어로

틀린 표현 이지만 C 에선

’매개 변수’ 라고부른다.

그렇다면, 위의 예제를 한 번 살펴볼까요?

int main() {
  int my_money = 100000;
  printf("2009.12.12 재산 : $%d \n", slave(my_money));

  return 0;
}

일단, slave 함수를 호출하는

호출자(caller)의

코드를 살펴 보도록하자.

printf 에서, 맨 뒤에 %d

에 들어갈 값으로

slave(my_money) 가 반환 하는

값을 넣었다. slave(my_money) 가

반환하는 값을 먼저

넣기 위해선 slave 함수를

호출해야 하는데

이 때 my_money 의

값이 slave 함수의 인자로 전달이 된다.

그러면 slave 함수는 아래의 코드를 실행하겠다.

{
  master_money += 10000;
  return master_money;
}

즉, master_money 에

10000 을 더한 후,

그 값을 반환하게 된다.

따라서, 100000 에

10000 이더해진

110000 이 출력이 된다.

이번에는 과연 성공적으로

컴파일 될지 의문이 드는

예제를 한 번 만들어 보았다.

/* 될까? */
#include <stdio.h>
int slave(int my_money) {
  my_money += 10000;
  return my_money;
}
int main() {
  int my_money = 100000;
  printf("2009.12.12 재산 : $%d \n", slave(my_money));
  printf("my_money : %d", my_money);

  return 0;
}

성공적으로 컴파일 하면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
2009.12.12 재산 : $110000 
my_money : 100000
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

아마도, 앞의 내용을 열심히 배우신 분들은

위 코드가 정상적으로 실행될 것이라는 것을

알고 계셨을 것이다 하지만,

그렇지 못한 분들을 위해 설명 하자면

int slave(int my_money) {
  my_money += 10000;
  return my_money;
}

위 slave 함수는 my_money 를

인자로 받고 있다.

여기서 중요한 점은 my_money 가

slave의 변수라는 것이다.

그렇다면 slave 함수를

호출하는 부분을 볼까?

int main() {
  int my_money = 100000;
  printf("2009.12.12 재산 : $%d \n", slave(my_money));
  printf("my_money : %d", my_money);

  return 0;
}

음, slave 함수를 호출할 때 main

함수 내부에서 선언된 my_money의

값을 slave 함수의 변수인 my_money 에

전달하고 있다. 즉, 각 함수 내부에서

선언된 my_money 들은 이름은 같지만

서로 다른 변수 이고, 메모리 상의 다른

위치를 점유하고 있다. 즉,

우리가 보기에 두 변수는

똑같은 것으로 보여도 적어도

컴퓨터가 보기에는

두 변수는 서로 다른 것들.

두 번째로 주목할 점은

값이 전달된다는 것이다.

이는 아까 제가 위에서

부터 누누히 강조해 온

점이기도 한데, slave 함수를 호출할 때 slave

함수의 my_money 인자에는 값이 전달된다.

즉, main 함수의 my_money 의 100000 이라는

값이 slave 함수의 my_money 라는

인자에 저장되어 들어간다.

따라서, slave 함수에서

my_money의 값을

아무리 깽판쳐도 main 함수의

my_money 변수 에는 전혀 영향을

주지 않는다는 것. 왜냐하면 slave 함수의

my_money 변수는 단지 main함수의

my_money 와 같은 값을 가진 채로

초기화된 메모리 상의 또다른

변수 이기 때문. 이건 마치

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/1.png

이라고 했는데 a 의 값이 b 와 같이

1 증가함을 바라는 것과 같다.

아무튼, 결과적으로 main
함수에서 두 번째 printf 문에서 main

함수의 my_money 의 값을

출력했을 때 에는 전혀 변하지 않은

100000 이 출력된다.

그렇다면 우리가 다른 함수의 변수의 값을

수정하고자 하는 함수를 만들고 싶다면

어떻게 해야 될까? 우리가 앞에서 배운

내용을 생각해보면 “각 함수의 세계는 너무나

배타적이여서 각 함수는 서로에 무슨

변수가 있는지 모른다.

사실 (정확히 말하자면 각 함수의 형태

(리턴형, 함수의 이름,인자들의 형(type)) 빼고는)

서로에 대해 아는 것이 완전히 없다.”

그럼, 정말로 우리는 다른 함수에서 정의된

변수의 값을 수정하는 함수는 결코

작성할 수 없는 것일까?

답은 아니오 이다. 놀랍게도

포인터를 이용하면 된다

(드디어 포인터가 쓸모 있어지나요?).

일단, 이것까지 이야기 하면 강좌가

너무 길어지므로 오늘은 이쯤에서

끝내도록 하고 어떻게 포인터로 가능할까

에 대해서 다음 강좌가 나올 때 까지

생각해보도록하자.

포인터로 받는 인자

이전기록에 함수에 대해선 잘 이해하셨는지요?

그리고, 마지막에 던진 의미 심장한(?) 질문에는

답을 생각했을것 으로 생각되며 우리는

이전 Chapter12에서 포인터에 대해서 다루어왔다.

그 때 동안 늘 머리속에 맴돌았던 생각은

“도대체 이건 어디에 써먹는것인가?”였다.

하지만, 이번 강좌에서 그 질문에 대한

해답을 찾을 수 있었으면 한다.

일단, 간단히 이전에 포인터에

대해서 배웠던 내용을 리뷰 하자면

포인터는 특정한 변수의 메모리 상의

주소값을 저장하는 변수로, int 형 변수의

주소값을 저장하면 int*,

char 이면 char* 형태로 선언된다.

또한 * 단항 연산자를 이용하여,

자신이 가리키는

변수를 지칭할 수 있으며 & 연산자를

이용하여 특정한 변수의 주소값을 알아낼 수 있다.

만일 위 내용중에 한 마디라도

이해가 안되는 부분이 있다면 Chapter12

포인터 강좌를 다시 읽어 보시기를

바란다. 그렇지 않다면 아래의 내용을

계속 읽어가도록 하자.

우리는 지난 기록에서

다음과 같이 단순한 형태로는

다른 함수에서 정의된 변수의 값을

바꿀 수 없다고 했었다.

/* ???? */
#include <stdio.h>
int change_val(int i) {
  i = 3;
  return 0;
}
int main() {
  int i = 0;

  printf("호출 이전 i 의 값 : %d \n", i);
  change_val(i);
  printf("호출 이후 i 의 값 : %d \n", i);

  return 0;
}

왜 main 함수 안에서 정의된 i 의 값이

바뀌지 않는지는 잘 아시겠지만

그래도 한 번 확인해 보도록하자.

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
호출 이전 i 의 값 : 0 
호출 이후 i 의 값 : 0 
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

i 의 값이 전혀 바뀌지 않았음을 알 수 있다.

그 이유는 함수 change_val 을 호출 할 때,

change_val 함수 안에서 정의된 변수 i 는

main 함수의 i 의 값을 전달 받은 후에,

change_-val 함수 안에서 정의된

변수 i 의 값을 3 으로 변경하게 된다.

여기서 중요한 점은 ’main 함수의 i 가 아닌

change_val 함수 안에서 정의된

변수 i 의 값이 3
으로 변경’ 된다는 것이다.

결론적으로 main 함수의 i 의

값에는 아무런 영향도 미치지 못하고

위와 같은 현상이 벌어지는 것이다.

위 과정을 그림으로 표현하면 아래와 같다.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/7.png

하지만, 여러분은 지난 3 개의 강좌를

통해 포인터에 대해

귀가 아플 만큼 들어 보았을 것이다.

그리고, 여기에서 그 아이디어를

적극적으로 활용하고자 한다.

이전의 방법을 통해서 다른 함수에

정의된 변수들의 값을 변경할 때

직면했던 문제는 바로 각 함수는

다른 함수의 변수들에 대해 아는것이

아무것도 없다는 것이다.

즉 A 라는 함수에서 i 라는 변수를

이용한다면 컴파일러는 이 변수 i 가

오직 A 함수에서만 정의되었다고

생각하지 다른 함수에서

정의되었는지는 상관하지않다는 것이다.

그렇지만 궁여지책으로 유일하게

가능했던 것은 인자를 이용해서

다른 함수에 정의된 변수들의 ’값’

을 전달하는 것이였다.

하지만 그렇게 해도

여전히 불가능해 보였다.

/* 드디어 써먹는 포인터 */
#include <stdio.h>
int change_val(int *pi) {
  printf("----- chage_val 함수 안에서 -----\n");
  printf("pi 의 값 : %p \n", pi);
  printf("pi 가 가리키는 것의 값 : %d \n", *pi);

  *pi = 3;

  printf("----- change_val 함수 끝~~ -----\n");
  return 0;
}
int main() {
  int i = 0;

  printf("i 변수의 주소값 : %p \n", &i);
  printf("호출 이전 i 의 값 : %d \n", i);
  change_val(&i);
  printf("호출 이후 i 의 값 : %d \n", i);
  return 0;
}

성공적으로 컴파일 하면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
i 변수의 주소값 : 0061FF1C 
호출 이전 i 의 값 : 0
----- chage_val 함수 안에서 -----
pi 의 값 : 0061FF1C
pi 가 가리키는 것의 값 : 0       
----- change_val 함수 끝~~ ----- 
호출 이후 i 의 값 : 3
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

여러분의 출력결과와 다를 수 있다.

호우! 눈으로 보고도 믿기지 않으십니까?

호출 이후의 i 의 값이 0 에서 3 으로 바뀌었다.

이게 무슨일 인가?

이건 우리가 여태까지 꼭 하고야 말겠던

바로 그 작업 아닌가? 바로 다른 함수에서

정의된 변수의 값을 바꾸는 것 말이지.

그런데, 위 코드를 조금씩

뜯어 들여보다 보면 방법은 매우
간단하다는 것을 알 수 있다.

물론, 이 강의를 보고 계시는

현명한 독자들은 이미 짐작 했을 것이지만.

int change_val(int *pi)

일단, 함수의 정의부분을 살펴보자면

int 형의 변수를 가리키는 pi 라는

이름의 포인터로 인자를 받고 있다.

그리고 main 함수에서 이 함수를

어떻게 호출했는지 보면

change_val(&i);

즉, 인자에 main 함수에서 정의된

i 라는 변수의 ’주소값’ 을

인자로 전달하고 있다.

따라서 change_val

함수를 호출하였을

때 pi 에는 i 의 주소값이

들어가게 된다.

즉, pi 는 i 를 가리키게 된다.

{
   printf("----- chage_val 함수 안에서 -----\n");
   printf("pi 의 값 : %p \n", pi);
   printf("pi 가 가리키는 것의 값 : %d \n", *pi);

   *pi = 3;

   printf("----- change_val 함수 끝~~ -----\n");
   return 0;
}

pi 가 i 의 주소값을

가지고 있으므로 pi 를

출력했을 때 그

값은 i 의 주소값과

같을 수 밖에 없다.

이는 두 번째 printf

문장에서 확인할 수 있다.

또한 그 아래 *pi 를

통해서 i 를 간접적으로

접근할 수 있다.

왜냐하면 * 라는

단항 연산자의

의미가 ’내가 가지는

주소값에 해당하는

변수를 의미해라’

이기 때문에 *pi 는 pi 가

가리키고 있는

변수인 i 를 의미할 수 있게

된다. 즉, pi 를 통해서 굳게

떨어져 있던 main 과 change_val

함수의 세계 사이에

다리가놓이게 되는 것이다.

간혹 pi 가 main 함수에서

정의된 것이라고 착각하는

분들이 있는데,

pi 역시 change_val 함수

내에서 정의된 변수 이다.

(놀라운건 본인도 이랬었다···)

또한 *pi = 3 을 통해

’pi 가 가리키고 있는 변수’ 의

값을 3 으로 변경할 수 있다.

여기서 pi가 i 를 가리키므로

i 의 값을 3 으로 변경할 수 있겠다.

따라서,

printf("호출 이후 i 의 값 : %d \n", i);

에는 i 의 값이 성공적으로

변경되어 3 이 출력되는 것이다.

위 과정을 그림으로 나타내면

아래와 같다.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/8.png

두 변수의 값을 교환하는 함수

/* 두 변수의 값을 교환하는 함수 */
#include <stdio.h>
int swap(int a, int b) {
  int temp = a;

  a = b;
  b = temp;

  return 0;
}
int main() {
  int i, j;

  i = 3;
  j = 5;

  printf("SWAP 이전 : i : %d, j : %d \n", i, j);
  swap(i, j); // swap 함수 호출~~
  printf("SWAP 이후 : i : %d, j : %d \n", i, j);

  return 0;
}

성공적으로 컴파일 했으면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
SWAP 이전 : i : 3, j : 5 
SWAP 이후 : i : 3, j : 5        
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

흠. 일단 우리가 원하던

결과가 나오지 않았다.

소스 상단의 주석에서도

볼 수 있듯이 swap

함수는 두 변수의 값을

교환해 주는 함수 이다.

우리가 원하던 것은 SWAP

이후에 i 에는 5 가 j에는

3 이 들어 있는 것인데

전혀 바뀌지 않았다.

이에 대해 이야기 하기 전에

소스 코드에서

보이는 새로운 것들에 대해

이야기 해보도록하자.

int swap(int a, int b)

swap 함수의 정의를 보면 직관적으로

인자가 2 개나 있다는 것을 알 수 있다.

맞다. 이 swap 함수는 호출시

2 개의 인자를 전달해주어야 한다.

물론 인자가 더 늘어난다면 반점(,) 을

이용해서 계속 늘려나가면 된다.

예를 들어서

int this_function_has_many_argumenets(int a, char b, int* k, long d, double c,
                                      int aa, char bb, int* kk, double cc)

와 같이. 아무튼, swap 함수를 살펴 보면

int swap(int a, int b) {
  int temp = a;

  a = b;
  b = temp;

  return 0;
}

로 두 개의 int 형 인자를 받아 들이고 있다.

이 때, 내부를 보면

temp 라는 변수에 a 의 값을저장한다.

그리고 변수 a 에 변수 b 의 값을 는다.

이제, 변수 b 에 a 의 값을 넣어야 하는데,

현재 변수 a 에는 b 의 값이 이미 들어가

있으므로 이전에 저장하였던 a 의

값인 temp 변수의 값을 b 에 넣으면 된다.

일단, 내용상으로는 전혀 하자가 없어 보인다.

printf("SWAP 이전 : i : %d, j : %d \n", i, j);

swap(i, j); // swap 함수 호출~~

printf("SWAP 이후 : i : %d, j : %d \n", i, j);

그런데, 말이죠. main 함수에서

i,j 의 값을 바꾸려고 swap 함수를

호출하였더니 전혀 뒤바뀌지

않은 채로 출력되었다.

도대체 왜 그런가?

물론, 여러분은 다 알고 있을 것.

swap 함수의 변수 a,b 가 모두

swap 함수 내부에서 선언된

변수들이란 것이다.

다시말해 변수 a 와 b 는 i 와 j 와

어떠한 연관도 없다.

다만, a 와 b 의 초기값이 i, j 와

동일하였다는 것만 빼고.
이는 마치 아래의

작업을 한 것과 같다.

int i, j;
int temp, a, b;
/* 함수를 호출하여 함수의 인자를 전달하는 부분 */
a = i;
b = j;
/* 함수 몸체의 내용을 실행 */
temp = a;
a = b;
b = temp;

그러니 i 나 j 의 값이 바뀔리 만무한다.

아무튼, 위 함수가 호출되는 과정을

그림으로 표현하면 아래와 같다.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/9.png

그렇다면 어떻게 해야 할까?

다 알고 있을것이다 포인터를 활용하자!

/* 올바른 swap 함수 */
#include <stdio.h>
int swap(int *a, int *b) {
  int temp = *a;

  *a = *b;
  *b = temp;

  return 0;
}
int main() {
  int i, j;

  i = 3;
  j = 5;

  printf("SWAP 이전 : i : %d, j : %d \n", i, j);

  swap(&i, &j);

  printf("SWAP 이후 : i : %d, j : %d \n", i, j);

  return 0;
}

성공적으로 컴파일 하면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
SWAP 이전 : i : 3, j : 5 
SWAP 이후 : i : 5, j : 3        
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\>

드디어 원하던 것이 이루어졌다.

바로 i 와 j 의 값이 서로 뒤바뀐(swap)것이다.

그런데, 이전에 이야기 하였던 내용을 잘

숙지하였더라면 위 함수가

왜 제대로 호출하는지 쉽게 알 수 있다.

int swap(int *a, int *b) {
  int temp = *a;

  *a = *b;
  *b = temp;

  return 0;
}

먼저 swap 함수를 살펴 보도록하자.

이는 int 형을 가리키는 포인터

변수를 인자로 가지고 있다.

일단, swap 함수 내에서 두 변수를

교환하는 과정은 위와 동일하니 이에

대해서는 이야기 하지 않도록 하겠다.

이 때, main 함수에서는 swap 함수를

아래와 같이 호출한다.

printf("SWAP 이전 : i : %d, j : %d \n", i, j);

swap(&i, &j); // 호출

printf("SWAP 이후 : i : %d, j : %d \n", i, j);

바로 a 와 b 에 i 와 j 의

주소값을 전달하여

a 와 b 로 하여금 i 와 j 를

가리키게 만든 것.

따라서, swap 함수 내부에서는

a 와 b 의 값을 교환하는 것이 아니라

a 와 b 가 가리키는 두 변수의

값을 교환했으므로

(*a, *b) 결과적으로 i 와 j 의

값이 바뀌게 된 것다.

어떤가, 간단하지 않는가?

결론적으로 정리하자면

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/2.png

포인터를 인자로 받은 함수에 대해선

지속적으로 이야기 할 것이므로

지금 막상 이해가 잘 안된다고

해도 큰 걱정할 필요는 없다.

함수의 원형

우리가 여태까지 사용하였던

함수들은 모두

main 함수 위에서

정의되고 있었다.

그러면, 그 정의를

main 함수 아래에서

한다면 어떻게 될까?

사실, 대부분의

사람들의 경우 main 함수를

제일 위에 놓고 나머지

함수들은 main 함수 뒤에

정의하게 된다.

아무튼, 위의 코드를 살짝

바꿔보면 아래와 같다.

/* 될까? */
#include <stdio.h>
int main() {
  int i, j;

  i = 3;
  j = 5;

  printf("SWAP 이전 : i : %d, j : %d \n", i, j);
  swap(&i, &j);
  printf("SWAP 이후 : i : %d, j : %d \n", i, j);

  return 0;
}
int swap(int *a, int *b) {
  int temp = *a;

  *a = *b;
  *b = temp;

  return 0;
}

컴파일 하게 되면 아래와 같은 경고 창을 볼 수 있다.

 컴파일 오류
 warning C4013: ‘swap’이(가) 정의되지 않았습니다. extern은 int형을 반환하는
   ,→ 것으로 간주합니다.

 

흠, 일단은 무시하고 실행해 보도록 하자.

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
test.c: In function 'main':
test.c:10:5: warning: implicit declaration of function 'swap' [-Wimplicit-function-declaration]
     swap(&i, &j);
     ^~~~
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
SWAP 이전 : i : 3, j : 5 
SWAP 이후 : i : 5, j : 3        
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

잘만 된다. 그렇다면 아무런 문제가 없는 것일까?

사실, 함수를 적절히 잘 이용하기만 하면

큰 문제는 발생하지 않는다. 그런데.

사람도 역시 사람인지라, 프로그래밍 하다가

실수로 인자의 개수를 부족하게 쓰거나,

올바르지 않는 타입의 변수

(예를 들어 인자가 int* 인데,)
(int 변수를 썼다든지) 를

사용하는 수가 발생하게 된다.

더군다나, 우리의 예제에서는 함수가

겨우 한 개 밖에 없었지만 실제

프로그래밍 시에는 수십개의

함수를 이용하기 때문. 그렇다면,

여러분이 완벽한 인간이 아니라는

가정 하에 인자 하나를 누락시켜 본다.

위 코드의 함수 호출 부분을

swap(&i, &j);

에서

swap(&i);

로 변경해 보도록하자.

컴파일 하면 여전히 위와 동일한

경고가 나오는데 특별히 내가

인자를 누락 했다는

말은 하지 않는다.

그리고, 실행해보면

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/10.png

컴파일시 아무런 오류 메세지도 없었는데

실행해 보면 위와 같이 덜컹 오류가 나타낸다.

이런 해괴한 일이 아닐 수 없다.

게다가, 컴파일러는 내가 인자를

어디서 인자를 누락했는지

조차 표시해주지 않기 때문에 오류를

찾기 힘들어 질 수 밖에 없다.

물론, 우리의 예제는 짧기 때문에 찾기 쉽지만

진짜 같은 프로그램을 제작하면 코드가

보통 수천∼수만 줄에 달한다는 것만을

기억하도록하자.

이번에는 swap 함수 호출 부분을

swap(&i, j); 로 변경해보도록하자.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/11.png

역시 이번에도 동일한 형태의 프로그램

오류가 컴파일시 오류 하나 없었는데도 불구하고

나타났다. 이 역시 포인터 전달 해야 되는데,

그냥 정수값을 전달해서 포인터 b 가 메모리의

올바르지 않은 공간에 접근하여 발생한 일이다.

참으로 곤욕스러운 일이 아닐 수 없다. 우리

가 아무리 대단하다고 해도 실수를 할 수 있는 법인데,

컴파일러는 이러한 실수를 하나도 잡아내지

못하고 있다.

그러나, 우리의 C 언어가 이를 용납할 수 있는가?

C 언어에서는 멋진 해결책이 있습니다. 바로,

함수의 원형(prototype) 를 이용하는 것이다.

/* 함수의 원형 */
#include <stdio.h>
int swap(int *a, int *b); // 이 것이 바로 함수의 원형
int main() {
  int i, j;

  i = 3;
  j = 5;

  printf("SWAP 이전 : i : %d, j : %d \n", i, j);
  swap(&i, &j);
  printf("SWAP 이후 : i : %d, j : %d \n", i, j);

  return 0;
}
int swap(int *a, int *b) {
  int temp = *a;

  *a = *b;
  *b = temp;

  return 0;
}

성공적으로 컴파일 하면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
SWAP 이전 : i : 3, j : 5 
SWAP 이후 : i : 5, j : 3        
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\>

잘 출력된다. 이번에는 컴파일시

경고나 오류의 흔적 조차 찾아볼 수 없었다.

#include <stdio.h>
int swap(int *a, int *b); // 이것이 바로 함수의 원형
int main() {
  int i, j;

  i = 3;
  j = 5;
  // ... (생략)

소스 코드의 제일 윗부분을 보면

위와 같이 한 줄이 추가된 것을 볼 수 있다.

이는 바로 ’함수의 원형’ 이라 부르는 것이다.

이는 사실 함수의 정의 부분을

한 번 더 써준 것 뿐이다

 

(주의할 점은 함수의 원형에는 정의)

(와는 달리 뒤에 ; 를 붙인 다는 것입니다).

그런데, 이 한줄이 컴파일러에게

다음과 같은 사실을 알려준다.

 

여, 이 소스코드에 이러 이러한 함수가

정의되어 있으니까 잘 살펴 보도록

 

다시말해, 컴파일러에게 이 소스코드에 사용되는

함수에 대한 정보를 제공하는 것이다. 다시 말해

실제 프로그램에는 전혀 반영되지 않는 정보.

그렇지만, 우리가 앞서 하였던 실수들을 하지

않도록 도와준다. 만일, 위와 같이 함수의

원형을 삽입한 상태에서

인자를 &i 하나로 지워 보도락하자.
즉, swap(&i, &j) 를 swap(&i); 로

변경해보도록하자.

 

그럼 컴파일 시 아래와 같은 오류를 만나게 된다.

 컴파일 오류
 error C2198: ‘swap’ : 호출에 매개 변수가 너무 적습니다.

호우! 우리가 앞서 함수의 원형을 집어 넣지

않았을 때 에는 인자(매개 변수)를 하나 줄여도

아무말하지 않던 컴파일러가 원형을 삽입하고 나니

위와 같이 정확한 위치에 내가 어딜 잘못했는지 잡아냅니다.

 

이것이 가능한 이유가 바로 컴파일러에게

내가 무슨 무슨 함수를 이용할 것인지

함수의 원형을 통해 이야기 하였기 때문이다.

내가, int swap(int *a, int *b) 라는 함수가

있다는 것을 원형을 이용해 알려주었기

때문에 컴파일러는 우리가 swap

함수를 사용하면 꼭 2 개의

인자를 이용한다는 사실을 알게 되어

내가 인자를 하나만 적었을 때

틀렸다고 알려 준 것이다.

 

그렇다면

swap(&i, &j) 를 swap(&i, j) 로 바꿔보면

어떻게 될까?

 컴파일 오류
 warning C4047: ‘함수’ : ‘int *’의 간접 참조 수준이 ‘int’과(와) 다릅니다.
 warning C4024: ‘swap’ : 형식 및 실제 매개 변수 2의 형식이 서로 다릅니다.

 

실질적인 오류는 발생하지 않았지만 일단,

내가 잘못하였다는 것을 알려준다.

컴파일러는 역시 원형을 통해 두 번째

매개 변수의 타입이 무엇인지

알고 있기에 그냥 int 를 사용하면

함수의 두번째 매개변수와 내가 인자에

전달하는 변수의 형과 다르다는 사실을 알려 준다.

다만, 여기서 아까와 같이 오류가 출력되지 않는 이유는

int* 도 사실 int 형 데이터 이기 때문에 j 가 (int *) 로

캐스팅 되어 전달되므로, 아까와 같은 강한

오류 메세지는 출력되지 않는다.

그러나, 여전히 프로그래머의 잘못을 지적하고 있다.

이러한 연유에서, 함수의 원형을 집어넣는

일은 여러분들이 ’반드시’ 하셔야 되는 일이다.

물론, main 함수 위에 함수를 정의하면

상관 없지만 사실 99.9% 의

프로그래머들은 함수를

 

main 함수의 뒤에 정의하고

원형을 앞에 추가하는 것을

선호하니

여러분들도 트렌드를 따르시기 바란다.

 

배열을 인자로 받기
이번에는 배열을 인자로 받아 들이는

함수에 대해서 생각해보도록하자.

 

이번 예제에서 우리가 만들게된

함수는 바로, 배열을 인자로 받아서

그 배열의 각 원소의 값을

1 씩 증가시키는 함수다.

#include <stdio.h>
int add_number(int *parr);
int main() {
  int arr[3];
  int i;

  /* 사용자로 부터 3 개의 원소를 입력 받는다. */
  for (i = 0; i < 3; i++) {
     scanf("%d", &arr[i]);
}

  add_number(arr);

  printf("배열의 각 원소 : %d, %d, %d", arr[0], arr[1], arr[2]);

  return 0;
}
int add_number(int *parr) {
  int i;
  for (i = 0; i < 3; i++) {
    parr[i]++;
}
 return 0;
}

성공적으로 컴파일 했으면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
10
11
15
배열의 각 원소 : 11, 12, 16     
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

음, 역시 함수가 잘 작동한다.

우리가 10, 11, 15 를 입력했을 때,

함수를 통해서 각 원소가 1 씩 증가하여

11, 12, 16 이 되었다.

일단, add_number

함수 부터 살펴 보도록 하죠.

int add_number(int *parr)

우리가, 앞서 말한 내용에 따르면

’특정한 타입의 값을 변경하는

함수를 제작하려면, 반드시 그 타입을

가리키는 포인터를 인자로

가져야 한다’라고 했었다.

그렇다면, 우리가 arr 이라는 배열을

가리키는 포인터가 바로 add_number 의

인자로 와야 하는데,

우리가 12 – 3 강에서 배운 내용에 따르면

int arr[3] 와 같은 일차원 배열을 가리키는

포인터는 바로 int* 형이라 했었다.

(잘 모르겠다면 여기를 눌러서 기록을(를))

( 다시 보시기 바랍니다)

따라서, add_number(int *parr) 이라 하면

arr 을 가리키도록 인자를 받을 수 있다.

함수를 호출 할 때 아래와 같이 하였다.

add_number(arr);

그런데, 우리가 이전에 배운 바에 따르면

arr 은 배열의 시작 주소 값을 가지고

있다고 하였다. 즉, arr = &arr[0] 인 것이다.

따라서, parr 에는 arr 배열의 시작 주소,

즉 배열 arr 을 가리키게 된다.

{
  int i;
  for (i = 0; i < 3; i++) {
    parr[i]++;
}
 return 0;
}

마지막으로 함수의 몸체를

살펴보도록하자.

parr[i] 를 통해 parr 이

가리키는 배열의 (i + 1)

번째 원소에 접근할 수 있다

(arr[1] 이 배열의 두 번째)

(원소 이므로).

따라서, parr[i]++ 을 통해서

배열의 각 원소들의 크기를 모두

1 씩 증가시키게 된다.

사실, 위 함수가

어떻게 돌아가는지

잘 이해하기 위해서는 포인터와

배열에 대한 거의 완벽한 이해를 필요로 한다.

만약 그러지 않는다면

모래사장에 빌딩 짓는 것처럼,

C 언어에 대한 개념을

완전히 잊어버릴 수 있으니

모른 다면 꼭 뒤로 가기를 하여

복습을 하시기 바란다.

반복 숙달 훈련 만이 살길이다!

/* 입력 받은 배열의 10 개의 원소들 중 최대값을 출력 */
#include <stdio.h>
/* max_number : 인자로 전달받은 크기 10 인 배열로 부터 최대값을 구하는 함수 */
int max_number(int *parr);
int main() {
  int arr[10];
  int i;

/* 사용자로 부터 원소를 입력 받는다. */
for (i = 0; i < 10; i++) {
  scanf("%d", &arr[i]);
}
  printf("입력한 배열 중 가장 큰 수 : %d \n", max_number(arr));
  return 0;
}
int max_number(int *parr) {
  int i;
  int max = parr[0];

  for (i = 1; i < 10; i++) {
   if (parr[i] > max) {
     max = parr[i];
   }
 }
  return max;
}

성공적으로 컴파일 한다면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
100 50 102 300 900 700 550 400 800 600
입력한 배열 중 가장 큰 수 : 900 
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\>

이번 예제는 사용자들로 부터 정수

10 개를 입력 받아서, 그 수들 중 가장

큰 수를 뽑아내는 프로그램 이다.

먼저 max_number 함수 부터

살펴보도록하자.

int max_number(int *parr) {
  int i;
  int max = parr[0];

  for (i = 1; i < 10; i++) {
    if (parr[i] > max) {
    max = parr[i];
   }
 }

 return max;
}

일단, 소스 코드를

해석하는데에는 큰 어려움이

없을 것 같다. 일단 처음 max 에 parr 이

가리키는 배열의 [0],

즉 첫번째 원소의 값을 넣었다.

그리고 아래 for 문에서

만약 parr[i]가 max 보다 크면

max 의 값을 parr[i] 로 대체하고 있다.

결과적으로 i 값이 9 까지 되었을때에는 max 에

parr 중 가장 큰 원소의 값이 들어가게 된다.

만일, max 보다 더 큰 원소가 parr 에 있다면

max 의 값은 그 큰 원소의 값으로

바뀌었기 때문에 모순이다.

결과적으로 우리가 입력한 10 개의

원소들 중 가장 큰 원소가 출력된다.

함수 사용 연습하기
사실, 아직까지 함수가 왜 이리 중요한 것인지

감이 잘 오지 않는 분들이 있을 것이다. 그래서,

그러하신 분들을 위해 함수의 중요성을

절실히 느낄 수 있는 예제를 준비하였다.

다음의 두 소스 코드를 비교해 보면서

어떤 것이 나은지 생각해보도록하자

/* 함수를 이용하지 않은 버전 */
#include <stdio.h>
int main() {
  char input;

  scanf("%c", &input);

  if (48 <= input && input <= 57) {
    printf("%c 는 숫자 입니다 \n", input);
  } else {
    printf("%c 는 숫자가 아닙니다 \n", input);
  }

  return 0;
}
/* 함수를 이용한 버전 */
#include <stdio.h>
int isdigit(char c); // c 가 숫자인지 아닌지 판별하는 함수
int main() {
  char input;

  scanf("%c", &input);

  if (isdigit(input)) {
    printf("%c 는 숫자 입니다 \n", input);
  } else {
    printf("%c 는 숫자가 아닙니다 \n", input);
  }

  return 0;
}
int isdigit(char c) {
  if (48 <= c && c <= 57) {
   return 1;
  } else
    return 0;
}

일단, 첫번째 소스의 경우 길이가 짧다.

다만 이해하기가 힘이든다.

if (48 <= input && input <= 57) {
}

printf 문이 없다고 했을 때

위 코드가 input 이 숫자인지

아닌지 판별하는지 쉽게 구분이 가는가?

이는 특별히 주석을 넣지 않는 한

매우 어렵다. 사실, 숫자의 경우

아스키 코드의 값이 48 에서 57 이기 때문에

위 코드를 사용하였는데 아스키 코드표를

외우고 다니지 않는 한 이해하기 상당히

難·し·い 즉

어·렵·다

그렇다면 함수를

이용한 버전을 살펴 보도록하자.

if (isdigit(input)) {
}

일단 isdigit 라는 이름만 보고도

이 함수는 input 이 숫자 인지 아닌지

(is digit? ) 판별하는 함수 임을 알 수 있다.

물론, isdigit 함수 내부에도 첫번째

소스와 동일한 과정이 진행되지만

이 함수가 무슨 작업을 하는지

알기 때문에 소스를

이해하기 훨씬 쉬워진다.

뿐만 아니라, 어떠한

문자가 숫자인지 반복해서

확인하는 경우에도 함수를

이용하면 편히 사용할 수 있다.

아직까지도 왜 함수를 써야 하는지

모르겠다고 해서 큰 문제는 아니다.

나중에 가면 자연스럽게

깨닫게 될 것.

여러가지 인자들
드디어, 함수만 세번째 기록이다

아마 이전 기록에서 배운 내용들 중

어려운 것은 없으리라 판단된다.

물론, 이번 기록의 내용도 이전까지의

내용을 잘 숙지 하셨더라면 무난하게

넘어갈 수 있으리라 생각된다.

지난번 내용을 상기해보며
지난번 내용은 잘 기억하고 있는가?

다시 한 번 요약해 보자면,

“어떠한 함수가 특정한 타입의

변수/배열의 값을 바꾸려면

함수의 인자는 반드시 타입을 가리키는

포인터 형을 이용해야 한다!” 였다.사실,

이 문장이 이해가 잘 되지 않았던 분들이

있으리라 생각된다. 하지만, 이번 기록을 보고 난다면

이 문장의 의미를 정확하게 파악할 수 있을 것다.

/* 눈 돌아가는 예제. 포인터가 가리키는 변수를 서로 바꾼다. */
#include <stdio.h>

int pswap(int **pa, int **pb);
int main() {
  int a, b;
  int *pa, *pb;

  pa = &a;
  pb = &b;

  printf("pa 가 가리키는 변수의 주소값 : %p \n", pa);
  printf("pa 의 주소값 : %p \n \n", &pa);
  printf("pb 가 가리키는 변수의 주소값 : %p \n", pb);
  printf("pb 의 주소값 : %p \n", &pb);

  printf(" ------------- 호출 -------------- \n");
  pswap(&pa, &pb);
  printf(" ------------- 호출끝 -------------- \n");

  printf("pa 가 가리키는 변수의 주소값 : %p \n", pa);
  printf("pa 의 주소값 : %p \n \n", &pa);
  printf("pb 가 가리키는 변수의 주소값 : %p \n", pb);
  printf("pb 의 주소값 : %p \n", &pb);
  return 0;
}
int pswap(int **ppa, int **ppb) {
  int *temp = *ppa;

  printf("ppa 가 가리키는 변수의 주소값 : %p \n", ppa);
  printf("ppb 가 가리키는 변수의 주소값 : %p \n", ppb);

  *ppa = *ppb;

  *ppb = temp;

  return 0;
}

성공적으로 컴파일 하면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
pa 가 가리키는 변수의 주소값 : 0x7ffc5ffd7520
pa 의 주소값 : 0x7ffc5ffd7528

pb 가 가리키는 변수의 주소값 : 0x7ffc5ffd7524
pb 의 주소값 : 0x7ffc5ffd7530
------------- 호출 --------------
ppa 가 가리키는 변수의 주소값 : 0x7ffc5ffd7528
ppb 가 가리키는 변수의 주소값 : 0x7ffc5ffd7530
------------- 호출끝 --------------
pa 가 가리키는 변수의 주소값 : 0x7ffc5ffd7524
pa 의 주소값 : 0x7ffc5ffd7528

pb 가 가리키는 변수의 주소값 : 0x7ffc5ffd7520
pb 의 주소값 : 0x7ffc5ffd7530
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\>

동지들의 출력결과는 위 사진과 다를 수 있다.

pa 가 가리키는 변수의 주소값은

(즉, pa 의 값이지요) 0x7520 였다.

(편의상 앞에 공통된 0x7ffc5ffd 는 생략합시다)

pb 가 가리키는 변수의 주소값은 0x7524 이였다.

그런데 말이다. pswap

함수를 호출하고 나니, pa 가 가리키는

변수의 주소값은 0x7524 이 되고, pb 가
가리키는 변수의 주소값은 0x7520 가 되었다.

즉, 두 포인터가 가리키는

변수가 서로 뒤바뀐 것.

이 때, 우리는 이와 같은 함수를 만들기 위해서,

인자를 어떤 형식으로 취해야 될까?

앞서 말했듯 이, 특정한 타입의 변수의 값을 바꾸려면,

특정한 타입을 가리키는 포인터로

인자를 취해야 된다고 했다.

그런데, 이 예제의 경우, 특정한 타입은 int* 타입이다.

그렇다면 int* 타입을 가리키는 포인터의 타입은?

음. 기록을 잘 복습하였다면 int** 타입

이라고 말할 수 있겠지요.

따라서, 우리는 위 이야기를

토대로 아래와 같이 함수를 정의하였다.

int pswap(int **ppa, int **ppb)

상당히, 잘한 것이다.

이제, 함수의 몸체를 봐보도록하자.

int pswap(int **ppa, int **ppb) {
  int *temp = *ppa;

  printf("ppa 가 가리키는 변수의 주소값 : %p \n", ppa);
  printf("ppb 가 가리키는 변수의 주소값 : %p \n", ppb);

  *ppa = *ppb;
  *ppb = temp;

  return 0;
}

일단, int* 형의 temp 변수를 만들어서

*ppa 의 값을 저장하고 있다.

그런데, *ppa 의 값은 무엇일까?

만일 우리가 위 예제 처럼 pswap

함수를 호출하였다고 하면, ppa 는

pa 를 가리키고 있고, ppb 는

pb 를 가리키고 있겠지.

따라서, *ppa 라 하면 pa 의 값을 이야기 한다.

그런데, pa 는 int* 형이므로, pa 의 값을 보관하는

변수는 반드시 int* 이여야 되겠다.

따라서, 우리는 int* 형의 temp 변수를 정의하였다.

그 아래의 내용은 이전에 만들어 보았던

swap 함수와 동일하다.

printf("ppa 가 가리키는 변수의 주소값 : %p \n", ppa);
printf("ppb 가 가리키는 변수의 주소값 : %p \n", ppb);

그렇다면 우리는

위 두개의 printf 문장에서

어떤 결과가

출력될 지 예측 가능하다.

위 예제에서

ppa 가 pa 를 가리키고

있으므로 ppa 의

값을 출력하면

pa 의 주소값이 나오고,

ppb 도 마찬가지로

나오겠다. 위 출력결과에서

실제로 같다는 것을

확인할 수 있다. 어떤가. pswap

함수가 이해가 되지않는가?

위 과정을 그림으로 표현하면 아래와 같다.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/12.png

그렇다면, 이번에는 이차원 배열을

인자로 받는 함수에 대해서 생각해 보도록 하자.

/* 2 차원 배열의 각 원소를 1 씩 증가시키는 함수 */
#include <stdio.h>
/* 열의 개수가 2 개인 이차원 배열과, 총 행의 수를 인자로 받는다. */
int add1_element(int (*arr)[2], int row);
int main() {
  int arr[3][2];
  int i, j;

 for (i = 0; i < 3; i++) {
   for (j = 0; j < 2; j++) {
     scanf("%d", &arr[i][j]);
   }
}

add1_element(arr, 3);

 for (i = 0; i < 3; i++) {
   for (j = 0; j < 2; j++) {
      printf("arr[%d][%d] : %d \n", i, j, arr[i][j]);
    }
  }
  return 0;
}
int add1_element(int (*arr)[2], int row) {
  int i, j;
  for (i = 0; i < row; i++) {
    for (j = 0; j < 2; j++) {
      arr[i][j]++;
    }
  }

  return 0;
}

성공적으로 컴파일 하였다면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F> ./test
1 2 3 4 5 6
arr[0][0] : 2
arr[0][1] : 3
arr[1][0] : 4
arr[1][1] : 5
arr[2][0] : 6
arr[2][1] : 7
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F>

잘 실행된다. 일단, 함수의 정의

부분 부터 살펴보도록하자.

int add1_element(int (*arr)[2], int row) {
  int i, j;
  for (i = 0; i < row; i++) {
    for (j = 0; j < 2; j++) {
      arr[i][j]++;
   }
 }

 return 0;
}

이 함수는 인자를

두 개 받고 있는데

하나는 열의 개수가

2 개인 이차원 배열을

가리키는 포인터이고,

하나는 함수의 행의

수를 받는 인자이다.

for (i = 0; i < row; i++) {
  for (j = 0; j < 2; j++) 
    arr[i][j]++;
   }
}

우리는 row 를 통해 이

이차원배열의 행의 개수를 알 수 있고,

열의 개수는 이미 알고 있으므로

(배열포인터에서)

각 원소를 1 씩 증가시키는

작업을 시행할 수 있게된다.

위와 같이 말이죠. 우리는

포인터를 잘 배워서 헷갈릴 문제는 없지만

많은 사람들에게 다음과 같이 인자를

받는것이 어렵게 느껴진다.

int add1_element(int (*arr)[2], int row)

그래서, 오직 함수의

인자의 경우에서만

위 형태의 인자를

다음과 같이도 표현할 수 있다.

int add1_element(int arr[][2], int row)

이는 오직 함수의 인자에서만

적용되는 것이다. 만일

int parr[][3] = arr;

와 같은 문장을 이용했더라면

컴퓨터는 parr 을 ’열의 개수가 3 개이고

행의 개수는 정해지지 않는배열’ 이라

생각해서 오류를 내게 된다.

(만일 행의 개수를 생략했다면)

(배열을 정의시 초기화도)

(해주어야 되는데는)

(위는 그러지 않으므로) 암튼,

함수의 인자에서만 가능한

형태라는 것을 기억해

주시기 바란다.

덧붙여서 응용력을 살짝 이용하면

다차원 배열의 인자도 정의할 수 있다.

예를 들어서

int multi(int (*arr)[3][2][5]) {
  arr[1][1][1][1] = 1;
  return 0;
}

혹은

int multi(int arr[][3][2][5]) {
  arr[1][1][1][1] = 1;
  return 0;
}

로 하면 된다.

상수인 인자

/* 상수를 인자로 받아들이기 */
#include <stdio.h>
int read_val(const int val);
int main() {
  int a;
  scanf("%d", &a);
  read_val(a);
  return 0;
}
int read_val(const int val) {
  val = 5; // 허용되지 않는다.
  return 0;
}

컴파일 하게 되면 아래와 같은 오류를 만나게 된다.

 컴파일 오류
 error C2166: l-value가 const 개체를 지정합니다.

이건 우리가 이전에 상수의 값을 변경하려고

했었을 때 만났던 오류 인것 같다. 맞다.

우리가 val 을 const int 로 선언하였기

때문에 함수를 호출 할 때,

val 의 값은 인자로 전달된값으로

초기화 되고 결코 바뀌지 않는다.

즉, val 은 a 의 값으로 상수로 초기화 된 것.

따라서, 함수 내부에서 val = 5 와 같이

val 의 값을 바꾸려 한다면 오류가 나겠지.

왜냐하면 val 은 상수이다.

상수로 인자를 받아들이는

경우 대부분은 함수를 호출 해도

그 인자의 값이 바뀌지 않는

경우에 자주 사용합니다만,

자세한 내용은 나중에 좀더

다루도록 하겠다.

함수 포인터

아마, ’함수 포인터’ 라는 말을 들었을 때는

조금 의아하는 감이 있지 않을까 한다.

함수 포인터라니, 함수를 가리킨다는 것인가?

그럼, 함수가 메모리 상에 있다는 건가? 맞다.

사실, 프로그램의 코드 자체가 메모리 상에

존재합니다. 우리는 이전에 컴파일러가 하는

작업이 바로 우리가 ’인간에 친숙한 언어’ 로

쓰여진 프로그램 코드를 ’컴퓨터에 친숙한 언어,

즉 수 데이터들’ 로 바꿔주어

실행 파일을 생성한다고 배웠다.

이렇게, 바뀐 실행 파일을 실행하게 되면

프로그램의 수 코드가

메모리 상에 올라가게 된다.

다시말해, 메모리 상에

함수의 코드가 들어간다는 것이다.

이때, 변수를 가리키는 포인터 처럼

함수 포인터는 메모리 상에 올라간

함수의 시작 주소를 가리키는
역할을 하게 된다.

그렇다면, 함수 포인터가

함수를 가리키기 위해서는

그 함수의 시작 주소값을

알아야 한다. 그런데,

배열과 마찬가지로 함수의 이름이

바로 함수의 시작 주소값을 나타낸다.

/* 함수 포인터 */
#include <stdio.h>
int max(int a, int b);
int main() {
  int a, b;
  int (*pmax)(int, int);
  pmax = max;

  scanf("%d %d", &a, &b);
  printf("max(a,b) : %d \n", max(a, b));
  printf("pmax(a,b) : %d \n", pmax(a, b));

  return 0;
}
int max(int a, int b) {
  if (a > b)
    return a;
  else
    return b;
  return 0;
}

성공적으로 컴파일 했다면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
10 15
max(a,b) : 15
pmax(a,b) : 15
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

역시 우리가 예상했던 데로 잘

흘러가는 것 같다.

함수 포인터는 어떻게 정의하는지

살펴보도록하자.

int (*pmax)(int, int);

일단, 위는 함수 포인터 pmax 의 정의 입다.

위 정의를 보고 다음과 같은 사실을 알 수 있다.

’이 함수 포인터 pmax 는 함수의

리턴값이 int 형이고, 인자

두 개가 각각 int 인 함수를 가리키는구나!’

. 따라서, 우리는 pmax 함수 포인터로

특정한 함수를 가리킬 때,

그 함수는 반드시 pmax 의

정의와 일치해야 한다.

함수 포인터의 일반적인

정의는 다음과 같다.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/3.png

이제 pmax 가 max를 가리키게 되는

부분을 보도록하자.

pmax = max;

max 함수를 살펴보면 pmax 의

정의와 일치하므로, max 함수의 시작

주소값을 pmax 에 대입할 수 있게 된다.

이 때, 앞에서도 말했듯이

특정한 함수의 시작 주소값을

알려면 그냥 함수 이름을

넣어주면 된다.

pmax = &max 와

같은 형식은 틀린 것.

printf("max(a,b) : %d \n", max(a, b));
printf("pmax(a,b) : %d \n", pmax(a, b));

pmax 는 이제 max 함수를 가리키므로

pmax 를 통해 max 함수가 할 수 있었던

모든 작업들을 할 수 있게 된다.

이때도 역시 그냥

pmax 를 max 처럼

이용하면 된다. 이는 배열에서

int arr[3];
int *p = arr;
arr[2]; // p[2] 와 정확히 일치
p[2];

와 같이 arr[2] 와 p[2] 가 동일한 것과 같다.

아무튼 max(a,b) 를 하나 pmax(a,b) 를

하나 결과는 똑같이 나오게 된다.

/* 함수 포인터 */
#include <stdio.h>
int max(int a, int b);
int donothing(int c, int k);
int main() {
  int a, b;
  int (*pfunc)(int, int);
  pfunc = max;

  scanf("%d %d", &a, &b);
  printf("max(a,b) : %d \n", max(a, b));
  printf("pfunc(a,b) : %d \n", pfunc(a, b));

  pfunc = donothing;
  printf("donothing(1,1) : %d \n", donothing(1, 1));
  printf("pfunc(1,1) : %d \n", pfunc(1, 1));
  return 0;
}
int max(int a, int b) {
  if (a > b)
    return a;
  else
    return b;
  return 0;
}
int donothing(int c, int k) { return 1; }

성공적으로 컴파일 했다면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
10 123
max(a,b) : 123
pfunc(a,b) : 123
donothing(1,1) : 1
pfunc(1,1) : 1
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\>

일단, 우리는 이전의 예제와

동일한 형태의 함수 포인터

pfunc 을 정의하였다.

int (*pfunc)(int, int);

이는 ’리턴형이 int 이고

두 개의 인자

각각의 포인터 형이 int 인

함수를 가리킨다.

그런데, donothing

함수와 max 함수 모두

이 조건을 만족하고 있다.

즉, 이들은 인자의 변수들도

다루고 하는 일도 다르지만

리턴값이 int 로

같고 두 개의 인자 모두

int 이므로 pfunc 이 이 두개의 함수를

가리킬 수 있는 것.

pfunc = max;
scanf("%d %d", &a, &b);
printf("max(a,b) : %d \n", max(a, b));
printf("pfunc(a,b) : %d \n", pfunc(a, b));
pfunc = donothing;
printf("donothing(1,1) : %d \n", donothing(1, 1));
printf("pfunc(1,1) : %d \n", pfunc(1, 1));

따라서, 위와 같이 했을 때

pfunc 이, 자기가 가리키는

함수의 역할을

제대로 하고 있다는 것을

알 수 있었다. 그런데.

함수 포인터를 만들 때,

인자의 형이 무엇인지

알기 힘든 경우가 종종 있다.

예를 들어 아래와 같은

함수의 원형을 본다.

int increase(int (*arr)[3], int row)

두 번째 인자의 형은

int 라는 것은 알겠는데

첫번째 인자의 형은

도대체 뭘까요? 사실,

간단하다. 특정한 타입의

인자를 판별하는

일은 단순히변수의 이름만을

빼버리면 된다. 따라서,

첫번째 인자의 형은 int (*)[3] 이다.

즉, increase 함수를 가리키는

함수 포인터의 원형은

아래와 같다.

int (*pfunc)(int (*)[3], int);

간단하지 않는가?

이것을 이전에

이차원 배열을 인자로 받았던

함수에 적용시켜 보면 정확히

작동한다는것을 알 수 있다.

생각해볼 문제에 대한 아이디어
친애하는 동지 여러분.

이전 기록의 예제가 중요한 만큼,

예제에 좀더 쉽게 접근할 수 있는

아이디어에 관해서

짧은 힌트 형식으로

기록을 작성하도록 하겠다.

물론, 언제까지나 힌트일 뿐 완전한

코드는 여러분이 완성시켜야 한다.

생각해볼 문제 1

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/4.png

일단, 여러분이 직면했을 가장 큰 문제는

’어떻게 정렬하는 프로그램’ 을

만드느냐 이였겠는가?.

정렬을 하는 방법

(보통 알고리즘 이라 표현합니다)에는

여러가지가 있다.

가장 직관적으로 이해하기 쉬운 것은

버블 정렬(Bubble sorting) 이라 불리는

것인데 컴퓨터가 다음과 같은 규칙을

통해 작업을 한다.

예를 들어 5, 1, 4, 2, 8 을 정렬한다고 하자.

( 5 1 4 2 8 ) -> ( 1 5 4 2 8 )

버블 정렬 알고리즘은 처음

두 개의 원소를 비교해 왼쪽이 크면

자리를 바꾼다. 이 경우, 5 가 1
보다 더 크기 때문에 1 과 5 의

자리를 바꾸었다.

( 1 5 4 2 8 ) -> ( 1 4 5 2 8 )

그 다음 두 원소를 비교한다.

이번에도 5 가 더 크므로 4 와 자리를 바꾼다.

( 1 4 5 2 8 ) -> ( 1 4 2 5 8 )

그 다음 두 원소 5,2 를 비교한다.

이번에도 5 가 더 크므로 자리를 바꾼다.

( 1 4 2 5 8 ) -> ( 1 4 2 5 8 )

그 다음 두 원소 5,8 를 비교한다.

이번에는 오른쪽이 더 크므로 자리를

안바꿔도 된다. 끝 원소 까지 비교하였다면,

가장 큰 원소가 가장 오른쪽에 위치하게 된다.

(왜 그런지는 잘 알겠지요?)

( 1 4 2 5 8 ) -> ( 1 4 2 5 8 )

이제 다시 처음부터 두 원소를

골라 비교해보자.

( 1 4 2 5 8 ) -> ( 1 2 4 5 8 )

위와 같은 작업들을 쭉 시행한다.

( 1 2 4 5 8 ) -> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) -> ( 1 2 4 5 8 )

마지막까지 비교하였다면

다시 처음으로 간다.

그렇다면 이를 언제까지 반복해야 할까?

더이상 자리가 바뀌는 원소들이

없을 때 까지 해야 하지않겠는가?

( 1 2 4 5 8 ) -> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) -> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) -> ( 1 2 4 5 8 )
( 1 2 4 5 8 ) -> ( 1 2 4 5 8 )

위 작업을 완료하였다면,

컴퓨터는 더이상 자리가 바뀌는

원소들이 없다는 것을 알아채고 정렬을

그만하게 된다

음, 역시 정확하게

1,2,4,5,8 로 정렬이 되었다.

일단, 위 버블 정렬 알고리즘을

C 언어에서 구현하기 위해서

저는 여러분들이 다음과 같은 함수를

만들어주기를 원한다.

Bubble_sort(int* arr, int num_elements), swap(int* pele)

Bubble_sort 함수는 말그대로

정렬을 하는 함수 이다.

이 때, num_elements 로 arr 이 가리키는

배열의 원소 개수를 알아야 하겠다

그리고 swap 함수는 pele 가 가리키는

원소와 그 다음 원소를 서로 뒤바꿔주는

함수다. 따라서 Bubble_sort

함수가 pele 함수를 호출해야
되겠다 사실 버블 정렬은 매우

비효율적인 정렬 알고리즘 이다.

하지만 구현하가 매우

단순하여 정렬해야

될 것이 작은 경우에는

이를 자주 이용하게 된다.

정렬 알고리즘에 대해

궁금하신 분들은 여기

클릭해서 정렬 알고리즘의

세상에 입성 해 보도록하자.

생각해볼 문제 2

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/5.png

유클리드 호제법은 어떠한 두 수의

최대공약수를 계산하는데

쓰이는 방법이다.

방법 자체는 간단하다.

1. 두 수를 m 과 n 이라 하자. (m > n)

2. m 을 n 으로 나눈

   나머지를 계산한다. (m % n)

3. m % n 이 0 이라면 n 값이 맨 처음

  두 수의 최대공약수이다. (종료)

4. m % n 이 0 이 아니였다면,

   m % n 과 n 중 큰 것을 m,

   작은 것을 n 이라 한 후 ① 로 돌아간다.

예를 들어서 63 와 35 의

최대공약수를 구한다고 해보도록하자.

그렇다면 유클리드 호제법을 이용하면

아래와 같은 과정을 거친다.

1. m = 63, n = 35

2. 63 % 35 = 28

3. 28 이 0 이 아니므로, 28 과 35 를 비교하는데

            35 가 크므로 m = 35, n = 28

4. m = 35, n = 28

5. 35 % 28 = 7

6. 7 이 0 이 아니므로, 7 과 28 을 비교,

             28 이 크므로 m = 28, n = 7

7. m = 28, n = 7

8. 28 % 7 = 0

9. 0 이므로, n 값 ( 7 ) 이 맨 처음 두 수의 최대공약수.

             즉, 63 과 35 의 최대공약수는 7 이다

사실, 왜 위 과정을 거치면

두 수의 최대공약수가

나오는지에 대한 증명은 간단하다.

수학적 지식이 없다면

이해가 안갈 수 도 있지만,

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/13.png

때문에 그렇다. 유클리드 호제법은

두 개의 수의 최대공약수를

찾는데에만 사용하였지만 이를

어떻게 N 개의 수의 공통된 최대공약수를

찾는데 응용할 수 있을까? 답은 간단하다.

처음 두 수의 최대 공약수를 구한다.

그리고, 그 다음수와

구한 최대공약수의

최대 공약수를 계산한다.

그리고 이를 쭉 반복한다.

예를 들어서

18, 24, 40, 60의

최대공약수를

구한다고 해봅시다.

18 과 24 의 최대공약수는

6다. 그러면 이제 6 과 40 의

최대공약수를 계산한다.

이는 2 이다. 그러면 이제 2 와 60

의 최대공약수를 계산한다.

이는 2 이다. 따라서,

이 4 개의 수의 공통된

최대공약수는 2 가된다.

여기에도 수학적 원리가 있지만

간단하기 때문에 넘어가도록 하겠다.

동지 여러분이 생각해보도록 하자

생각해볼 문제 4

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/6.png

여러분은 ’자기 자신을 호출한다’ 에

대해서 많은 고민을 많이 하셨을 것이다.

일단, 아래의 코드를

직접 컴파일 후 실행해보시라

#include <stdio.h>
int recursive(int n) {
  printf("난 인자가 %d 에요! \n", n);
  if (n <= 0) return 0;

  recursive(0);
}
int main() {
  recursive(3);
  return 0;
}

성공적으로 컴파일 했다면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
난 인자가 3 에요! 
난 인자가 0 에요! 
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

흠. 어느 정도 예측 가능했던 결과.

그렇다면, 아래의 코드는 어떨까?

#include <stdio.h>
int recursive(int n) {
  printf("난 인자가 %d 에요! \n", n);
  if (n <= 0) return 0;

  recursive(n - 1);

  return 0;
}
int main() {
  recursive(3);
  return 0;
}

성공적으로 컴파일 했다면

PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> gcc -o test test.c
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> ./test
난 인자가 3 에요!
난 인자가 2 에요!
난 인자가 1 에요!
난 인자가 0 에요!
PS D:\■■■■■_fFF■\■■■g■■■m■■■\F\> 

일단, 컴퓨터 상에서

위 코드는 아래의 순서로 실행된다.

1. main 함수에서 recursive(3) 을 호출함
2. recursive 에서 n = 3 ’난 인자가 3 이에요’ 를 출력
3. n 이 0 이하가 아니므로 넘어감
4. recursive(n-1) 즉 recursive(2) 를 호출
5. recursive 에서 n = 2 ’난 ∼ 2 ∼ ’ 를 출력 (∼ 는 생략)
6. n 이 0 이하가 아니므로 넘어감
7. recursive(n-1) 즉 recursive(1) 을 호출
8. recursive 에서 n = 1, ’ ∼ 1 ∼ ’ 를 출력
9. n 이 0 이하가 아니므로 넘어감
10. recursive(n-1) 즉 recursive(0) 을 호출
11. recursisve 에서 n = 0, ’∼ 0 ∼’ 을 출력
12. n 이 0 이하이므로 return 0;
13. n = 1 이였던 recursive 에서 return 0;
14. n = 2 이였던 recursive 에서 return 0;
15. n = 3 이였던 recursive 에서 return 0;
16. main 함수로 돌아감.

사실, 위 작업이 이해가

잘 안되는 수도 있습니다만..

나중에 변수의 정의

범위에 대해 배우게 된다면

좀더 쉽게 이해할 수 있으실 것이다.

아무튼 위 사실을 활용해서

1 부터 n 까지 곱하는

재귀 함수를 만들어보시라

(위와 같이 자기 자신을 호출하는 함수를)

(재귀함수, 영어)

(recursive function 이라합니다.)

생각해볼 문제 4

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/7.png

이 문제는 그다지 어려운

아이디어 같은 것이

필요한 것이 아니므로

생략하도록 하겠다. 사실,

난이도는 중하 정도 된다.

생각해볼 문제 5

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/8.png

사실 이 문제는 잘못낸 문제다.

물론 아이디어는 충분히 구현할 수 있지만

동지 여러분은 아직 ’문자열’에

대한 개념이 없기 때문에 정확하게

구현할 수 는 없지만 생각 정도는 할 수 있다.

일단 위 프로그램을 어떻게 만들 것인지에

대해 생각해 놓은 것을 보도록하자.

나중에 필요한 개념을 다 배우고나면

하실 수 있을 것이다.

(참고적으로 문제에 조건)

(하나가 빠졌는데 N,M 은 모두)

(36 이하 입니다. 왜냐하면 숫자를 이용시)

(0,1,…,9,A,B,.. 로 사용하는데)

(알파벳이 26 개이므로 총 36 진수)

(까지 나타낼 수 있거든요)

•  사용자로 부터 무슨 진법에서 무슨 진법으로
    변환할 지 입력받습니다. (N,M 입력)
•   N 진법의 수를 입력받습니다.
•   그 수를 각 자리로 분해해 int 배열에
    값을 넣습니다.  이 때, 값은 십진수로 넣습니다.
    예를 들어서 16 진법으로 7AE 를 입력받았다면
    digit[0] = 14, digit[1] = 10, digit
    [2] = 7 로 넣으면 됩니다. 참고로,
    올바르지 않은 숫자가 사용되면
    종료합니다. (예를 들어서 2 진법인데)
    (3 이란 숫자를 사용함)
•  이 수를 십진수로 변환합니다.
    (NtoDec 함수 제작 요망)
•  이 십진수를 다시 M 진법의 수로
   변환합니다.
   (DectoM 함수 제작 요망)

물론, 이 문제는 꼭 안푸셔도 된다.

나중에 개념을 좀더 배우다

보면 풀 수 있는 스킬들을

습득하실것이다.

생각해볼 문제 6

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/9.png

에라토스테네스의 체.. 이름이 정말 어렵다.

사실 이는 간단하다. 말그대로 숫자들만 걸러내는

체(sieve) 인데, 아래와 같은 방식으로

숫자를 걸러내어 소수들을 찾는다.

• 수들을 쭉 쓴다.
• 2 의 배수들을 다 지운다.
• 2 에서 가장 가까운 안지워진 수를 찾는다.
아마도 3 일 것이다. (소수 찾았다!)
• 3 의 배수들을 다 지운다.
• 3 에서 가장 가까운 안지워진 수를 찾는다.
아마도 5 일 것이다. (소수 찾았다!)
• 5 의 배수들을 다 지운다.
• 5 에서 가장 가까운 안지워진 수를 찾는다.
아마도 7 일 것이다. (소수 찾았다!)
• 7 의 배수들을 다 지운다.
• 그 뒤로 쭈우욱 같은 작업을 실시

위키피디아에서 이 과정을 알기 싶게

애니메이션으로 나타낸 자료가

있으니 보시기 바란다.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/14.png

생각해볼 문제 7

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%83%88%20%ED%8F%B4%EB%8D%94/10.png

int 자료형은 대략 42 억,

그러니까 10 자리 정도의

수 밖에 사용할 수 없었다.

그런데 문제에서 요구하는 것은

무려 1000 자리나!

이걸 도대체 어떻게 하라는 말일까.

사실, 간단하다. 크기가 1000 인

char 배열을 만들어서 배열의 한 원소를

수의 한 자리라고 생각하면 된다.

이게 도대체 무슨 말일까?

예를 들어서 char BigNum[1000];

을 정의하였다고 할 때, 사용자가

만일 123456 을 입력하였다면

BigNum[999] = 6,

BigNum[998] = 5,

… BigNum[994] = 1 로 하면 된다.

이러한 형식의 두 수를 더하는

연산을 하기 위해서는 각 원소를

더한 뒤, 받아올림이

있으면 그 다음 원소에

더해주고 하는 방식으로

쭉 나가면 된다.

어떤가? 간단하지 않는가?

곱셈은 쉽게 하면 덧셈을 여러번 반복해서

호출하는 것으로 해결될 수 있지만 연산

속도가 느리므로 인간이 곱셈하는 방식으로

하는 것을 권한다. 그렇다면

문제는 나눗셈인데 나눗셈 역시 뺄셈을
반복하는 것으로 해결 될 수 있지만

역시 느리므로,

인간이 나눗셈 하는

방식으로 계산하는 함수를
만들어보시라.

그럼. 행운을 빈다.

https://raw.githubusercontent.com/KuraiLuna/KuraiLuna.github.io/master/practical/C/%EC%94%B9%EC%96%B4%EB%A8%B9%EB%8A%94%20C%20%EC%96%B8%EC%96%B4/Chapter-11-%ED%95%A8%EC%88%98/%EC%98%A4%EB%8A%98%EB%8F%84_%EC%A2%8B%EC%9D%80_%ED%95%98%EB%A3%A8.png