[C++ Primer Plus] Chapter 8. 함수의 활용
Table of Contents
C++ 기초 플러스 책을 읽고 공부한 노트입니다.
참조 변수 #
- 실제 변수의 대용이름. 함수의 매개변수에 사용한다.
- 포인터와 달리 초기화로만 참조를 설정할 수 있다.
- 참조 매개변수가
const
일 경우 다음 조건일 때 임시변수가 생성된다. 그래서 원본 데이터가 변하지 않는다.- (1) 올바른 데이터형이지만 lvalue가 아닐 때
- (2) 잘못된 데이터형이지만 올바른 데이터형으로 변환할 수 있을 때
double RefCube( const double & ref )
{
return ref * ref * ref;
}
double dNum = 5.0;
long lNum = 5L;
RefCube( lNum ); // 2
RefCube( dNum + 5.0 ); // 1
RefCube( 7.0 ); // 1
- rvalue 참조는
&&
를 사용한다.- lvalue는 단일 표현식 이후에도 없어지지 않고 지속되는 객체이다.
- rvalue는 표현식 종료 이후에는 더 이상 존재하지 않는 암시적인 값이다.
- rvalue 참조는 Chapter 18에 나올 move semantics에 쓰인다.
double && rref1 = 13.0; // 리터럴 상수
double && rref2 = 2.0 + dNum; // 표현식
double && rref3 = sqrt(2.0); // rvalue를 반환하는 함수
매개변수에 const 사용이 좋은 이유 #
- 실수로 데이터형 변경을 막을 수 있다.
const
와const
가 아닌 매개변수를 모두 처리할 수 있다.- 참조로 매개변수가 전달되면 임시변수를 생성해서 사용할 수 있다.
구조체 참조 #
struct Student
{
//...
}
Student& Func(Student& s)
{
//...
}
Student s1, s2;
// 대입 구문으로 값이 복사된다.
s1 = Func(s2);
// 만약 Func 함수의 리턴값이 const이면 안 되는 구문이다.
// 리턴 값이 const이면 상수 참조이기 때문이다.
Func(s2) = s1;
- 구조체를 참조로 리턴하는 경우 전체 구조체를 임시 장소에 복사한 후 다시 복사하는 과정이 없고, 직접적으로 복사되기 때문에 보다 효율적이다.
- 함수가 종료될 때 수명이 끝나는 임시 변수를 참조로 리턴하지 않도록 주의해야 한다.
- 해결 방안은 매개변수로 전달된 참조를 리턴하거나, 포인터를 사용하는 방법이 있다.
struct Student
{
//...
}
const Student& Func(Student& s)
{
Student *ptr = new Student; // delete를 까먹을 수 있다. (unique_ptr이 괜찮은 대안이다)
*ptr = s;
return *ptr; // 포인터를 사용해서 구조체를 참조로 리턴한다.
}
string 객체 참조 매개변수에 C 스타일 문자열 매개변수 전달하기 #
string
클래스가char *
형을string
으로 변환한다.const
참조 형식의 매개변수라면 임시 변수를 만들게 된다.
void Func(string str) // str과 “Hello”는 서로 주소가 다르다.
{
//...
}
Func("Hello");
객체의 상속과 참조 #
ostream
을 상속받는ofstream
형 객체들은ostream &
참조형으로 받을 수가 있다.
ios_base::fmtflags initial;
initial = cout.setf(ios_base::fixed); // 고정소수점 표기. 바꾸기 전 포맷팅 설정을 리턴한다.
cout.precision(1); // 소수점 이하 숫자 개수
cout.setf(ios::showpoint); // 0이더라도 소수점 표시
cout.width(17); // 필드 폭 설정 ( 한번 출력 후 초기화됨)
cout.setf(initial); // 이전 포맷팅 설정 복원
참조 매개변수를 사용하는 이유 #
- 전달되는 데이터 객체의 변경을 위해
- 객체 대신 참조를 전달해서 속도를 높이기 위해
- 매개변수가 배열이라면 참조 매개변수는 배열의 크기를 구체적으로 명시해야 하므로, 포인터가 유일한 대안이겠다.
디폴트 매개변수 #
- 함수의 원형에 명시한다.
- 어떤 매개변수를 디폴트 매개변수로 만들려면, 그 오른쪽에 있는 모든 매개변수를 디폴트 매개변수로 만들어야 한다.
void Func(int one, int two = 2, int three = 3)
{
//...
}
Func (1, ,3); // (X) 매개변수를 건너뛸 수는 없다.
함수 오버로딩 (함수의 다형) #
- 서로 다른 여러 개의 함수가 하나의 이름을 공유하는 것.
- 함수를 호출하면 매개변수 리스트(= 함수 시그내처)에 따라 알맞은 시그내처를 가진 함수원형을 찾아준다.
- 리턴형이 달라도, 시그내처가 같으면 오버로딩이 불가능하다. 반면, 시그내처가 다르면 다른 리턴형을 가질 수 있다.
- 함수 오버로딩이 되어서 여러 개의 함수가 존재하는데, 알맞은 시그내처가 없을 경우 컴파일 에러가 난다.
void Solution (int num)
{
//...
}
void Solution (float num)
{
//...
}
double test = 5.0;
Solution(test); // double형 매개변수를 가진 시그내처가 없으므로 컴파일 에러
- 데이터형과 그 참조는 서로 같은 것으로 친다.
const
와const
가 아닌 변수는 구별된다.- 이름장식
- 컴파일러는 오버로딩된 함수들의 이름을 알아보기 어려운 내부 형식으로 변환하여 암호화한다. 이것으로 서로 구분한다.
오버로딩 참조 매개변수 #
변경가능 lvalue ( x ) | const lvalue ( 1 ) | rvalue ( x + y ) | |
---|---|---|---|
void Solution ( int & num ); |
O | X | X |
void Solution ( const int & num ); |
O | O | O |
void Solution ( int && num ); |
X | X | O |
함수 템플릿 #
// 함수 템플릿 원형. class 대신 typename을 사용해도 된다.
template <class T>
void Swap (T & a, T & b);
// 함수 템플릿 정의
template <class T>
void Swap (T & a, T & b)
{
//...
}
함수 템플릿의 제한 #
template <typename T>
int Add(T a, T b)
{
return a + b;
}
- 이경우
T
가struct
라면 작동하지 않는다. 대안은…?
- 연산자를 오버로드한다.
- 특별한 형에 대하여 특화된 템플릿을 정의한다. (명시적 특수화)
명시적 특수화 #
- 다른
Swap()
템플릿을 사용하지 말고, 주어진 형에 맞게 특별히 명시적으로 정의된 이 함수 정의를 사용해라. - 조건
- 매개변수 동일
- 반환형 동일
- 앞에
template <>
붙이기
struct Student
{
//...
}
// <Student>에서 Student 생략가능.
template <> void Swap<Student>(Student & s1, Student & s2)
{
//...
}
암시적 구체화, 명시적 구체화 #
-
구체화: 컴파일러가 특정 데이터형에 맞는 함수 정의를 생성하기 위해 템플릿을 사용한 결과.
-
Swap()
템플릿을 사용해서, 주어진 형에 맞는 함수 정의를 생성해라. -
암시적 구체화: 컴파일러에게 암시적으로
Swap()
템플릿을 사용해서,x
,y
형에 맞는 함수 정의를 생성하라고 알린다. -
명시적 구체화: 컴파일러에게 명시적으로
Swap()
템플릿을 사용해서,int
형에 맞는 함수 정의를 생성하라고 알린다.
// 암시적 구체화
Swap(x, y);
// 명시적 구체화
template void Swap<int> (int, int);
Swap<int> (x, y);
컴파일러는 어떤 함수를 선택할까? #
- 순서
- 일반 함수 > 명시적 특수화 > 일반 템플릿
- 가능한 함수가 둘 이상이면 모호하기 때문에 에러가 발생한다.
- 사용자가 컴파일러에게 적절한 함수를 사용하도록 리드할 수 있다.
Swap<>(x, y); // <>는 템플릿 함수를 선택해야한다는 것을 의미한다.
Swap<int>(x, y);
decltype 키워드 #
- declared type의 줄임말이다.
- 특정 인스턴스가 생성되기 전까지 타입을 결정할 수 없을 때 템플릿을 정의하는 데 사용하는 특별한 방법이다.
- 내부 괄호로 한번 더 묶으면 그 안의 표현식을 lvalue참조로 본다.
int x = 1, y = 2;
decltype (x + y) xpy; // x+y과 동일한 타입의 xpy를 만들어라
xpy = x + y;
decltype (x + y) xpy = x + y; // 위와 같은 구문
decltype ((x)) xr = x; // int & 타입
template<class T1, class T2>
auto Solution(T1 x, T2 y) -> decltype(x + y) // 적절한 x + y의 리턴형을 선언할 수 있다.
{
return x + y;
}