C, C++

[C++] 참조(Reference)란?: 참조 vs 포인터 vs 일반변수, 반환 타입으로 참조, & 연산자의 사용방법

깃짱 2024. 4. 29. 12:30
반응형
반응형

 

 

💋 참조란?

✔️ 정의

참조는 어떤 변수의 다른 이름을 제공합니다.

예를 들어, 변수 robert에 대한 참조를 만들면, 이 참조를 통해 robert 변수를 직접 조작할 수 있습니다.

int robert = 10;
int& bob = robert;  // bob은 robert의 참조
bob = 20;           // robert의 값도 20으로 변경됨

bobrobert의 저장 위치에 대한 참조이며, bob을 통해 robert를 조작할 수 있습니다. bob에서 이루어진 변경은 모두 robert에 반영됩니다.

헷갈리죠?

✔️ 참조 vs 일반변수

만약에 bob이 참조변수가 아니라 일반변수였다 고 생각해 봅시당

int robert = 10;
int bob = robert;
bob = 20;

이렇게 되면 bob은 단지 robert의 10을 복사해갔다가 20으로 변경한 것 뿐, 위의 코드 실행 후에 robert는 10, bob은 20이 되는건데! 참조를 하게 되면 bob에다가 단지 복사가 아니라 실제 변수가 저장된 메모리 주소를 공유하게 됩니다.

참조 변수를 통해 값을 변경하면, 원본 변수의 값도 같이 변경됩니다.

✔️ 이거 왜씀?

C++에서는 C의 포인터를 사용한 call by reference 방식을 더 간단하고 안전하게 만들기 위해서 참조를 사용합니다. 물론 C++에서는 C언어의 대부분을 그대로 사용할 수 있기 때문에 여기서도 포인터를 사용할 수는 있습니다.

하지만 참조를 통해서 포인터를 사용해야 하는 복잡성을 줄여주며, 코드를 더 명확하고 쉽게 만들어 줍니다.

이해가 안된다면 아래의 예시를 보면서 이해를 해보도록 합시당

💋 포인터 vs 참조

함수에 파라미터를 전달할 때 복사가 아닌 원본 변수를 직접 조작하고 싶을 때 포인터 또는 참조를 사용합니다.

여기서는 간단히 사용 방식, 코드상에서의 차이에 대해서만 소개하고 뒤에서 더 자세히 어떤 점이 개선되었는지 설명하겠슴니당

✔️ 포인터를 사용한 Call by Reference

C언어에서는 함수가 변수의 값을 직접 변경하기 위해서 포인터를 사용합니다.

예를 들어, 변수의 값을 함수 내에서 변경하고자 할 때 아래와 같이 call-by-reference를 해야 합니다.

void increment(int *ptr) {
    (*ptr)++;
}

int main() {
    int num = 10;
    increment(&num);  // 변수의 주소를 전달
    printf("%d", num);  // 출력 결과: 11
    return 0;
}

여기서 increment 함수는 포인터를 파라미터로 받아 그 포인터가 가리키는 값을 증가시킵니다. 함수를 호출할 때는 변수 num의 주소를 &num으로 넘겨줍니다.

✔️ 참조를 이용한 Call by Reference

C++에서는 이러한 패턴을 더 간단하고 안전하게 구현할 수 있는 참조를 사용할 수 있습니다.

참조를 사용하면 포인터와 달리 메모리 주소를 직접 다룰 필요 없이 변수를 직접 참조할 수 있습니다.

void increment(int& num) {
    num++;
}

int main() {
    int num = 10;
    increment(num);  // 참조를 이용한 호출
    cout << num;  // 출력 결과: 11
    return 0;
}

여기서 increment 함수는 정수 참조를 매개변수로 받습니다. main 함수에서는 변수 num을 그냥 전달하면 됩니다. C++의 참조는 포인터처럼 주소를 명시적으로 다루지 않으므로 더 간결하고 안전한 코드 작성이 가능합니다.

💋 반환 타입이 참조일 경우?!!

함수가 변수의 참조를 반환하면, 이 함수의 반환값을 변수처럼 사용할 수 있습니다.

이걸 사용해서 연산자 오버로딩을 더 자연스럽게 구현할 수 있습니다.

double& getMax(double& a, double& b) {
    return (a > b) ? a : b;
}

int main() {
    double x = 5.0, y = 10.0;
    getMax(x, y) = 100.0;  // y의 값이 100.0으로 변경됨
    return 0;
}

주의사항! 반환되는 참조는 반드시 유효한 메모리 주소를 가리켜야 합니다.

함수 호출이 끝나면 사라져버리는 지역 변수의 참조를 반환하는 것은 위험합니당

항상 유효 범위와 생명 주기를 고려하여 안전하게 참조를 반환해야 합니다.

✔️ 값으로 반환받기 (double result)

double result = getMax(variable1, variable2);의 경우

  • 함수 getMax이 참조를 반환하더라도, result 변수에는 원본 변수의 값이 복사
  • 이후에 result의 값을 변경해도 원본 variable에는 아무런 영향이 없습니다.
  • 원본 데이터의 보호를 위해 사용

✔️ 참조로 반환받기 (double& result)

double& refResult = getMax(variable1, variable2);의 경우

  • refResultvariable참조로 직접 연결
  • refResult를 통해 값을 변경하면 원본 variable의 값도 변경
  • 데이터 복사 없이 원본 데이터를 직접 조작할 필요가 있을 때 사용

💋 참조가 그래서 뭐가 이득?

주로 프로그래밍의 안전성, 간결성을 높이기 위해서!

✔️ 간결성과 직관성

참조는 코드를 더 직관적이고 간결하게 만들어 줍니다. 포인터를 사용할 때는 주소에 접근하고 역참조(*)를 통해 값을 조작해야 하지만, 참조는 마치 일반 변수처럼 사용할 수 있어 코드가 더 읽기 쉽고 이해하기 쉬워집니다.

✔️ 안전성

참조는 초기화되면 변경할 수 없으며, 반드시 유효한 객체를 가리켜야 합니다. 이는 포인터처럼 NULL이 될 수 있거나, 의도치 않게 변경되는 문제 를 방지합니다. 포인터는 실수로 잘못된 메모리 주소를 참조하거나, 해제된 메모리를 참조하는 등의 실수를 범하기 쉽습니다. 참조는 개발자의 실수 가능성을 줄여 줍니다.

✔️ 함수 오버로딩과 연산자 오버로딩

C++에서 참조는 함수 오버로딩과 연산자 오버로딩에 중요한 역할을 합니다. 참조를 사용함으로써 개발자는 특정 타입의 인자를 받는 여러 버전의 함수를 정의할 수 있으며, 이는 객체 지향 프로그래밍의 다형성을 지원하는 데 기여합니다.

💋 [번외] & 기호는 참조에서도 쓰이고 포인터에서도 쓰이는데,,?

C++에서 & 기호는 컨텍스트에 따라 두 가지 다른 의미로 사용됩니다.

하나는 포인터와 관련된 용도이고, 다른 하나는 참조를 정의하는데 사용됩니다.

둘 다 같은 기호 &를 사용하지만, 기능적으로는 완전 다릅니다.

✔️ 주소 연산자(Address Operator)로서의 &

포인터와 관련하여 &주소 연산자로 사용되며, 변수의 메모리 주소를 반환합니다.

예를 들어, 변수 a의 주소를 얻기 위해 &a라고 작성할 수 있습니다. 이렇게 얻은 주소는 포인터 변수에 저장될 수 있습니다.

cppCopy code
int a = 5;
int* p = &a;  // p는 변수 a의 주소를 저장하는 포인터

✔️ 참조 선언자(Reference Declaration)로서의 &

참조를 선언할 때 &참조 선언자로 사용됩니다. 이는 변수의 별명을 만드는 데 사용되며, 참조는 선언된 변수의 또 다른 이름으로 사용됩니다.

한 번 참조가 초기화되면, 이 참조는 초기화된 변수와 동일하게 작동하며 변경할 수 없습니다.

cppCopy code
int a = 10;
int& refA = a;  // refA는 변수 a의 참조
refA = 20;      // a의 값이 20으로 변경됨

 

 

도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
비밀댓글과 메일을 통해 오는 개인적인 질문은 받지 않고 있습니다. 꼭 공개댓글로 남겨주세요!

 

반응형