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

Why are pointers not convertible to references?

本文关键字:引用 转换 不能 指针 为什么      更新时间:2023-10-16

我在多个来源中读到,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?
    ...
}

据我所知,如果我传递一个intFooRef,编译器会从变量的地址为我创建一个指针(引用),但是如果类型已经是一个指针,那么解引用似乎毫无意义。在我看来,就像我对一个指针进行解引用一样,只是为了让编译器从对我来说似乎毫无意义的解引用值创建另一个指针。
仅仅复制指针而不是仅仅引用+区别值不是更简单/更高效吗?(也许这就是真正发生的事情?)

我错过了什么吗?在这种情况下调用FooRef是否比调用FooPointer慢?
引用和指针在编译过程中真的产生相同的代码吗?

引用可以通过指针在底层实现这一事实是无关紧要的。许多编程概念可以根据其他事物来实现。你可能会问,当while可以用gotojmp来实现时,我们为什么要用while循环。不同的语言概念的意义在于使事情对程序员来说更容易,而引用是为方便程序员而设计的语言概念。

你可能误解了引用的目的。引用给了你指针积极的一面(传递起来很便宜),但由于它们和常规值有相同的语义,它们消除了使用指针带来的许多危险(指针算术,空指针等)更重要的是,引用与c++类型系统中的指针是完全不同的类型,允许两者互换是疯狂的(这会破坏引用的目的) 。

引用语法被设计成,目的是反映常规值语义的语法,同时为您提供便宜地传递内存地址的能力,而不是复制整个值。

现在,看看你的例子:

FooRef(*p); // why do I have to dereference the pointer?

必须对指针解引用,因为FooRef接受int的引用,而不是对int*的引用。注意,还可以使用对指针的引用:

void FooPointerRef(const int*&);

接受指针引用的函数允许在函数内部修改指针的内存地址。在您的示例中,必须显式地解引用指向镜像值语义的指针。否则,查看函数调用FooRef(p)的人会认为FooRef要么接受值指针或引用指针-但不是(非指针)值或引用。

通过引用传递的形参的实际实参始终与形参的类型相同,无论是否通过指针解引用获得,也无论如何实现引用传递。

我在多个来源中读到,一个c++参考不过是一个有编译时间限制的指针。

不要相信你读到的一切。

c++中的指针是不同的数据类型。如果c++将强制一个指向X&amp的X*指针;参考,那么下面的代码应该如何表现?

int x=5;
void* px = (void*)&x;
void*& prx = px;

除此之外,事情将会非常奇怪-因为你想要的是沉默解引用,传递NULL将导致调用者代码中的分段,但调用者将没有语法方法看到它。

你会得到什么作为这种困惑的回报?实现细节和语言抽象之间的更多混淆。仅此而已。

这里有很多答案,你自己看看吧:

#include <cstdio>
int main(void) 
{
    int p = 5;
    int *p_ptr = &p;
    int &p_ref = p;

    printf("Address of p     = %xn", &p);
    printf("Address of p_ref = %xn", &p_ref);
    printf("Value   of p_ptr = %xn", p_ptr);
    printf("Address of p_ptr = %xn", &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 ...

关于引用的最好的部分是它们很容易在代码外优化。引用在其生命周期中只能引用一个对象,指针在其生命周期中可以引用许多不同的对象,编译器必须警惕这一事实。

(注意:这显然只是微软编译器产生的汇编,而结果可能因编译器而异,我怀疑大多数编译器都可以完成)

这肯定会产生一些有趣的混淆情况:

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

如果你必须不断地记住是否取消引用,这会让你感到困惑。

另一个原因是它简化了规范。编译器可以假定引用总是指向一个有效的对象。它可以根据这个假设进行优化。它保持这种保证的方式是使遵从空指针成为非法行为。如果在上面的例子中不需要取消对p的引用,那么它就不能得到这些保证。

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

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

他们可以写c++,你可以自动从指针转换到引用吗?确定。没有真正的技术原因可以解释为什么他们不能这样做。但是这门语言的作者认为它带来的问题比解决方案更多。

有句老话说:"当你把你能想到的一切都放进去的时候,一门语言还是不完整的。"当你把所有可能的东西都去掉后,一门语言才算完整。"许多人会认为c++有太多的包袱,不能遵循这句格言,但它仍然尽可能地尝试。