[C++] 함수

haaana·2023년 11월 20일
0



6. 함수


6.1 함수

📌 함수 Function

함수 란 특정한 작업을 수행하기 위해 독립적으로 설계된 코드를 말한다.

C++에서의 함수는 특정 작업을 캡슐화할 때 유용하게 사용된다.

함수는 왜 필요한가?

함수의 가장 큰 장점은 반복적인 프로그래밍을 피할 수 있다는 것이다.
특정한 작업을 여러 번 수행해야 할 경우 해당 작업을 수행하는 함수를 만들면 된다.
그리고 그 작업이 필요할 때마다 함수를 호출하여 해당 작업을 간편하게 수행할 수 있다.

인수 & 매개변수 Argument & parameter

인수 는 실인수, 실매개변수라고도 한다.

다음은 인수의 예시이다.

int main() {
	...
    i = get_max(2, 3);
    ...
}

여기서 인수는 2와 3이다.

매개변수 는 형식인수, 형식매개변수라고도 한다.

다음은 매개변수의 예시이다.

int get_max(int x, int y) {
	...
    ...
    ...
}

여기서 매개변수는 x와 y이다.

디폴트 매개변수는 인자를 전달하지 않아도 디폴트 값이 대신 전달된다.

다음은 디폴트 매개변수의 예시이다.

int get_max(int x, int y=10) {
	...
    ...
    ...
}

여기서 주의할 점은 디폴트 매개변수는 뒤에서 앞으로만 정의가 가능하다.

int get_max(int x, int y=10); // OK
int get_max(int x, int y=10, int z=20); // OK
int get_max(int x=10, int y=10, int z=20); // OK

int get_max(int x=10, int y); // Error
int get_max(int x=10, int y, int z=20); // Error

반환값 Return Value

반환값 은 호출된 함수가 호출한 곳으로 결과값을 전달하는 것을 말한다.
인수는 여러 개가 가능하지만, 반환값 은 하나만 가능하다.

다음은 반환값의 예시이다.

return 0;
return x;
return x + y;
return x * y;

📌 사용자 함수

함수의 선언

함수의 선언은 컴파일러에 함수에 대해 미리 알리는 것을 말한다.

함수의 선언 방법은 다음과 같다.

반환자료형 함수명 ( 매개변수 1 , ... );

선언 방법을 예제로 살펴보자.

int sum(int a, int b);

함수의 정의

C 언어에서는 반환자료형 을 명시하지 않으면 int 형으로 가정했다.

하지만 C++에서는 반드시 반환자료형 을 명시해야 한다.

함수는 입력 을 받아서 명령어를 수행한 다음 결과 를 생성한다.

함수의 정의 방법은 다음과 같다.

반환자료형 함수명 ( 매개변수 1 , ... ) {
	수행할 명령문;
}

정의 방법을 예제로 살펴보자.

int sum(int a, int b) {
    int result = a + b;

    return result;
}

함수의 호출

함수는 호출하여 사용한다.

마지막으로 함수의 호출 방법은 다음과 같다.

함수명 ( 매개변수 1의 값 , ... );

호출 방법을 예제로 살펴보자.

sum(10, 20);

다음은 두 수의 합을 구하는 함수를 만들고, 그 함수를 호출하는 소스 코드이다.

#include <iostream>

using namespace std;

int sum(int a, int b) {
    int result = a + b;

    return result;
}

int main() {
    int result = sum(10, 20);

    printf("두 수의 합: %d\n", result);

    return 0;
}

이번에는 다음의 조건을 만족하는 정수의 제곱을 계산하는 함수를 만들어보자.
(매개변수 값은 터미널에서 입력받는다.)

코드의 조건

  • 반환값: int
  • 함수명: square
  • 매개변수: n (int)

일단 반환값과 함수명, 매개변수가 주어졌기 때문에 함수를 선언한다.

int square(int n);

이제 정수의 제곱을 계산하는 명령어를 만들어 함수를 정의한다.
제곱을 계산하는 수식은 다음과 같다.

int result = n * n;

이 수식을 사용하여 square 함수를 정의한다.

int square(int n) {
    int result = n * n;

    return result;
}

이제 main 함수에서 square 함수를 호출한다.
이때, 매개변수 값을 터미널에서 입력받아야 하기 때문에 cin 객체를 사용하여 값을 입력받는다. (cin 객체의 사용은 표준입출력에서 설명했다.)

int main() {
    int num, result;
    
    printf("정수를 입력하세요: ");
    cin >> num;

    result = square(num);
    printf("정수의 제곱: %d\n", result);

    return 0;
}

이제 소스 코드를 합쳐보자.

#include <iostream>

using namespace std;

int square(int n);

int main() {
    int num, result;
    
    printf("정수를 입력하세요: ");
    cin >> num;

    result = square(num);
    printf("정수의 제곱: %d\n", result);

    return 0;
}

int square(int n) {
    int result = n * n;

    return result;
}

마지막으로 다음의 조건을 만족하는 평균 점수를 계산하는 함수와, 그에 맞는 학점을 구하는 함수를 만들어보자. (매개변수 값은 터미널에서 입력받는다.)

코드의 조건

  • 반환값: int, char
  • 함수명: avg_score (int), choose_score (char)
  • 매개변수: n1 , n2 , n3 , result_avg (int)
  • 조건문: switch

학점 기준은 다음과 같다.

학점점수
A100~90점
B89~80점
C79~70점
D69~60점
F~59점

일단 반환값과 함수명, 매개변수가 주어졌기 때문에 평균 점수를 계산하는 함수와 학점을 계산하는 함수를 각각 선언한다.

int avg_score(int n1, int n2, int n3);
int choose_score(int result_avg);

이제 평균 점수를 계산하는 함수와 학점을 계산하는 함수를 각각 정의한다.
평균 점수를 계산하는 수식은 다음과 같다.

int sum_score = n1 + n2 + n3;
int avg_score = sum_score / 3;

이 수식을 사용하여 avg_score 함수를 정의한다.

int avg_score(int n1, int n2, int n3) {
    int sum_score = n1 + n2 + n3;
    int avg_score = sum_score / 3;

    return avg_score;
}

이제 switch 문을 사용하여 choose_score 함수를 정의한다. (학점은 학점 기준표를 참고한다.)

char choose_score(int result_avg) {
    char result_score;

    switch (result_avg / 10) {
        case 10:
        case 9:
            result_score = 'A';
            break;
        case 8:
            result_score = 'B';
            break;
        case 7:
            result_score = 'C';
            break;
        case 6:
            result_score = 'D';
            break;
        default:
            result_score = 'F';
            break;
    }

    return result_score;
}

이제 main 함수에서 avg_score , choose_score 함수를 호출한다.
이때, 매개변수 값을 터미널에서 입력받아야 하기 때문에 cin 객체를 사용하여 값을 입력받는다.

int main() {
	int n1, n2, n3, result_avg;
    char result_score;
    
    printf("첫번째 점수를 입력하세요: ");
    cin >> n1;
    printf("두번째 점수를 입력하세요: ");
    cin >> n2;
    printf("세번째 점수를 입력하세요: ");
    cin >> n3;
    printf("내 점수: %d, %d, %d\n", n1, n2, n3);

    result_avg = avg_score(n1, n2, n3);
    printf("평균 점수: %d\n", result_avg);

    result_score = choose_score(result_avg);
    printf("\n학점은 %c 입니다.\n", result_score);

    return 0;
}

이제 소스 코드를 합쳐보자.

#include <iostream>

using namespace std;

int avg_score(int n1, int n2, int n3);
char choose_score(int result_avg);

int main() {
	int n1, n2, n3, result_avg;
    char result_score;
    
    printf("첫번째 점수를 입력하세요: ");
    cin >> n1;
    printf("두번째 점수를 입력하세요: ");
    cin >> n2;
    printf("세번째 점수를 입력하세요: ");
    cin >> n3;
    printf("내 점수: %d, %d, %d\n", n1, n2, n3);

    result_avg = avg_score(n1, n2, n3);
    printf("평균 점수: %d\n", result_avg);

    result_score = choose_score(result_avg);
    printf("\n학점은 %c 입니다.\n", result_score);

    return 0;
}

int avg_score(int n1, int n2, int n3) {
    int result_sum = n1 + n2 + n3;
    int result_avg = result_sum / 3;

    return result_avg;
}

char choose_score(int result_avg) {
    char result_score;

    switch (result_avg / 10) {
        case 10:
        case 9:
            result_score = 'A';
            break;
        case 8:
            result_score = 'B';
            break;
        case 7:
            result_score = 'C';
            break;
        case 6:
            result_score = 'D';
            break;
        default:
            result_score = 'F';
            break;
    }

    return result_score;
}

📌 오버로딩 함수 Overloading Function

오버로딩 함수 란 같은 이름의 함수를 여러 개 정의하는 것을 말한다.

오버로딩 함수 : 동일한 함수명, 다른 매개변수의 개수 & 다른 데이터형

오버로딩 함수의 장점은 함수의 이름을 재사용 가능하다는 것이다.

// 오버로딩 함수를 사용하지 않을 경우
square_int(int n);
square_double(double n);
square_short(short n);

// 오버로딩 함수를 사용하는 경우
square(int n);
square(double n);
square(short n);

다음은 오버로딩 함수를 사용할 때의 주의사항이다.

int square(int n1);

int square(int n1, int n2); // OK
int square(int n1, double n2); // OK
double square(double n1); // OK

double square(int n1); // Error - 모호한 호출
float square(int n1, int n2); // Error - 모호한 호출

다음은 오버로딩 함수의 예시이다.

#include <iostream>

using namespace std;

int square(int n);
double square(double n);

int main() {
	// 인수가 정수이므로 정수를 제곱하는 함수가 호출됨
    int result_int = square(2);
    
    // 인수가 실수이므로 실수를 제곱하는 함수가 호출됨
    double result_double = square(0.5);
    
    printf("정수의 제곱: %d\n", result_int);
    printf("실수의 제곱: %.4f\n", result_double);

    return 0;
}

// 정수 값을 제곱하는 함수
int square(int n) {
    int result = n * n;

    return result;
}

// 실수 값을 제곱하는 함수
double square(double n) {
    double result = n * n;

    return result;
}

📌 인라인 함수 Inline Function

인라인 함수 란 함수 호출을 하지 않고 함수의 모든 코드를 호출된 자리에 바로 삽입하는 것을 말한다.

inline : in (내부) + line (프로그램 코드 라인)

인라인 함수로 선언하면 함수 호출 시 정의로 이동하지 않고, 컴파일 도중에 해당 함수 자리에 함수를 삽입한다.
→ 함수를 실행하기 위해 정의로 이동할 필요가 없기 때문에 실행 속도가 빠른 장점이 있다.

인라인 함수의 특징

  • 컴파일러가 inline화를 결정한다.
  • 사용자가 inline 선언을 하지 않아도 컴파일러가 대신 할 수 있다.
  • 함수가 비대하거나 재귀 호출일 경우, 컴파일러가 거절할 수 있다.
  • 메모리적으로 일반 함수보다 불리한 점이 있다.
    → 만약 10번 호출하면 10번 삽입한다.
  • 데이터형에 독립적이지 못하기 때문에 템플릿화 해야한다.

인라인 함수의 정의 방법은 다음과 같다.

inline 데이터형 함수명 ( ... ) {
	...
}

매크로 함수와의 차이점은 무엇인가?

  • 매크로 함수는 기계적인 대치라 잘못 사용될 가능성이 있다.
  • 매크로 함수에서는 타입 검사를 하지 않는다.
    → 데이터형에 의존적인 함수가 아니다.

다음은 인라인 함수의 예제이다.

#include <iostream>

using namespace std;

inline int square(int n);

int main() {
    // 인수가 정수이므로 정수를 제곱하는 함수가 호출됨
    int result = square(2);
    printf("정수의 제곱: %d\n", result);

    return 0;
}

// 정수 값을 제곱하는 함수
inline int square(int n) {
    return n * n;
}

📌 매크로 함수 Macro Function

매크로 함수#define 을 사용하여 인자를 함수처럼 동작할 수 있도록 하는 것을 말한다.

매크로 함수의 특징

  • 매크로 함수라고 부르지만 단순히 치환하는 것이기 때문에 실제 함수는 아니다.
  • 함수의 선언과 비슷하지만 매크로 함수는 인자가 데이터형에 의존적이지 않다.
  • 매크로 함수 내부에서 자기 자신을 호출할 수 없다.

매크로 함수의 사용 방법은 다음과 같다.

#define ( 함수명 ) ( 함수의 기능 )

다음은 매크로 함수의 예제이다.

#include <iostream>

using namespace std;

#define square(n) ((n) * (n))

int main() {
    int n = 5;

    int result1 = square(n);
    int result2 = square(n + 5);
    
    printf("정수의 제곱: %d\n", result1);
    printf("정수의 제곱: %d\n", result2);

    return 0;
}

6.2 변수의 범위

📌 지역 변수와 전역 변수

지역 변수 Local variable

지역 변수 란 함수의 내부에서 선언되는 변수를 말한다.

지역 변수는 어디서나 선언 가능하다.

다음은 지역 변수의 예시이다.

#include <iostream>

using namespace std;

int func1();
int func2(int n);

int main() {
    func1();
    int result = func2(10);
    printf("result: %d\n", result);

    return 0;
}

int func1() {
    int num1 = 10;
    {
        int num2 = 20;
        int result_sum = num1 + num2;
        printf("두 수의 합: %d\n", result_sum);
    }
    int result_sqaure = num1 * num1;
    printf("정수의 제곱: %d\n", result_sqaure);

    return 0;
}

int func2(int n) {
    int num1 = 50;
    int num2 = 100;
    
    for (int i=0; i<n; i++) {
        int temp = num1 + num2;
        num1 = num2;
        num1 = temp;
    }
    return num1;
}

지역 변수는 범위를 벗어나면 사용하지 못한다.

int func1() {
    int num1 = 10;
    {
        int num2 = 20;
        int result = num1 + num2;
    }

    return result; // Error - 지역 변수 result의 범위를 벗어남
}

전역 변수 Global variable

전역 변수 란 함수의 외부에서 선언되는 변수를 말한다.

전역 변수는 어디서나 접근 가능하다.

다음은 전역 변수의 예시이다.

#include <iostream>

using namespace std;

int num1 = 50;
int num2 = 100;
int num3 = 10;

int func1(int n);

int main() {
    int result = func1(num3);
    printf("result: %d\n", result);

    return 0;
}

int func1(int n) {
    for (int i=0; i<n; i++) {
        int temp = num1 + num2;
        num1 = num2;
        num1 = temp;
    }
    return num1;
}

📌 저장 유형 지정자

저장 유형 지정자 static

지역 변수static 을 붙이면 정적 변수 로 바뀐다.

다음은 static 의 예시이다.

#include <iostream>

using namespace std;

int func1();

int main() {
    func1();
    func1();
    func1();

    return 0;
}

int func1() {
    int num1 = 0;
    static int num2 = 0; // static을 붙이면 지역 변수가 정적 변수로 바뀜

    num1++;
    num2++;

    printf("num1: %d, num2: %d\n", num1, num2);
    
    return 0;
}

6.3 순환

📌 순환 Recursion

순환 이란 알고리즘이나 함수가 도중에 자기 자신을 다시 호출하여 문제를 해결하는 기법을 말한다.

대표적으로 팩토리얼 이 있다.

순환 알고리즘은 다음과 같은 부분들을 포함한다.

  • 순환 호출을 하는 부분
  • 순환 호출을 멈추는 부분

순환 호출을 멈추는 부분이 없다면?

시스템 오류가 발생할 때까지 무한정으로 호출하게 된다.

다음은 팩토리얼 프로그래밍의 예시이다.

#include <iostream>

using namespace std;

int factorial(int n);

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

    return 0;
}

int factorial(int n) {
    if (n <= 1)
        return 1; // 순환을 멈추는 부분
    else
        return (n * factorial(n-1)); // 순환 호출을 하는 부분
}




profile
평범한 개발자

0개의 댓글