-
[C++] 함수 기본C++/이것이 C++이다 2021. 2. 12. 00:02
프로그래밍 언어에서 함수는 수학적인 함수의 기본적인 의미와 어느정도 일맥상통 합니다.
임의의 원소 (입력값)에 대하여, 특정하게 대응하는 원소 (출력값)이 존재하는 개념을 함수라 합니다.
이번 글에서는 C++에서의 함수의 여러가지 부분을 살펴 볼 것입니다.
1. 디폴트 파라미터
더보기함수의 파라미터에 대하여, 그 파라미터에 대한 '기본 값' 을 설정할 수 있습니다.
다만, 디폴트 파라미터의 설정은 다음과 같은 규칙을 만족해야 합니다.
1. 디폴트 파라미터는 반드시 오른쪽부터 기술해야 한다. 2. 파라미터가 여러개일 경우 디폴트 파라미터 설정시, 그 오른쪽에 있는 모든 파라미터에 기본 값을 기술해야 한다. 3. 함수 호출시, 파라미터는 왼쪽부터 순서대로 입력되며 입력되지 않은 파라미터에 대하여 디폴트 파라미터를 적용한다.
순서대로 간단한 예제를 몇 가지 살펴보겠습니다.
1.1 디폴트 파라미터는 반드시 오른쪽부터 기술해야 한다.
#include <iostream> void function(int a = 5, int b){ std::cout << a << " " << b; } void main(){ function(10); }
C2548 : 'function' : 매개변수 2에 대한 기본 매개변수가 없습니다.
2. 디폴트 파라미터 설정시, 파라미터 오른쪽의 모든 파라미터에 대해 기본 값을 기술해야 한다.
#include <iostream> void function(int a = 5, int b, int c = 10){ std::cout << a << " " << b; } void main(){ function(10); }
C2548 : 'function' : 매개변수 2에 대한 기본 매개변수가 없습니다.
3. 함수 호출 시 입력되지 않은 파라미터에 대하여 디폴트 파라미터를 적용한다.
#include <iostream> void function(int a, int b = 7){ std::cout << a << " " << b; } void main(){ function(10); }
10 7
디폴트 파라미터는 유지보수의 관점에서 매우 유용한 기능중 하나가 아닐까 싶습니다.
이미 설계된 함수에 대한 기능 확장으로 파라미터가 추가되었을 경우를 가정할 때
추가되는 파라미터에 디폴트 파라미터를 설정 할 수 없다면, 함수를 호출하는 모든 소스 코드를 수정하거나
해당 함수의 버전을 올리지 않는 선택을 해야 할 것입니다.
물론 함수의 파라미터가 수정되는 큰 변경은 일어나지 않도록 설계하는 것이 가장 중요할 것 입니다.
2. 함수 오버로딩
더보기오버로딩 (Overloading)과 오버라이딩(Overwritting)은 용어의 유사성 때문에 많이 헷갈리는 용어 중 하나입니다.
하지만, 두 용어는 이름만 비슷하고 적용되는 상황이 전혀 다릅니다.
오버로딩에 대한 이야기를 하기 전에, 함수의 구조를 다시 한번 짚어보도록 하겠습니다.
함수의 구성은 아래와 같습니다.
[Return type] [Calling Convention] [Function name]([Parameter list]);
Return type : 함수의 반환 형식 (void, int, double... etc)
Calling convention : 함수의 호출 규칙 (생략가능, _cdecl, _stdcall, _fastcall)
Function name : 함수의 이름
Parameter list : 함수의 파라미터 리스트
오버로딩이란 '함수의 다중 정의' 를 의미합니다.
조금 더 쉽게 풀면 '같은 이름의 함수를 여러개 정의한다' 정도가 될 것 같습니다.
그리고, 그 오버로딩은 위 4가지 구성중 'Parameter list'에 차이를 둠으로써 정의됩니다.
예제를 살펴보겠습니다.
#include <iostream> int add(int a, int b){ return a + b; } float add(float a, float b){ return a + b; } void main(){ std::cout << add(1, 2) << "\n"; std::cout << add(1.2, 3.4) << "\n"; }
파라미터를 다르게 두면, 호출자 측의 파라미터를 보고 컴파일러가 판단하여 호출하게 됩니다.
여기서 소소한 문제가 발생합니다.
위에서 기술한 '디폴트 파라미터'와 조합될 시, 절대로 호출되지 않는 함수가 탄생할 위험이 발생합니다.
예제를 살펴보겠습니다.
#include <iostream> void function(int a){ std::cout << "Function(int)\n"; } void function(int a, int b = 5){ std::cout << "Function(int, int)\n"; } void main(){ function(10); // Line 12 }
E0308 : 오버로드 된 함수 "function"의 인스턴스 중 두 개 이상이 인수 목록과 일치합니다.
위의 예제는 컴파일 되지 않습니다.
main함수에서 호출하는 function 함수가 어떤 function 함수인지 모호하기 때문입니다.
다만, 'Line 12'라는 주석이 된 코드를 제거하면 ( = 호출을 하지 않으면)
문제 없이 컴파일됩니다.
호출 할 수 없는 함수가 메모리에 올라가는 것과
특정한 목적이 있었을 함수가 호출할 수 없게 되는 것
두 가지 측면에서 문제가 발생할 수 있습니다.
3. 템플릿
더보기상술한 '함수 오버로딩'의 예제로, 2개의 int를 더하는 함수와, 2개의 float을 더하는 함수를 살펴보았습니다.
위와 같은 방식으로 각각의 변수 타입에 대해 작동하는 같은 기능의 함수를 여럿 정의하는 것은
불필요한 오버헤드가 될 수 있고, 이후 유지보수 측면에서도 불편함이 늘어날 것 입니다.
이러한 불필요함을 한번에 해결해 줄 수 있는것이 템플릿 이다.
예제를 살펴보겠습니다.
#include <iostream> template <typename T> T function(T parameter){ std::cout << typeid(parameter).name() << "\n"; return parameter } void main(){ function(1); function(false); function(1.2); function('a'); function("abc"); }
int bool double char char const *
위와 같이 함수에 template <typename [Name]>의 예약어를 적어두는 것으로
템플릿을 이용할 수 있습니다.
구체적으로 함수의 선언에 들어가는 변수들 (파라미터, 반환형식)에 대하여
호출 측에서 정할 수 있도록 하는 것 입니다.
템플릿이라는 단어의 사전적인 정의와 비슷하게, '틀'의 역할을 하는 예약어가 템플릿 입니다.
한 함수에 대하여, 두 개 이상의 템플릿을 이용할 수 있습니다.
예제를 살펴보겠습니다.
#include <iostream> template <typename A, typename B, typename C> A function(B param1, C param2){ // Function return null; }
위와 같이 typename을 여럿 기술하는 것으로
템플릿으로 들어가는 변수의 종류를 늘릴 수 있습니다.
4. 인라인 함수
더보기함수는 호출되면 스택에 쌓이고, 파라미터가 존재할 경우 메모리 복사가 발생합니다.
내부적으로 제어도 함수 내부로 이동하게 됩니다.
이런 오버헤드를 줄이기 위해, 짧고 단순한 함수는 매크로로 정의해서 사용할 수 있습니다.
예제를 살펴보겠습니다.
#include <iostream> #define ADD(a, b)((a) + (b)) void main(){ std::cout << ADD(1, 2) << "\n"; }
3
하지만, 매크로가 만능은 아닙니다.
파라미터의 형식을 지정할 수 없고, 논리적인 문제가 발생할 가능성도 존재합니다.
또한 가독성이 좋지 않다는 문제가 있습니다.
인라인 함수란, 함수의 앞에 inline 예약어를 붙이는 함수입니다.
인라인 함수는 매크로처럼 함수의 호출이 일어나지 않습니다.
상술한 오버헤드가 일어나지 않는다는 장점이 있습니다.
예제를 살펴보겠습니다.
#include <iostream> inline int add(int a, int b) return a + b; } void main(){ std::cout << add(1, 2) << "\n"; }
3
inline 예약어를 제외하면, 기존에 보던 함수와 다른 점이 없습니다.
호출스택이나, 메모리복사, 제어이동 등이 일어나지 않으면 완벽한 함수가 아닌가?
하는 생각이 지금 이 글을 작성하는 중에도 들고 있습니다.
하지만, 인라인 함수는 매크로처럼 소스코드의 복사가 발생합니다.
소스코드의 길이에 대한 오버헤드와, 함수 호출에 대한 오버헤드간의 관계에 대해서
차후 다른 글에서 다루어 보도록 하겠습니다.
함수에 대한 내용을 다루었으니 클래스.... 에 대한 내용을 다루고 싶지만
C++에는 다른 언어와 차별되는 내용이 아직 남아있습니다.
C++에서의 식별자 충돌, 스코프 제한과 관련된 기능입니다.
다음 글은 '네임스페이스' 에 대해 다루어보도록 하겠습니다.
짧지 않은 글 읽어주심에 감사드립니다.
다음에 또 뵙겠습니다.
'C++ > 이것이 C++이다' 카테고리의 다른 글
[C++] 클래스 기본 문법 1 (0) 2021.02.27 [C++] 클래스를 살펴보기 전에 (0) 2021.02.27 [C++] 네임스페이스 (0) 2021.02.12 [C++] C와 C++ (0) 2021.01.29 [C++] 이 카테고리에서는 (0) 2021.01.29