아임'준
[C언어 기초] 함수 본문
C언어 / 함수 / void / return
*해당 글에 있는 코드들은 모두 복사해서 사용할 수 있게끔 만들어두었습니다. 주석만 보고 이해하려고 하지 말고 코드를 복사해서 직접 실행하며 그 결과를 통해 이해하고자 하면 더 도움이 될것입니다. 물론 그냥 복사 붙여넣기 하는 것보다 본인이 직접 코드를 타이핑하는 것이 이해와 후에 사용할 때에 더 큰 도움이 될 것이니 이 점 명심하고 공부해주시기 바랍니다.
오늘은 함수에 대해서 공부해보도록 하겠다. 지난 시간에 진행한 포인터에 비하면 아주 쉬운 내용일 것이라 생각이 된다.
프로그래밍에서 함수는 수학의 함수와는 다르다. 예전에도 설명한 바가 있지만 main 또한 함수이다.
내 의견을 넣어 간단하게 정의내려보자면 함수는 '어떤 기능을 하는 일정한 틀' 이라고 표현할 수 있을 것 같다.
printf 함수를 살펴보자. 이 printf 함수 또한 정해진 형식에 맞게 우리가 입력을 해주면 특정한 기능을 하는 함수이다.
scanf도 마찬가지이다. 정해진 형식에 맞게 입력해주면 scan을 진행해주는 함수이다.
이제 이러한 함수를 우리 손으로 우리가 직접 만들 수 있는 것이다.
함수가 없어도 코드는 짤 수 있다. 하지만 함수를 사용하는 이유는 다음과 같다.
누군가의 이름을 물어보고 "안녕하세요 00님" 이라는 문장을 출력하는 코드가 있다고 해보자.
#include <stdio.h>
#include <stdlib.h>
int main(){
char name[20];
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
printf("안녕하세요 %s님\n",name);
}
근데 이걸 여러번 반복한다 해보자 5명의 이름을 물어보고 안녕하세요를 출력한다고 해보자. 반복문을 사용하지 않는다고 했을 때 중복인 표현이 보인다.
#include <stdio.h>
#include <stdlib.h>
int main(){
char name[20];
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
printf("안녕하세요 %s님\n",name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
printf("안녕하세요 %s님\n",name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
printf("안녕하세요 %s님\n",name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
printf("안녕하세요 %s님\n",name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
printf("안녕하세요 %s님\n",name);
}
물론 반복문을 써도 깔끔해진다. 하지만 그것을 더 깔끔하게 해주는 것이 바로 함수이다.
필요할 때 호출하여 사용할 수 있고 고치고 싶은 부분은 함수만 고치면 된다. 예를 들어 내가 위의 코드에서 '안녕하세요 00님'이 아니라 welcome 00' 이라고만 출력한다고 생각해보자. 그럴 경우 우리는 welcome으로 고치기 위해서 5개의 printf문을 고쳐야한다. 지금 5개 정도야 고칠 수 있지만 후에 코드의 길이가 길어진다거나 반복하는 횟수가 늘어난다면 이를 고치기는 쉽지 않을 것이다 그래서 쓰는 것이 함수이다. 아래를 보자
#include <stdio.h>
#include <stdlib.h>
void ask_welcome(char *a){
printf("안녕하세요 %s님\n",a);
}
int main(){
char name[20];
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
}
아래 ask_welcome은 내가 만든 사용자 정의 함수이다. 이제 '안녕하세요 00님'에서 'welcome 00'으로 고치고 싶다면 제일 위의 함수만 고쳐주면 된다.
#include <stdio.h>
#include <stdlib.h>
void ask_welcome(char *a){
printf("welcome %s\n",a);
}
int main(){
char name[20];
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
printf("당신의 이름은 무엇입니까?");
scanf("%s",name);
ask_welcome(name);
}
강력하지 않은 예시를 들어서 잘 느끼지 못할 수도 있지만 함수를 선언하고 사용하는 것은 코드의 길이가 길어질수록 매우 편하다.
반복되는 기능의 일부분을 고치고 싶을 때 함수의 일부분만 고치면 되므로 더 유지보수를 쉽게 할 수 있으며 코드의 길이가 훨씬 짧아지게 할 수도 있다.
그럼 함수를 어떻게 생성할 수 있을까? 다음과 같은 형식을 통해 생성할 수 있다.
(자료형) (함수 이름)((매개변수 선언)){
(함수 내용)
}
혹시 0.1 글에서 main함수를 설명할 때 줄그어져있던 다음 문장을 읽어본 사람이 있는가?

줄이 그어져 읽기 어렵지만 사실 이때 함수에 대한 대략적인 설명을 한 것이다. 해당 내용을 보면 반환, 매개 인자, 등의 단어가 들어가있다.
이를 차근차근 알아보도록 하겠다.
먼저 반환값이라는게 무엇인지 알아야 한다.
반환값은 쉽게 말해서 함수가 끝날 때 어떤 값을 주는지 혹은 내뱉는지에 대한 단어이다. 함수에서 반환은 이루어질 수도 있고 안 이루어질 수도 있다. 여러분의 선택에 따라 반환값을 만들지 말지를 정하면 된다.
함수가 어떤 값을 반환한다는 것을 더 잘 이해하기 위해 지난 시간에 배운 rand() 함수에 대해서 말해보도록 하겠다. rand()%범위 라는 구문을 통해 범위 내의 난수를 생성할 수 있었던 이 함수는 사실 생성한 난수를 반환하는 함수이다. 그렇기에 rand()%범위 라는 구문을 통해 나온 난수는 다른 변수에 저장하여 사용하는 것이다.
그때도 같은 예시를 들어 설명했지만 아래 예시를 보자.
a=rand()%100; //과 같은 변수에 저장하는 형식이 아닌
rand()%100; //과 같이 그냥 적은 것은
3;
77;
//과 다른 점이 없다
위의 뜻은 rand함수는 범위 내의 난수를 생성하여 '반환'하는 함수이므로 rand()%100이 어떤 값을 뱉어내는 것이기에 그 값을 어느 변수에 저장하지 않고 그냥 쓴다면 사실상 그냥 숫자; 한것과 다르지 않다는 것이다. 이렇게 어떤 함수가 종료되고 그 함수에서 어떤 값을 뱉어내는 것이 바로 반환값이다.
그럼 반환은 어떻게 해야할까? 반환은 return 이라는 구문을 통해 이루어진다.
예를 들어 정수 두개를 입력받아 두 정수의 합을 반환하는 함수를 만들었다고 생각해보자.
int plus(int a, int b){
return a+b;
}
지금 설명에서 중요한 부분은 return 구문이다. 위의 나머지는 일단 정수 하나는 a 나머지 하나는 b라고 생각하면 된다. 그럼 두 정수의 합은 a+b이고 이 a+b 값을 함수를 통해 return(반환)한다는 것이다.
return을 하지 않는 함수도 있을 수 있다. 함수의 목적이 무엇인지에 따라 본인이 함수가 어떤 값을 반환하게 할 것인지 아니면 반환하지 않게 할 것인지를 결정할 수 있다. 예를 들어 반환한는 값 없이 단순 a+b의 값을 출력하기만을 목적으로 하는 함수라면 다음과 같이 return 문 없이도 작성할 수 있다.
void plus(int a, int b){
printf("%d",a+b);
}
여기서 한가지 알아두고 넘어갈 내용이 있는데 함수 내부에서 return문이 실행되면 함수를 탈출하게 된다. 이는 return 뒤의 구문들은 실행되지 않는다는 뜻이며 return으로 적절하게 함수를 제어할 수 있다는 뜻이다. 이는 나중에 다시 알아보도록 하자.
이제 다시 위의 예시를 봐보자.
int plus(int a, int b){
return a+b;
}
이번에는 함수 선언 시 자료형에 대해서 설명을 하도록 하겠다. 함수 선언시 사용해야하는 자료형을 선택하는 것은 생각보다 쉽다.
반환하는 값이 있는 함수일 시 그 반환값의 자료형을 함수의 자료형으로 설정해주면 된다. 위의 예시의 경우 정수인 a와 정수인 b를 더한 값은 정수이므로 이 함수를 선언할 때 사용해야할 자료형은 정수인 int형이다.
그렇다면 반환값이 없는 함수에서는 어떤 자료형을 사용해야할까? 바로 void 라는 자료형을 사용해야한다. void는 빈 이라는 뜻을 가진 단어이다. 이미 위에 출력만을 위한 함수를 예시로 들며 void plus라고 정의한 바가 있다.
void plus(int a, int b){
printf("%d",a+b);
}
그럼 (매개변수 선언)이라고 적혀있는 곳은 어떻게 쓰는 것일까? 위에서 당장 a와 b에 각각 하나의 정수가 들어간다고 이해하고 넘어가라고 적어놓았다. 매개변수는 함수에 필요한 인자를 받아올 것이라고 생각하면 된다.
위의 경우 두개의 정수를 받아서 그 정수들의 합을 구하는 것이니 두개의 정수를 받는 작업이 필요하다. 그래서 각각의 정수를 a와 b라는 자리에 받아서 그 과정을 진행한 것이며 a와 b는 정수이니 선언에 int 자료형을 사용하였다. 물론 이름이 a나 b가 아니라 num1, num2 등 원하는 변수 명을 사용하면 된다.
여기서 조금 더 설명하고 가야할 부분이 있다. 일단 헷갈릴 수 있는 배열 같은 경우 int a[]나 int *a 등을 이용하여 매개변수 쪽에 넣어줄 수 있다. 해당 이유는 포인터 편에서 배운 *의 역할에 대해서 생각해보면 이해해볼 수 있을 것이다. 또 함수가 어떻게 매개변수에 넣은 값을 처리하는지에 대해 이야기도 해줘야 할 것 같은데 이는 함수를 사용하는 방법까지 공부한 후 이야기해보도록 하자.
함수는 이미 만들어진 도구라고 생각하면 된다. 그리고 그 도구들에 넣는 재료들의 역할이 다 정해져 있고 그 모든 과정을 함수 내부에 코드로 작성해주면 우리가 지어준 재료들의 이름에 맞게 함수가 일을 수행하는 것이다. 이렇게 함수를 만드는 방법에 대해서 배워보았다.
함수를 만드는 방법을 배웠으면 이제 함수를 사용하는 방법에 대해서 알아야한다. 함수 사용 방법도 간단하다.
위의 plus 함수들을 봐보자.
#include<stdio.h>
int plus(int a, int b){
return a+b;
}
void plus_p(int a, int b){
printf("%d\n",a+b);
}
int main(){
int num1=1;
int num2=5;
int res = plus(num1,num2);
printf("%d\n",res);
printf("%d\n",plus(res,num2));
plus_p(res,num1);
}
다음과 같이 함수를 사용할 수 있다. 반환값이 있는 함수라면 적절하게 다른 곳에 반환값을 대입해주며 원하는 용도로 사용하면 되며 함수를 실행하는 방식은 함수이름(인자) 와 같은 형식으로 실행할 수 있다.
인자란 무엇일까? 인자는 매개변수에 들어갈 실질적인 값을 의미한다. 위의 경우 plus와 plus_p 함수의 매개 변수는 둘 다 a와 b라고 명시해주었다. 그러면 이제 a와 b에 들어가는 실제 수들이 인자이다. 첫번째 함수 사용처인 res 변수의 경우 num1 과 num2가 그 인자라고 할 수 있겠으며 두번째로 프린트문 안에 있는 plus의 경우 res, num2가 인자이며 마지막으로 plus_p의 경우 res와 num1이 인자일 것이다.
여기서 알아두고 갈 점이 있다. 씨언어는 함수를 위에서부터 읽으므로 함수가 다른 곳에서 쓰이는 경우 자기가 쓰이는 곳보다 먼저 함수가 선언돼있어야 한다. 참고로 굳이 함수를 꼭 앞에 적어주지 않고 아래와 같이 적어줄 수도 있다.
#include<stdio.h>
int plus(int a, int b);
void plus_p(int a, int b){
printf("%d\n",a+b);
}
int main(){
int num1=1;
int num2=5;
int res = plus(num1,num2);
printf("%d\n",res);
printf("%d\n",plus(res,num2));
plus_p(res,num1);
}
int plus(int a, int b){
return a+b;
}
맨 위의 plus 함수의 경우 미리 plus라는 함수가 있다는 것을 통해 main 함수 내에서 plus가 사용됨에도 불구하고 아 이미 정의된 함수니까 괜찮겠군! 하고 컴파일이 된다.
또 한가지 말해줄 점은 전역변수를 사용한다면 굳이 함수에 매개인자로 넣지 않아도 된다. 그냥 그 전역변수의 이름을 함수 안에 그대로 적어서 사용할 수 있다. 그런데 그렇다면 전역변수로 선언한 변수의 이름과 함수에서 매개변수나 함수 내부에서 선언한 변수의 이름이 같을 때는 어떻게 될까? 이 경우 전역변수를 불렀다고 생각하지 않고 함수 내에서 따로 변수가 생겼다고 생각하고 사용한다. 그러니까 이름을 잘 지어야한다.
이제 함수를 만들 수 있고 함수도 쓸 수 있다. 그런데 몇가지 더 알아둬야 할 것이 있다. 함수는 매개변수에 인자를 집어넣어서 특정 기능을 수행하는 것이다. 그런데 이 인자를 받아오는 방식이 인자를 원본으로 가져오는 것이 아닌 인자의 복사본을 만들어서 이를 사용하는 것이다. 이해가 안되는 사람을 위해서 아래 예시를 보자.
#include <stdio.h>
void a_plus(int a){
printf("%d\n",a);
a++;
printf("%d\n",a);
}
int main(){
int b=0;
a_plus(b);
printf("%d\n",b);
}
위의 코드 출력 결과를 예측해보자. 혹시 0 1 1이 순서대로 나온다고 생각했는가? 그렇다면 틀렸다. 방금 함수는 어떤 변수의 원본을 가져오는 것이 아닌 복사본을 만들어서 가져오는 것이라고 했다. 그렇기 때문에 위의 코드에서 b의 복사본을 만들어서 그것을 출력하고 거기에 1을 더해서 출력을 했던 말던 원본인 b에는 행해지는 것이 없다. 그렇기에 결과는 0 1 0 순으로 출력될 것이다.
그럼 내부에서만 바뀌는거면 쓸데없는 것 아니냐고 말할 수 있다. 하지만 우리는 포인터를 괜히 배운 것이 아니다. 포인터를 배운 우리는 어렵지만 아이디어를 생각해 볼 수도 있다. 그러면 함수한테 그 변수의 직접적인 위치를 알려주면 되지 않을까? 하고. 그럼 어떻게 해야할까?
단순하다. 함수의 매개변수를 포인터형 변수로 선언하면 된다.
#include<stdio.h>
void a_plus(int *a){
printf("%d\n",*a);
(*a)++;
printf("%d\n",*a);
}
int main(){
int b=0;
a_plus(&b);
printf("%d\n",b);
}
바뀐 점을 찾아보자. 먼저 가장 우선적으로 신경써야 할 것은 함수에 넣는 인자가 바뀐 것이다. 이전에는 어떤 값을 주었지만 이번에는 그 변수의 주소를 주었다. 그렇게 들어간 그 주소는 함수의 매개변수 a에 저장되었다. (int *a라고 적힌 것이 헷갈린다면 int* a라고 생각하자). 그럼 지금 a는 이 주소를 담고 있으므로 그 주소에 담긴 값을 표현하기 위해서는 *a를 통해 printf문을 통해서 출력해주어야 한다. 이후 이 값에 1을 더하고 싶다면 *a에 1을 더해줘야 하는 것이므로 (*a)++ 라는 구문으로 표현할 수 있다.
앞으로도 함수를 만들 때 이렇게 포인터형 변수에 대해서 헷갈린다면 하나하나 아 a가 지금 주소니까 값을 바꾸려면 이렇게 해야하는 구나 하는 느낌으로 차근차근 하다보면 어느샌가 익숙해져 있을 것이다.
앞으로 여러분이 함수를 만든다고 하면 다음과 같은 순서로 만들 수 있을 것이다. 일단 어떠한 기능을 가지는 함수인지를 생각하고 이 함수가 결과를 어떻게 표현할 것인지를 생각해야한다. 예를 들어 두 값이 같은지를 비교하는 함수라면 이 값이 같은지 아닌지를 어떻게 표현할지에 대해서 생각을 해야한다. 단순히 출력으로 이 두 값이 같은지 아닌지를 출력하는게 용도인지 아니라면 같으면 1 아니면 0을 반환하는 식으로 어떤 값을 반환하는 것이 용도인지 생각하고 만들어야 한다는 것이다. 또 함수 안에 함수를 넣을 수도 있으며(사실 이미 main에서 사용하는 것이 함수 안의 함수이다) 함수가 자기 자신을 부르는 재귀 함수라는 것도 있다. 이는 나중에 배워보도록 하겠다. 오늘 마무리는 학교에서 실습하다 보면 자주 쓰는 두개의 값을 바꾸는 swap함수를 만들어보고 끝내겠다.
swap 함수 만들기
1. swap 함수는 두개의 정수를 입력받아 두 정수를 서로 뒤바꿔 저장하는 함수이다.
2. 포인터형 변수를 선언해야함에 주의한다.
정답:
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
다음 시간에는 구조체를 공부해보도록 하겠다.
질문, 오타 지적, 오류 지적 등은 댓글로 환영합니다.

'[C] > [C : 개념]' 카테고리의 다른 글
[C언어] 파일 입출력 (0) | 2021.02.15 |
---|---|
[C언어 기초] 구조체 (0) | 2021.02.10 |
[C언어 기초] 포인터 : 배열과 포인터, 메모리 할당 (0) | 2021.01.09 |
[C언어 기초] 배열 : 일차원 배열, 다차원 배열, 배열로 선언된 문자열 (2) | 2021.01.05 |
[C언어 기초] 반복문 : for, while, do-while, 이중반복 및 제어문 + 지역,전역변수 (0) | 2021.01.01 |