C ++에서 포인터 변수와 참조 변수의 차이점은 무엇입니까?


3262

참조는 구문 설탕이라는 것을 알고 있으므로 코드를 읽고 쓰기가 더 쉽습니다.

그러나 차이점은 무엇입니까?


100
포인트 2는 "포인터는 NULL이 될 수 있지만 참조는 허용되지 않습니다. 형식이 잘못된 코드 만 NULL 참조를 생성 할 수 있으며 동작은 정의되지 않습니다."라고 생각합니다.
Mark Ransom

19
포인터는 또 다른 유형의 객체이며 C ++의 모든 객체와 마찬가지로 변수 일 수 있습니다. 반면에 참조는 객체가 아니며 변수 일뿐 입니다.
Kerrek SB 2016 년

19
int &x = *(int*)0;gcc에서 경고없이 컴파일됩니다 . 참조는 실제로 NULL을 가리킬 수 있습니다.
Calmarius

20
참조는 변수 별칭
Khaled.K

20
나는 첫 문장이 어떻게 전체 오류인지 좋아합니다. 참조에는 자체 의미가 있습니다.
궤도에서 가벼움 경주

답변:


1707
  1. 포인터를 다시 할당 할 수 있습니다 :

    int x = 5;
    int y = 6;
    int *p;
    p = &x;
    p = &y;
    *p = 10;
    assert(x == 5);
    assert(y == 10);
    

    참조는 초기화 할 수 없으며 초기화시 할당되어야합니다.

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. 포인터는 스택에 자체 메모리 주소와 크기 (x86의 경우 4 바이트)를 가지고있는 반면, 참조는 원래 메모리와 동일한 메모리 주소를 공유하지만 스택의 일부 공간을 차지합니다. 참조는 원래 변수 자체와 주소가 동일하므로 참조를 동일한 변수의 다른 이름으로 생각하는 것이 안전합니다. 참고 : 포인터가 가리키는 내용은 스택 또는 힙에있을 수 있습니다. 참조를 피하십시오. 이 진술에서 내 주장은 포인터가 스택을 가리켜 야한다는 것이 아닙니다. 포인터는 메모리 주소를 보유하는 변수 일뿐입니다. 이 변수는 스택에 있습니다. 참조에는 스택에 자체 공간이 있고 주소는 참조하는 변수와 동일하므로 스택 대 힙 에 대한 추가 정보. 이것은 컴파일러가 알려주지 않는 실제 참조 주소가 있음을 의미합니다.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. 여분의 간접 레벨을 제공하는 포인터에 대한 포인터를 가질 수 있습니다. 참조는 한 수준의 간접 참조 만 제공합니다.

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
    
  4. 포인터는 nullptr직접 지정할 수 있지만 참조는 할 수 없습니다. 충분히 노력하고 방법을 알고 있다면 참조 주소를 만들 수 있습니다 nullptr. 마찬가지로 충분히 노력하면 포인터에 대한 참조를 가질 수 있으며 해당 참조는을 포함 할 수 있습니다 nullptr.

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
    int &r = *p;  <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
    
  5. 포인터는 배열을 반복 할 수 있습니다. 당신이 사용할 수있는 ++포인터가 가리키는 그 다음 항목으로 이동하고, + 45 요소로 이동합니다. 이것은 포인터가 가리키는 객체의 크기에 관계없이 발생합니다.

  6. 포인터가 가리키는 *메모리 위치에 액세스하려면 포인터를 참조 해제해야 하지만 참조는 직접 사용할 수 있습니다. 클래스 / 구조체에 대한 포인터는 ->멤버에 액세스하는 데 사용되는 반면 참조는을 사용합니다 ..

  7. 참조는 배열에 채워 넣을 수 없지만 포인터는 (@litb 사용자가 인용)

  8. 상수 참조는 임시에 바인딩 될 수 있습니다. 포인터는 (간접적 인 것이 없다면) 할 수 없습니다 :

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.
    

    이것은 const&인수리스트 등에서 사용하기에 더 안전합니다.


23
...하지만 NULL을 역 참조하는 것은 정의되어 있지 않습니다. 예를 들어 참조가 NULL인지 테스트 할 수 없습니다 (예 : & ref == NULL).
Pat Notz

69
숫자 2는 사실 이 아닙니다 . 참조는 단순히 "같은 변수의 다른 이름"이 아닙니다. 참조는 포인터와 매우 유사한 방식으로 함수 등에 전달되고 클래스 등에 저장 될 수 있습니다. 그것들은 그들이 가리키는 변수와 독립적으로 존재합니다.
Derek Park

31
브라이언, 스택은 관련이 없습니다. 참조 및 포인터는 스택에서 공간을 차지할 필요가 없습니다. 둘 다 힙에 할당 될 수 있습니다.
데릭 파크

22
Brian, 변수 (이 경우 포인터 또는 참조)에 공간이 필요하다는 사실이 스택에 공간이 필요하다는 의미 는 아닙니다 . 포인터와 참조는 힙을 가리킬 뿐만 아니라 실제로 힙에 할당 될 수 있습니다 .
Derek Park

38
또 다른 중요한 차이점 : 참조는 배열로 채워질 수 없습니다
Johannes Schaub-litb

382

C ++ 참조 란 무엇입니까 ( C 프로그래머 용 )

참조는 A와 생각 될 수 상수 포인터 (상수 값에 대한 포인터와 혼동하지!) 자동 간접으로, 즉 적용됩니다 컴파일러 *당신을위한 연산자.

모든 참조는 널이 아닌 값으로 초기화해야합니다. 그렇지 않으면 컴파일이 실패합니다. 참조의 주소를 얻는 것은 불가능합니다. 주소 연산자는 대신 참조 된 값의 주소를 반환합니다. 또한 참조에 대해 산술을 수행 할 수도 없습니다.

C 프로그래머는 간접적으로 발생하거나 함수 시그너처를 보지 않고 값이나 포인터로 인수가 전달되는 경우 더 이상 명확하지 않으므로 C ++ 참조를 싫어할 수 있습니다.

C ++ 프로그래머는 안전하지 않은 것으로 간주되므로 포인터 사용을 싫어할 수 있습니다. 비록 참조가 가장 사소한 경우를 제외하고는 상수 포인터보다 더 안전하지는 않지만 자동 간접의 편리함이없고 다른 의미 적 의미를 지니고 있습니다.

C ++ FAQ 의 다음 문장을 고려하십시오 .

참조는 종종 기본 어셈블리 언어의 주소를 사용하여 구현되지만 참조를 객체에 대한 재미있는 모양의 포인터로 생각 하지 마십시오 . 참조 객체입니다. 객체에 대한 포인터 나 객체의 복사본이 아닙니다. 그것은 이다 오브젝트.

그러나 참조가 실제로 객체라면 어떻게 매달려있는 참조가있을 수 있습니까? 관리되지 않는 언어에서는 참조가 포인터보다 '불량한'것이 불가능합니다. 일반적으로 범위 경계를 넘어 값을 안정적으로 별칭으로 지정할 수있는 방법은 없습니다!

왜 C ++ 참조가 유용하다고 생각합니까?

는 C의 배경에서 오는, C ++ 참조는 다소 바보 개념으로 보일 수도 있지만, 사람은 여전히 가능 포인터 대신에 그들을 사용한다 : 자동 간접는 이다 편리하고 처리 할 때 참조가 특히 유용하게 RAII -하지만 때문에 어떤 인식 안전 그러나 관용적 코드 작성이 덜 어색해지기 때문에 이점이 있습니다.

RAII는 C ++의 핵심 개념 중 하나이지만 복사 의미론과 사소하게 상호 작용합니다. 참조로 객체를 전달하면 복사가 필요 없으므로 이러한 문제를 피할 수 있습니다. 언어로 된 참조가 없으면 대신 포인터를 사용해야하므로 사용하기가 더 번거로우므로 최선의 솔루션이 대안보다 쉬워야한다는 언어 설계 원칙을 위반합니다.


17
@kriss : 아니요, 자동 변수를 참조로 반환하여 매달려있는 참조를 얻을 수도 있습니다.
벤 Voigt

12
@kriss : 컴파일러가 일반적인 경우를 감지하는 것은 사실상 불가능합니다. 클래스 멤버 변수에 대한 참조를 반환하는 멤버 함수를 고려하십시오. 안전하고 컴파일러에서 금지해서는 안됩니다. 그런 다음 해당 클래스의 자동 인스턴스가있는 호출자는 해당 멤버 함수를 호출하고 참조를 리턴합니다. 프레스토 : 매달려있는 참조. 그리고 그렇습니다. 문제를 일으킬 것입니다. @kriss : 그게 내 요점입니다. 많은 사람들은 포인터에 대한 참조의 장점은 참조가 항상 유효하지만 그렇지 않다고 주장합니다.
벤 Voigt

4
@kriss : 아닙니다. 자동 저장 기간의 객체에 대한 참조는 임시 객체와 매우 다릅니다. 어쨌든, 당신은 잘못된 포인터를 역 참조하여 유효하지 않은 참조 만 얻을 수 있다는 진술에 반례를 제공했습니다. Christoph는 정확합니다. 참조는 포인터보다 안전하지 않습니다. 참조를 독점적으로 사용하는 프로그램은 여전히 ​​유형 안전을 깨뜨릴 수 있습니다.
벤 Voigt

7
참조는 일종의 포인터가 아닙니다. 기존 객체의 새로운 이름입니다.
catphive

18
@catphive : 언어 의미론으로 가면 true이고, 실제로 구현을 보아도 true가 아닙니다. C ++는 C보다 훨씬 더 '매직'언어이며 참조에서 마법을 제거하면 포인터로 끝납니다
Christoph

191

정말 pedantic하고 싶다면 포인터로 할 수없는 참조로 할 수있는 한 가지가 있습니다 : 임시 객체의 수명을 연장하십시오. C ++에서 const 참조를 임시 객체에 바인딩하면 해당 객체의 수명이 참조의 수명이됩니다.

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

이 예에서 s3_copy는 연결의 결과 인 임시 객체를 복사합니다. 본질적으로 s3_reference는 임시 객체가됩니다. 실제로는 참조와 수명이 동일한 임시 객체에 대한 참조입니다.

const그것 없이 시도하면 컴파일에 실패해야합니다. 상수가 아닌 참조를 임시 객체에 바인딩하거나 해당 문제에 대한 주소를 취할 수 없습니다.


5
그러나 이것의 사용 사례는 무엇입니까?
Ahmad Mushtaq

20
s3_copy는 임시를 생성 한 다음이를 s3_copy로 복사하는 반면 s3_reference는 임시를 직접 사용합니다. 그런 다음 실제로 헛소리를 내려면 컴파일러가 첫 번째 경우 복사본 구성을 제거 할 수있는 반환 값 최적화를 살펴 봐야합니다.
매트 가격

6
@digitalSurgeon : 마법이 아주 강력합니다. 객체 수명은 const &바인딩 의 사실에 의해 연장되며, 참조가 범위를 벗어날 때만 실제 참조 유형 의 소멸자 (참조 유형, 즉 기준일 수 있음)가 호출됩니다. 참조이므로 사이에 슬라이싱이 발생하지 않습니다.
David Rodríguez-dribeas

9
C ++ 11 업데이트 : 마지막 문장은 상수 가 아닌 rvalue 참조를 임시에 바인딩 할 있고 수명이 연장되는 동작이 동일 하기 때문에 "비 상수 lvalue 참조를 임시에 바인딩 할 수 없습니다"로 표시되어야합니다 .
Oktalist

4
@AhmadMushtaq : 이것의 주요 용도는 파생 클래스 입니다. 상속이없는 경우 RVO / 이동 구성으로 인해 값이 싸거나 무료 인 값 의미론을 사용할 수도 있습니다. 당신이있는 경우 그러나 Animal x = fast ? getHare() : getTortoise()다음 x고전 슬라이싱 문제를 직면하게 될 것이다,하면서 Animal& x = ...제대로 작동합니다.
Arthur Tacca

128

구문 설탕과는 별도로 참조는 const포인터가 아닌 포인터 const입니다. 참조 변수를 선언 할 때 참조하는 내용을 설정해야하며 나중에 변경할 수 없습니다.

업데이트 : 이제 그것에 대해 더 생각하면 중요한 차이점이 있습니다.

const 포인터의 대상은 주소를 가져오고 const 캐스트를 사용하여 바꿀 수 있습니다.

참조 대상은 UB가 부족한 방식으로 대체 할 수 없습니다.

이를 통해 컴파일러는 참조에 대해 더 많은 최적화를 수행 할 수 있습니다.


8
나는 이것이 가장 좋은 대답이라고 생각합니다. 다른 사람들은 다른 짐승처럼 참조와 포인터에 대해 이야기하고 행동이 어떻게 다른지 설명합니다. 일을 더 쉽게 만들지 않습니다. 나는 항상 참조 T* const가 다른 구문 설탕을 사용 하는 것으로 이해했습니다 (코드에서 많은 *를 제거합니다).
Carlo Wood

2
"주소를 취하고 const 캐스트를 사용하여 const 포인터의 대상을 바꿀 수 있습니다." 그렇게하는 것은 정의되지 않은 동작입니다. 자세한 내용은 stackoverflow.com/questions/25209838/… 를 참조하십시오.
dgnuff

1
참조의 참조 또는 const 포인터 (또는 const 스칼라)의 값을 변경하려고 시도하는 것은 동등하지 않습니다. 수행 할 수있는 작업 : 암시 적 변환에 의해 추가 된 const 한정을 제거하십시오 int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);.
curiousguy

1
여기서 차이점은 UB와 문자 그대로 불가능합니다. C ++에는 참조 지점을 변경할 수있는 구문이 없습니다.

불가능하지는 않지만 더 어렵게 참조를 모델링하는 포인터의 메모리 영역에 액세스하여 내용을 변경할 수 있습니다. 확실히 할 수 있습니다.
니콜라스 부 스케

126

대중적인 의견과 달리 NULL 인 참조를 가질 수 있습니다.

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

물론, 참조로 처리하기가 훨씬 어렵습니다.하지만 관리하면 머리카락을 찢어 버리게됩니다. C ++에서는 참조가 본질적으로 안전 하지 않습니다 !

기술적으로 이것은 null 참조 가 아닌 잘못된 참조입니다. C ++은 다른 언어에서 볼 수 있듯이 널 참조를 개념으로 지원하지 않습니다. 다른 종류의 유효하지 않은 참조도 있습니다. 모든 유효하지 않은 참조가의 망령 제기 정의되지 않은 동작을 단지 것 유효하지 않은 포인터를 사용하는 등,.

실제 오류는 참조에 할당하기 전에 NULL 포인터의 역 참조에 있습니다. 그러나 해당 조건에서 오류를 생성하는 컴파일러는 알지 못합니다. 오류는 코드에서 더 많은 지점으로 전파됩니다. 이것이이 문제를 너무 교활하게 만듭니다. 대부분의 경우 NULL 포인터를 역 참조하면 해당 지점에서 충돌이 발생하며이를 파악하기 위해 많은 디버깅 작업이 필요하지 않습니다.

위의 예는 짧고 고안되었습니다. 보다 실제적인 예는 다음과 같습니다.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

null 참조를 얻는 유일한 방법은 잘못된 코드를 통한 것임을 반복하고 싶습니다. 일단 코드가 있으면 정의되지 않은 동작이 발생합니다. 그것은 결코 널 참조를 확인하기 위해 이해되지 않는다; 예를 들어 시도해 볼 수 if(&bar==NULL)...있지만 컴파일러는 존재하지 않는 명령문을 최적화 할 수 있습니다 ! 유효한 참조는 NULL이 될 수 없으므로 컴파일러의 관점에서 비교는 항상 거짓이며 if절을 죽은 코드로 제거 할 수 있습니다. 이것이 정의되지 않은 동작의 본질입니다.

문제를 피하는 올바른 방법은 참조를 만들기 위해 NULL 포인터를 역 참조하지 않는 것입니다. 이를 수행하는 자동화 된 방법은 다음과 같습니다.

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

글쓰기 능력이 더 좋은 사람이이 문제를 더 오래 본다면 Jim Hyslop과 Herb Sutter의 Null References참조 하십시오 .

널 포인터 역 참조의 위험에 대한 다른 예는 Raymond Chen이 코드를 다른 플랫폼 으로 포팅 할 때 정의되지 않은 동작 노출을 참조하십시오 .


63
해당 코드에는 정의되지 않은 동작이 포함되어 있습니다. 기술적으로 널 포인터로 설정 한 것 외에는 아무것도 할 수 없으며 비교하십시오. 프로그램이 정의되지 않은 동작을 호출하면 큰 보스에게 데모를 제공 할 때까지 올바르게 작동하는 것처럼 보일 수 있습니다.
KeithB

9
mark에 유효한 인수가 있습니다. 포인터가 NULL 일 수 있고 확인해야한다는 인수도 실제가 아닙니다. 함수가 NULL이 아닌 함수를 요구하면 호출자가 그렇게해야합니다. 따라서 호출자가 정의되지 않은 동작을 호출하지 않으면 마크는 나쁜 참조 그랬던 것처럼
litb - 요하네스 SCHAUB

13
설명이 잘못되었습니다. 이 코드는 NULL 인 참조를 만들거나 만들지 않을 수 있습니다. 동작은 정의되어 있지 않습니다. 완벽하게 유효한 참조를 만들 수 있습니다. 참조를 전혀 작성하지 못할 수 있습니다.
David Schwartz

7
@David Schwartz, 표준에 따라 작동 해야하는 방식에 대해 이야기하고 있다면 정확합니다. 그러나 그것은 내가 말하는 것이 아닙니다 . 나는 매우 인기있는 컴파일러로 실제로 관찰 된 행동에 대해 이야기하고 있으며 일반적인 컴파일러와 CPU 아키텍처에 대한 나의 지식을 기반으로 아마도 일어날 일 에 대해 추정하고 있습니다. 참조가 더 안전하고 참조가 잘못 될 수 있다고 생각하지 않기 때문에 포인터보다 우수한 참조를 믿는다면 언젠가는 간단한 문제로 인해 혼란에 빠질 것입니다.
마크 랜섬

6
널 포인터를 역 참조하는 것이 잘못되었습니다. 참조를 초기화하기 위해 그렇게하는 모든 프로그램은 잘못되었습니다. 포인터에서 참조를 초기화하는 경우 항상 포인터가 유효한지 확인해야합니다. 이것이 성공하더라도 존재하지 않는 객체를 참조하기 위해 참조를 떠나는 경우 언제든지 기본 객체를 삭제할 수 있습니다. 당신이 말하는 것은 좋은 것입니다. 여기서 진짜 문제는 참조 할 때 참조가 "널"인지 확인할 필요가 없으며 포인터가 최소한 어설 션되어야한다고 생각합니다.
t0rakka

115

가장 중요한 부분을 잊었습니다.

포인터가있는 ->
멤버 액세스는 참조가있는 멤버 액세스.

foo.bar이다 명확하게 우수 foo->bar것과 같은 방식으로 VI가 있다 명확하게 우수 이맥스 :-)


4
@Orion Edwards> 포인터가있는 멤버 액세스는->> 멤버 액세스가있는 레퍼런스를 사용합니다. 이것은 100 % 사실이 아닙니다. 포인터에 대한 참조를 가질 수 있습니다. 이 경우,-> struct Node {Node * next;를 사용하여 역 참조 된 포인터의 멤버에 액세스합니다. }; 노드 * 먼저; // p는 포인터에 대한 참조입니다. void foo (Node * & p) {p-> next = first; } Node * bar = 새 노드; foo (바); -OP : rvalues와 lvalues의 개념에 익숙하십니까?

3
스마트 포인터는 둘 다 있습니다. (스마트 포인터 클래스의 메소드) 및-> (기본 유형의 메소드).
JBR 윌킨슨

1
@ user6105 Orion Edwards 진술은 실제로 100 % 사실입니다. "참조 해제 된 포인터의 멤버에 액세스" 포인터에는 멤버가 없습니다. 포인터가 참조하는 객체에는 멤버가 있으며, 그에 대한 액세스 ->는 포인터 자체와 마찬가지로 포인터에 대한 참조를 제공하는 것입니다.
Max Truxa

1
그 이유 .->: emacs 나 vi 함께 할 수있는 뭔가가
ARTM

10
@artM-농담이었고, 영어가 모국어가 아닌 사람들에게는 이치에 맞지 않을 것입니다. 사과드립니다. vi가 emacs보다 좋은지 설명하는 것은 전적으로 주관적입니다. 어떤 사람들은 vi가 훨씬 우수하다고 생각하고 어떤 사람들은 정반대라고 생각합니다. 마찬가지로, 내가 사용하는 생각 .사용하는 것보다 더 나은 ->,하지만 단지 이맥스 대 VI처럼, 그것은 전적으로 주관적인 그리고 당신은 아무것도 증명할 수
오리온 에드워즈

74

참조는 포인터와 매우 유사하지만 컴파일러 최적화에 도움이되도록 특별히 제작되었습니다.

  • 참조는 컴파일러가 변수에 대한 참조 별칭을 추적하기가 훨씬 쉬워 지도록 설계되었습니다. "참조 산술"및 참조 재 할당 없음의 두 가지 주요 기능이 매우 중요합니다. 이를 통해 컴파일러는 컴파일 할 때 어떤 변수가 별칭을 참조하는지 파악할 수 있습니다.
  • 참조는 메모리 주소가없는 변수 (예 : 컴파일러가 레지스터에 넣는 변수)를 참조 할 수 있습니다. 지역 변수의 주소를 사용하면 컴파일러가 레지스터에 변수를 넣기가 매우 어렵습니다.

예로서:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

최적화 컴파일러는 우리가 a [0]과 a [1]에 상당히 많이 액세스하고 있음을 알 수 있습니다. 알고리즘을 최적화하여 다음을 수행하고 싶습니다.

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

이러한 최적화를 위해서는 호출 중에 아무것도 배열 [1]을 변경할 수 없음을 증명해야합니다. 이것은 오히려 쉽습니다. i는 2보다 작지 않으므로 array [i]는 array [1]을 참조 할 수 없습니다. maybeModify ()는 a0을 참조 (aliasing array [0])로 제공합니다. "참조"산술이 없기 때문에 컴파일러는 어쩌면 maybeModify가 x의 주소를 얻지 못한다는 것을 증명해야하며 array [1]을 변경하는 것이 아무것도 없음을 증명했습니다.

또한 a0에 임시 레지스터 사본이있는 동안 향후 호출에서 a [0]을 읽고 쓸 수있는 방법이 없음을 증명해야합니다. 많은 경우에 참조가 클래스 인스턴스와 같은 영구 구조에 저장되지 않는 것이 분명하기 때문에 이는 종종 증명하기가 쉽지 않습니다.

이제 포인터로 같은 일을하십시오.

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

동작은 동일합니다. 어쩌면 우리는 이미 포인터를 주었기 때문에 maybeModify가 배열 [1]을 수정하지 않았다는 것을 증명하기가 훨씬 더 어렵다. 고양이가 가방에서 나왔습니다. 이제는 훨씬 더 어려운 증명을해야합니다. maybeModify의 정적 분석은 & x + 1에 쓰지 않는다는 것을 증명합니다. 또한 array [0]을 참조 할 수있는 포인터를 저장하지 않는다는 것을 증명해야합니다. 까다로운.

최신 컴파일러는 정적 분석에서 점점 더 나아지고 있지만 항상 도움을주고 참조를 사용하는 것이 좋습니다.

물론 그러한 영리한 최적화를 제외하고 컴파일러는 필요할 때 실제로 참조를 포인터로 바꿉니다.

편집 :이 답변을 게시한지 5 년 후, 나는 동일한 주소 지정 개념을 보는 다른 방법과 참조가 다른 실제 기술적 차이를 발견했습니다. 참조는 포인터가 할 수없는 방식으로 임시 객체의 수명을 수정할 수 있습니다.

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

호출에 의해 생성 된 것과 같은 임시 개체는 일반적으로 createF(5)식이 끝날 때 소멸됩니다. 그러나 refC ++는 해당 객체를 참조에 바인딩함으로써 ref범위를 벗어날 때까지 해당 임시 객체의 수명을 연장합니다 .


사실, 몸은 볼 수 있어야합니다. 그러나, maybeModify관련된 어떤 것의 주소를 취하지 않는 것을 결정하는 것은 x많은 포인터 산술이 발생하지 않는 것을 증명하는 것보다 실질적으로 쉽다.
Cort Ammon

옵티마이 저는 이미 여러 가지 포인터 포인터가 발생하지 않는다고 여러 가지 이유로 확인합니다.
벤 Voigt

"참조는 포인터와 매우 유사하다"-의미 론적으로, 적절한 맥락에서-생성 / 구현 된 관점에서 정의 / 요구 사항이 아닌 일부 구현에서만 가능하다. 나는 당신이 이것을 지적했다는 것을 알고 있으며, 실제적인 용어로 귀하의 게시물에 동의하지 않지만, '참조는 포인터와 같거나 보통 포인터로 구현됩니다'와 같은 간단한 설명을 너무 많이 읽는 사람들에게는 이미 많은 문제가 있습니다. .
underscore_d

나는 누군가가의 라인을 따라 쓸모없는 주석으로 잘못 표시되었다고 생각합니다 void maybeModify(int& x) { 1[&x]++; }. 위에서 언급 한 다른 의견들
Ben Voigt

68

실제로, 참조는 실제로 포인터와 다릅니다.

컴파일러는 이름을 메모리 주소와 연관시켜 변수에 대한 "참조"를 유지합니다. 컴파일 할 때 변수 이름을 메모리 주소로 변환하는 작업입니다.

참조를 작성할 때, 포인터 변수에 다른 이름을 지정한다고 컴파일러에게 알리십시오. 그렇기 때문에 변수가 될 수없는 변수이기 때문에 참조가 "널을 가리킬 수"없습니다.

포인터는 변수입니다. 다른 변수의 주소를 포함하거나 null 일 수 있습니다. 중요한 것은 포인터에 값이 있고 참조에는 참조하는 변수 만 있다는 것입니다.

이제 실제 코드에 대한 설명이 있습니다.

int a = 0;
int& b = a;

여기서는 다른 변수를 만들지 않습니다 a. 값을 보유한 메모리 내용에 다른 이름을 추가하기 만하면 a됩니다. 이 메모리는 이제 두 개의 이름을 가지고, a그리고 b, 그것은 하나의 이름을 사용하여 해결할 수 있습니다.

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

함수를 호출 할 때 컴파일러는 일반적으로 인수를 복사 할 메모리 공간을 생성합니다. 함수 시그니처는 작성해야하는 공백을 정의하고이 공백에 사용해야하는 이름을 제공합니다. 매개 변수를 참조로 선언하면 컴파일러가 메서드 호출 중에 새 메모리 공간을 할당하는 대신 입력 변수 메모리 공간을 사용하도록 지시합니다. 함수가 호출 범위에 선언 된 변수를 직접 조작한다고 말하는 것은 이상하게 보일 수 있지만 컴파일 된 코드를 실행할 때 더 이상 범위가 없다는 것을 기억하십시오. 평평한 평평한 메모리가 있으며 함수 코드는 모든 변수를 조작 할 수 있습니다.

extern 변수를 사용할 때와 같이 컴파일러가 컴파일 할 때 참조를 알지 못하는 경우가있을 수 있습니다. 따라서 참조는 기본 코드에서 포인터로 구현되거나 구현되지 않을 수 있습니다. 그러나 내가 준 예제에서 포인터로 구현되지 않았을 가능성이 큽니다.


2
참조는 변수에 대한 것이 아니라 l 값에 대한 참조입니다. 이 때문에 실제 별칭 (컴파일 타임 구성)보다 포인터에 훨씬 더 가깝습니다. 참조 할 수있는 표현식의 예는 * p 또는 * p ++입니다.

5
맞아, 참조가 항상 새로운 포인터가하는 방식으로 스택에 새로운 변수를 넣지는 않을 수도 있다는 사실을 지적하고있었습니다.
Vincent Robert

1
@VincentRobert : 포인터와 동일하게 작동합니다. 함수가 인라인되면 참조와 포인터가 모두 최적화됩니다. 함수 호출이 있으면 객체의 주소를 함수에 전달해야합니다.
벤 Voigt

1
int * p = NULL; int & r = * p; NULL을 가리키는 참조; if (r) {}-> boOm;)
sree

2
컴파일 단계에 대한이 초점은 런타임에 참조가 전달 될 수 있다는 것을 기억할 때까지 정적 앨리어싱이 창 밖으로 나올 때까지 멋지게 보입니다. (그리고 참조는 일반적 으로 포인터로 구현되지만 표준에는이 방법이 필요하지 않습니다.)
underscore_d

45

참조는 절대 될 수 없습니다 NULL.


10
반대의 예는 Mark Ransom의 답변을 참조하십시오. 이것은 참고 문헌에 대해 가장 자주 주장되는 신화이지만 신화입니다. 표준에 의한 유일한 보장은 NULL 참조가있을 때 즉시 UB를 사용한다는 것입니다. "이 차는 안전합니다. 절대로 도로에서 벗어날 수 없습니다. (어쨌든 차에서 내리면 어떻게 될지 책임을지지 않습니다. 폭발 할 수도 있습니다.")
cmaster -모니카 복원

17
@cmaster : 유효한 프로그램 에서 참조는 null 일 수 없습니다. 그러나 포인터는 가능합니다. 이것은 신화가 아니라 사실입니다.
user541686

8
@Mehrdad 예, 유효한 프로그램은 도로에 남아 있습니다. 그러나 프로그램이 실제로 시행하도록하는 트래픽 장벽은 없습니다. 도로의 큰 부분에는 실제로 표시가 없습니다. 밤에는 도로에서 내리기가 매우 쉽습니다. 또한 이러한 버그가 발생할 수 있다는 버그를 디버깅하는 데 중요합니다 . null 포인터처럼 프로그램이 충돌하기 전에 null 참조가 전파 될 수 있습니다. 그리고 그것이 void Foo::bar() { virtual_baz(); }끝나면 segfaults 와 같은 코드가 있습니다 . 참조가 널일 수 있음을 모르는 경우, 널을 원래 위치로 추적 할 수 없습니다.
cmaster-monica reinstate

4
int * p = NULL; int & r = * p; NULL을 가리키는 참조; if (r) {}-> boOm;) –
sree

10
@sree int &r=*p;는 정의되지 않은 동작입니다. 그 시점에서, 당신은 필요가 없습니다 "NULL을 참조를 가리키는,"당신은 프로그램이 더 이상에 대해 추론 할 수있다 전혀를 .
cdhowie

35

참조와 포인터 모두 다른 값에 간접적으로 액세스하는 데 사용되지만 참조와 포인터 사이에는 두 가지 중요한 차이점이 있습니다. 첫 번째는 참조가 항상 객체를 참조한다는 것입니다. 참조를 초기화하지 않고 정의하는 것은 오류입니다. 할당 동작은 두 번째로 중요한 차이점입니다. 참조에 할당하면 참조가 바인딩 된 객체가 변경됩니다. 다른 객체에 대한 참조를 리 바인드하지 않습니다. 초기화되면 참조는 항상 동일한 기본 개체를 참조합니다.

이 두 프로그램 조각을 고려하십시오. 첫 번째로 한 포인터를 다른 포인터에 할당합니다.

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

할당, ival 후에 pi로 주소 지정된 객체는 변경되지 않습니다. 할당은 pi의 값을 변경하여 다른 객체를 가리 키도록합니다. 이제 두 개의 참조를 할당하는 유사한 프로그램을 고려하십시오.

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

이 할당은 참조 자체가 아니라 ri가 참조하는 값인 ival을 변경합니다. 할당 후에도 두 참조는 여전히 원래 객체를 참조하며 해당 객체의 값도 동일합니다.


"참조는 항상 객체를 참조"는 완전히 거짓입니다
Ben Voigt

32

추상적이거나 학문적 인 방식으로 컴퓨터 언어를 공부하는 데 익숙하지 않은 경우 의미 론적으로 차이가있을 수 있습니다.

최상위 수준에서 참조의 개념은 참조가 투명한 "별칭"이라는 것입니다. 컴퓨터가 주소를 사용하여 작동하게 할 수도 있지만 걱정할 필요는 없습니다. 기존 객체의 "단지 다른 이름"으로 생각해야하며 구문에 반영됩니다. 포인터보다 엄격하므로 매달려있는 포인터를 만들 때보 다 매달려있는 참조를 만들 때 컴파일러가보다 확실하게 경고 할 수 있습니다.

그 외에도 포인터와 참조 사이에는 실질적인 차이점이 있습니다. 그것들을 사용하는 문법은 분명히 다르며, 참조를 "다시 앉히거나", 아무 것도 참조하지 않거나, 참조를 가리키는 포인터를 가질 수 없습니다.


27

참조는 다른 변수의 별칭이며 포인터는 변수의 메모리 주소를 보유합니다. 참조는 일반적으로 전달 된 객체가 사본이 아니라 객체 자체가되도록 함수 매개 변수로 사용됩니다.

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use. 

20

차지하는 공간에 대한 부작용 (코드 실행없이)을 실제로 볼 수 없으므로 차지하는 공간은 중요하지 않습니다.

반면, 참조와 포인터의 주요 차이점은 const 참조에 할당 된 임시는 const 참조가 범위를 벗어날 때까지 유지된다는 것입니다.

예를 들면 다음과 같습니다.

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

인쇄합니다 :

in scope
scope_test done!

이것은 ScopeGuard가 작동하도록하는 언어 메커니즘입니다.


1
참조 주소를 사용할 수는 없지만 물리적으로 공간을 차지하지는 않습니다. 최적화를 제외하면 가장 확실하게 할 수 있습니다.
궤도에서 가벼움 경주

2
그럼에도 불구하고 "스택에 대한 참조는 공간을 전혀 차지하지 않습니다"라는 것은 사실상 허위입니다.
궤도에서 가벼움 레이스

1
@Tomalak는 컴파일러에도 의존합니다. 그러나 그렇습니다. 나는 그것을 제거하는 것이 덜 혼란 스럽다고 생각합니다.
MSN

1
어떤 특정한 경우에 그것은 가능하거나 그렇지 않을 수 있습니다. 따라서 범주 적 주장으로 "하지 않습니다"는 잘못되었습니다. 그것이 내가 말하는 것입니다. :) [문제에 대한 표준의 내용을 기억할 수 없습니다. 참조 구성원의 규칙은 "공간을 차지할 수 있습니다 참조"의 일반적인 규칙을 부여 할 수있다, 그러나 나는 해변에 여기에 나와 함께 표준의 내 복사본이없는 : D]
궤도의 밝기 경주

20

이것은 튜토리얼을 기반으로합니다 . 작성된 내용이 더 명확 해집니다.

>>> The address that locates a variable within memory is
    what we call a reference to that variable. (5th paragraph at page 63)

>>> The variable that stores the reference to another
    variable is what we call a pointer. (3rd paragraph at page 64)

간단히 기억하십시오.

>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)

또한 거의 모든 포인터 자습서를 참조 할 수 있듯이 포인터는 포인터 산술에서 지원되는 객체로 포인터를 배열과 비슷하게 만듭니다.

다음 진술을보십시오.

int Tom(0);
int & alias_Tom = Tom;

alias_Tomint로서 이해 될 수있다 alias of a variable(서로 다른 typedef이다 alias of a type) Tom. 또한 그러한 진술의 용어를 잊어 버린 것도 괜찮습니다 Tom.


1
그리고 클래스에 참조 변수가있는 경우 초기화 목록에서 nullptr 또는 유효한 객체로 초기화해야합니다.
Misgevolution

1
이 답변의 문구는 실제로 사용하기에는 너무 혼란 스럽습니다. 또한 @Misgevolution,?로 참조를 초기화하도록 독자에게 진지하게 추천하고 nullptr있습니까? 이 스레드의 다른 부분을 실제로 읽었습니까?
underscore_d

1
내 나쁜, 내가 말한 그 바보 같은 일에 대해 죄송합니다. 그 당시 나는 잠을 잃었을 것입니다. 'NULLptr로 초기화'는 완전히 잘못되었습니다.
Misgevolution

19

참조는 일부 메모리에 지정된 다른 이름이 아닙니다. 사용법에서 자동으로 역 참조되는 불변 포인터입니다. 기본적으로 다음과 같이 요약됩니다.

int& j = i;

내부적으로됩니다

int* const j = &i;

13
이것은 C ++ 표준이 말하는 것이 아니며 컴파일러가 답변에 설명 된 방식으로 참조를 구현할 필요는 없습니다.
jogojapan

@jogojapan : C ++ 컴파일러가 참조를 구현하는 데 유효한 방법은 const포인터 를 구현하는 올바른 방법이기도합니다 . 이러한 유연성은 참조와 포인터 사이에 차이가 있음을 증명하지 않습니다.
Ben Voigt

2
@BenVoigt 어떤 하나의 유효한 구현도 다른 하나의 유효한 구현이지만,이 두 개념의 정의에서 명백한 방식으로 따르지 않는 것이 사실 일 수 있습니다. 좋은 대답은 정의에서 시작했을 것이며, 두 사람에 대한 주장이 궁극적으로 동일한 이유를 설명했습니다. 이 답변은 다른 답변에 대한 일종의 의견 인 것 같습니다.
jogojapan

참조 객체에 지정된 다른 이름입니다. 컴파일러는 차이점을 알 수없는 한 "as-if"규칙이라고 알려진 모든 종류의 구현이 가능합니다. 여기서 중요한 부분은 차이점을 알 수 없다는 것입니다. 포인터에 스토리지가없는 것을 발견하면 컴파일러에 오류가있는 것입니다. 참조에 스토리지가 없음을 발견 할 수있는 경우 컴파일러는 여전히 준수합니다.
sp2danny

18

직접적인 대답

C ++에서 참조 란 무엇입니까? 객체 유형이 아닌 특정 유형의 인스턴스 .

C ++의 포인터는 무엇입니까? 객체 유형 인 특정 유형의 인스턴스 .

에서 개체 유형의 ISO C ++ 정의 :

오브젝트 타입 (아마도이다 이력서 함수 형태가 아닌 참조 형 아닌 아니다 restrict로) 형 이력서 보이드.

개체 유형은 C ++에서 유형 유니버스의 최상위 범주입니다. 참조는 최상위 범주이기도합니다. 그러나 포인터는 아닙니다.

포인터와 참조는 복합 유형 의 맥락에서 함께 언급됩니다 . 이것은 기본적으로 참조가없는 C에서 상속 된 (및 확장 된) 선언자 구문의 특성 때문입니다. (포인터는 여전히 "unityped"반면 게다가, C ++ (11) 이후 참조 선언자 하나 이상의 종류가있다 : &+ &&*.) 이러한 맥락에서 C 비슷한 스타일 "확장자"로 언어 특정 초안을 작성하는 것은 다소 합리적이다 그래서 . (나는 여전히 선언자의 구문이 구문 표현력 을 많이 낭비하고 인간 사용자와 구현을 모두 좌절 시킨다고 주장 할 것입니다 . 따라서 모든 것이 내장 될 자격이 없습니다새로운 언어 디자인. PL 디자인에 대해서는 완전히 다른 주제입니다.)

그렇지 않으면, 포인터가 함께 참조가있는 특정 유형의 유형으로 포인터를 규정 할 수있는 것은 중요하지 않습니다. 그것들은 구문 유사성 외에는 너무 적은 공통 속성을 공유하므로 대부분의 경우에 그것들을 조합 할 필요가 없습니다.

위의 설명은 "포인터"및 "참조"만 유형으로 언급합니다. 인스턴스와 관련하여 변수와 같은 흥미로운 질문이 있습니다. 오해가 너무 많습니다.

최상위 카테고리의 차이점은 포인터에 직접 연결되지 않은 많은 구체적인 차이점을 이미 보여줍니다.

  • 객체 유형에는 최상위 cv한정자가 있을 수 있습니다 . 참조는 할 수 없습니다.
  • 객체 유형의 변수는 추상 머신 시맨틱에 따라 스토리지를 차지 합니다. 참조가 스토리지를 차지할 필요는 없습니다 (자세한 내용은 아래 오해에 대한 섹션 참조).
  • ...

참고 문헌에 대한 몇 가지 특별한 규칙 :

  • 복합 선언자는 참조에 대해 더 제한적입니다.
  • 참조가 축소 될 수 있습니다 .
    • &&템플릿 매개 변수 공제 중 참조 축소를 기반으로 한 매개 변수 에 대한 특수 규칙 ( "전달 참조") 은 매개 변수의 "완벽한 전달" 을 허용 합니다.
  • 참조에는 초기화에 특별한 규칙이 있습니다. 참조 유형으로 선언 된 변수의 수명은 확장을 통해 일반 객체와 다를 수 있습니다.
    • BTW, 초기화와 관련된 몇 가지 다른 컨텍스트는 std::initializer_list유사한 참조 수명 연장 규칙을 따릅니다. 벌레의 또 다른 캔입니다.
  • ...

오해

구문 설탕

참조는 구문 설탕이라는 것을 알고 있으므로 코드를 읽고 쓰기가 더 쉽습니다.

기술적으로 이것은 명백한 잘못입니다. 참조는 의미상의 차이없이 다른 기능으로 정확하게 대체 될 수 없기 때문에 C ++의 다른 기능의 구문 설탕이 아닙니다.

유사하게, 람다-표현식 은 C ++의 다른 기능의 구문 설탕 이 아닙니다 . 캡처 된 변수의 선언 순서 와 같은 "지정되지 않은"속성으로 정확하게 시뮬레이션 할 수 없기 때문에 이러한 변수의 초기화 순서는 중요 할 수 있습니다. 중요한.)

C ++는이 엄격한 의미에서 몇 가지 종류의 구문 설탕 만 가지고 있습니다. 하나의 인스턴스 (C로부터 상속)는 내장 된 (비 과부하) 연산자 [], 바로 위에 내장 단항 연산자 조합의 특정 형태의 동일한 의미 속성을 갖는 정의 *및 이진+ .

저장

따라서 포인터와 참조는 모두 같은 양의 메모리를 사용합니다.

위의 진술은 단순히 잘못되었습니다. 이러한 오해를 피하려면 대신 ISO C ++ 규칙을 살펴보십시오.

가입일 [intro.object] / 1 :

... 물체는 구성 기간, 수명 및 파괴 기간에 저장 영역을 차지합니다. ...

가입일 [dcl.ref / 4 :

참조에 스토리지가 필요한지 여부는 지정되지 않았습니다.

이것은 시맨틱 속성입니다.

실용

포인터가 언어 디자인의 의미에서 참조와 함께 사용하기에 충분하지 않더라도 매개 변수 유형을 선택할 때와 같이 다른 컨텍스트에서 포인터를 선택할 수 있다는 논란이 여전히 남아 있습니다.

그러나 이것은 전체 이야기가 아닙니다. 내 말은, 당신이 고려해야 할 포인터 대 참조보다 더 많은 것들이 있다는 것을 의미합니다.

이러한 특정 선택을 고수 할 필요가 없다면 대부분의 경우 대답이 짧습니다. 포인터를 사용할 필요가 없으므로 그렇게하지 마십시오 . 포인터는 일반적으로 예상하지 못한 것들을 너무 많이 암시하기 때문에 충분히 나쁘고 코드의 유지 관리 성 및 심지어 이식성을 손상시키는 너무 많은 암시 적 가정에 의존합니다. 불필요하게 포인터에 의존하는 것은 분명히 나쁜 스타일이므로 현대적인 C ++의 의미에서 피해야합니다. 목적을 재고하면 포인터가 대부분의 경우 마지막 정렬의 기능 이라는 것을 알게 될 것 입니다.

  • 언어 규칙에 따라 특정 유형을 명시 적으로 사용해야하는 경우가 있습니다. 이러한 기능을 사용하려면 규칙을 준수하십시오.
    • 복사 생성자는 특정 유형의 필요 이력서 - &1 매개 변수 유형으로 참조 유형을. (보통 const자격 이 있어야합니다 .)
    • 이동 생성자는 특정 유형의 필요 이력서 - &&1 매개 변수 유형으로 참조 유형을. (그리고 보통 한정자가 없어야합니다.)
    • 연산자의 특정 과부하에는 참조 또는 비 참조 유형이 필요합니다. 예를 들면 다음과 같습니다.
      • operator=특수 멤버 함수로 오버로드 하려면 복사 / 이동 생성자의 첫 번째 매개 변수와 유사한 참조 유형이 필요합니다.
      • Postfix ++에는 더미가 필요합니다 int.
      • ...
  • 값으로 전달 (즉, 비 참조 유형 사용)으로 충분하다는 것을 알고 있다면, 특히 C ++ 17 필수 복사 제거를 지원하는 구현을 사용할 때 직접 사용하십시오. ( 경고 : 그러나 필요성에 대한 철저한 추론은 매우 복잡 할 수 있습니다 .)
  • 소유권이있는 일부 핸들을 조작 하려면 원시 포인터 대신 unique_ptr및 같은 스마트 포인터 shared_ptr(또는 불투명 한 경우 직접 만든자가 포인터)를 사용하십시오.
  • 범위에 대해 일부 반복을 수행하는 경우 원시 포인터가 매우 구체적으로 더 잘 수행 할 것으로 확신하지 않는 한 원시 포인터 대신 반복자 (또는 표준 라이브러리에서 아직 제공하지 않은 일부 범위)를 사용하십시오. 사례.
  • 값으로 전달하는 것이 충분하고 명시적인 nullable 의미를 원한다면 std::optionalraw 포인터 대신와 같은 래퍼를 사용하십시오 .
  • 값으로 전달이 위의 이유로 이상적이지 않고 널 입력 가능 의미를 원하지 않는 경우 {lvalue, rvalue, forwarding} -references를 사용하십시오.
  • 전통적인 포인터와 같은 의미론을 원할 때도 종종 observer_ptrLibrary Fundamental TS 와 같이 더 적절한 것이 있습니다 .

현재 언어에서는 예외를 해결할 수 없습니다.

  • 위의 스마트 포인터를 구현할 때 원시 포인터를 처리해야 할 수도 있습니다.
  • 특정 언어 상호 운용 루틴에는와 같은 포인터가 필요합니다 operator new. (단, 이력서는 - void*당신이 일부 비 부합하는 확장에 의존하지 않는 예기치 않은 포인터를 arithmetics 배제하기 때문에 여전히 매우 다른 및 일반 객체 포인터에 비해 안전 void*GNU의 같은 있습니다.)
  • 함수 포인터는 캡처없이 람다 식에서 변환 할 수 있지만 함수 참조는 불가능합니다. 이러한 경우에는 제네릭이 아닌 코드에서 함수 포인터를 사용해야합니다. 심지어 의도적으로 널 입력 가능 값을 원하지 않습니다.

따라서 실제로 대답은 매우 분명합니다. 의심스러운 경우 포인터를 피하십시오 . 다른 것이 더 적절하지 않은 명백한 이유가있는 경우에만 포인터를 사용해야합니다. 위에서 언급 한 몇 가지 예외적 인 경우를 제외하고, 그러한 선택은 거의 항상 C ++에만 국한되지는 않습니다 (그러나 언어 구현에 따라 다를 수 있음). 이러한 인스턴스는 다음과 같습니다.

  • 구식 (C) API를 제공해야합니다.
  • 특정 C ++ 구현의 ABI 요구 사항을 충족해야합니다.
  • 특정 구현에 대한 가정을 기반으로 런타임에 다양한 언어 구현 (다양한 어셈블리, 언어 런타임 및 일부 고급 클라이언트 언어의 FFI 포함)과 상호 운용해야합니다.
  • 극단적 인 경우 번역 (컴파일 및 링크)의 효율성을 향상시켜야합니다.
  • 극단적 인 경우에는 기호 팽창을 피해야합니다.

언어 중립주의

일부 Google 검색 결과 (C ++에 국한되지 않음) 를 통해 질문 이 표시되면 잘못된 위치 일 가능성이 큽니다.

C ++의 참조는 본질적으로 일류가 아니기 때문에 상당히 "홀수"입니다. 그것들은 참조되는 객체 또는 함수로 취급 되므로 왼쪽 피연산자와 같은 일류 연산을 지원할 기회가 없습니다 . 참조 된 객체의 유형에 독립적으로 멤버 액세스 연산자 . 다른 언어는 참조에 대해 유사한 제한이 있거나 없을 수 있습니다.

C ++의 참조는 다른 언어의 의미를 보존하지 않을 것입니다. 예를 들어, 일반적으로 참조는 C ++에서와 같은 값에 대해 null이 아닌 속성을 의미하지 않으므로 이러한 가정은 다른 언어에서는 작동하지 않을 수 있습니다 (예 : Java, C #, ...).

일반적으로 다른 프로그래밍 언어로 된 참조 사이에는 공통 속성이 여전히있을 수 있지만 SO에서 다른 질문으로 남겨 두겠습니다.

(참고 : ALGOL 68 vs. PL / I와 같은 "C-like"언어가 포함 된 것보다 문제가 더 빠를 수 있습니다 .)


17

C ++에서는 포인터에 대한 참조가 가능하지만 그 반대는 불가능합니다. 참조에 대한 포인터는 불가능합니다. 포인터에 대한 참조는 포인터를 수정하기위한보다 명확한 구문을 제공합니다. 이 예를보십시오 :

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

그리고 위 프로그램의 C 버전을 고려하십시오. C에서는 포인터 포인터 (다중 간접)를 사용해야하며 혼란으로 이어지고 프로그램이 복잡해 보일 수 있습니다.

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

포인터 참조에 대한 자세한 내용은 다음을 방문하십시오.

내가 말했듯이 참조에 대한 포인터는 불가능합니다. 다음 프로그램을 시도하십시오 :

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}

16

다음 중 하나가 필요하지 않으면 참조를 사용합니다.

  • 널 포인터는 센티넬 값으로 사용될 수 있으며, 종종 함수 과부하 또는 부울 사용을 피하는 저렴한 방법입니다.

  • 포인터에서 산술을 수행 할 수 있습니다. 예를 들어p += offset;


5
참조로 선언 된 &r + offset위치 를 쓸 수 있습니다r
MM

15

내가 언급하지 않은 포인터와 참조 사이에는 근본적인 차이점이 있습니다. 참조는 함수 인수에서 참조 별 통과 의미를 활성화합니다. 처음에는 보이지 않지만 포인터는 값별 의미 만 제공합니다. 이것은 이 기사 에서 아주 잘 설명되었습니다 .

감사합니다. & rzej


1
참조와 포인터는 모두 핸들입니다. 둘 다 객체 가 참조로 전달되는 의미를 제공 하지만 핸들 이 복사됩니다. 차이 없음. (사전 검색 키와 같은 핸들을 갖는 다른 방법도 있습니다)
Ben Voigt

나는 또한 이렇게 생각했었다. 그러나 왜 그렇지 않은지 설명하는 링크 된 기사를 참조하십시오.
Andrzej가

2
@Andrzj : 그것은 내 의견에서 단일 문장의 매우 긴 버전입니다 : 핸들이 복사됩니다.
벤 Voigt

이 "손잡이가 복사되었습니다"에 대한 자세한 설명이 필요합니다. 나는 기본적인 아이디어를 이해하지만 물리적으로 참조와 포인터 모두 변수의 메모리 위치를 가리킨다 고 생각합니다. 별칭이 값 변수를 저장하고 변수 값이 변경되거나 다른 것으로 업데이트되는 것과 같은가요? 나는 초보자이며 어리석은 질문으로 표시하지 마십시오.
Asim

1
@Andrzej False. 두 경우 모두 값으로 전달이 발생합니다. 참조는 값으로 전달되고 포인터는 값으로 전달됩니다. 그렇지 않으면 초보자를 혼동합니다.
Miles Rout

14

혼란에 추가 할 위험이 있으므로 입력을 던지고 싶습니다. 컴파일러가 참조를 구현하는 방법에 달려 있지만 gcc의 경우 참조가 스택의 변수 만 가리킬 수 있다는 생각 실제로 올바르지 않은 경우, 예를 들어 다음을 수행하십시오.

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

이것을 출력하는 것 :

THIS IS A STRING
0xbb2070 : 0xbb2070

메모리 주소조차 정확히 같다는 것을 알면 참조가 힙의 변수를 성공적으로 가리키고 있음을 의미합니다! 이제 당신이 정말로 이상한 것을 원한다면, 이것은 또한 작동합니다 :

int main(int argc, char** argv) {
    // In the actual new declaration let immediately de-reference and assign it to the reference
    std::string &str_ref = *(new std::string("THIS IS A STRING"));
    // Once again, it works! (at least in gcc)
    std::cout << str_ref;
    // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
    delete &str_ref;
    /*And, it works, because we are taking the memory address that the reference is
    storing, and deleting it, which is all a pointer is doing, just we have to specify
    the address with '&' whereas a pointer does that implicitly, this is sort of like
    calling delete &(*str_ptr); (which also compiles and runs fine).*/
}

이것을 출력하는 것 :

THIS IS A STRING

따라서 참조는 후드 아래의 포인터이며 둘 다 메모리 주소를 저장하고 있으며 주소가 가리키는 위치와 관련이 없습니다. std :: cout << str_ref; 삭제 후 삭제 & str_ref? 글쎄, 분명히 잘 컴파일되지만 런타임에 더 이상 유효한 변수를 가리 키지 않기 때문에 세그먼트 화 오류가 발생합니다. 우리는 본질적으로 여전히 존재하지만 (범위를 벗어나지 않을 때까지) 깨진 참조를 가지고 있습니다.

다시 말해, 참조는 포인터 메커니즘을 추상화 한 포인터 일 뿐이며, 안전하고 사용하기 쉬워집니다 (실수로 포인터 연산, '.'와 '->'등을 섞지 않음 등). 위의 예제와 같이 말도 안됩니다.)

이제 컴파일러가 참조를 처리하는 방법에 관계없이 참조 예상대로 작동하기 위해 특정 메모리 주소에서 특정 변수를 참조 해야 하므로 항상 일종의 포인터를 갖습니다. '참조'라는 용어).

참조로 기억해야 할 유일한 주요 규칙은 선언시 정의해야한다는 것입니다 (헤더의 참조를 제외하고는 헤더에 포함 된 객체가 그것을 정의하기에는 너무 늦었습니다.)

위의 예는 단지 참조가 무엇인지 보여주는 예일뿐입니다. 그런 식으로 참조를 사용하고 싶지 않을 것입니다! 참조를 올바르게 사용하려면 여기에 이미 머리에 못을 박는 많은 답변이 있습니다.


14

또 다른 차이점은 void 유형에 대한 포인터를 가질 수 있으며 (무엇에 대한 포인터를 의미) void에 대한 참조는 금지된다는 것입니다.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

나는이 특별한 차이에 정말로 만족한다고 말할 수 없다. 주소가있는 모든 항목에 대한 의미 참조가 허용되고 참조에 대해 동일한 동작이 허용되는 것이 훨씬 좋습니다. 참조를 사용하여 memcpy와 같은 일부 C 라이브러리 함수를 정의 할 수 있습니다.


13

또한 인라인 된 함수에 대한 매개 변수 인 참조는 포인터와 다르게 처리 될 수 있습니다.

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

포인터 버전 1을 인라인 할 때 많은 컴파일러가 실제로 메모리에 쓰기를 강제합니다 (주소를 명시 적으로 사용함). 그러나 그들은 더 최적의 레지스터에 참조를 남길 것입니다.

물론 인라인되지 않은 함수의 경우 포인터와 참조가 동일한 코드를 생성하므로 함수에 의해 수정 및 반환되지 않은 경우 참조보다 값으로 내장 함수를 전달하는 것이 좋습니다.


11

참조의 또 다른 흥미로운 용도는 사용자 정의 유형의 기본 인수를 제공하는 것입니다.

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

기본 플레이버는 '바인드 const 참조를 임시에 대한 참조'참조를 사용합니다.


11

이 프로그램은 질문에 대한 답변을 이해하는 데 도움이 될 수 있습니다. 이것은 참조 "j"와 변수 "x"를 가리키는 포인터 "ptr"의 간단한 프로그램입니다.

#include<iostream>

using namespace std;

int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"

cout << "x=" << x << endl;

cout << "&x=" << &x << endl;

cout << "j=" << j << endl;

cout << "&j=" << &j << endl;

cout << "*ptr=" << *ptr << endl;

cout << "ptr=" << ptr << endl;

cout << "&ptr=" << &ptr << endl;
    getch();
}

프로그램을 실행하고 출력을 살펴보면 이해할 수 있습니다.

또한 10 분을 절약하고 다음 동영상을 시청하십시오. https://www.youtube.com/watch?v=rlJrrGV0iOg


11

나는 여기에서 다루지 않은 또 다른 요점이 있다고 생각합니다.

포인터와 달리 참조는 참조 하는 객체 와 구문 적으로 동일 합니다. 즉, 객체에 적용 할 수있는 모든 작업은 참조를 위해 작동하며 정확히 동일한 구문을 사용합니다 (물론 초기화는 예외입니다).

이것은 피상적 인 것처럼 보일 수 있지만이 속성은 여러 C ++ 기능에 중요합니다.

  • 템플릿 . 템플릿 매개 변수가 오리 입력 때문에, 형태의 구문 특성은 문제가 너무 자주 같은 템플릿을 모두 사용할 수있는 모든 것입니다 TT&.
    (또는 std::reference_wrapper<T>어느 여전히에 대한 암시 적 캐스트에 의존 T&)는
    그 커버를 서식 파일 모두 T&T&&더 일반적이다.

  • L 값 . 문 고려 str[0] = 'X';그것은 단지 C-문자열 일 것이다 참조 없음을 ( char* str). 문자를 참조로 리턴하면 사용자 정의 클래스가 동일한 표기법을 가질 수 있습니다.

  • 생성자를 복사 합니다. 문법적으로는 객체에 대한 포인터가 아닌 생성자를 복사하기 위해 객체를 전달하는 것이 좋습니다. 그러나 복사 생성자가 값으로 객체를 가져갈 수있는 방법은 없습니다. 동일한 복사 생성자를 재귀 적으로 호출하게됩니다. 여기서는 참조가 유일한 옵션으로 남습니다.

  • 운영자 과부하 . 참조를 사용 operator+(const T& a, const T& b)하면 동일한 호출 표기법을 유지하면서 운영자 호출에 대한 간접 참조를 도입 할 수 있습니다 . 이는 정기적으로 오버로드 된 기능에도 적용됩니다.

이러한 요점은 C ++ 및 표준 라이브러리의 상당 부분을 강화하므로 참조의 주요 특성입니다.


" 암시 적 캐스트 "캐스트는 구문 구조이며 문법에 존재합니다. 캐스트는 항상 명시 적입니다
curiousguy

9

포인터와 참조 사이에는 기술적으로 다른 중요한 차이점이 있습니다. 포인터로 함수에 전달 된 인수는 비 const 참조로 함수에 전달 된 인수보다 훨씬 더 잘 보입니다. 예를 들면 다음과 같습니다.

void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);

void bar() {
    std::string x;
    fn1(x);  // Cannot modify x
    fn2(x);  // Cannot modify x (without const_cast)
    fn3(x);  // CAN modify x!
    fn4(&x); // Can modify x (but is obvious about it)
}

다시 C로 돌아가는 것처럼 보이는 호출은 fn(x)값으로 만 전달할 수 있으므로 확실히 수정할 수는 없습니다 x. 인수를 수정하려면 포인터를 전달해야합니다 fn(&x). 따라서 인수가 앞에 &오지 않으면 수정되지 않는다는 것을 알았습니다. ( &때로는 const포인터로 큰 읽기 전용 구조를 전달해야하기 때문에 대화가 수정되었다는 것은 사실이 아닙니다 .)

어떤 사람들은 이것이 코드를 읽을 때 유용한 기능이라고 주장하지만 const, 함수가 결코을 기대하지 않더라도 포인터 매개 변수는 항상 비 참조 가 아닌 수정 가능한 매개 변수에 사용해야한다고 주장 합니다 nullptr. 즉, 사람들은 fn3()위와 같은 기능 서명을 허용해서는 안된다고 주장합니다 . Google의 C ++ 스타일 가이드 라인 이 그 예입니다.


8

아마도 어떤 은유가 도움이 될 것입니다. 데스크탑 화면 공간의 맥락에서-

  • 참조는 실제 창을 지정해야합니다.
  • 포인터는 해당 창 유형의 인스턴스가 0 개 이상 포함되도록 화면상의 공간 위치를 요구합니다.

6

포인터와 참조의 차이점

포인터는 0으로 초기화되고 참조는 초기화되지 않습니다. 실제로 참조는 객체를 참조해야하지만 포인터는 널 포인터가 될 수 있습니다.

int* p = 0;

그러나 우리는 int& p = 0;또한 가질 수 없습니다 int& p=5 ;.

실제로 올바르게 수행하려면 처음에 객체를 선언하고 정의한 다음 해당 객체에 대한 참조를 만들 수 있으므로 이전 코드의 올바른 구현은 다음과 같습니다.

Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;

또 다른 중요한 점은 초기화하지 않고 포인터를 선언 할 수 있지만 항상 변수 또는 객체를 참조 해야하는 참조의 경우에는 수행 할 수 없다는 것입니다. 그러나 이러한 포인터 사용은 위험하므로 일반적으로 포인터가 실제로 무언가를 가리키고 있는지 확인합니다. 참조의 경우 선언 중에 객체를 참조하는 것이 필수적이라는 것을 이미 알고 있기 때문에 그러한 검사가 필요하지 않습니다.

또 다른 차이점은 포인터가 다른 객체를 가리킬 수 있지만 참조는 항상 동일한 객체를 참조한다는 것입니다.이 예제를 보자.

Int a = 6, b = 5;
Int& rf = a;

Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.

rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

또 다른 요점 : STL 템플릿과 같은 템플릿이있는 경우 이러한 종류의 클래스 템플릿은 연산자 []를 사용하여 새 값을 쉽게 읽거나 할당 할 수 있도록 항상 포인터가 아닌 참조를 반환합니다.

Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="

1
우리는 여전히 할 수 있습니다 const int& i = 0.
Revolver_Ocelot

1
이 경우 참조는 읽기 전용으로 사용되며 "const_cast"는 참조가 아닌 포인터 만 허용하므로 "const_cast"를 사용해도이 const 참조를 수정할 수 없습니다.
dhokar.w

1
const_cast는 참조와 함께 잘 작동합니다 : coliru.stacked-crooked.com/a/eebb454ab2cfd570
Revolver_Ocelot

1
참조를 캐스트하지 않고 참조를 캐스트하고 있습니다. const int & i =; const_cast <int> (i); 참조에 새로운 값을 작성하고 할당 할 수 있도록 참조의 불일치를 버리려고하지만 불가능합니다. 집중 해주세요 !!
dhokar.w

5

차이점은 프로그램 실행 중 일정하지 않은 포인터 변수 (상수에 대한 포인터와 혼동하지 않아야 함)가 언젠가 변경 될 수 있으며 포인터 시맨틱을 사용해야하며 (&, *) 연산자가 필요하지만 초기화시 참조를 설정할 수 있다는 것입니다 만 (그래서 생성자 이니셜 라이저 목록에서만 설정할 수 있지만 다른 방법으로는 할 수는 없습니다) 일반 값 액세스 시맨틱을 사용하십시오. 아주 오래된 책에서 읽은 것처럼 연산자 오버로드를 지원하기 위해 기본적으로 참조가 도입되었습니다. 이 스레드에서 누군가가 언급했듯이 포인터는 0 또는 원하는 값으로 설정할 수 있습니다. 0 (NULL, nullptr)은 포인터가 아무것도 초기화되지 않았 음을 의미합니다. 널 포인터를 역 참조하는 것은 오류입니다. 그러나 실제로 포인터에는 올바른 메모리 위치를 가리 키지 않는 값이 포함될 수 있습니다. 차례로 참조는 올바른 유형의 rvalue를 항상 제공한다는 사실 때문에 사용자가 참조 할 수없는 것에 대한 참조를 초기화하지 못하게합니다. 참조 변수를 잘못된 메모리 위치로 초기화하는 방법에는 여러 가지가 있지만 세부 사항을 자세히 살펴 보지 않는 것이 좋습니다. 기계 수준에서 포인터와 참조는 포인터를 통해 균일하게 작동합니다. 필수 참고 문헌에서 구문 설탕이라고 가정 해 봅시다. rvalue 참조는 이것과 다릅니다. 자연스럽게 스택 / 힙 객체입니다. 참조 변수를 잘못된 메모리 위치로 초기화하는 방법에는 여러 가지가 있지만 세부 사항을 자세히 다루지 않는 것이 좋습니다. 기계 수준에서 포인터와 참조는 포인터를 통해 균일하게 작동합니다. 필수 참고 문헌에서 구문 설탕이라고 가정 해 봅시다. rvalue 참조는 이것과 다릅니다. 자연스럽게 스택 / 힙 객체입니다. 참조 변수를 잘못된 메모리 위치로 초기화하는 방법에는 여러 가지가 있지만 세부 사항을 자세히 다루지 않는 것이 좋습니다. 기계 수준에서 포인터와 참조는 포인터를 통해 균일하게 작동합니다. 필수 참고 문헌에서 구문 설탕이라고 가정 해 봅시다. rvalue 참조는 이것과 다릅니다. 자연스럽게 스택 / 힙 객체입니다.


4

간단히 말해, 참조는 변수의 대체 이름 인 반면 포인터는 다른 변수의 주소를 보유하는 변수입니다. 예 :

int a = 20;
int &r = a;
r = 40;  /* now the value of a is changed to 40 */

int b =20;
int *ptr;
ptr = &b;  /*assigns address of b to ptr not the value */
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.