- 함수(Function)
- 함수와 포인터
- 함수의 원형(Prototype)
- 배열을 함수의 인자로 받기
- 함수 포인터
- Q&A
- 마치며
아래의 예시를 봅시다.
#include <stdio.h>
int return_func() {
printf("난 실행된다 \n");
return 0;
printf("난 안돼 ㅠㅠ \n");
}
int main() {
return_func();
return 0;
}
(리턴 타입) (함수 이름)(매개변수){ ... }
- 반환 타입
- 함수의 이름
: 함수가 하는 작업을 설명해주는 이름을 권장
:()
는 함수의 이름에 포함되지 않지만, 함수임을 알려주는 표시
예제에서는
int return_func()
이 되겠죠.
- 몸체(Body)
: 함수가 무슨 일을 하는지 알 수 있는 부분
예제에서는
printf("난 실행된다 \n");
return 0;
printf("난 안돼 ㅠㅠ \n");
가 되겠죠.
- 호출(Call)
: 실제 함수를 호출하는 부분
예제에서는
return_func();
가 되겠죠
컴퓨터는 프로그램을 실행할 때, main함수부터 찾습니다.
즉, 컴퓨터는 프로그램을 실행할 때, main함수를 호출함으로써 시작합니다.
만약, main함수가 없다면 컴퓨터는 오류를 출력합니다.
int main()
위에서 배운 내용을 토대로 보면,
위 함수는 리턴형이 int이고, 이름은 main이라는 것을 알 수 있습니다.
그렇다면, main함수의 리턴값은 누가 받을까요?
그것은 바로, 운영체제가 받아들입니다.
보통 main함수가 정상적으로 종료되면 0을, 비정상적으로 종료되면 1을 반환합니다.
#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;
}
함수의 정의 부분을 보면, int master_money
가 추가 되어있습니다.
이는
"나를 호출하는 코드로부터 어떤 값을 mater_money
라는 int형 변수에 인자(혹은 매개변수)를 받아들이겠다."
라는 의미입니다.
- 인자(Argument)
: 함수를 호출한 것과, 함수를 서로 연결해 주는 통신 수단
: 매개 변수(Parameter)라고도 불립니다.
단, 주의해야 할 점이 있습니다.
- caller와 callee에 쓰인 변수는, 서로 다른 변수입니다.
(따라서 메모리 상의 다른 위치에 저장되어 있습니다.)- 값이 전달 됩니다.
즉, 함수의 세계는 너무나 배타적이어서,
각 함수는 서로에 무슨 변수가 있는지 모른다.
사실 (Return Type, 함수의 이름, 인자들의 type) 말고는 서로에 대해 아는 것이 없다.
라고 생각하면 좋습니다.
그렇다면 다른 함수에서 정의된 변수의 값을 수정할 수는 없을까요?
가능합니다.
바로 포인터로 말이죠...
다음 예제를 봅시다.
#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;
}
[Reulst]
호출 이전 i 의 값 : 0
호출 이후 i 의 값 : 0
사실, main함수에서 사용된 i
와 chage_val
함수에 쓰인 i
는 다른, 별개의 개체입니다.
그래서 프로그램을 실행해도 똑같은 결과가 나옵니다.
그렇다면 다음 예제는 어떨까요?
#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;
}
[Reulst]
i 변수의 주소값 : 0x7ffd3928afc4
호출 이전 i 의 값 : 0
----- chage_val 함수 안에서 -----
pi 의 값 : 0x7ffd3928afc4
pi 가 가리키는 것의 값 : 0
----- change_val 함수 끝~~ -----
호출 이후 i 의 값 : 3
i
의 값이 0
에서 3
으로 바뀌었습니다.
함수의 정의부분을 보면,
int형 변수를 가리키는 pi
라는 포인터를 매개변수로 받고 있습니다.
그리고 main함수에서는 call을 할 때, 인자로 i
의 주소값을 전달 했습니다.
즉, pi
는 i
의 주소값이 들어갔으므로,
pi
는 i
를 가리키게 됩니다.
함수의 Body부분을 보게 되면,
*pi
를 통해서 i
를 간접적으로 접근할 수 있습니다.
이러한 특성을 이용하면, 두 변수의 값을 교환하는 함수도 만들 수 있습니다.
#include <stdio.h>
void swap(int* i, int* j) {
int p = *i;
*i = *j;
*j = p;
}
int main() {
int a = 2;
int b = 5;
printf("SWAP 이전의 값\n");
printf("a : %d, b : %d\n", a, b);
swap(&a, &b);
printf("SWAP 이후의 값\n");
printf("a : %d, b : %d\n", a, b);
return 0;
}
[Reulst]
SWAP 이전의 값
a : 2, b : 5
SWAP 이후의 값
a : 5, b : 2
결론적으로
어떠한 함수가 특정한 타입의 변수 / 배열의 값을 바꾸기 위해서는,
함수의 인자는 반드시 그 타입을 가리키는 포인터를 이용해야 한다.
지금까지 우리가 보았던 함수들은,
main함수의 위에서 정의했습니다.
그런데, 사실 대부분의 사람들은 main함수를 제일 상단에 위치시키고,
그 밑에 함수들을 정의한다고 합니다.
그런데 그렇게 실행을 하면 컴파일 에러가 발생합니다.
그리고 실제 프로그래밍 시에는
수많은 함수를 사용하기 때문에,
호출 시 인자의 개수나 타입을 맞추지 못하는 실수도 할 수 있습니다.
그런데 그렇게 하면 Run-time에러가 발생합니다...
하..
그러면 어떻게 해야하나요?
바로 함수의 원형(Prototype)을 이용하는 것입니다.
#include <stdio.h>
void swap(int* i, int* j); // Prototype
int main() {
int a = 2;
int b = 5;
printf("SWAP 이전의 값\n");
printf("a : %d, b : %d\n", a, b);
swap(&a, &b);
printf("SWAP 이후의 값\n");
printf("a : %d, b : %d\n", a, b);
return 0;
}
void swap(int* i, int* j) {
int p = *i;
*i = *j;
*j = p;
}
이렇게 하면 어떠한 오류도 없이 잘 실행됩니다.
main함수 위에 보면 함수의 정의 부분을 한 번 더 쓰고 ;
을 붙인,
'함수의 원형(Prototype)'이 추가 되었습니다.
이는, 컴파일러에게
"다음과 같은 함수가 정의되어 있으니까 잘 살펴봐"
라고 알려주는 것입니다.
- 함수의 원형(Prototype)
: 컴파일러에게 소스코드에 사용되는 함수에 대한 정보를 제공하는 것
함수의 원형을 사용하면,
인자의 타입이나 개수를 맞추지 못해도,
Run-time 에러가 아닌, 컴파일 에러를 일으크게 됩니다.
즉, 내가 어디서 어떻게 틀렸는지 알 수 있는 것이죠.
따라서, 함수의 원형(Prototype)을 사용하는 것이 권장됩니다.
사실 대부분의 프로그래머들은 main함수의 뒤에 함수를 정의하므로,
원형을 추가해주는 것이 좋습니다.
우리가 앞서, 특정한 타입의 값을 변경하는 함수를 만들기 위해서는
반드시 그 타입을 가리키는 포인터를 인자로 가져야 한다고 했습니다.
그러다면 일차원 배열(int형)을 가리키는 타입은 int *
타입이라고 했습니다.
다음 예제는, 배열의 각 원소에 +1을 한 값을 출력하는 예제입니다.
#include <stdio.h>
int add_number(int* p);
int main() {
int arr[5] = {1, 2, 3};
add_number(arr);
for (int i = 0; i < 3; i++) {
printf("arr[%d] : %d\n", i, arr[i]);
}
return 0;
}
int add_number(int* p) {
for (int i = 0; i < 3; i++) {
p[i]++;
}
return 0;
}
[Reulst]
arr[0] : 2
arr[1] : 3
arr[2] : 4
- 함수 포인터
'함수 포인터'는 말 그대로
함수를 가리키는 포인터입니다.
함수도 변수와 마찬가지로 메모리 상에 존재하며,
이 함수 포인터는 메모리 상에 올라간 함수의 시작주소를 가리킵니다.
이 때,배열과 마찬가지로
함수의 이름이 함수의 시작 주소값을 나타냅니다.
- 함수 포인터
(함수의 리턴Type) (*포인터 이름)(각 인자들의 Type)
#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;
}
int (*pmax)(int, int);
을 통해서,
"함수 포인터 pmax는 함수의 리턴 Type이 int이며, 두 개의 인자는 int형인 함수를 가리키는 구나"
라는 것을 알게 됩니다.
pmax = max;
을 통해,
max
의 시작 주소값을 pmax
에 대입할 수 있게 됩니다.
(pmax = &max
는 틀린 것입니다.)
이를 통해, pmax
는 max
함수 자체를 가리킵니다.
그런데 이 때, 인자의 Type을 명확히 알기 어려운 경우가 있습니다.
이 때는, 함수의 원형(Prototype)에서 변수의 이름만 빼면 됩니다.
즉,
int increase(int (*arr)[3], int row)
는
int increase(int (*)[3], int)
가 되는 것이죠.
-
드디어 함수입니다.
사실 Python에서는 함수를 사용하기가 쉬웠는데,
C에서는 조금 까다롭다고 알고 있는데요..
아직 많이 강좌가 많이 남아있으니,
무리하지 않고 찬찬히 살펴보겠습니다....😢
[Reference] : 위 글은 다음 내용을 참고, 인용하여 만들어졌습니다.