함수의 기본적인 형태
[inline] 리턴_타입 함수_이름(인수_목록)
{
함수 본문
}
C++은 두 가지 인수 전달 형태인 값에 의한 호출과 레퍼런스에 의한 호출을 구별한다.
값에 의한 호출(Pass by Value)함수에 인자를 전달하면 기본적으로 복사본이 만들어진다.
void increment(int x){
x++;
}
int main(void)
{
int i=4;
increment(i);
cout<<"i is "<<i<<'\n';
return 0;
}
레퍼런스에 의한 호출(Pass by Reference)함수의 매개변수를 수정하려면 인수를 레퍼런스로 전달하여야 한다.
void increment(int& x)
{
x++;
}
연산 결과와 같은 임시 변수들을 레퍼런스로 전달할 수 없다.
ex) increment(i+9); 오류 발생
벡터 및 행렬과 같은 큰 자료는 값으로 인자를 전달할 경우, 복사본을 생성함으로써 메모리를 많이 소모하기 때문에
거의 항상 레퍼런스에 의해 전달된다.
double two_norm(const vector& v){ ... }
상수 레퍼런스는 변경 가능한(mutable) 레퍼런스와 달리 임시 변수를 허용한다.
alpha=two_norm(v+w);
Default value인수가 일반적으로 같은 값을 갖는다면, 그 값을 기본값으로 선언해 줄 수 있다.
double root(double x, int degree=2){ ... }
x=root(3.5, 3);
y=root(7);
인수 목록의 끝에서만 여러 개의 기본값을 선언할 수 있다.기본값을 갖는 인수 뒤에는 기본값이 없는 인수를 가질 수 없다.
Return표현식 템플릿 기법을 사용하면, 큰 데이터를 반환할 수도 있다.
void는 실제로 타입이 아니라, 값 반환을 생략할 수 있는 자리 표시자(placeholder)이다.
void 함수에서 인수가 없는 return을 통해 보다 일찍 함수를 종료 할 수 있다.
(void 개체는 정의할 수 없음)
Inline_컴파일러에게 함수를 인라인 함수로 처리하도록 요청함함수를 호출하는데 컴퓨터는 비교적 많은 자원을 사용한다.(레지스터를 저장, 스택에 인수를 복사 등)
이와 같은 오버헤드를 피하기 위해 컴파일러는 함수 호출을 인라인할 수 있다.
함수 호출을 인라인하면, 해당 부분을 함수에 포함된 연산으로 대체한다.
inline double square(double x){ return x*x; }
함수 호출이 함수자체의 내용 복사본으로 대체되어 함수 오버헤드가 제거된다.
하지만, 인라인 함수가 모든 함수 호출에 대해 적절한 위치에서 확장하기 때문에
인라인 함수가 길거나 인라인 함수가 반복적으로 호출되는 경우,
컴파일된 코드를 약간 더 크게 만들 수 있다.
Overloading함수의 매개변수 선언이 충분히 다르면 같은 이름을 공유할 수 있다.
이를 함수 오버로딩(Function Overloading)이라고 함
#include <iostream>
#include <cmath>
int divied(int a, int b){
return a/b;
}
float divied(float a, float b){
return std::floor(a/b);
}
int main(void){
int x=5, y=2;
float n=5.0, m=2.0;
std::cout<<divide(x, y)<<std::endl;
std::cout<<divide(n, m)<<std::endl;
std::cout<<divide(x, m)<<std::endl;
}
함수 호출 과정
1. 인수 타입과 정확히 일치하는 오버로드가 있는지 확인(Overload Resolution)한다.(있을 경우 해당 오버로드를 취함)
2. 정확히 일치하는 오버로드가 없을 경우,
변환 후 일치하는 오버로드가 있는지 확인한다.
0개: 오류, 일치하는 함수를 찾을 수 없음
1개: 해당 오버로드를 취함
> 1개: 오류, 모호한 호출
위 예제에서 divide(x, m)에서 정확하게 일치하는 오버로드가 없으며, 암시적 변환(Implicit Conversion)에 의해
둘 다 오버로드 할 수 있기 때문에 모호함, 오류 발생
함수 오버로드는 시그니처(Signature)가 서로 달라야 한다.
시그니처
- 함수 이름
- 항(Arity)이라고 하는 인자의 개수
- 해당 순서대로) 인자의 타입
리턴 타입이나 인수 이름만 다른 오버로드는 동일한 시그니처를 가지기 때문에, 오버로드 되지 않고 (금지된)재정의된다
void f(int x){ ... }
void f(int y){ ... }
long f(int x){ ... }
void f(int x){ ... }
void f(int& x){ ... }
void f(const int& x){ ... }
레퍼런스 기호가 있으면 인수 타입이 다른 인수 타입으로 바뀌기 때문에 f(int) f(int&)가 공존할 수 있다.
하지만, 세 함수 호출 모두 모호하다.
따라서 레퍼런스와 값 인자를 갖는 오버로드가 혼합되어 있는 경우 대부분 오류가 발생한다.
main 함수main 함수는 두 가지 표준 시그니처를 가진다.
int main(void)
int main(int argc, char* argv[])
int main(int argc, char** argv)
두 번째 표준 시그니처에서 argc는 인수 개수를 나타내며, argv는 인수 목록을 포함한다.
argv[0]은 대부분의 시스템에서 호출한 실행 파일의 이름이다.
main 함수는 프로그램이 올바르게 끝났는지 여부를 나타내는 종료 코드로 정수를 반환한다.
0(또는 <cstdlib>의 EXIT_SUCCESS 매크로)는 성공을 나타내고 그 외 값은 실패를 나타낸다.
main 함수에서 return 문을 생략하더라도, 표준을 준수하여 자동으로 return 0;가 삽입된다.