개발개발/c++

c++의 참조변수 (reference variable)

유잉유잉유잉 2024. 12. 29. 13:38
728x90

 

🎯레퍼런스

  • 참조
  • 레퍼런스를 이용하여 다른 변수를 참조하는 변수를 만들 수 있습니다.
  • 참조를 하게되면 해당 변수의 값을 변경할 수 있게 됩니다.
  • 참조 대상은 반드시 레퍼런스 선언과 동시에 초기화(지정)되어야 합니다.
  • 레퍼런스 선언 이후 대상 변경은 불가능합니다.
  • 모든 타입은 레퍼런스로 만들 수 있습니다.
  • 참조하게 되는 변수의 별칭, 또다른 이름, 별명 이라고 생각하면 이해가 쉽습니다.
  • 포인터와 다르게 참조하는 변수의 주소를 저장하고 있진 않습니다.
    • 선언시 해당 변수를 참조한다고 컴파일러에게 알려줍니다
    • 따로 메모리공간을 할당하지 않습니다
    • 해당 변수의 별칭만 한 개 더 생기게 됩니다.

 

✅ 예제1

int number = 500;

// refNumber는 number 변수를 참조하게 되어 
// refNumber를 이용하여 number의 값을 변경할 수 있게 된다.
int& refNumber = number;

refNumber = 100;

std::cout<< number << std::endl; // 100 출력

 

 

✅ 예제2

int number = 500;
int number2 = 50;
int& refNumber = number;

// refNumber가 참조하는 number에 number2의 값을 "대입"
refNumber = number2;
std::cout << number << std::endl; // 50 출력

refNumber = 100;

std::cout << number << std::endl; // 100 출력
std::cout << number2 << std::endl; // 50 출력

참조된 변수는 초기화 이후 변경할 수 없습니다. 

refNumber = number2;는 단순히 refNumber가 참조하는 number에 number2의 값을 대입하게 됩니다.

 

 

✅ 예제3 : 함수의 인자로 구조체 또는 클래스 레퍼런스로 사용

struct FItem
{
    char name[32];
    EItemType itemType;
    bool isActive = true;
    int option = 0;
};

void Print( FItem& item )
{
	// 출력용 함수에서 레퍼런스 타입 변수의 값을 마음대로 바꿀 수 있음
    item.option = 1;
    // ...
}

int main()
{
    FItem item;
    strcpy_s( item.name, "목검" );
    item.itemType = EItemType::weapon;
    item.isActive = true;
    item.option = 0;

    FItem& refItem = item;

    Print( refItem );
    
    return 0;
}

위 예시에서 함수에 레퍼런스 인자로 받을 경우 

레퍼런스를 통해 item의 실제 값을 마음대로 바꿀 수 있습니다.

의도하지 않은 설계였을 경우 값이 변경될 수 있기에 위험해 집니다.

 

void Print( const FItem& item )

함수 인자로 레퍼런스를 사용할 때, 원본 변수의 변경을 원치 않는다면 

const 키워드를 사용하면 변경을 막을 수 있습니다.

 

 

추가로 item을 함수의 인자로 넘기는 것보다 

item의 참조 변수인 refItem을 이용하는게 속도가 훨씬 빠릅니다.

(값 복사하지 않고 참조하기 때문)

 

 

✅ 예제3-2 - 함수 인자로 일반 변수의 레퍼런스 사용 

void Print( int& item )

함수의 인자로 일반 값 타입 변수의 레퍼런스로 넘기게 될 경우

오히려 더 느릴 수 있습니다. 

 

 

✅ 예제4 - 코드 정리에 사용

struct FA
{
    int a;
    int b;
    __int64 big;
};

struct FB
{
    FA structA;
    int b;
};

int main()
{
	FB test;
    // 이런식으로 코드를 깔끔하게 사용할 수 있다.
	__int64& big = test.structA.big;   
}

 

 

✅ 예제5 - 함수 반환형이 참조자

int Add( int& num )
{
    ++num;
    return num;
}

int main()
{
    int num = 1;
    int num2 = Add( num );

    --num;
    
    std::cout << num << std::endl;
    std::cout << num2 << std::endl;
    
    return 0;
}

num2는 Add함수에서 num의 증가된 값을 "대입" 받습니다

num과 num2는 같은 주소를 가지고 있지 않습니다.

 

int& Add( int& num )
{
    ++num;
    return num;
}

int main()
{
    int num = 1;
    int num2 = Add( num );
    int& num3 = Add( num );
    int num4 = num3;

    ++num;

    std::cout << num << std::endl;
    std::cout << num2 << std::endl;
    std::cout << num3 << std::endl;
    std::cout << num4 << std::endl;
    
    return 0;
}

  • num은 add를통해 2번 증가, 그후 전치 연산을 통해서 한번더 증가 되었기 때문에 4
  • num2는 add(num) 호출 후 증가된 num의 값(2)을 "대입" 받아서 2
  • num3는 add(num) 호출 후 증가된 num의 값(3)이 된 후 num을 참조하게 되어 num의 값인 4
  • num4는 num3이 참조하는 num의 값을 대입 받아서 3 

 

 

 

 

‼️함수에서 참조자 반환시 주의하기 - 댕글링 레퍼런스

int& GetNum()
{
    int num = 0;
    return num;
}

int main()
{
    int& refNum = GetNum();

    refNum = 2;

    std::cout << refNum << std::endl;

    return 0;
}

GetNum() 함수는 지역 변수를 참조하여 반환하고 있습니다.

num 변수는 GetNum() 함수가 끝나면서 메모리에서 소멸하게 되는데,

refNum은 소멸된 메모리를 참조하게 됩니다.

 

num을 반환할 때 경고가 뜨게 됩니다.

 

해제된 메모리를 참조하는 참조자댕글링 레퍼런스 (Dangling Reference) 라고 합니다.

함수에서 참조자를 반환할 때는 지역변수를 반환하여 댕글링 레퍼런스가 발생하지 않았는지

각별히 주의해야 합니다. 

 

 

하지만 컴파일러에 따라 제 경우 처럼 출력이 되는 경우도 있습니다.

 

 

 

참고 : 

easycoding91님의 티스토리

 

 

728x90