* 참조자
int A = 99;
int &B = A;
int *C = &A;
B++;
//A == 100
(*C)++;
//A == 101
- 참조자는 포인터가 *를 사용해 데이터를 실제로 변경하는 것처럼 해당 데이터에 직접 접근할 수 있습니다.
int A = 99;
int E = 1;
int &B = A;
int* C = &A;
int& D = E;
B++;
//A == 100
(* C)++;
//A == 101
printf("%d\n", A); //101
printf("%p\n", &B); //A의 주소
printf("%p\n", C); //A의 주소
printf("%d\n", E); //1
B = D;
//A == 1
B--;
//A == 0
printf("%d\n", A); //0
printf("%p\n", &B); //A의 주소
printf("%p\n", C); //A의 주소
printf("%d\n", E); //1
- B가 A의 참조자이므로 B = D를 수행한 순간 D가 E의 참조자이므로 A = E가 이루어집니다.
- A--;를 했으므로 A의 값은 0입니다.
- E의 값은 변하지 않아서 1입니다.
* 매개변수로서의 참조자
void func(int &A)
{
A *= A;
}
void func_ref(int *A)
{
*A *= *A;
}
void func_ref_con (int const &A)
{
A * A;
}
int const& func_ret_con(int const&A)
{
return A * A;
}
int main()
{
int a;
func(a); //a의 값에 실제로 영향을 줌
func_ref(&a); //a의 값에 실제로 영향을 줌
func(a + 3); //컴파일 에러, 불가능
func_ref(&(a + 3)); //컴파일 에러, 불가능
func_ref_con(a + 3); //const & 매개변수의 경우 임시 변수를 넘길수 있으나, a를 변경할 수 없음
int c = func_ret_con(a + 5); //임시 변수를 넘길 수 있고, c에 임시 변수를 Return할 수 있음
return 0;
}
- 참조자를 통해 매개변수로 넘어간 변수의 값이 실제로 영향을 받게 됩니다.
- 포인터를 통해 매개변수로 넘어간 변수 역시 영향을 받지만, *연산자를 계속 써줘야 합니다.
- 임시로 생성되는 값을 매개변수로 넘길 수 없게 됩니다.
- const &를 사용하는 매개변수는 임시 변수를 넘길 수 있으나, 기존 변수의 값을 변경할 수는 없습니다.
- const &를 반환하는 함수는 임시 변수를 반환할 수 있으며, 기존 반환 방식보다 효율적입니다. (복사 한 번이 생략됩니다.)
- 결론적으로 참조를 사용하는 이유를 꼽자면
1. 매개 변수의 데이터 변경을 위해서
2. 함수 호출의 효율성 증가를 위해서
정도로 볼 수 있겠습니다.
[참고] 함수에서 배열 형태의 매개변수를 변경하려면 포인터를 써야 합니다.
여기서 더 깊은 이해를 하기 위해서는 l-value와 r-value를 알아야 하는데, 다음에 알아보도록 하겠습니다.
* 일반화 프로그래밍
- 여러가지 데이터 형을 포함하여 일반형으로 프로그래밍 하는 것을 일반화 프로그래밍이라 합니다.
- C++은 함수의 다형성을 지원하기는 하지만, 같은 함수를 타입만 바꿔서 복사하는 대신, 템플릿 함수를 이용합니다.
#include <iostream>
template <typename T>
void swap(T &A, T &B)
{
T temp;
temp = A;
A = B;
B = temp;
}
int main()
{
int a= 0 , b = 2;
swap(a, b);
printf("%d %d", a, b);
return 0;
}
- swap 함수는 이제, 여러 데이터 형을 지원하는 함수가 되었습니다.
- typename 대신 class를 사용해도 동일하게 동작합니다.
- 컴파일러 단계에서는 템플릿 함수를 호출할 때, 해당 매개변수에 맞는 함수를 새로 생성합니다.
예시) Swap((int)a, (int)b) --> Swap(int &, int &) {}
Swap((double)a, (double)b) --> Swap(double&, double&) {}
즉, 최종 단계의 코드에서는 템플릿 함수는 없고 호출된 만큼의 같은 이름의 함수가 여러개 존재하게 됩니다.
또한, 템플릿 함수 역시 오버로딩이 가능합니다.
예시)
template <typename T>
swap(T &A, T&B)
templata <typename T>
swap(T *A, T*B, int C)
* 함수의 구체화/ 특수화
- 구체화는 호출하는 측에서 함수를 구체화 하는 것을 의미합니다.
- 특수화는 정의하는 측에서 함수를 명시하는 것을 의미합니다.
* 암시적 구체화
template 함수는 받는 자료형에 따라 어떤 함수가 호출될 지 모릅니다.
하지만, 우리가 Swap(4, 5)와 같이 int 형을 넣어주게 되면
Swap 함수는 int로 구체화 되어 동작합니다.
이를 구체화라고 합니다.
#include <iostream>
template<typename T>
void func(T &a)
{
printf("%d \n", sizeof(a));
}
template<typename T>
void func(T&& a)
{
printf("%d \n", sizeof(a));
}
int main()
{
func(4); //4
func(4.0l); //12
return 0;
}
- 암시적 구체화는 직접 자료형을 명시하지 않아도, 매개변수를 보고 암시적으로 함수를 구체화하는 것을 의미합니다.
- 위 예시를 보시면, func에 들어간 매개변수가 int와 long double이므로 다르게 동작하는 것을 볼 수 있습니다.
* 명시적 구체화
- 템플릿 함수는 호출 시 우리가 <> 괄호 안에 타입을 명시적으로 적어줄 수 있습니다.
- 이와 같이 직접 매개변수 타입을 명시하여 구체화하는 것을 명시적 구체화라 합니다.
#include <iostream>
template<typename T>
void func(T &a)
{
printf("%d \n", sizeof(a));
}
template<typename T>
void func(T&& a)
{
printf("%d \n", sizeof(a));
}
int main()
{
func<double>(4); //8
return 0;
}
위 예시는 아까와 동일하게 int 4를 매개변수로 넣어주지만, double 형 매개변수라고 명시해주었습니다.
따라서, 받는 함수는 매개변수를 double로 취급하여 8을 출력합니다.
* 명시적 특수화
- 만약, 템플릿 함수가 어떤 자료형에는 다른 동작을 하길 원한다면 명시적 특수화를 이용해볼 수 있습니다.
#include <iostream>
template <typename T>
void swap(T &A, T &B)
{
T temp;
temp = A;
A = B;
B = temp;
}
template <>
void swap(char& A, char& B)
{
return;
}
int main()
{
int a= 0 , b = 2;
swap(a, b);
printf("%d %d\n", a, b); //2 0
char c = '3';
char d = '4';
swap(c, d);
printf("%c %c", c, d); //3 4
return 0;
}
- 위와 같은 코드에서는, swap 함수가 char 자료형을 받았을 때에만 swap하지 않도록 했습니다.
#include <iostream>
template <typename T>
void swap(T &A, T &B)
{
T temp;
temp = A;
A = B;
B = temp;
}
void swap(int& A, int& B)
{
return;
}
int main()
{
int a= 1 , b = 2;
swap(a, b);
printf("%d %d\n", a, b); //1 2
int c = 3;
int d = 4;
swap<>(c, d);
printf("%d %d", c, d); //4 3
return 0;
}
- 같은 int형으로 호출함에도, swap<>(c, d); 구문은 템플릿 함수를 호출했습니다.
- 함수가 서로 다른 템플릿 자료형 2개를 받아 리턴해야 할 경우에는 함수를 만들기 어렵습니다.
* decltype()
decltype()은 ()안의 결과를 타입으로 사용하는 함수입니다.
어떤 타입이 될지 모르는 경우에 쓸 수 있습니다.
#include <iostream>
template <typename T1, typename T2>
auto plus(T1 &A, T2 &B)
{
typedef decltype(A + B) rettype;
rettype APB = A + B;
return APB;
}
int main()
{
int a = 1;
double b = 2;
double C = plus(a, b);
return 0;
}
- 위와 같은 경우, 임의의 매개 변수 2개를 더했을 때, 어떤 타입으로 결정날 지 알 수 없습니다.
- 함수 내에서는 decltype() 구문을 이용해, 결정된 타입을 사용하는 변수를 쓸 수 있습니다.
- 함수의 return형의 경우, 어떤 타입이 나올 지 알 수 없는 관계로 auto를 이용합니다.
'공부 및 정리 > C++' 카테고리의 다른 글
[메모] l-value와 r-value (0) | 2022.12.20 |
---|---|
C++의 기초 - 7 (0) | 2022.12.20 |
C++의 기초 - 5 (0) | 2022.12.12 |
C++의 기초 - 4 (0) | 2022.11.30 |
C++의 기초 - 3 (0) | 2022.07.12 |