0

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

    ...
}

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

我在这里错过了什么吗?在这种情况下打电话FooRef会比打电话慢FooPointer吗?
并且在编译过程中引用和指针真的会产生相同的代码吗?

4

6 回答 6

9

可以根据引擎盖下的指针来实现引用这一事实是无关紧要的。许多编程概念可以根据其他事物来实现。你可能会问为什么我们有while循环什么时候while可以用gotoor来实现jmp。不同语言概念的重点是使程序员更容易做事,而引用是为方便程序员而设计的语言概念。

您可能误解了引用的目的。引用为您提供了指针的积极方面(传递成本低),但由于它们与常规值具有相同的语义,它们消除了使用指针带来的许多危险:(指针算术、悬空指针等)更多重要的是,引用是与 C++ 类型系统中的指针完全不同的类型,如果允许两者互换(这会破坏引用的目的),那将是一种疯狂。

参考语法旨在反映常规值语义的语法 - 同时为您提供廉价传递内存地址而不是复制整个值的能力。

现在,转向您的示例:

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

您必须在此处取消对指针的引用,因为FooRef需要对 an的引用int,而不是对 an 的引用int*。请注意,您还可以引用指针

void FooPointerRef(const int*&);

引用指针的函数使您能够从函数内修改指针的内存地址。在您的示例中,您必须显式取消引用指向镜像值语义的指针。否则,查看函数调用FooRef(p)的人会认为它FooRef要么采用按值的指针,要么采用按引用的指针——但不是(非指针)值或引用。

于 2013-05-06T00:51:43.743 回答
2

通过引用传递的参数的实际参数始终与参数类型相同,无论它是否通过取消引用指针获得,也不管通过引用传递是如何实现的。

于 2013-05-06T00:52:12.070 回答
1

我在多个来源中读到 C++ 引用只不过是一个具有编译时间限制的指针。

不要相信你读到的一切。

于 2013-05-06T00:55:27.770 回答
1

C++ 中的指针是不同的数据类型。如果 C++ 将强制X* 指针指向 X& 引用,那么下面的代码应该如何表现?

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

此外,事情会很奇怪——因为你想要的是静默取消引用,传递 NULL 会导致调用者代码中的分段,但调用者将无法看到它的语法方式。

而你会得到什么来回报这种混乱?实现细节和语言抽象之间的更多混淆。而已。

于 2013-05-06T01:04:10.017 回答
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

如果您必须不断记住是否取消引用,那将是令人困惑的。

另一个原因是它简化了规范。允许编译器假设一个引用总是引用一个有效的对象。它可以根据该假设进行优化。它保持这种保证的方式是使遵循空指针成为非法。如果您不必在上面的示例中取消引用 p,那么它就无法获得这些保证。

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

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

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

有句老话:“把你能想到的一切都放进去,语言是不完整的。当你把所有可能的东西都拿出来时,语言才是完整的。” 许多人会争辩说,C++ 有太多的包袱,无法忠实于这句格言,但它仍然会尽可能地尝试。

于 2013-09-01T04:01:43.907 回答
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 ...

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

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

于 2013-05-06T01:40:41.537 回答