학습(공부)하는 블로그 :: 10. 함수
 

 
반응형
블로그 이미지
학습하고 공부한 것을 보고 싶을때 다시 볼려고 요약해서 정리한 블로그입니다. 좋은 정보는 서로 공유합시다. 깨비형
« 2026/7 »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Archive»


Category»

Notice»

Recent Post»

Recent Comment»

Recent Trackback»

07-19 17:23

10. 함수

학과 수업 노트/C언어 기초 배우기 | 2026. 7. 5. 09:10 | Posted by 깨비형
반응형

 

💡 1분 핵심 요약 (TL;DR)

  • 함수는 재사용 가능한 코드 블록이다 — printf, scanf도 사실은 전부 함수다
  • void는 반환값이 없는 함수, 값을 돌려주려면 반환형을 정하고 return을 써야 한다
  • main 함수는 C언어가 약속한 프로그램의 시작점이다
  • ✅ 값만 넘기면(call by value) 원본은 안 바뀐다 — 원본을 바꾸려면 주소값(포인터)을 넘겨야 한다
  • ✅ 함수마다 독립된 메모리 공간이 생기므로, 변수 이름이 같아도 서로 영향을 주지 않는다

 

 

 

01 함수는 왜 필요할까

사실 우리는 이미 함수를 쓰고 있었다 — printf, scanf

 

지금까지 당연하게 써온 printfscanf도 사실 전부 함수입니다. 함수를 만든다는 건 지금까지 작성한 코드를 묶어서 재사용할 수 있게 만드는 일입니다. 다르게 말하면, 내가 만든 코드가 재사용할 만큼 가치가 있다는 뜻이기도 합니다.

 

기능 하나가 10줄짜리 코드라고 해보겠습니다. 이걸 함수로 묶지 않고 10번 반복 실행하려면 100줄이 필요합니다. for문을 쓰면 12줄로 줄일 수 있지만, 반복문은 그 자리에서만 반복시킬 수 있다는 한계가 있습니다. 함수로 묶으면 원하는 위치 어디서든 자유롭게 호출할 수 있고, 10번을 호출해도 22줄이면 충분합니다.

 

방식 10줄짜리 기능을 10번 실행할 때 필요한 줄 수
그대로 10번 복사 100줄
for문으로 반복 12줄
(단, 그 자리에서만 반복 가능)
함수로 묶어서 10번 호출 22줄
(원하는 위치 어디서든 호출 가능)

 

 

함수를 처음 정의할 때는 살짝 손해처럼 보이지만, 호출 횟수가 늘어날수록 이득이 커집니다. 이게 함수를 쓰는 가장 기본적인 이유입니다.

 

 

 

02 함수 정의와 호출, 기본 구조 잡기

반환형 + 함수 이름 + 매개변수, 그리고 return의 의미

 

📖 함수 정의 기본 구조
반환형 함수이름(매개변수) { 실행할 코드; }
함수를 실행할 때는 함수이름(재료); 형태로 씁니다. 지금까지 무심코 써온 int main()도 사실 함수를 정의하는 방식 그대로입니다.

 

void 함수

· 반환값이 없는 함수
· return을 쓸 필요가 없다
· 어떤 동작만 수행하고 끝난다
  반환값 있는 함수

· 함수 앞에 반환형(int, double 등) 명시
· return 값; 으로 결과를 되돌려준다
· 반환형과 return 값의 타입이 일치해야 한다

 

✓ return을 이렇게 이해하면 쉽다
return은 "유언을 남긴다"는 이미지로 기억하면 됩니다. 함수가 실행된 그 자리에 값을 남기고(되돌려주고) 사라진다는 뜻입니다. 그리고 return은 값을 남기는 동시에 함수를 즉시 종료시키는 역할도 합니다. return 아래에 코드가 더 있어도 실행되지 않습니다.

 

 

 

03 main 함수의 진짜 정체

왜 우리는 main()을 실행한다고 따로 적은 적이 없을까

 

모든 프로그램에는 시작점이 필요합니다. 컴퓨터를 켜면 ROM에서부터 부팅이 시작돼 운영체제가 켜지는 것처럼요. C언어는 이 시작점을 main 함수로 약속해뒀습니다. C언어는 코드를 한 줄씩 실행하는 게 아니라, 파일 전체를 한 번에 읽어들인 뒤 프로그램으로 만듭니다(이 과정을 컴파일이라고 부릅니다). 그리고 컴파일이 끝나면 main 함수부터 실행을 시작합니다.

 

① 전체 읽기
코드 파일 전체를 한 번에 읽는다
② 컴파일
읽은 내용을 프로그램으로 만든다
③ main 실행
약속된 시작점부터 실행한다

 

 

우리가 따로 "main을 실행해라"라고 코드를 적은 적이 없는데도 항상 실행됐던 이유가 바로 이겁니다. 사람으로 치면 심장이 뛰어야 몸이 움직이는 것처럼, main 함수가 있어야 프로그램이 돌아가기 시작합니다.

 

 

 

04 실전 예제 — 양치질 함수 만들기

가장 단순한 void 함수부터 시작해보자

 

반환값이 없는 void 함수를 직접 만들어보겠습니다. "칫솔에 치약을 짜고 이를 닦고 입을 헹군다"라는 동작을 하나의 함수로 묶어보겠습니다.

 

양치질 함수 (void, 매개변수 없음)

#include <stdio.h>

// 양치질 함수
void brushing() {
    printf("칫솔에\n");
    printf("치약을 짜고\n");
    printf("이를 닦고\n");
    printf("입을 헹굽니다.\n");
}

// C언어 시작점, 시작 함수
int main() {
    for (int i = 0; i < 3; i++) {
        brushing();
    }

    return 0;
}

 

▶ 실행 결과

칫솔에
치약을 짜고
이를 닦고
입을 헹굽니다.
칫솔에
치약을 짜고
이를 닦고
입을 헹굽니다.
칫솔에
치약을 짜고
이를 닦고
입을 헹굽니다.

 

✓ 함수 안이 여러 줄이어도 호출은 한 줄이다
brushing() 함수 안에는 4줄이 들어있지만, 3번 반복 실행하는 데 필요한 건 for문 + brushing() 한 줄뿐입니다. brushing()을 그대로 3번 나열해도 되지만, 함수 안의 코드가 길어질수록 for문과 함께 묶어 호출하는 쪽이 훨씬 유리해집니다.

 

 

 

 

05 매개변수와 반환값 함께 쓰기

매개변수는 함수를 실행하기 위한 '재료'다

 

매개변수(parameter)는 함수를 실행하기 위해 필요한 재료라고 생각하면 이해하기 쉽습니다. 함수를 정의할 때 소괄호 안에 어떤 재료가 필요한지 적어두면, 호출할 때 그 재료를 넣어줘야 합니다. 본격적으로 매개변수를 다루기 전에, return이 함수를 즉시 종료시킨다는 사실부터 코드로 직접 확인해보겠습니다.

 

✅ 직접 실습해본 코드 — return 뒤의 코드는 실행되지 않는다

#include <stdio.h>

int calculate() {
    return 3; // return -> 함수 종료 & 반환값을 실행한 곳에 넘겨준다
    return 4; // 여기는 절대 실행되지 않는다
}

int main() {
    int result = calculate();
    printf("결과는 %d\n", result);

    return 0;
}

 

▶ 실행 결과

결과는 3

 

두 번째 return 4;는 결과에 아무 영향을 주지 못합니다. 첫 번째 return 3;에서 함수가 이미 종료되어버렸기 때문입니다. 실무에서는 대부분의 컴파일러가 이런 코드에 "도달할 수 없는 코드(unreachable code)" 경고를 띄워줍니다. 이제 여기에 매개변수를 더해보겠습니다.

 

매개변수로 계산해서 반환하기

#include <stdio.h>

int calculator(int n1) {
    return n1 * 3;
}

int main() {
    int result = calculator(4);
    printf("결과는 %d\n", result);

    result = calculator(5);
    printf("결과는 %d\n", result);

    return 0;
}

 

▶ 실행 결과

결과는 12
결과는 15

 

흐름을 따라가 보면, main이 실행되면서 calculator(4)를 호출하고, calculator 내부에서 return n1 * 3이 실행되는 순간 함수는 종료되고 12라는 값이 호출한 자리(result)에 남습니다. 매개변수에 어떤 값을 넣느냐에 따라 결과가 달라지는 것을 확인할 수 있습니다.

 

매개변수로 숫자만 받을 수 있는 건 아닙니다. 앞서 만든 양치질 함수에 "누가 양치질을 하는지"를 문자 배열(문자열) 매개변수로 받아보겠습니다.

 

문자 배열을 매개변수로 받기

#include <stdio.h>

// 양치질 함수 (char *username 으로 받아도 동일하게 동작한다)
void brushing(char username[]) {
    printf("%s는 \n", username);
    printf("칫솔에\n");
    printf("치약을 짜고\n");
    printf("이를 닦고\n");
    printf("입을 헹굽니다.\n");
}

int main() {
    brushing("스펜서");
    brushing("마크");

    return 0;
}

 

▶ 실행 결과

스펜서는
칫솔에
치약을 짜고
이를 닦고
입을 헹굽니다.
마크는
칫솔에
치약을 짜고
이를 닦고
입을 헹굽니다.

 

📖 char username[]과 char *username, 뭐가 다를까
매개변수 자리에서는 char username[]char *username이 사실상 동일하게 동작합니다. 배열 이름은 첫 번째 요소의 주소로 취급되기 때문입니다. 두 표기 모두 "문자열의 시작 주소를 받는다"는 같은 의미이며, 어떤 스타일을 쓸지는 취향의 문제입니다.

 

 

 

06 실전 예제 — 삼각형 넓이 구하는 함수

매개변수 2개, 소수형 반환값까지 함께 써보자

 

삼각형 넓이 공식은 밑변 × 높이 ÷ 2입니다. 밑변(w)과 높이(h) 두 개의 매개변수를 받아서, 소수형(double) 결과를 반환하는 함수를 만들어보겠습니다.

 

삼각형 넓이 함수

#include <stdio.h>

double triangle(double w, double h) {
    // 실수 연산임을 명확히 하기 위해 2.0으로 나눈다
    double area = w * h / 2.0;
    return area;
}

int main() {
    double w1 = 5.0, h1 = 6.0;
    double area1 = triangle(w1, h1);
    printf("삼각형 넓이는 %.2lf\n", area1);

    // 변수 없이 값을 바로 넣어서 호출해도 된다
    printf("삼각형 넓이는 %.2lf\n", triangle(4.0, 3.0));

    return 0;
}

 

▶ 실행 결과

삼각형 넓이는 15.00
삼각형 넓이는 6.00

 

 

5 × 6 ÷ 2 = 15, 4 × 3 ÷ 2 = 6으로 정확히 계산되는 것을 확인할 수 있습니다. 함수 호출 결과를 변수에 담아 쓸 수도 있고, printf 안에 바로 넣어서 사용할 수도 있습니다.

 

 

 

07 변수의 스코프 — 이름이 같아도 안전한 이유

함수마다 독립된 임시 메모리 공간이 생긴다

 

위 삼각형 예제에서 main의 변수triangle 함수 안의 매개변수(w, h) 이름이 겹쳐도 전혀 문제가 없었던 이유가 궁금할 수 있습니다. 함수가 호출될 때마다 일어나는 일을 순서대로 보면 이렇습니다.

 

① 함수 호출
코드를 읽다가 함수를 만난다
② 임시 공간 생성
그 함수만을 위한 메모리 공간이 새로 만들어진다
③ 코드 실행
그 공간 안에서 함수의 코드가 실행된다
④ 종료 후 소멸
return을 만나면 값만 남기고 공간은 사라진다

 

📖 같은 이름, 다른 사람
"대한민국의 스펜서"와 "미국의 스펜서"는 이름은 같아도 완전히 다른 사람인 것처럼, 메모리 공간이 다르면 변수 이름이 같아도 서로 다른 변수로 취급됩니다. main의 영역과 triangle의 영역은 애초에 분리된 공간이기 때문에 안전합니다.

 

말로만 들으면 아직 아리송할 수 있으니, main에서 triangle의 매개변수와 완전히 똑같은 이름(w, h)으로 변수를 만들어서 직접 확인해보겠습니다.

 

✅ 직접 실습해본 코드 — 이름이 같아도 충돌하지 않는다

#include <stdio.h>

double triangle(double w, double h) {
    double area = w * h / 2.0;
    return area;
}

int main() {
    // main에도 w, h라는 이름을 그대로 사용한다
    double w = 5.0, h = 6.0;
    double area = triangle(w, h);
    printf("삼각형 넓이는 %.2lf", area);

    return 0;
}

 

▶ 실행 결과

삼각형 넓이는 15.00

 

main의 w, h와 triangle의 매개변수 w, h는 이름은 같지만 서로 다른 메모리 공간에 있어서 전혀 충돌하지 않습니다. 에러도 나지 않고, 정확히 15.00이라는 결과가 나옵니다. 이것이 바로 함수마다 독립된 임시 공간이 생긴다는 원리를 눈으로 확인하는 가장 직관적인 방법입니다.

 

 

 

 

08 값 전달 vs 주소 전달 — Swap 함수로 확인하기

왜 함수 안에서 값을 바꿨는데 바깥에서는 그대로일까

 

n1과 n2 두 변수의 값을 서로 맞바꾸는(swap) 함수를 만들어보겠습니다. 먼저 평범하게 값을 그대로 넘겨서 시도해보겠습니다.

 

❌ 값만 전달 — swap이 실패하는 경우

#include <stdio.h>

void swap1(int n1, int n2) {
    printf("swap1에 들어온 값: %d %d\n", n1, n2);

    int temp = n1;
    n1 = n2;
    n2 = temp;

    printf("swap1에서 바뀐 값: %d %d\n", n1, n2);
}

int main() {
    int n1 = 5, n2 = 3;
    printf("n1과 n2: %d %d\n", n1, n2);

    swap1(n1, n2);

    printf("함수 실행 후 n1과 n2: %d %d\n", n1, n2);

    return 0;
}

 

▶ 실행 결과 (예상과 다르게 원본은 그대로)

n1과 n2: 5 3
swap1에 들어온 값: 5 3
swap1에서 바뀐 값: 3 5
함수 실행 후 n1과 n2: 5 3

 

함수 안에서는 분명히 3과 5로 바뀌었는데, main으로 돌아오니 5와 3 그대로입니다. 이유는 swap1(n1, n2)를 호출하는 순간, n1과 n2라는 변수 자체가 아니라 5와 3이라는 값만 복사돼서 전달되기 때문입니다. 함수 안의 n1, n2는 main의 n1, n2와는 완전히 별개의 변수입니다.

 

원본을 바꾸려면 값이 아니라 주소값을 전달해야 합니다. 매개변수를 포인터로 선언하고, 함수를 호출할 때는 &로 변수의 주소를 넘겨줍니다.

 

✅ 주소 전달 — swap이 성공하는 경우

#include <stdio.h>

void swap2(int *n1, int *n2) {
    int temp = *n1;
    *n1 = *n2;
    *n2 = temp;
}

int main() {
    int n1 = 5, n2 = 3;
    printf("원본 값 - n1: %d, n2: %d\n", n1, n2);

    // 주소값을 넘겨주기 위해 &를 붙인다
    swap2(&n1, &n2);

    printf("함수 실행 후 - n1: %d, n2: %d\n", n1, n2);

    return 0;
}

 

▶ 실행 결과 (원본이 실제로 바뀜)

원본 값 - n1: 5, n2: 3
함수 실행 후 - n1: 3, n2: 5

 

 

📖 이번엔 이 정도만 기억해도 충분하다
포인터(*)와 주소(&)의 자세한 원리는 다음 강의에서 본격적으로 다룹니다. 지금은 "값만 넘기면 원본이 안 바뀌고, 주소를 넘기면 원본이 바뀐다"는 결과 차이만 기억해두고, 포인터를 배운 뒤 이 예제로 다시 돌아오는 것을 추천합니다.

 

 

 

09 함수 선언과 정의를 나누는 이유

함수가 많아지면 main이 아래로 밀려난다

 

C언어는 함수를 호출하기 전에 그 함수가 먼저 정의(또는 선언)되어 있어야 합니다. 문제는 함수를 여러 개 만들다 보면 main 함수가 자꾸 아래로 밀려난다는 점입니다. 이럴 때는 함수의 선언정의를 분리할 수 있습니다.

 

📖 선언 vs 정의
선언은 "이런 함수가 있다"는 것만 미리 알려주는 명세서입니다(세미콜론으로 끝). 정의는 실제 코드 구현입니다. 선언을 파일 위쪽에 모아두면, 실제 구현은 아래로 내려도 됩니다.

 

 

선언은 위에, 정의는 아래에

#include <stdio.h>

// 함수 선언 — 명세서
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int divide(int a, int b);

int main() {
    int result = add(3, 5);
    printf("%d\n", result);

    return 0;
}

// 함수 정의 — 실제 구현
int add(int a, int b) {
    return a + b;
}

int sub(int a, int b) {
    return a - b;
}

int mul(int a, int b) {
    return a * b;
}

int divide(int a, int b) {
    return a / b;
}

 

▶ 실행 결과

8

 

위쪽만 봐도 "이 프로그램에는 어떤 함수들이 있구나"를 바로 파악할 수 있고, 실제 구현이 궁금하면 아래로 내려가서 확인하면 됩니다. 함수가 몇 개 없을 때는 굳이 나눌 필요는 없지만, 함수가 많아질수록 유용한 방식입니다.

 

⚠️ 나누기 함수 이름을 div로 짓지 않은 이유
나누기 함수 이름으로 div를 쓰고 싶을 수 있는데, div는 <stdlib.h>에 이미 정의된 표준 라이브러리 함수 이름입니다. 지금처럼 stdio.h만 포함했다면 문제가 없지만, 나중에 stdlib.h를 함께 포함하면 이름이 충돌할 수 있습니다. 그래서 이번 예제에서는 divide라는 이름을 대신 사용했습니다. 표준 라이브러리에 이미 있는 이름은 되도록 피하는 것이 안전한 습관입니다.

 

 

 

📌 핵심 정리

· 함수 : 코드를 묶어 재사용성을 높이는 단위 — 중복 방지, 가독성, 유지보수성이 모두 좋아진다
· void : 반환값이 없는 함수. 값을 돌려주려면 반환형을 정하고 return을 쓴다
· return : 값을 호출한 자리에 남기는 동시에 함수를 즉시 종료시킨다
· main 함수 : C언어가 프로그램의 시작점으로 약속해둔 함수
· 매개변수 : 함수를 실행하기 위한 재료. 함수 정의와 호출 양쪽에 맞춰서 넣는다
· 변수 스코프 : 함수마다 독립된 메모리 공간이 생기므로 이름이 같아도 서로 다른 변수다
· 값 전달 vs 주소 전달 : 값만 넘기면 원본이 안 바뀌고, 주소(포인터)를 넘겨야 원본이 바뀐다
· 함수 선언과 정의 분리 : 선언은 위에 모아두고 실제 구현은 아래로 내려 가독성을 높일 수 있다

 

※ 이 글은 아래 동영상으로 학습한 내용을 요약한 글입니다.

 

출처 동영상: https://youtu.be/oHF9azyx9Ho?si=ueTIjrkcPtwWwf0h

 

 

태그 : C언어 함수 사용법함수 매개변수 반환값C언어 main 함수 역할void 함수란함수 return 사용법C언어 변수 스코프값 전달 주소 전달 차이C언어 swap 함수함수 선언과 정의 차이C언어 코딩 기초 강의

반응형

'학과 수업 노트 > C언어 기초 배우기' 카테고리의 다른 글

12. 포인터  (0) 2026.07.14
11. 배열  (0) 2026.07.12
9. Switch Case문  (0) 2026.07.03
8. 반복문 while문과 무한반복  (0) 2026.07.02
7. 반복문 - for문  (0) 2026.04.29
: