C언어는 동일한 이름의 함수를 2개 이상 만들 수 없다.
그러나, C++ 에서는 namespace 를 활용하여 관련된 코드들을 묶어서 하나로 관리할 수 있다.
namespace Phone
{
namespace Iphone
{
void call()
{
std::cout<<"Iphone call"<<std::endl;
}
void text()
{
std::cout<<"Iphone text"<<std::endl;
}
}
namespace Galaxy
{
void call()
{
std::cout<<"Galaxy call"<<std::endl;
}
void text()
{
std::cout<<"Galaxy text"<<std::endl;
}
}
}
namespace 요소 접근 방법
using Phone::Iphone;
call();
text();
// => Iphone namespace 의 call,text 함수 호출
2-2) 특정 namespace 의 특정 함수만 사용하는 경우using Phone::Iphone::call;
call();
text(); // error
// => Iphone namespace 의 call 함수 호출
namespace 의 핵심은 Full name 으로 사용하는 것. (프로젝트의 규모가 커질수록 전역공간에 using 을 사용하는 것은 충돌의 확률을 높인다.)
struct Point
{
int x;
int y;
}
int a = 10;
int arr[5]={1,2,3,4,5}
Point p(1,2);
(), {}, = 초기화 등 다양한 초기화가 타입별로 다르다. -> 헷깔릴 수 있다.
그래서 등장한 것이 uniform initialization. 말 그대로 initialize 방법을 통일한 것.
struct Point
{
int x;
int y;
}
// direct initalization
int a{10};
int arr[5]{1,2,3,4,5}
Point p{1,2};
// copy initialization
int a={10};
int arr[5]={1,2,3,4,5}
Point p={1,2};
int a = 7.1; // 캐스팅이되어 7로 a에 저장된다.
int a{7.1} // 컴파일 에러가 발생한다. 즉 더 안전하다.
C++ 에서는 default parameter 지정이 가능하다.
int add(int a, int b, int c = 0) 과 같이 맨 끝에서 부터 차례대로 지정이 가능.
함수의 선언부와 구현부가 나눠져 있다고 생각해보자.
int add(int a, int b = 0);
int main()
{
cout<<add(1,2);
}
int add(int a, int b=0)
{
return a+b;
}
위와 같은 코드가 있다고 가정해보자. 이때 컴파일러의 동작을 살펴보면 코드를 순차적으로 읽으며 add 함수의 선언부에서 "아, add 함수는 두번째 파라미터에 default 값이 세팅되어 있구나"라고 생각할 것이다. 그리고 밑에 구현부를 보고는 "오잉? 두번째 파라미터에 default 값을 다시 정의하려고 그러네?" 라고 생각하여 컴파일 에러를 발생시킨다.
add(1) 을 호출하면 컴파일러가 파라미터를 하나만 보내는 것이 아니라, add(1,0) 으로 바꿔 보낸다.
C++ 에서는 동일한 이름의 함수를 사용 가능하다. 단, 파라미터 타입 또는 파라미터 개수가 바껴야 한다.
이것이 편리한 이유는 사용자 입장에서는 동일한 함수처럼 생각을 할 수 있다. 예를 들어, add(1,0) 과 add(1.2, 2,1) 이 있다면 overloading 을 활용하여 두개의 서로다른 함수를 만들 수 있지만, 이 api 를 사용하는 사용자 입장에서는 동일한 함수라고 느껴질 것이다.
주의 : defualt 파라미터가 있을 때 조심하자.
int add(int a, int b=0);
int add(int a);
int main()
{
add(1,2); // 정상적으로 호출된다.
add(1); // ambiguous 하다며 컴파일 에러가 발생.
}
// add.c
int add(int a, int b)
{
return a+b;
}
// add.h
int add(int a, int b);
// main.cpp
#include "add.h"
int main()
{
add(3, 4);
}
위 코드를 빌드하면 링킹 에러가 발생한다. 왜일까? 우리가 앞서 말했듯이 c++ 에서는 함수에서는 컴파일러가 name mangling 을 한다. 근데, main.cpp 파일에서는 add 함수를 _Z6addii 와 같은 형태로 name mangling 하여 함수를 찾을 것이다. 그런데, add.c 에서는 그냥 add 라는 함수로 컴파일이 될 것이다. 그래서, main.cpp 에서 add 함수를 찾을 수가 없다.
=> 이를 해결하기 위해 extern "C" 라는 것을 사용한다.
// add.c
int add(int a, int b)
{
return a+b;
}
// add.h
extern "C"{
int add(int a, int b);
}
// main.cpp
#include "add.h"
int main()
{
add(3, 4);
}
위 코드를 보고 add.h 를 include 하는 main.cpp 에서는 "아, add 함수는 C로 되어있으니, name mangling 하지 말고, add 라는 함수로 찾으면 되겠구나" 라고 생각한다.
주의점
// add.c
int add(int a, int b)
{
return a+b;
}
// add.h
#ifdef __cplusplus
extern "C"{
#endif
int add(int a, int b);
#ifdef __cplusplus
}
#endif
// main.c
#include "add.h"
int main()
{
add(3, 4);
}
이렇게 만들어 놓으면 main.c 가 되었다고 해도 __cplusplus 가 정의되어 있지 않으니 그냥 int add(int, int) 만 컴파일되고, cpp 로 컴파일 시에는 extern "C" 까지 포함이 되어 컴파일된다.
// overloading 사용 시
int add(int a, int b)
{
return a+b;
}
double add(double a, double b)
{
return a+b;
}
short add(short a, short b)
{
return a+b;
}
// template 사용 시
template <class T>
T add(T a, T b)
{
return a+b;
}
template 은 그 자체로 메모리에 올라가지 않는다! (실체를 갖지 않는다.)
template 을 실제로 사용해야 코드에 함수로서 메모리에 잡힌다. (실체가 생긴다 = instantiation)
template 사용법
inline int add(int a, int b)
{
return a+b;
}
int main()
{
int k = add(1,2);
// => add가 inline 함수이므로 add(1,2)가 바로 a+b로 치환된다.
// int k = a+b; 와 같다.
}
// math.h
inline double getPI();
template <class T>
T add(T a, T b);
int mul(int a, int b);
// math.cpp
inline double getPI()
{
return 3.14;
}
template <class T>
T add(T a, T b)
{
return a+b;
}
int mul(int a, int b)
{
return a*b;
}
// main.cpp
#include "math.h"
int main()
{
int addVal = add<int>(1,2);
double PI = getPI();
int mulVal = mul(1,2);
}
위과 같은 코드가 있다고 생각해보자. 저 코드는 정상적으로 빌드가 될까? -> no
inline함수 : inline 함수는 컴파일 시점에 컴파일러가 함수 호출 부분을 구현으로 바꿔줘야 한다. 근데, math.h 에서 선언만 되어있을 뿐 구현부를 컴파일러는 알 수가 없다. 이것을 알 수 있는 시점은 링크 시점이다. 따라서 컴파일 에러가 발생한다.
template 함수 : 앞에서 템플릿을 컴파일 했을 때 기계어를 봤지만, 템플릿 함수를 컴파일을 하면 함수가 생성된다. 즉, main.cpp 에서 add(1,2) 를 봤을 때 컴파일러는 컴파일 시점에 int add 함수를 만들어야 한다. 그러나 컴파일러는 구현부를 알 수가 없다. math.cpp 에 정의되어있으니깐. 따라서 에러가 발생한다.
따라서 inline 함수와 template 함수는 헤더파일에 구현을 해줘야한다.
// math.h
inline double getPI()
{
return 3.14;
}
template <class T>
T add(T a, T b)
{
return a+b;
}
int mul(int a, int b);
// math.cpp
int mul(int a, int b)
{
return a*b;
}
// main.cpp
#include "math.h"
int main()
{
int addVal = add<int>(1,2);
double PI = getPI();
int mulVal = mul(1,2);
}
int add(int a, int b)
{
return a+b;
}
auto add(int a, int b) -> int
{
return a+b;
}
template <class T>
T add(T a, T b)
{
return a+b;
}
int main()
{
add(1,2);
add(1.1,2.1);
add(3,7.7); // error
}
위와 같은 코드에서 add(3, 7.7) 은 에러가 발생한다. 그럼 어떻게 변경할까? class T1, class T2 로 받아보자. 그리고 반환 타입은 decltype을 써서 a+b의 값을 컴파일러가 추론하여 반환하도록 하자.
template <class T1, class T2>
decltype(a+b) add(T1 a, T2 b)
{
return a+b;
}
int main()
{
add(1,2);
add(1.1,2.1);
add(3,7.7); // error
}
근데 이렇게하면 에러가 발생한다. 왜냐면 a, b 가 선언되지 않은 상태에서 decltype(a+b) 를 했기 때문에.(컴파일러는 앞에서 뒤로 읽으니깐)
=> 이때 suffix return 사용
template <class T1, class T2>
auto add(T1 a, T2 b) -> decltype(a+b)
{
return a+b;
}
int main()
{
add(1,2);
add(1.1,2.1);
add(3,7.7); // ok
}
int add(int a, int b)
{
return a+b;
}
int main()
{
add(1.2, 2.3);
}
위와 같이 하면 캐스팅이 되어 add(1,2) 로 넘어간다.
int add(int a, int b)
{
return a+b;
}
double add(double, double) = delete;
int main()
{
add(1.2, 2.3);
}
위와 같이 하면 add(1.2, 2.3) 을 해도 컴파일 에러가 발생한다.
이는 나중에 배울 class 에서 자세히 다룸.
struct Point
{
int x;
int y;
};
void print(Point p)
{
cout<<p.x << p.y<<endl;
}
int main()
{
Point p{1,2};
print(p);
}
struct Point
{
int x;
int y;
};
void print(const Point& p)
{
cout<<p.x << p.y<<endl;
}
int main()
{
Point p{1,2};
print(p);
}
reference 는 call by reference, return by reference 의 관점에서 중요하다. 즉, reference 는 return 을 하던, 파라미터로 전달하던 임시객체가 생성되지 않는다. 이는 오버헤드를 줄여주고, return 시에는 lvalue 로 사용이 가능하다는 장점이 있다.