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




포인터 의 참조 자 (24)

나는 참조가 문법적인 설탕이라는 것을 안다. 그래서 코드는 읽고 쓰는 것이 더 쉽다.

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

아래 답변 및 링크 요약 :

  1. 바인딩 후 참조를 다시 할당 할 수 없으면 포인터를 여러 번 다시 할당 할 수 있습니다.
  2. 포인터는 아무데도 가리킬 수 없으며 ( NULL ), 참조는 항상 객체를 나타냅니다.
  3. 포인터로 할 수있는 것처럼 참조 주소를 가져올 수 없습니다.
  4. "참조 연산"은 없습니다 (그러나 참조로 가리키는 객체의 주소를 가져 와서 &obj + 5 에서처럼 포인터 연산을 할 수 있습니다).

오해를 명확히하기 위해 :

C ++ 표준은 컴파일러가 참조를 구현하는 방법을 피하기 위해 매우 신중하지만 모든 C ++ 컴파일러는 참조를 포인터로 구현합니다. 즉, 다음과 같은 선언입니다.

int &ri = i;

완전히 최적화되지 않은 경우 포인터와 동일한 양의 저장소를 할당하고 해당 주소를 해당 저장소에 저장합니다.

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

일반적으로,

  • 유용하고 자체적으로 문서화 할 수있는 인터페이스를 제공하기 위해 함수 매개 변수와 반환 유형에서 참조를 사용하십시오.
  • 알고리즘과 데이터 구조를 구현하기위한 포인터를 사용하십시오.

재미있는 읽기 :


컴퓨터 언어를 추상적 또는 학술적 방식으로 공부하는 것에 익숙하지 않은 경우 밀교 적으로 나타날 수있는 의미상의 차이가 있습니다.

최상위 레벨에서 참조 개념은 투명한 "별칭"입니다. 컴퓨터가 주소를 사용하여 작동하도록 할 수도 있지만 걱정하지 않아도됩니다. 기존 개체의 "다른 이름"으로 간주해야하며 구문에 반영됩니다. 포인터보다 더 엄격하므로 매달린 참조를 만들 때보다 매달린 포인터를 만들 때보 다 컴파일러에서보다 확실하게 경고 할 수 있습니다.

그 외에도 물론 포인터와 참조 사이에는 실제적인 차이가 있습니다. 그 (것)들을 사용하기 위하여 통어론은 명백하게 다르, 당신은 참조를 "re-seat", 아무 것도에 참조가 있고, 참고에 포인터가있을 수 없다.


그것이 차지할 공간의 어떤 부작용 (코드 실행없이)을 실제로 볼 수 없기 때문에 어느 정도의 공간이 차지하는지는 중요하지 않습니다.

반면에 참조와 포인터 사이의 주요한 차이점은 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가 작동 할 수있게 해주는 언어 메커니즘입니다.


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

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

허락하신다면 참고 문헌을 사용하는 것이 훨씬 더 어렵습니다. 그러나 그것을 관리하면 머리를 찢어서 찾으려고합니다. 참조는 본질적으로 C ++에서 안전 하지 않습니다 !

기술적으로 이는 null 참조가 아닌 잘못된 참조입니다. C ++은 다른 언어에서 볼 수있는 것처럼 null 참조를 개념으로 지원하지 않습니다. 다른 종류의 잘못된 참조도 있습니다. 유효하지 않은 포인터를 사용하는 것과 마찬가지로 잘못된 참조는 정의되지 않은 동작 의 유령을 발생시킵니다.

실제 오류는 참조에 할당되기 전에 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 참조가 잘못된 형식의 코드를 통해 얻을 수있는 유일한 방법은 일단 정의되지 않은 동작이 발생했다는 것을 반복하고 싶습니다. null 참조를 확인하는 것은 결코 의미가 없습니다 . 예를 들어 if(&bar==NULL)... 시도해 볼 수는 있지만 if(&bar==NULL)... 컴파일러가 문을 최적화하지 못할 수도 있습니다! 유효한 참조는 NULL이 될 수 없으므로 컴파일러의 뷰에서 비교가 항상 거짓이며 if 절을 불필요한 코드로 제거 할 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 참조참조 하십시오.

NULL 포인터 역 참조의 위험에 대한 또 다른 예는 레이몬드 첸 (Raymond Chen)이 다른 플랫폼 으로 코드를 포팅하려고 시도 할 때 정의되지 않은 동작 노출 입니다.


포인터와 참조의 차이점

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

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 템플릿과 같은 템플릿이있을 때 이러한 종류의 클래스 템플릿은 포인터가 아닌 참조를 반환하므로 operator []를 사용하여 쉽게 새 값을 읽거나 할당 할 수 있습니다.

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 "="

나는 항상 this 규칙에 따라 C ++ 핵심 지침에서 결정합니다 .

"인자 없음"이 유효한 옵션 일 때 T &보다 T *를 선호합니다.


포인터와 참조 사이에는 매우 중요한 비 기술적 차이가 있습니다. 포인터로 함수에 전달 된 인수는 비 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함수가 절대로 a를 기대하지 않더라도 비 참조 가 아닌 수정 가능한 매개 변수에 항상 사용되어야한다고 주장 합니다 nullptr. 즉, 사람들은 fn3()위와 같은 기능 서명 이 허용되어서는 안된다고 주장합니다 . Google의 C ++ 스타일 가이드 라인 이 이에 대한 예입니다.


가장 중요한 부분을 잊어 버렸습니다.

포인터를 사용한 구성원 액세스 사용 ->
참조가있는 회원 액세스 .

foo.barviEmacs 보다 분명히 우수하다는 것과 같은 방식으로 foo->bar 보다 분명히 우월합니다 :-)


두 참조와 포인터는 다른 함수의 한 함수의 지역 변수를 변경하는 데 사용할 수 있습니다. 둘 다 함수에 인수로 전달되거나 함수에서 리턴 될 때 큰 오브젝트의 복사를 저장하여 효율성 향상을 얻을 수 있습니다. 위의 유사점에도 불구하고 참조와 포인터 사이에는 다음과 같은 차이점이 있습니다.

참조는 포인터보다 덜 강력합니다.

1) 참조가 생성되면 나중에 다른 객체를 참조 할 수 없습니다. 다시 장착 할 수 없습니다. 이것은 종종 포인터로 수행됩니다.

2) 참조는 NULL 일 수 없습니다. 포인터는 종종 유효한 것으로 가리키고 있지 않음을 나타 내기 위해 NULL이됩니다.

3) 참조가 선언되면 초기화되어야합니다. 포인터에는 이러한 제한이 없습니다.

위의 제한 사항으로 인해 C ++의 참조는 연결된 목록, 트리 등과 같은 데이터 구조를 구현하는 데 사용할 수 없습니다. Java에서 참조에는 위의 제한이 없으며 모든 데이터 구조를 구현하는 데 사용할 수 있습니다. Java에서 참조가 더 강력 해지면 Java가 포인터를 필요로하지 않는 주된 이유입니다.

참조가 더 안전하고 사용하기 쉽습니다.

1) 안전 : 참조를 초기화해야하므로 야생 포인터와 같은 야생 참조가 존재하지 않을 수 있습니다. 유효한 위치를 참조하지 않는 참조를 가질 수도 있습니다.

2) 사용하기 쉬움 : 참조에 값에 액세스 할 때 역 참조 연산자가 필요하지 않습니다. 일반 변수처럼 사용할 수 있습니다. '&'연산자는 선언시에만 필요합니다. 또한 멤버에 액세스하는 데 화살표 연산자 (->)가 필요한 포인터와 달리 객체 참조의 멤버는 도트 연산자 ( '.')로 액세스 할 수 있습니다.

위의 이유와 함께, 포인터를 사용할 수없는 복사 생성자 인수 같은 곳은 거의 없습니다. 복사 생성자에서 인수를 사용하려면 참조를 사용해야합니다. ++ 같은 연산자를 오버로딩 할 때도 비슷하게 참조를 사용해야합니다 .


사실, 참조는 실제로 포인터와 같지 않습니다.

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

참조를 만들 때 포인터 변수에 다른 이름을 할당한다는 것을 컴파일러에만 알립니다. 그래서 참조가 "null을 가리킬"수없는 이유는 변수가 될 수 없기 때문입니다.

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

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

int a = 0;
int& b = a;

여기 a 를 가리키는 또 다른 변수를 생성하지 않습니다. 당신은 단지 a 의 값을 a 메모리 내용에 다른 이름을 추가 a 것이다. 이 메모리에는 이제 두 개의 이름 인 ab 가 있으며 두 개의 이름을 사용하여 주소 지정할 수 있습니다.

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

int a;
increment(a);

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

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


이 프로그램은 질문의 답을 이해하는 데 도움이 될 수 있습니다. 이것은 참조 "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


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

  • 참조에서는 실제 창을 지정해야합니다.
  • 포인터는 화면에 공간의 위치가 필요하다는 것을 나타냅니다.이 공간에는 0 개 이상의 해당 창 유형의 인스턴스가 포함됩니다.

정말로 pedagonic하고 싶다면 포인터로 할 수없는 참조를 사용하여 할 수있는 한 가지가 있습니다. 임시 객체의 수명을 연장하는 것입니다. 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 없이 이것을 시도하면 컴파일에 실패합니다. 임시 오브젝트에 const가 아닌 참조를 바인드 할 수 없으며, 그 문제에 대해 주소를 지정할 수도 없습니다.


포인터와 참조 사이에는 근본적인 차이점이 있습니다. 언급하지 않은 사람은 언급 한 적이 없습니다. 참조는 함수 인수에서 참조별로 의미 전달을 가능하게합니다. 포인터는 처음에는 보이지 않지만 포인터는 값에 대한 의미를 제공합니다. 이것은 이 기사 에서 매우 잘 설명되어 있습니다.

감사합니다 & rzej


  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. 포인터는 자체 메모리 주소와 크기를 스택에 가지고 있지만 (참조 번호는 원래 변수와 함께) 동일한 메모리 주소를 공유하지만 스택의 일부 공간을 차지합니다. 참조는 원래 변수 자체와 동일한 주소를 가지기 때문에 참조를 동일한 변수의 다른 이름으로 생각하는 것이 안전합니다. 참고 : 포인터가 가리키는 점은 스택 또는 힙에있을 수 있습니다. 참고 자료. 이 문장에서 내 주장은 포인터가 스택을 가리켜 야한다는 것이 아니다. 포인터는 메모리 주소를 보유하는 변수 일뿐입니다. 이 변수는 스택에 있습니다. 참조는 스택에 자체 공간을 가지고 주소는 참조하는 변수와 동일하기 때문에. 스택 대 힙 에 더 많은 정보가 있습니다. 이것은 컴파일러가 알려주지 않는 참조의 실제 주소가 있음을 의미합니다.

    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. 포인터는 배열을 반복 할 수 있습니다. ++ 를 사용하여 포인터가 가리키는 다음 항목으로 이동하고 + 4 를 사용하여 다섯 번째 요소로 이동할 수 있습니다. 포인터가 가리키는 객체의 크기와 상관 없습니다.

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

  7. 포인터는 메모리 주소를 보유하는 변수입니다. 참조가 구현되는 방법에 관계없이 참조는 참조하는 항목과 동일한 메모리 주소를가집니다.

  8. 참조는 배열에 채워질 수 없지만 포인터는 (사용자 @litb에 의해 언급 됨)

  9. Const 참조는 임시에 바인딩 될 수 있습니다. 포인터는 할 수 없습니다 (어떤 간접 참조가없는 경우).

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

    이것에 의해, 인수리스트 등에서의 사용에있어 const& safe가됩니다.


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

    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. 

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


이는 tutorial 기반으로합니다 . 쓰여진 것은 그것을 더 분명하게합니다 :

>>> 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(서로 다른 typedefalias of a type) Tom. 그것은 또한 그러한 진술의 용어를 잊어도 괜찮습니다의 참조를 만드는 것입니다 Tom.


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;
}

또 다른 차이점은 void 타입에 대한 포인터를 가질 수 있다는 것입니다 (그리고 포인터는 무엇이든 의미합니다). 그러나 void에 대한 참조는 금지되어 있습니다.

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

나는이 특별한 차이에 대해 정말로 행복하다고 말할 수 없다. 나는 주소를 가진 어떤 것에도 의미의 참조를 허용하고 그렇지 않으면 참조를 위해 같은 행동을하는 것이 훨씬 더 좋을 것이다. 그것은 참조를 사용하여 memcpy와 같은 C 라이브러리 함수의 일부 동등한 것을 정의 할 수 있습니다.


혼란에 빠질 위험이 있으므로, 나는 약간의 입력을 던지고 싶다. 컴파일러가 어떻게 레퍼런스를 구현하는지에 달려 있다고 확신하지만, 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 호출 후? 음, 분명히 잘 컴파일되지만, 더 이상 유효한 변수를 가리키고 있지 않기 때문에 런타임에 세분화 오류가 발생합니다. 범위를 벗어날 때까지는 여전히 존재하는 손상된 참조가 있지만 쓸모가 없습니다.

즉, 참조는 포인터 메 커닉을 추상화 한 포인터 일 뿐이며 더 안전하고 사용하기 쉽습니다 (우발적 인 포인터 수학, 혼합되지 않음, '->'등). 위의 예와 같이 헛소리하지 마라.)

이제 컴파일러가 참조를 처리하는 방법에 관계없이 참조 특정 메모리 주소의 특정 변수를 참조하여 예상대로 작동 해야 하기 때문에 항상 후드 아래에 포인터 가 있습니다 (따라서 용어 '참조').

참조와 함께 기억해야 할 중요한 유일한 규칙은 선언시에 정의되어야한다는 것입니다 (헤더의 참조는 예외입니다.이 경우 헤더가 포함 된 객체가 생성자에 정의되어 있어야합니다. 그것을 정의하기에는 너무 늦었습니다).

위의 예는 참고서가 무엇인지 보여주는 예제 일 뿐이며, 그런 방식으로 참조를 사용하고 싶지는 않습니다. 참고 문헌의 적절한 사용법에 관해서는 머리에 못을 박은 이미 여기에 대한 답변이 많이 있습니다.


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

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;
}

디폴트 플레이버는 '참조에 대한 임시 속성에 대한 바인딩 참조'를 사용합니다.


참조는 일부 메모리에 주어진 다른 이름이 아닙니다. 이것은 사용법에 따라 자동으로 참조 해제되는 불변 포인터입니다. 근본적으로 그것은 아래로 귀결된다 :

int& j = i;

내부적으로

int* const j = &i;

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


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

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을 인라이닝 할 때 많은 컴파일러가 실제로 메모리에 쓰기를 강제합니다 (주소를 명시 적으로 취함). 그러나 그들은 더 최적 인 레지스터에 레퍼런스를 남겨 둘 것이다.

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





c++-faq