리얼 개발

[C++] 4. 함수(Function) 본문

C++/어려운 C++

[C++] 4. 함수(Function)

econo-my 2024. 6. 26. 19:20

저번 포스팅에서 제어문에 대해 살펴보았다.

 

c언어의 함수와 똑같지만 c++ 에선 더 추가적인 기능들의 함수들이 있다. 함수의 개념부터 살펴보겠다.

 

함수의 기본 개념

프로그래밍에서 함수의 기본 개념은 프로그램을 구성하는 기본 모듈이다. 함수는 코드를 재사용 가능하게 하고, 프로그램을 논리적인 단위로 나눠준다. 한 마디로 단순 반복작업을 줄이기 위해 사용하는 것이다.

함수는 반환 타입, 함수 이름, 매개 변수 리스트, 함수 본문으로 구성된다.

// 함수 정의
int add(int a, int b) {
    return a + b;
}

// 함수 호출
int result = add(3, 4); // result는 7

 

 

함수 정의와 프로토타입

#include <iostream>
using namespace std;

int add(int a, int b); //함수 프로토타입

int main()
{
	int answer = 1 + add(4, 5);
	cout << answer << endl;
    return 0;
}

int add(int a, int b) //함수 정의
{
	return a + b;
}

 

프로토타입은 코드가 순차적으로 읽히는 동안 정의되지 않은 함수가 호출되어도 문제없도록 컴파일러에게 함수가 어떻게 생겼는지 알려주는 작업이다.

  • 리턴타입
  • 인자의 갯수
  • 인자의 타입

이로써 컴파일러가 함수에 대하여 올바르게 처리할 수 있도록 해준다. 이를 하지 않았을 때의 문제는 두 함수가 서로를 호출할 때이다.

#include <iostream>
using namespace std;

//add 입장에서 sub이 존재하는지 컴파일 타임에 알 수 없다.
int add(int a, int b) { return sub(a, b); }
int sub(int a, int b) { return add(a, b); }

int main()
{
	int answer = 1 + add(4, 5);
	cout << answer << endl;
	return 0;
}

 

 

지역 변수와 전역 변수

함수 내에서 선언된 변수는 지역 변수로, 해당 함수가 실행되는 동안만 유효하다. 함수 외부에서 선언된 변수는 전역 변수로, 프로그램이 실행되는 동안 유지된다. 하지만 static 키워드를 사용하면 함수 내에서도 값을 유지할 수 있다.

#include <iostream>
using namespace std;
int globalVar = 10; // 전역 변수

void func() 
{
    int localVar = 5; // 지역 변수
    static int staticVar = 0; // static 변수

    localVar++;
    staticVar++;
    cout << "localVar: " << localVar << 
           ", staticVar: " << staticVar << endl;
}

int main() 
{
    func(); // localVar: 6, staticVar: 1
    func(); // localVar: 6, staticVar: 2
    return 0;
}

 

 

콜 스택 (Call Stack) - 함수의 동작 원리

함수는 스택을 통해 작동한다.

 

스택은 다음과 같이 나중에 들어온 데이터가 처음으로 나가는 데이터 구조를 말한다. (뷔페 접시를 생각해보면 될 것 같다) 스택에 들어가는 것을 푸쉬, 스택에서 나가는 것을 팝이라고 한다.

콜 스택은 함수 호출과 복귀를 관리하는 메모리 구조이다. 함수가 호출되면 해당 함수의 프레임이 스택에 푸시되고, 함수가 종료되면 스택에서 팝된다. 이 과정에서 지역 변수와 함수의 상태가 유지된다.

함수가 호출될 때

  1. 인자가 있다면 인자들을 스택에 푸쉬하고
  2. 불리는 함수에 대한 스택 프레임을 푸쉬하고
  3. 불리는 함수의 지역변수들이 푸쉬된다.

함수가 반환될 때

  1. 할당된 지역변수를 팝하여 제거하고
  2. 반환될 함수에 대한 스택프레임을 제거하고
  3. 푸시 되었던 인자들도 팝하여 제거한다.

 

재귀 함수

함수는 자기 자신을 호출할 수 있다. 이를 재귀 함수라고 한다. 재귀 함수는 주로 반복적인 작업을 처리할 때 유용하지만, 잘못 사용한면 스택이 넘치는 스택 오버플로우를 일으킬 수 있다.

// 팩토리얼 재귀 함수
int factorial(int n) 
{
    if (n <= 1) return 1;
    else return n * factorial(n - 1);
}

// 함수 호출
int result = factorial(5); // result는 120

 

이 다음 디폴트 매개변수부터는 C++에만 존재하는, C언어의 함수와는 다른 새로운 개념들이 등장한다.

 

디폴트 매개변수

함수에 인자가 전달되지 않았을 경우 설정될 기본값을 지정하는 방법이다.

#include <iostream>
using namespace std;


int add(int a = 0, int b = 1)
{
	return a + b;
}

int main()
{
	cout << add(); //1 출력
	return 0;
}

다음과 같이 인자를 넣어주지 않았지만 1이 출력되는 것을 볼 수 있다.

 

함수 오버로딩

같은 이름의 함수를 파라미터를 다르게 함으로써 여러개 만들어 내는 것이다.

int add(int a, int b)
{
	return a + b;
}

double add(double a, double b)
{
	return a + b
}

int add(int a, int b, int c)
{
	return a + b + c;
}

float add(float a, float b, double c)
{
	return a + b + c;
}

 

함수 오버로딩은 반드시 다른 파라미터를 가져야 한다. (갯수, 자료형)

하지만 리턴 타입으로 함수 오버로딩을 사용할 수 없다. 왜냐하면 함수를 호출하는 곳에서 타입 캐스팅을 활용해 얼마든지 바꿀 수 있기 때문이다.

 

 

템플릿 함수

템플릿을 사용하면 함수의 자료형을 일반화할 수 있다. 컴파일러는 템플릿을 기반으로 실제 자료형에 맞는 함수를 자동으로 생성해준다. 이를 통해 코드의 재사용성을 높일 수 있다.

 

  • 일반적인 사용
#include <iostream>
using namespace std;

template<typename T>
T add(T a, T b) //int, float, double 어떤 자료형이든 들어갈 수 있다.
{
	return a + b;
}

int main()
{
	cout << add(1.3, 3.3);
	return 0;
}

 

  • 인스턴스화
#include <iostream>
using namespace std;


template<typename T>
T add(T a, T b)
{
	return a + b;
}

int main()
{
	cout << add(1.3, 3.3); //묵시적 인스턴스화
	cout << add<int>(1.3, 3.3); //명시적 인스턴스화
	return 0;
}
 

명시적 : 해당 함수에 어떤 자료형으로 받을지 미리 결정해놓는다.

묵시적 : 해당 함수에 어떤 인자가 들어왔는지 보고 타입을 결정한다.

 

 

  • 템플릿 특수화
#include <iostream>
using namespace std;


template<typename T>
T add(T a, T b)
{
	return a + b;
}

template<>
float add(float a, float b)
{
	return a + b;
}

int main()
{
	cout << add(1.3f, 3.3f); //아래 함수 호출
	cout << add(1.0, 2.0); //위의 함수 호출
	return 0;
}

 

템플릿이 있는 상태에서, 특정 자료형에 대해 다른 코드를 실행하고 싶을 때 사용한다.

 

 

인라인 함수

인라인 함수는 함수 호출의 오버헤드를 줄이기 위해 사용된다. 컴파일러는 인라인 함수의 호출을 실제 함수 코드로 대체한다. 그러나 인라인 함수는 무분별하게 사용하면 코드 크기가 오히려 커질 수 있으므로 주의가 필요하다.

#include <iostream>
using namespace std;


inline int add(int a, int b)
{
	return a + b;
}

int main()
{
	cout << add(1, 2); //a + b로 대체한다.
	return 0;
}

 

하지만 inline 같은 경우는 컴파일러가 똑똑해져서 알아서 다 해준다. 사용자가 명시적으로 써놔도 무시할 수도 있고, 쓰지 않아도 써서 프로그램을 실행시킬 수 있다.

'C++ > 어려운 C++' 카테고리의 다른 글

[C++] 6. 포인터와 참조  (0) 2024.06.28
[C++] 5. 배열과 구조체 (Array and Struct)  (0) 2024.06.26
[C++] 3. 제어문 (control_statement)  (0) 2024.06.26
[C++] 2. Dealing with Data  (0) 2024.06.26
[C++] 1. Hello World  (0) 2024.06.26