我在多个来源中读到,C ++引用只不过是一个带有编译时间限制的指针。
如果这是真的,为什么我被迫取消引用指针以将其传递给需要参数的函数?
void FooRef(const int&);
void FooPointer(const int*);
int main()
{
int* p = new int(5);
FooPointer(p);
FooRef(*p); // why do I have to dereference the pointer?
...
}
据我了解,如果我要将int
传递给FooRef
,编译器会为我创建变量地址的指针(引用),但是如果类型已经是指针然后解除引用它似乎毫无意义。在我看来,我正在取消引用一个指针,只是让编译器从取消引用的值创建另一个指针,这对我来说似乎毫无意义。
仅复制指针而不仅仅是引用+引用该值是不是更简单/更高效? (也许这真的发生了什么?)
我在这里遗漏了什么吗?并且在这种情况下调用FooRef
比调用FooPointer
慢吗?
引用和指针在编译期间是否真的产生相同的代码?
答案 0 :(得分:9)
引用可以实现在引擎盖下的指针是无关紧要的。许多编程概念可以根据其他事情来实现。您也可以在while
或while
方面实施goto
时,问我们为什么会有jmp
次循环。不同语言概念的要点是为程序员提供更容易,而引用是为方便程序员而设计的语言概念。
您可能误解了引用的目的。引用为你提供指针的正面(便宜传递),但由于它们具有与常规值相同的语义,它们消除了使用指针带来的许多危险:(指针算术,悬空指针等)更多重要的是,引用是一个完全不同的类型而不是C ++类型系统中的指针,如果允许两者可以互换(这会破坏引用的目的,那将是疯狂的。)
参考语法在目的中被设计为镜像常规值语义的语法 - 同时为您提供廉价传递内存地址而不是复制整个值的能力。
现在,转到你的例子:
FooRef(*p); // why do I have to dereference the pointer?
您必须在此处取消引用指针,因为FooRef
将引用带到int
,而不是int*
的引用。请注意,您还可以使用引用指针:
void FooPointerRef(const int*&);
引用指针的函数使您可以从函数内修改指针的内存地址。在您的示例中,您必须显式取消引用镜像值语义的指针。否则,看到函数调用FooRef(p)
的人会认为FooRef
要么是逐个值的指针,要么是逐个引用的指针 - 而不是(非指针)值或者参考
答案 1 :(得分:2)
通过引用传递的参数的实际参数始终与参数的类型相同,无论是否通过解引用指针获取,以及无论如何实现传递引用。
答案 2 :(得分:1)
我在多个来源中读到C ++引用只不过是一个 指针有编译时限制。
不要相信你读过的一切。
答案 3 :(得分:1)
C ++中的指针是不同的数据类型。如果C ++将强制指向X&的X *指针。参考,那么下面的代码应该如何表现?
int x=5;
void* px = (void*)&x;
void*& prx = px;
此外,事情会非常奇怪 - 因为你想要的是静音取消引用,传递NULL将导致调用者代码中的分段,但调用者将没有语法方法来查看它。
你会得到什么以回报这种混乱?实现细节和语言抽象之间存在一些混淆。仅此而已。
答案 4 :(得分:1)
这里有很多答案,只为自己看看:
#include <cstdio>
int main(void)
{
int p = 5;
int *p_ptr = &p;
int &p_ref = p;
printf("Address of p = %x\n", &p);
printf("Address of p_ref = %x\n", &p_ref);
printf("Value of p_ptr = %x\n", p_ptr);
printf("Address of p_ptr = %x\n", &p_ptr);
return 0;
}
输出:
Address of p = 16fd80
Address of p_ref = 16fd80
Value of p_ptr = 16fd80
Address of p_ptr = 16fd88
因此,就引用而言 - 引用与引用的对象具有相同的地址。指针的值是指向(或引用)的对象的地址,同时仍具有自己的单独地址。
当然,生成的内容:(cl /FA reftest.cpp
)
_TEXT SEGMENT
p$ = 32 ; all our variables
p_ptr$ = 40
p_ref$ = 48
main PROC
; ... snip ...
mov DWORD PTR p$[rsp], 5 ; p = 5
lea rax, QWORD PTR p$[rsp] ; p_ptr = &p
mov QWORD PTR p_ptr$[rsp], rax
lea rax, QWORD PTR p$[rsp] ; p_ref = p
mov QWORD PTR p_ref$[rsp], rax
对我来说也一样。
但请考虑一下:(cl /O2 /FA reftest.cpp
)
_TEXT SEGMENT
p$ = 48
p_ptr$ = 56
main PROC ; notice p_ref is gone
; ... snip ...
关于引用的最佳部分是它们很容易在代码之外进行优化。引用只能在其生命周期内引用一个对象,指针可以在其生命周期内引用许多不同的对象,并且编译器必须警惕这一事实。
(注意:这显然只是微软编译器产生的汇编,而结果可能因编译器而异,我怀疑大多数编译器可以将这一点拉下来)
答案 5 :(得分:1)
肯定会产生一些有趣的令人困惑的案例:
int a(int& x) { return x + 1; }
int b(int& x) { return x + 2; }
// now for the fun part, 'b' has another valid overload
int b(int* x) { return *x + 3; }
int myInt = 1;
int* p = &myInt; // get a pointer to myInt
cout << a(p); // calls int a(int&) by your rule. Syntax error in real C++
cout << b(p); // calls int b(int*), by your rule and real C++.
// Isn't that confusing?
cout << a(*p); // valid, always calls int a(int&)
cout << b(*p); // valid. Always calls b(int&)
// This isn't confusing, in real C++ or with your rule
如果你不得不经常记住是否要取消引用,那将会令人困惑。
它的另一个原因是它简化了规范。允许编译器假定引用ALWAYS引用有效对象。它可以基于该假设进行优化。它保持这种保证的方式是使空指针变为非法。如果您不必在上面的示例中取消引用p,则无法获得这些保证。
引用和指针的行为非常相似,除了以下内容:
他们是否可以编写C ++,您可以自动将指针转换为引用?当然。他们没有真正的技术原因。但该语言的作者认为它提供的问题多于解决方案。
有一句古老的格言,“当你把所有你想到的东西都放进去的时候,语言就不完整了。当你把所有的东西拿出来时,语言就完整了。”很多人会认为C ++有太多的包袱可以忠实于这句格言,但它仍然会尽力而为。