참조는 구문 설탕이라는 것을 알고 있으므로 코드를 읽고 쓰기가 더 쉽습니다.
그러나 차이점은 무엇입니까?
int &x = *(int*)0;
gcc에서 경고없이 컴파일됩니다 . 참조는 실제로 NULL을 가리킬 수 있습니다.
참조는 구문 설탕이라는 것을 알고 있으므로 코드를 읽고 쓰기가 더 쉽습니다.
그러나 차이점은 무엇입니까?
int &x = *(int*)0;
gcc에서 경고없이 컴파일됩니다 . 참조는 실제로 NULL을 가리킬 수 있습니다.
답변:
포인터를 다시 할당 할 수 있습니다 :
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;
포인터는 스택에 자체 메모리 주소와 크기 (x86의 경우 4 바이트)를 가지고있는 반면, 참조는 원래 메모리와 동일한 메모리 주소를 공유하지만 스택의 일부 공간을 차지합니다. 참조는 원래 변수 자체와 주소가 동일하므로 참조를 동일한 변수의 다른 이름으로 생각하는 것이 안전합니다. 참고 : 포인터가 가리키는 내용은 스택 또는 힙에있을 수 있습니다. 참조를 피하십시오. 이 진술에서 내 주장은 포인터가 스택을 가리켜 야한다는 것이 아닙니다. 포인터는 메모리 주소를 보유하는 변수 일뿐입니다. 이 변수는 스택에 있습니다. 참조에는 스택에 자체 공간이 있고 주소는 참조하는 변수와 동일하므로 스택 대 힙 에 대한 추가 정보. 이것은 컴파일러가 알려주지 않는 실제 참조 주소가 있음을 의미합니다.
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
assert(p == p2);
여분의 간접 레벨을 제공하는 포인터에 대한 포인터를 가질 수 있습니다. 참조는 한 수준의 간접 참조 만 제공합니다.
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);
포인터는 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
포인터는 배열을 반복 할 수 있습니다. 당신이 사용할 수있는 ++
포인터가 가리키는 그 다음 항목으로 이동하고, + 4
5 요소로 이동합니다. 이것은 포인터가 가리키는 객체의 크기에 관계없이 발생합니다.
포인터가 가리키는 *
메모리 위치에 액세스하려면 포인터를 참조 해제해야 하지만 참조는 직접 사용할 수 있습니다. 클래스 / 구조체에 대한 포인터는 ->
멤버에 액세스하는 데 사용되는 반면 참조는을 사용합니다 .
.
참조는 배열에 채워 넣을 수 없지만 포인터는 (@litb 사용자가 인용)
상수 참조는 임시에 바인딩 될 수 있습니다. 포인터는 (간접적 인 것이 없다면) 할 수 없습니다 :
const int &x = int(12); //legal C++
int *y = &int(12); //illegal to dereference a temporary.
이것은 const&
인수리스트 등에서 사용하기에 더 안전합니다.
참조는 A와 생각 될 수 상수 포인터 (상수 값에 대한 포인터와 혼동하지!) 자동 간접으로, 즉 적용됩니다 컴파일러 *
당신을위한 연산자.
모든 참조는 널이 아닌 값으로 초기화해야합니다. 그렇지 않으면 컴파일이 실패합니다. 참조의 주소를 얻는 것은 불가능합니다. 주소 연산자는 대신 참조 된 값의 주소를 반환합니다. 또한 참조에 대해 산술을 수행 할 수도 없습니다.
C 프로그래머는 간접적으로 발생하거나 함수 시그너처를 보지 않고 값이나 포인터로 인수가 전달되는 경우 더 이상 명확하지 않으므로 C ++ 참조를 싫어할 수 있습니다.
C ++ 프로그래머는 안전하지 않은 것으로 간주되므로 포인터 사용을 싫어할 수 있습니다. 비록 참조가 가장 사소한 경우를 제외하고는 상수 포인터보다 더 안전하지는 않지만 자동 간접의 편리함이없고 다른 의미 적 의미를 지니고 있습니다.
C ++ FAQ 의 다음 문장을 고려하십시오 .
참조는 종종 기본 어셈블리 언어의 주소를 사용하여 구현되지만 참조를 객체에 대한 재미있는 모양의 포인터로 생각 하지 마십시오 . 참조 는 객체입니다. 객체에 대한 포인터 나 객체의 복사본이 아닙니다. 그것은 이다 오브젝트.
그러나 참조가 실제로 객체라면 어떻게 매달려있는 참조가있을 수 있습니까? 관리되지 않는 언어에서는 참조가 포인터보다 '불량한'것이 불가능합니다. 일반적으로 범위 경계를 넘어 값을 안정적으로 별칭으로 지정할 수있는 방법은 없습니다!
는 C의 배경에서 오는, C ++ 참조는 다소 바보 개념으로 보일 수도 있지만, 사람은 여전히 가능 포인터 대신에 그들을 사용한다 : 자동 간접는 이다 편리하고 처리 할 때 참조가 특히 유용하게 RAII -하지만 때문에 어떤 인식 안전 그러나 관용적 코드 작성이 덜 어색해지기 때문에 이점이 있습니다.
RAII는 C ++의 핵심 개념 중 하나이지만 복사 의미론과 사소하게 상호 작용합니다. 참조로 객체를 전달하면 복사가 필요 없으므로 이러한 문제를 피할 수 있습니다. 언어로 된 참조가 없으면 대신 포인터를 사용해야하므로 사용하기가 더 번거로우므로 최선의 솔루션이 대안보다 쉬워야한다는 언어 설계 원칙을 위반합니다.
정말 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
그것 없이 시도하면 컴파일에 실패해야합니다. 상수가 아닌 참조를 임시 객체에 바인딩하거나 해당 문제에 대한 주소를 취할 수 없습니다.
const &
바인딩 의 사실에 의해 연장되며, 참조가 범위를 벗어날 때만 실제 참조 유형 의 소멸자 (참조 유형, 즉 기준일 수 있음)가 호출됩니다. 참조이므로 사이에 슬라이싱이 발생하지 않습니다.
Animal x = fast ? getHare() : getTortoise()
다음 x
고전 슬라이싱 문제를 직면하게 될 것이다,하면서 Animal& x = ...
제대로 작동합니다.
구문 설탕과는 별도로 참조는 const
포인터가 아닌 포인터 const
입니다. 참조 변수를 선언 할 때 참조하는 내용을 설정해야하며 나중에 변경할 수 없습니다.
업데이트 : 이제 그것에 대해 더 생각하면 중요한 차이점이 있습니다.
const 포인터의 대상은 주소를 가져오고 const 캐스트를 사용하여 바꿀 수 있습니다.
참조 대상은 UB가 부족한 방식으로 대체 할 수 없습니다.
이를 통해 컴파일러는 참조에 대해 더 많은 최적화를 수행 할 수 있습니다.
T* const
가 다른 구문 설탕을 사용 하는 것으로 이해했습니다 (코드에서 많은 *를 제거합니다).
int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);
.
대중적인 의견과 달리 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이 코드를 다른 플랫폼 으로 포팅 할 때 정의되지 않은 동작 노출을 참조하십시오 .
가장 중요한 부분을 잊었습니다.
포인터가있는 ->
멤버 액세스는 참조가있는 멤버 액세스.
->
는 포인터 자체와 마찬가지로 포인터에 대한 참조를 제공하는 것입니다.
.
와 ->
: emacs 나 vi 함께 할 수있는 뭔가가
.
사용하는 것보다 더 나은 ->
,하지만 단지 이맥스 대 VI처럼, 그것은 전적으로 주관적인 그리고 당신은 아무것도 증명할 수
참조는 포인터와 매우 유사하지만 컴파일러 최적화에 도움이되도록 특별히 제작되었습니다.
예로서:
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)
식이 끝날 때 소멸됩니다. 그러나 ref
C ++는 해당 객체를 참조에 바인딩함으로써 ref
범위를 벗어날 때까지 해당 임시 객체의 수명을 연장합니다 .
maybeModify
관련된 어떤 것의 주소를 취하지 않는 것을 결정하는 것은 x
많은 포인터 산술이 발생하지 않는 것을 증명하는 것보다 실질적으로 쉽다.
void maybeModify(int& x) { 1[&x]++; }
. 위에서 언급 한 다른 의견들
실제로, 참조는 실제로 포인터와 다릅니다.
컴파일러는 이름을 메모리 주소와 연관시켜 변수에 대한 "참조"를 유지합니다. 컴파일 할 때 변수 이름을 메모리 주소로 변환하는 작업입니다.
참조를 작성할 때, 포인터 변수에 다른 이름을 지정한다고 컴파일러에게 알리십시오. 그렇기 때문에 변수가 될 수없는 변수이기 때문에 참조가 "널을 가리킬 수"없습니다.
포인터는 변수입니다. 다른 변수의 주소를 포함하거나 null 일 수 있습니다. 중요한 것은 포인터에 값이 있고 참조에는 참조하는 변수 만 있다는 것입니다.
이제 실제 코드에 대한 설명이 있습니다.
int a = 0;
int& b = a;
여기서는 다른 변수를 만들지 않습니다 a
. 값을 보유한 메모리 내용에 다른 이름을 추가하기 만하면 a
됩니다. 이 메모리는 이제 두 개의 이름을 가지고, a
그리고 b
, 그것은 하나의 이름을 사용하여 해결할 수 있습니다.
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
함수를 호출 할 때 컴파일러는 일반적으로 인수를 복사 할 메모리 공간을 생성합니다. 함수 시그니처는 작성해야하는 공백을 정의하고이 공백에 사용해야하는 이름을 제공합니다. 매개 변수를 참조로 선언하면 컴파일러가 메서드 호출 중에 새 메모리 공간을 할당하는 대신 입력 변수 메모리 공간을 사용하도록 지시합니다. 함수가 호출 범위에 선언 된 변수를 직접 조작한다고 말하는 것은 이상하게 보일 수 있지만 컴파일 된 코드를 실행할 때 더 이상 범위가 없다는 것을 기억하십시오. 평평한 평평한 메모리가 있으며 함수 코드는 모든 변수를 조작 할 수 있습니다.
extern 변수를 사용할 때와 같이 컴파일러가 컴파일 할 때 참조를 알지 못하는 경우가있을 수 있습니다. 따라서 참조는 기본 코드에서 포인터로 구현되거나 구현되지 않을 수 있습니다. 그러나 내가 준 예제에서 포인터로 구현되지 않았을 가능성이 큽니다.
참조는 절대 될 수 없습니다 NULL
.
void Foo::bar() { virtual_baz(); }
끝나면 segfaults 와 같은 코드가 있습니다 . 참조가 널일 수 있음을 모르는 경우, 널을 원래 위치로 추적 할 수 없습니다.
int &r=*p;
는 정의되지 않은 동작입니다. 그 시점에서, 당신은 필요가 없습니다 "NULL을 참조를 가리키는,"당신은 프로그램이 더 이상에 대해 추론 할 수있다 전혀를 .
참조와 포인터 모두 다른 값에 간접적으로 액세스하는 데 사용되지만 참조와 포인터 사이에는 두 가지 중요한 차이점이 있습니다. 첫 번째는 참조가 항상 객체를 참조한다는 것입니다. 참조를 초기화하지 않고 정의하는 것은 오류입니다. 할당 동작은 두 번째로 중요한 차이점입니다. 참조에 할당하면 참조가 바인딩 된 객체가 변경됩니다. 다른 객체에 대한 참조를 리 바인드하지 않습니다. 초기화되면 참조는 항상 동일한 기본 개체를 참조합니다.
이 두 프로그램 조각을 고려하십시오. 첫 번째로 한 포인터를 다른 포인터에 할당합니다.
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을 변경합니다. 할당 후에도 두 참조는 여전히 원래 객체를 참조하며 해당 객체의 값도 동일합니다.
추상적이거나 학문적 인 방식으로 컴퓨터 언어를 공부하는 데 익숙하지 않은 경우 의미 론적으로 차이가있을 수 있습니다.
최상위 수준에서 참조의 개념은 참조가 투명한 "별칭"이라는 것입니다. 컴퓨터가 주소를 사용하여 작동하게 할 수도 있지만 걱정할 필요는 없습니다. 기존 객체의 "단지 다른 이름"으로 생각해야하며 구문에 반영됩니다. 포인터보다 엄격하므로 매달려있는 포인터를 만들 때보 다 매달려있는 참조를 만들 때 컴파일러가보다 확실하게 경고 할 수 있습니다.
그 외에도 포인터와 참조 사이에는 실질적인 차이점이 있습니다. 그것들을 사용하는 문법은 분명히 다르며, 참조를 "다시 앉히거나", 아무 것도 참조하지 않거나, 참조를 가리키는 포인터를 가질 수 없습니다.
참조는 다른 변수의 별칭이며 포인터는 변수의 메모리 주소를 보유합니다. 참조는 일반적으로 전달 된 객체가 사본이 아니라 객체 자체가되도록 함수 매개 변수로 사용됩니다.
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.
차지하는 공간에 대한 부작용 (코드 실행없이)을 실제로 볼 수 없으므로 차지하는 공간은 중요하지 않습니다.
반면, 참조와 포인터의 주요 차이점은 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가 작동하도록하는 언어 메커니즘입니다.
이것은 튜토리얼을 기반으로합니다 . 작성된 내용이 더 명확 해집니다.
>>> 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_Tom
int로서 이해 될 수있다 alias of a variable
(서로 다른 typedef
이다 alias of a type
) Tom
. 또한 그러한 진술의 용어를 잊어 버린 것도 괜찮습니다 Tom
.
nullptr
있습니까? 이 스레드의 다른 부분을 실제로 읽었습니까?
참조는 일부 메모리에 지정된 다른 이름이 아닙니다. 사용법에서 자동으로 역 참조되는 불변 포인터입니다. 기본적으로 다음과 같이 요약됩니다.
int& j = i;
내부적으로됩니다
int* const j = &i;
const
포인터 를 구현하는 올바른 방법이기도합니다 . 이러한 유연성은 참조와 포인터 사이에 차이가 있음을 증명하지 않습니다.
C ++에서 참조 란 무엇입니까? 객체 유형이 아닌 특정 유형의 인스턴스 .
C ++의 포인터는 무엇입니까? 객체 유형 인 특정 유형의 인스턴스 .
에서 개체 유형의 ISO C ++ 정의 :
오브젝트 타입 (아마도이다 이력서 함수 형태가 아닌 참조 형 아닌 아니다 restrict로) 형 이력서 보이드.
개체 유형은 C ++에서 유형 유니버스의 최상위 범주입니다. 참조는 최상위 범주이기도합니다. 그러나 포인터는 아닙니다.
포인터와 참조는 복합 유형 의 맥락에서 함께 언급됩니다 . 이것은 기본적으로 참조가없는 C에서 상속 된 (및 확장 된) 선언자 구문의 특성 때문입니다. (포인터는 여전히 "unityped"반면 게다가, C ++ (11) 이후 참조 선언자 하나 이상의 종류가있다 : &
+ &&
대 *
.) 이러한 맥락에서 C 비슷한 스타일 "확장자"로 언어 특정 초안을 작성하는 것은 다소 합리적이다 그래서 . (나는 여전히 선언자의 구문이 구문 표현력 을 많이 낭비하고 인간 사용자와 구현을 모두 좌절 시킨다고 주장 할 것입니다 . 따라서 모든 것이 내장 될 자격이 없습니다새로운 언어 디자인. PL 디자인에 대해서는 완전히 다른 주제입니다.)
그렇지 않으면, 포인터가 함께 참조가있는 특정 유형의 유형으로 포인터를 규정 할 수있는 것은 중요하지 않습니다. 그것들은 구문 유사성 외에는 너무 적은 공통 속성을 공유하므로 대부분의 경우에 그것들을 조합 할 필요가 없습니다.
위의 설명은 "포인터"및 "참조"만 유형으로 언급합니다. 인스턴스와 관련하여 변수와 같은 흥미로운 질문이 있습니다. 오해가 너무 많습니다.
최상위 카테고리의 차이점은 포인터에 직접 연결되지 않은 많은 구체적인 차이점을 이미 보여줍니다.
cv
한정자가 있을 수 있습니다 . 참조는 할 수 없습니다.참고 문헌에 대한 몇 가지 특별한 규칙 :
&&
템플릿 매개 변수 공제 중 참조 축소를 기반으로 한 매개 변수 에 대한 특수 규칙 ( "전달 참조") 은 매개 변수의 "완벽한 전달" 을 허용 합니다.std::initializer_list
유사한 참조 수명 연장 규칙을 따릅니다. 벌레의 또 다른 캔입니다.참조는 구문 설탕이라는 것을 알고 있으므로 코드를 읽고 쓰기가 더 쉽습니다.
기술적으로 이것은 명백한 잘못입니다. 참조는 의미상의 차이없이 다른 기능으로 정확하게 대체 될 수 없기 때문에 C ++의 다른 기능의 구문 설탕이 아닙니다.
유사하게, 람다-표현식 은 C ++의 다른 기능의 구문 설탕 이 아닙니다 . 캡처 된 변수의 선언 순서 와 같은 "지정되지 않은"속성으로 정확하게 시뮬레이션 할 수 없기 때문에 이러한 변수의 초기화 순서는 중요 할 수 있습니다. 중요한.)
C ++는이 엄격한 의미에서 몇 가지 종류의 구문 설탕 만 가지고 있습니다. 하나의 인스턴스 (C로부터 상속)는 내장 된 (비 과부하) 연산자 []
, 바로 위에 내장 단항 연산자 조합의 특정 형태의 동일한 의미 속성을 갖는 정의 *
및 이진+
.
따라서 포인터와 참조는 모두 같은 양의 메모리를 사용합니다.
위의 진술은 단순히 잘못되었습니다. 이러한 오해를 피하려면 대신 ISO C ++ 규칙을 살펴보십시오.
가입일 [intro.object] / 1 :
... 물체는 구성 기간, 수명 및 파괴 기간에 저장 영역을 차지합니다. ...
가입일 [dcl.ref / 4 :
참조에 스토리지가 필요한지 여부는 지정되지 않았습니다.
이것은 시맨틱 속성입니다.
포인터가 언어 디자인의 의미에서 참조와 함께 사용하기에 충분하지 않더라도 매개 변수 유형을 선택할 때와 같이 다른 컨텍스트에서 포인터를 선택할 수 있다는 논란이 여전히 남아 있습니다.
그러나 이것은 전체 이야기가 아닙니다. 내 말은, 당신이 고려해야 할 포인터 대 참조보다 더 많은 것들이 있다는 것을 의미합니다.
이러한 특정 선택을 고수 할 필요가 없다면 대부분의 경우 대답이 짧습니다. 포인터를 사용할 필요가 없으므로 그렇게하지 마십시오 . 포인터는 일반적으로 예상하지 못한 것들을 너무 많이 암시하기 때문에 충분히 나쁘고 코드의 유지 관리 성 및 심지어 이식성을 손상시키는 너무 많은 암시 적 가정에 의존합니다. 불필요하게 포인터에 의존하는 것은 분명히 나쁜 스타일이므로 현대적인 C ++의 의미에서 피해야합니다. 목적을 재고하면 포인터가 대부분의 경우 마지막 정렬의 기능 이라는 것을 알게 될 것 입니다.
&
1 매개 변수 유형으로 참조 유형을. (보통 const
자격 이 있어야합니다 .)&&
1 매개 변수 유형으로 참조 유형을. (그리고 보통 한정자가 없어야합니다.)operator=
특수 멤버 함수로 오버로드 하려면 복사 / 이동 생성자의 첫 번째 매개 변수와 유사한 참조 유형이 필요합니다.++
에는 더미가 필요합니다 int
.unique_ptr
및 같은 스마트 포인터 shared_ptr
(또는 불투명 한 경우 직접 만든자가 포인터)를 사용하십시오.std::optional
raw 포인터 대신와 같은 래퍼를 사용하십시오 .observer_ptr
Library Fundamental TS 와 같이 더 적절한 것이 있습니다 .현재 언어에서는 예외를 해결할 수 없습니다.
operator new
. (단, 이력서는 - void*
당신이 일부 비 부합하는 확장에 의존하지 않는 예기치 않은 포인터를 arithmetics 배제하기 때문에 여전히 매우 다른 및 일반 객체 포인터에 비해 안전 void*
GNU의 같은 있습니다.)따라서 실제로 대답은 매우 분명합니다. 의심스러운 경우 포인터를 피하십시오 . 다른 것이 더 적절하지 않은 명백한 이유가있는 경우에만 포인터를 사용해야합니다. 위에서 언급 한 몇 가지 예외적 인 경우를 제외하고, 그러한 선택은 거의 항상 C ++에만 국한되지는 않습니다 (그러나 언어 구현에 따라 다를 수 있음). 이러한 인스턴스는 다음과 같습니다.
일부 Google 검색 결과 (C ++에 국한되지 않음) 를 통해 질문 이 표시되면 잘못된 위치 일 가능성이 큽니다.
C ++의 참조는 본질적으로 일류가 아니기 때문에 상당히 "홀수"입니다. 그것들은 참조되는 객체 또는 함수로 취급 되므로 왼쪽 피연산자와 같은 일류 연산을 지원할 기회가 없습니다 . 참조 된 객체의 유형에 독립적으로 멤버 액세스 연산자 . 다른 언어는 참조에 대해 유사한 제한이 있거나 없을 수 있습니다.
C ++의 참조는 다른 언어의 의미를 보존하지 않을 것입니다. 예를 들어, 일반적으로 참조는 C ++에서와 같은 값에 대해 null이 아닌 속성을 의미하지 않으므로 이러한 가정은 다른 언어에서는 작동하지 않을 수 있습니다 (예 : Java, C #, ...).
일반적으로 다른 프로그래밍 언어로 된 참조 사이에는 공통 속성이 여전히있을 수 있지만 SO에서 다른 질문으로 남겨 두겠습니다.
(참고 : ALGOL 68 vs. PL / I와 같은 "C-like"언어가 포함 된 것보다 문제가 더 빠를 수 있습니다 .)
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;
}
내가 언급하지 않은 포인터와 참조 사이에는 근본적인 차이점이 있습니다. 참조는 함수 인수에서 참조 별 통과 의미를 활성화합니다. 처음에는 보이지 않지만 포인터는 값별 의미 만 제공합니다. 이것은 이 기사 에서 아주 잘 설명되었습니다 .
감사합니다. & rzej
혼란에 추가 할 위험이 있으므로 입력을 던지고 싶습니다. 컴파일러가 참조를 구현하는 방법에 달려 있지만 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? 글쎄, 분명히 잘 컴파일되지만 런타임에 더 이상 유효한 변수를 가리 키지 않기 때문에 세그먼트 화 오류가 발생합니다. 우리는 본질적으로 여전히 존재하지만 (범위를 벗어나지 않을 때까지) 깨진 참조를 가지고 있습니다.
다시 말해, 참조는 포인터 메커니즘을 추상화 한 포인터 일 뿐이며, 안전하고 사용하기 쉬워집니다 (실수로 포인터 연산, '.'와 '->'등을 섞지 않음 등). 위의 예제와 같이 말도 안됩니다.)
이제 컴파일러가 참조를 처리하는 방법에 관계없이 참조 는 예상대로 작동하기 위해 특정 메모리 주소에서 특정 변수를 참조 해야 하므로 항상 일종의 포인터를 갖습니다. '참조'라는 용어).
참조로 기억해야 할 유일한 주요 규칙은 선언시 정의해야한다는 것입니다 (헤더의 참조를 제외하고는 헤더에 포함 된 객체가 그것을 정의하기에는 너무 늦었습니다.)
위의 예는 단지 참조가 무엇인지 보여주는 예일뿐입니다. 그런 식으로 참조를 사용하고 싶지 않을 것입니다! 참조를 올바르게 사용하려면 여기에 이미 머리에 못을 박는 많은 답변이 있습니다.
또한 인라인 된 함수에 대한 매개 변수 인 참조는 포인터와 다르게 처리 될 수 있습니다.
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을 인라인 할 때 많은 컴파일러가 실제로 메모리에 쓰기를 강제합니다 (주소를 명시 적으로 사용함). 그러나 그들은 더 최적의 레지스터에 참조를 남길 것입니다.
물론 인라인되지 않은 함수의 경우 포인터와 참조가 동일한 코드를 생성하므로 함수에 의해 수정 및 반환되지 않은 경우 참조보다 값으로 내장 함수를 전달하는 것이 좋습니다.
참조의 또 다른 흥미로운 용도는 사용자 정의 유형의 기본 인수를 제공하는 것입니다.
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 참조를 임시에 대한 참조'참조를 사용합니다.
이 프로그램은 질문에 대한 답변을 이해하는 데 도움이 될 수 있습니다. 이것은 참조 "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
나는 여기에서 다루지 않은 또 다른 요점이 있다고 생각합니다.
포인터와 달리 참조는 참조 하는 객체 와 구문 적으로 동일 합니다. 즉, 객체에 적용 할 수있는 모든 작업은 참조를 위해 작동하며 정확히 동일한 구문을 사용합니다 (물론 초기화는 예외입니다).
이것은 피상적 인 것처럼 보일 수 있지만이 속성은 여러 C ++ 기능에 중요합니다.
템플릿 . 템플릿 매개 변수가 오리 입력 때문에, 형태의 구문 특성은 문제가 너무 자주 같은 템플릿을 모두 사용할 수있는 모든 것입니다 T
및 T&
.
(또는 std::reference_wrapper<T>
어느 여전히에 대한 암시 적 캐스트에 의존 T&
)는
그 커버를 서식 파일 모두 T&
와 T&&
더 일반적이다.
L 값 . 문 고려 str[0] = 'X';
그것은 단지 C-문자열 일 것이다 참조 없음을 ( char* str
). 문자를 참조로 리턴하면 사용자 정의 클래스가 동일한 표기법을 가질 수 있습니다.
생성자를 복사 합니다. 문법적으로는 객체에 대한 포인터가 아닌 생성자를 복사하기 위해 객체를 전달하는 것이 좋습니다. 그러나 복사 생성자가 값으로 객체를 가져갈 수있는 방법은 없습니다. 동일한 복사 생성자를 재귀 적으로 호출하게됩니다. 여기서는 참조가 유일한 옵션으로 남습니다.
운영자 과부하 . 참조를 사용 operator+(const T& a, const T& b)
하면 동일한 호출 표기법을 유지하면서 운영자 호출에 대한 간접 참조를 도입 할 수 있습니다 . 이는 정기적으로 오버로드 된 기능에도 적용됩니다.
이러한 요점은 C ++ 및 표준 라이브러리의 상당 부분을 강화하므로 참조의 주요 특성입니다.
포인터와 참조 사이에는 기술적으로 다른 중요한 차이점이 있습니다. 포인터로 함수에 전달 된 인수는 비 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 ++ 스타일 가이드 라인 이 그 예입니다.
포인터는 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 "="
const int& i = 0
.
차이점은 프로그램 실행 중 일정하지 않은 포인터 변수 (상수에 대한 포인터와 혼동하지 않아야 함)가 언젠가 변경 될 수 있으며 포인터 시맨틱을 사용해야하며 (&, *) 연산자가 필요하지만 초기화시 참조를 설정할 수 있다는 것입니다 만 (그래서 생성자 이니셜 라이저 목록에서만 설정할 수 있지만 다른 방법으로는 할 수는 없습니다) 일반 값 액세스 시맨틱을 사용하십시오. 아주 오래된 책에서 읽은 것처럼 연산자 오버로드를 지원하기 위해 기본적으로 참조가 도입되었습니다. 이 스레드에서 누군가가 언급했듯이 포인터는 0 또는 원하는 값으로 설정할 수 있습니다. 0 (NULL, nullptr)은 포인터가 아무것도 초기화되지 않았 음을 의미합니다. 널 포인터를 역 참조하는 것은 오류입니다. 그러나 실제로 포인터에는 올바른 메모리 위치를 가리 키지 않는 값이 포함될 수 있습니다. 차례로 참조는 올바른 유형의 rvalue를 항상 제공한다는 사실 때문에 사용자가 참조 할 수없는 것에 대한 참조를 초기화하지 못하게합니다. 참조 변수를 잘못된 메모리 위치로 초기화하는 방법에는 여러 가지가 있지만 세부 사항을 자세히 살펴 보지 않는 것이 좋습니다. 기계 수준에서 포인터와 참조는 포인터를 통해 균일하게 작동합니다. 필수 참고 문헌에서 구문 설탕이라고 가정 해 봅시다. rvalue 참조는 이것과 다릅니다. 자연스럽게 스택 / 힙 객체입니다. 참조 변수를 잘못된 메모리 위치로 초기화하는 방법에는 여러 가지가 있지만 세부 사항을 자세히 다루지 않는 것이 좋습니다. 기계 수준에서 포인터와 참조는 포인터를 통해 균일하게 작동합니다. 필수 참고 문헌에서 구문 설탕이라고 가정 해 봅시다. rvalue 참조는 이것과 다릅니다. 자연스럽게 스택 / 힙 객체입니다. 참조 변수를 잘못된 메모리 위치로 초기화하는 방법에는 여러 가지가 있지만 세부 사항을 자세히 다루지 않는 것이 좋습니다. 기계 수준에서 포인터와 참조는 포인터를 통해 균일하게 작동합니다. 필수 참고 문헌에서 구문 설탕이라고 가정 해 봅시다. rvalue 참조는 이것과 다릅니다. 자연스럽게 스택 / 힙 객체입니다.