I'm FanJae.

[C++] Reference 본문

C++/Basic

[C++] Reference

FanJae 2024. 8. 9. 13:55

※ 본 포스트는 코드누리 C++ Basic 강의 내용을 보고 정리한 포스트입니다.

 

1. Reference(참조)

 

1-1. Reference의 정의

#include <iostream>
int main()
{
    int  n = 10;
    int* p = &n;
    
    int& r = n;
    
    r = 20;
    
    std::cout << n << std::endl;
    std::cout << &n << std::endl;
    std::cout << &r << std::endl;
}

- C 언어에서는 변수의 주소값을 담을 수 있는 Pointer라는 것이 있다.

- C++에서는 포인터와 유사한 형태의 Reference라는 기능이 존재한다. (유사한 것이 결코 같은게 아니다.)

- 포인터 변수를 선언하는 것처럼 다음과 같이 선언이 가능하다.

int &r = n;

 - Reference란, 이미 존재하는 변수(메모리)에 대한 추가적인 별칭을 부여하는 문법이다.

- 기존 포인터가 이러한 형태를 띄는 느낌이다. 여기에 r이 추가된 이후, r에 의해 n의 값이 20으로 바뀐 이후 상황을 보자.

※ 즉, 이처럼 r을 이용해서 n에 접근할 수 있다. r과 n은 같은 메모리를 가지는 것을 확인할 수 있다.

- & 연산자는 크게 2가지의 기능이 존재한다.

 

① 변수의 주소를 구할 때 사용

int n = 0;
int* p = &n;

 

② 레퍼런스 변수를 선언할 때 사용

int& r = n;

 

1-2. Reference 와 함수 인자

void inc1(int n) { ++n; }
void inc2(int* p) { ++(*p);}
void inc3(int& r) { ++r; }

int main()
{
    int a = 10, b = 10, c = 10;
    
    inc1(a);
    inc2(&b);
    inc3(c); // 참조 변수의 경우는 주소가 아닌 값을 보내야 한다.
    
    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
}

- 원래 C 언어 에서는 Call by Reference를 직접 지원하지 않는다.

- 다만, 이를 Pointer를 사용하여, 유사한 형태를 만드는 것이 가능하다.

- C++ 에서는 Refernece가 존재하므로, Call by Reference를 지원한다.

- 참조 변수에 보낼때는 주소가 아닌 원본 값을 그대로 보내주는 것에 유의 해야 한다.

 

※ 함수 인자로 reference를 사용하면 포인터와 유사하게 전달된 인자의 값을 수정이 가능하다. 그리고, 포인터 보다 간결하고 안전하게 코드 작성이 가능하다.

void f1(int* p)
{
     if(p != 0)
     {
     }
}
void f2(int& r)
{
}

int main()
{
    int* p = 0; // 널 포인터는 있다.
    int& r; // error. 널 참조는 없다.
    // int& r = n;
}

- 이 예제에서, 포인터는 Null 값을 담을 수 있다. 따라서, 함수에서 연산 전에 널 포인터인지 검사를 하는 문장이 필요하다.

- 반면, Reference는 NULL로 초기화 하는것이 불가능하다. 반드시 초기화를 진행해야 한다.

 

① scanf vs std::cin

- 입력 받을때 C는 scanf를 C++은 std::cin을 사용한다.

- 이들은 모두 사용자가 입력한 값을 인자로 전달한 변수에 담아와야 한다.

 

scanf("%d", &n);  -> 입력한 값을 인자로 전달한 변수에 담아오기 위해 Call by pointer를 사용한다. 

std::cin >> n; -> 입력한 값을 인자로 전달한 변수에 담아오기 위해 Call by Reference를 사용한다. 


2. Const reference

struct Rect
{
    int left;
    int top;
    int right;
    int bottom;
};

void foo ( Rect r )
{
}

int main()
{
    Rect rc = {1, 1, 5, 5};
    foo(rc);
}

- foo()에 변수 rc를 전달하는데 rc의 상태를 변경하면 안된다고 가정하면, Call by Value로 전달하면 된다.

- 이와 관련해서 Call by value는 다음과 같은 특징이 있다.

 

① Call by value

- 인자로 전달된 변수를 수정하지 않겠다는 약속이다.

- 구조체 같은 사용자 정의 타입을 인자로 사용하는 경우 복사본에 대한 오버헤드가 존재한다.

 

※ 위와 같은 문제를 해결해주는 것이 바로 const reference이다.

struct Rect
{
    int left;
    int top;
    int right;
    int bottom;
};

//void foo ( Rect r )
void foo (const Rect& r)
{
}

int main()
{
    Rect rc = {1, 1, 5, 5};
    foo(rc);
}

 

2-1. const reference ( const Rect & )

- 복사본에 대한 오버헤드 없이 인자로 전달된 변수를 수정하지 않겠다는 약속이다.

- 이는 C++에서 가장 널리 사용되는 인자 전달 방식이다.

struct Rect
{
    int left;
    int top;
    int right;
    int bottom;
};

// f1과 f2중 좋은 코드는 무엇인가?
void f1(Rect r)        {}
void f2(const Rect& r) {} // 좋은 코드

// f3와 f4중 좋은 코드는 무엇인가?
void f3(int n)         {} // 좋은 코드
void f4(const int& r)  {}

- 인자의 값을 바꾸지 않는다는 전제하에  f1과 f2 중에는 f2가 더 좋은 코드이다. 복사본에 대한 오버헤드가 없기 때문이다.

- 하지만, f3와 f4 중에는 f3가 더 좋은 코드이다.

- f3,f4중 f3가 더 좋은 코드인 이유는 int와 같은 primitive type은 타입의 크기가 크지 않고, Reference를 사용하는 것 보다 더 많은 컴파일러 최적화가 지원된다.

 

2-2. 함수 인자를 받는 방법

 

① 인자의 값을 변경하는 경우

- Pointer, Reference 모두 가능.

- C++에서는 레퍼런스를 좀 더 많이 사용한다.

pointer : void swap(int* p1, int* p2)
reference : void swap(int& r1, int& r2)

 

② 인자의 값을 변경하지 않는 경우

- Pointer, Reference 모두 가능.

- C++에서는 레퍼런스를 좀 더 많이 사용한다.

User define type const refernece 사용.
일반적으로 타입의 크기가 크다
메모리 사용량 증가, 복사 생성자 호출의 오버헤드를 줄이기 위해 사용한다.
Primitive type call by value 사용.
타입의 크기가 크지 않고, 생성자 개념이 없고, Reference를 사용하는 것보다 더 많은 컴파일러 최적화가 지원 된다.

 

'C++ > Basic' 카테고리의 다른 글

[C++] Explicit casting  (0) 2024.08.11
[C++] nullptr  (0) 2024.08.11
[C++] 연산자와 제어문  (0) 2024.08.09
[C++] Function III  (0) 2024.08.08
[C++] Function III  (0) 2024.08.08
Comments