为什么指针不能转换为引用?

时间:2013-05-06 00:45:37

标签: c++ pointers reference

我在多个来源中读到,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慢吗? 引用和指针在编译期间是否真的产生相同的代码?

6 个答案:

答案 0 :(得分:9)

引用可以实现在引擎盖下的指针是无关紧要的。许多编程概念可以根据其他事情来实现。您也可以在whilewhile方面实施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,则无法获得这些保证。

引用和指针的行为非常相似,除了以下内容:

  • 指针可以指向null,而引用不能指向null
  • 引用可以引用没有地址的值(例如存储在寄存器中的值)。指针必须始终存储在内存中,这可能会更慢。

他们是否可以编写C ++,您可以自动将指针转换为引用?当然。他们没有真正的技术原因。但该语言的作者认为它提供的问题多于解决方案。

有一句古老的格言,“当你把所有你想到的东西都放进去的时候,语言就不完整了。当你把所有的东西拿出来时,语言就完整了。”很多人会认为C ++有太多的包袱可以忠实于这句格言,但它仍然会尽力而为。