C ++中的指针变量和引用变量之间有什么区别?

c++ pointers reference c++-faq

929893 观看

30回复

31384 作者的声誉

我知道引用是语法糖,因此代码更易于读写。

但是有什么区别呢?


以下答案和链接的摘要:

  1. 可以多次分配指针,而绑定后不能重新分配引用。
  2. 指针不能指向任何地方(NULL),而引用始终指向对象。
  3. 您不能像使用指针那样获取引用的地址。
  4. 没有“引用算术”(但是您可以获取引用指向的对象的地址,然后像中那样对它进行指针算术&obj + 5)。

为了澄清一个误解:

C ++标准非常小心,以避免指出编译器如何实现引用,但是每个C ++编译器都将引用实现为指针。也就是说,这样的声明:

int &ri = i;

如果尚未完全优化,则分配与指针相同的存储量,并将其地址i放入该存储中。

因此,指针和引用都使用相同数量的内存。

作为基本规则,

  • 在函数参数和返回类型中使用引用,以提供有用的自记录接口。
  • 使用指针来实现算法和数据结构。

有趣的读物:

作者: prakash 的来源 发布者: 2008 年 9 月 11 日

回应 30


117

18647 作者的声誉

除了语法糖,参考是一个const指针(指向一个const)。您必须在声明引用变量时建立它所引用的内容,并且以后不能更改它。

更新:现在我考虑了更多,有一个重要的区别。

可以通过获取其地址并使用const强制替换const指针的目标。

引用目标不能用UB以外的任何方式替换。

这应该允许编译器对参考进行更多优化。

作者: Arkadiy 发布者: 2008 年 9 月 11 日

1587

264992 作者的声誉

  1. 可以重新分配一个指针:

    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;
    
  2. 指针在堆栈上有其自己的内存地址和大小(x86上为4字节),而引用共享相同的内存地址(带有原始变量),但也占用了堆栈上的一些空间。由于引用具有与原始变量本身相同的地址,因此可以将引用视为同一变量的另一个名称。注意:指针指向的内容可以在堆栈或堆上。同上一个参考。我在此声明中的主张不是指针必须指向堆栈。指针只是保存内存地址的变量。此变量在堆栈上。由于引用在堆栈上有自己的空间,并且地址与引用的变量相同。堆栈与堆的更多信息。这意味着编译器不会告诉您引用的真实地址。

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. 您可以具有指向提供额外级别间接功能的指针。而引用仅提供一种间接级别。

    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);
    
  4. 指针可以直接分配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
    
  5. 指针可以遍历数组,您可以++用来转到指针所指向的下一个项目以及+ 4第五元素。无论指针指向的对象大小是多少。

  6. 指针需要取消引用*才能访问其指向的内存位置,而引用可以直接使用。指向类/结构的指针用于->访问其成员,而引用则使用.

  7. 指针是保存内存地址的变量。无论引用如何实现,引用都与其引用的项具有相同的内存地址。

  8. 引用不能塞入数组,而指针可以(由用户@litb提及)

  9. 常量引用可以绑定到临时对象。指针不能(并非没有间接):

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.
    

    这样可以const&更安全地在参数列表等中使用。

作者: Brian R. Bondy 发布者: 2008 年 9 月 11 日

39

0 作者的声誉

引用永远不可能NULL

作者: RichS 发布者: 2008 年 9 月 11 日

117

234047 作者的声誉

与流行观点相反,可能有一个为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);

我要重申的是,获取空引用的唯一方法是通过格式错误的代码,一旦获得该引用,您将获得未定义的行为。它从来没有有意义的检查空参考; 例如,您可以尝试,if(&bar==NULL)...但是编译器可能会优化该语句而不存在!有效引用永远不能为NULL,因此从编译器的角度来看,比较始终为false,可以随意将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 试图将代码移植到另一个平台时公开未定义的行为

作者: Mark Ransom 发布者: 2008 年 9 月 11 日

174

31302 作者的声誉

如果您想成为真正的书呆子,则可以使用引用执行某项操作,而不能使用指针进行引用:延长临时对象的寿命。在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它,它将无法编译。您不能将非常量引用绑定到临时对象,也不能使用它的地址。

作者: Matt Price 发布者: 2008 年 9 月 11 日

109

88189 作者的声誉

您忘记了最重要的部分:

成员访问与指针一起使用->
成员访问与引用一起使用.

foo.bar显然优于foo->bar以同样的方式,VI显然优于Emacs的 :-)

作者: Orion Edwards 发布者: 2008 年 9 月 11 日

15

6545 作者的声誉

I use references unless I need either of these:

  • Null pointers can be used as a sentinel value, often a cheap way to avoid function overloading or use of a bool.

  • You can do arithmetic on a pointer. For example, p += offset;

作者: Aardvark 发布者: 2008 年 9 月 12 日

10

6377 作者的声誉

Another interesting use of references is to supply a default argument of a user-defined type:

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;
}

The default flavor uses the 'bind const reference to a temporary' aspect of references.

作者: Don Wakefield 发布者: 2008 年 9 月 12 日

18

45925 作者的声誉

It doesn't matter how much space it takes up since you can't actually see any side effect (without executing code) of whatever space it would take up.

On the other hand, one major difference between references and pointers is that temporaries assigned to const references live until the const reference goes out of scope.

For example:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

will print:

in scope
scope_test done!

This is the language mechanism that allows ScopeGuard to work.

作者: MSN 发布者: 2008 年 9 月 12 日

63

28942 作者的声誉

Actually, a reference is not really like a pointer.

编译器保留对变量的“引用”,将名称与内存地址相关联。这是在编译时将任何变量名转换为内存地址的工作。

创建引用时,仅告诉编译器您已为指针变量分配了另一个名称。这就是为什么引用不能“指向null”的原因,因为变量不能是,也不能是。

指针是变量;它们包含其他一些变量的地址,或者可以为null。重要的是,指针具有一个值,而引用仅具有它所引用的变量。

现在对真实代码进行一些解释:

int a = 0;
int& b = a;

在这里,您没有创建另一个指向的变量a;您只是将另一个名称添加到具有值的内存内容中a。该内存现在有两个名称,ab,并且可以使用任一名称进行寻址。

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

调用函数时,编译器通常会为要复制到的参数生成内存空间。函数签名定义应创建的空间,并提供应用于这些空间的名称。将参数声明为引用只是告诉编译器在方法调用期间使用输入变量存储空间,而不是分配新的存储空间。说您的函数将直接操作在调用范围中声明的变量似乎有些奇怪,但是请记住,在执行编译的代码时,不再有范围。只有普通的平面内存,您的功能代码可以操纵任何变量。

现在,在某些情况下,例如在使用extern变量时,编译器可能无法知道引用。因此,引用可以或可以不被实现为基础代码中的指针。但是在我给您的示例中,很可能不会使用指针来实现它。

作者: Vincent Robert 发布者: 2008 年 9 月 19 日

354

135160 作者的声誉

什么是C ++参考(适用于C程序员

一个参考,可以看作是一个常量指针自动间接(不要用一个指针指向一个恒定值混淆!),即编译器将应用*运营商为您服务。

必须使用非null值初始化所有引用,否则编译将失败。既不可能获得引用的地址-地址运算符将返回引用值的地址-也不可能对引用进行算术运算。

C程序员可能不喜欢C ++引用,因为在发生间接调用或参数通过值或指针传递而无需查看函数签名的情况下,它将不再显而易见。

C ++程序员可能会不喜欢使用指针,因为它们被认为是不安全的-尽管除了在大多数琐碎的情况下,引用实际上并没有比常量指针更安全-缺少自动间接的便利,并且具有不同的语义含义。

请考虑以下C ++常见问题解答

即使参考使用底层汇编语言的地址经常被实现,请不要认为引用作为好笑的看着指针指向的对象。引用对象。它不是指向对象的指针,也不是对象的副本。它对象。

但是,如果引用确实是对象,那么怎么会有悬挂的引用呢?在非托管语言中,引用不可能比指针更“安全”-通常只有一种方法才能可靠地跨作用域边界使用别名!

为什么我认为C ++引用有用

来自C背景,C ++引用可能看起来有点愚蠢,但是在可能的情况下,仍然应该使用它们而不是指针:自动间接访问方便,并且在处理RAII时引用特别有用-但不是因为任何可察觉的安全性优势,而是因为它们使编写惯用代码变得不那么尴尬。

RAII是C ++的核心概念之一,但是它与复制语义非常重要地交互。通过引用传递对象避免了这些问题,因为不涉及复制。如果在语言中没有引用,则必须使用指针,因为指针使用起来比较麻烦,因此违反了语言设计原则,即最佳实践解决方案应该比替代方法更容易。

作者: Christoph 发布者: 2009 年 2 月 27 日

11

5522 作者的声誉

Also, a reference that is a parameter to a function that is inlined may be handled differently than a pointer.

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

Many compilers when inlining the pointer version one will actually force a write to memory (we are taking the address explicitly). However, they will leave the reference in a register which is more optimal.

Of course, for functions that are not inlined the pointer and reference generate the same code and it's always better to pass intrinsics by value than by reference if they are not modified and returned by the function.

作者: Adisak 发布者: 2009 年 10 月 15 日

12

17360 作者的声誉

Another difference is that you can have pointers to a void type (and it means pointer to anything) but references to void are forbidden.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

I can't say I'm really happy with this particular difference. I would much prefer it would be allowed with the meaning reference to anything with an address and otherwise the same behavior for references. It would allow to define some equivalents of C library functions like memcpy using references.

作者: kriss 发布者: 2010 年 1 月 29 日

33

967 作者的声誉

虽然引用和指针都用于间接访问另一个值,但是引用和指针之间有两个重要区别。首先是引用始终引用对象:定义引用而不初始化它是错误的。分配的行为是第二​​个重要区别:分配给引用会更改引用所绑定到的对象;它不会将引用重新绑定到另一个对象。初始化后,引用始终引用相同的基础对象。

考虑这两个程序片段。首先,我们将一个指针分配给另一个指针:

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

此分配更改ival,即ri引用的值,而不是引用本身。分配后,两个引用仍然引用其原始对象,并且这些对象的值现在也相同。

作者: Kunal Vyas 发布者: 2011 年 5 月 20 日

14

3887 作者的声誉

指针和引用之间有一个根本的区别,我没有看到有人提到过:引用在函数参数中启用了按引用传递语义。指针虽然一开始不可见,但它却没有:它们仅提供按值传递语义。这已经非常漂亮描述这篇文章

问候,&rzej

作者: Andrzej 发布者: 2012 年 2 月 6 日

24

1565 作者的声誉

A reference is an alias for another variable whereas a pointer holds the memory address of a variable. References are generally used as function parameters so that the passed object is not the copy but the object itself.

    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. 
作者: fatma.ekici 发布者: 2013 年 1 月 1 日

18

349 作者的声誉

A reference is not another name given to some memory. It's a immutable pointer that is automatically de-referenced on usage. Basically it boils down to:

int& j = i;

It internally becomes

int* const j = &i;
作者: tanweer alam 发布者: 2013 年 2 月 26 日

10

612 作者的声誉

This program might help in comprehending the answer of the question. This is a simple program of a reference "j" and a pointer "ptr" pointing to variable "x".

#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

作者: Arlene Batada 发布者: 2013 年 3 月 15 日

65

6292 作者的声誉

引用与指针非常相似,但是它们经过专门设计,有助于优化编译器。

  • 设计引用时,使编译器更容易跟踪哪些引用别名哪些变量。两个主要功能非常重要:没有“引用算术”和没有重新分配引用。这些允许编译器找出哪些引用在编译时别名哪些变量。
  • 引用可以引用没有内存地址的变量,例如编译器选择放入寄存器的变量。如果使用局部变量的地址,则编译器很难将其放入寄存器中。

举个例子:

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
}

为了进行这样的优化,它需要证明在调用过程中什么都不能改变array [1]。这很容易做到。我从不小于2,所以array [i]永远不能引用array [1]。mayModify()被赋予a0作为参考(别名为array [0])。因为没有“引用”算法,所以编译器只需要证明maynyModify永远不会获得x的地址,并且证明没有任何改变array [1]。

It also has to prove that there are no ways a future call could read/write a[0] while we have a temporary register copy of it in a0. This is often trivial to prove, because in many cases it is obvious that the reference is never stored in a permanent structure like a class instance.

Now do the same thing with pointers

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];
    }
}

The behavior is the same; only now it is much harder to prove that maybeModify does not ever modify array[1], because we already gave it a pointer; the cat is out of the bag. Now it has to do the much more difficult proof: a static analysis of maybeModify to prove it never writes to &x + 1. It also has to prove that it never saves off a pointer that can refer to array[0], which is just as tricky.

Modern compilers are getting better and better at static analysis, but it is always nice to help them out and use references.

Of course, barring such clever optimizations, compilers will indeed turn references into pointers when needed.

EDIT: Five years after posting this answer, I found an actual technical difference where references are different than just a different way of looking at the same addressing concept. References can modify the lifespan of temporary objects in a way that pointers cannot.

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

Normally temporary objects such as the one created by the call to createF(5) are destroyed at the end of the expression. However, by binding that object to a reference, ref, C++ will extend the lifespan of that temporary object until ref goes out of scope.

作者: Cort Ammon 发布者: 2013 年 9 月 1 日

18

359 作者的声誉

This is based on the tutorial. What is written makes it more clear:

>>> 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)

Simply to remember that,

>>> 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.)

What's more, as we can refer to almost any pointer tutorial, a pointer is an object that is supported by pointer arithmetic which makes pointer similar to an array.

Look at the following statement,

int Tom(0);
int & alias_Tom = Tom;

alias_Tom can be understood as an alias of a variable (different with typedef, which is alias of a type) Tom. It is also OK to forget the terminology of such statement is to create a reference of Tom.

作者: Life 发布者: 2014 年 1 月 13 日

13

279 作者的声誉

冒着增加混乱的风险,我想输入一些信息,我确定它主要取决于编译器如何实现引用,但是在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

Therefore a reference IS a pointer under the hood, they both are just storing a memory address, where the address is pointing to is irrelevant, what do you think would happen if I called std::cout << str_ref; AFTER calling delete &str_ref? Well, obviously it compiles fine, but causes a segmentation fault at runtime because it's no longer pointing at a valid variable, we essentially have a broken reference that still exists (until it falls out of scope), but is useless.

In other words, a reference is nothing but a pointer that has the pointer mechanics abstracted away, making it safer and easier to use (no accidental pointer math, no mixing up '.' and '->', etc.), assuming you don't try any nonsense like my examples above ;)

Now regardless of how a compiler handles references, it will always have some kind of pointer under the hood, because a reference must refer to a specific variable at a specific memory address for it to work as expected, there is no getting around this (hence the term 'reference').

The only major rule that's important to remember with references is that they must be defined at the time of declaration (with the exception of a reference in a header, in that case it must be defined in the constructor, after the object it's contained in is constructed it's too late to define it).

Remember, my examples above are just that, examples demonstrating what a reference is, you would never want to use a reference in those ways! For proper usage of a reference there are plenty of answers on here already that hit the nail on the head

作者: Tory 发布者: 2014 年 10 月 14 日

28

316898 作者的声誉

如果您不熟悉以抽象的甚至学术的方式学习计算机语言,则语义上的差异可能会显得深奥。

在最高级别上,引用的想法是它们是透明的“别名”。您的计算机可能使用一个地址来使它们工作,但您不必为此担心:您应该将它们视为现有对象的“只是另一个名称”,并且语法反映了这一点。它们比指针更严格,因此与将要创建悬挂指针时相比,编译器可以在要创建悬挂参考时更可靠地警告您。

除此之外,指针和引用之间当然还有一些实际的区别。使用它们的语法显然是不同的,并且您不能“重新安置”引用,不能引用虚无或具有指向引用的指针。

作者: Lightness Races in Orbit 发布者: 2014 年 10 月 29 日

8

2029 作者的声誉

也许有些隐喻会有所帮助。在桌面屏幕空间的上下文中-

  • 参考要求您指定一个实际的窗口。
  • 指针需要在屏幕上留出一定空间,以确保它包含零个或多个该窗口类型的实例。
作者: George R 发布者: 2014 年 12 月 27 日

15

7186 作者的声誉

在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;
}
作者: Destructor 发布者: 2015 年 2 月 9 日

5

449 作者的声誉

不同之处在于,非常量指针变量(不要与指向常量的指针混淆)可以在程序执行期间的某个时间更改,需要使用指针语义(&,*)运算符,而引用可以在初始化时设置仅(这就是为什么您只能在构造函数初始值设定项列表中设置它们,而不能以其他方式设置它们)并使用普通的值访问语义。基本上引入了引用,以支持操作员重载,就像我在一本非常古老的书中所读到的那样。正如该线程中有人指出的那样-指针可以设置为0或任何您想要的值。0(NULL,nullptr)表示指针不进行任何初始化。取消引用空指针是错误的。但是实际上指针可能包含不指向某个正确内存位置的值。反过来,引用也尝试不允许用户初始化对无法引用的内容的引用,因为您总是向其提供正确类型的右值。尽管有很多方法可以将引用变量初始化为错误的内存位置-最好不要将其深入了解细节。在机器级别,指针和引用均通过指针统一工作。假设在必不可少的参考文献中是句法糖。右值引用与此不同-它们自然是堆栈/堆对象。尽管有很多方法可以将引用变量初始化为错误的内存位置-最好不要将其深入了解细节。在机器级别,指针和引用均通过指针统一工作。假设在必不可少的参考文献中是句法糖。右值引用与此不同-它们自然是堆栈/堆对象。尽管有很多方法可以将引用变量初始化为错误的内存位置-最好不要将其深入了解细节。在机器级别,指针和引用均通过指针统一工作。假设在基本参考文献中是语法糖。右值引用与此不同-它们自然是堆栈/堆对象。

作者: Zorgiev 发布者: 2016 年 4 月 24 日

6

128 作者的声誉

指针和参考之间的区别

指针可以初始化为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 "="
作者: dhokar.w 发布者: 2017 年 1 月 6 日

9

2583 作者的声誉

我觉得这里还没有提到另一点。

与指针不同,引用在语法上等同于它们引用的对象,即,可以应用于对象的任何操作都可以使用引用进行操作,并且语法完全相同(当然是初始化)。

尽管这可能看起来很肤浅,但我相信此属性对于许多C ++功能至关重要,例如:

  • 模板。由于模板参数是鸭子类型的,因此类型的语法属性就很重要,因此通常同一模板可以与T和一起使用T&
    (或std::reference_wrapper<T>仍然依靠隐式转换到T&
    ,覆盖这两个模板T&T&&甚至更常见。

  • 左值。考虑一下str[0] = 'X';没有引用的语句,该语句仅适用于c字符串(char* str)。通过引用返回字符允许用户定义的类具有相同的符号。

  • 复制构造函数。从语法上讲,将对象传递给复制构造函数是有意义的,而不是将指针传递给对象。但是复制构造函数无法按值获取对象-它将导致对同一复制构造函数的递归调用。这将引用作为唯一的选择。

  • 操作员重载。通过引用,可以将间接引入到操作员调用中,例如,operator+(const T& a, const T& b)同时保留相同的中缀表示法。这也适用于常规的重载函数。

这些要点使C ++和标准库有相当大的一部分,因此这是引用的主要属性。

作者: Ap31 发布者: 2017 年 7 月 6 日

2

356 作者的声誉

我总是决定从C ++核心指导原则:

当“无参数”是有效选项时,将T *优先于T&

作者: Hitokage 发布者: 2017 年 10 月 19 日

8

3286 作者的声誉

指针和引用之间存在非常重要的非技术区别:通过指针传递给函数的参数比通过非常量引用传递给函数的参数更明显。例如:

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,即使函数从不期望a,也应该始终将指针参数用于可修改的参数而不是非引用nullptr。就是说,那些人争辩fn3()说不应该像上面那样使用函数签名。Google的C ++风格指南就是一个例子。

作者: Arthur Tacca 发布者: 2017 年 11 月 2 日

14

1153 作者的声誉

直接答案

C ++中的参考是什么?类型的某些特定实例不是对象类型

C ++中的指针是什么?类型的某些特定实例是对象类型

根据对象类型的ISO C ++定义

An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and not cv void.

It may be important to know, object type is a top-level category of the type universe in C++. Reference is also a top-level category. But pointer is not.

Pointers and references are mentioned together in the context of compound type. This is basically due to the nature of the declarator syntax inherited from (and extended) C, which has no references. (Besides, there are more than one kind of declarator of references since C++ 11, while pointers are still "unityped": &+&& vs. *.) So drafting a language specific by "extension" with similar style of C in this context is somewhat reasonable. (I will still argue that the syntax of declarators wastes the syntactic expressiveness a lot, makes both human users and implementations frustrating. Thus, all of them are not qualified to be built-in in a new language design. This is a totally different topic about PL design, though.)

Otherwise, it is insignificant that pointers can be qualified as a specific sorts of types with references together. They simply share too few common properties besides the syntax similarity, so there is no need to put them together in most cases.

Note the statements above only mentions "pointers" and "references" as types. There are some interested questions about their instances (like variables). There also come too many misconceptions.

The differences of the top-level categories can already reveal many concrete differences not tied to pointers directly:

  • Object types can have top-level cv qualifiers. References cannot.
  • Variable of object types do occupy storage as per the abstract machine semantics. Reference do not necessary occupy storage (see the section about misconceptions below for details).
  • ...

A few more special rules on references:

  • Compound declarators are more restrictive on references.
  • References can collapse.
    • Special rules on && parameters (as the "forwarding references") based on reference collapsing during template parameter deduction allow "perfect forwarding" of parameters.
  • References have special rules in initialization. The lifetime of variable declared as a reference type can be different to ordinary objects via extension.
    • BTW, a few other contexts like initialization involving std::initializer_list follows some similar rules of reference lifetime extension. It is another can of worms.
  • ...

The misconceptions

Syntactic sugar

I know references are syntactic sugar, so code is easier to read and write.

Technically, this is plain wrong. References are not syntactic sugar of any other features in C++, because they cannot be exactly replaced by other features without any semantic differences.

(Similarly, lambda-expressions are not syntactic sugar of any other features in C++ because it cannot be precisely simulated with "unspecified" properties like the declaration order of the captured variables, which may be important because the initialization order of such variables can be significant.)

C++ only has a few kinds of syntactic sugars in this strict sense. One instance is (inherited from C) the built-in (non-overloaded) operator [], which is defined exactly having same semantic properties of specific forms of combination over built-in operator unary * and binary +.

Storage

So, a pointer and a reference both use the same amount of memory.

The statement above is simply wrong. To avoid such misconceptions, look at the ISO C++ rules instead:

From [intro.object]/1:

... An object occupies a region of storage in its period of construction, throughout its lifetime, and in its period of destruction. ...

From [dcl.ref]/4:

It is unspecified whether or not a reference requires storage.

Note these are semantic properties.

Pragmatics

Even that pointers are not qualified enough to be put together with references in the sense of the language design, there are still some arguments making it debatable to make choice between them in some other contexts, for example, when making choices on parameter types.

But this is not the whole story. I mean, there are more things than pointers vs references you have to consider.

如果您不必坚持这种过度特定的选择,那么在大多数情况下,答案很短:您不必使用指针,而不必使用指针。指针通常很糟糕,因为它们暗示了太多您不期望的事情,并且它们将依赖太多隐式假设,从而破坏了代码的可维护性和(甚至)可移植性。不必要地依赖指针绝对是一种不好的样式,从现代C ++的意义上应该避免这种情况。重新考虑您的目的,您最终会发现在大多数情况下指针是最后一种功能

  • 有时,语言规则明确要求使用特定类型。如果要使用这些功能,请遵守规则。
    • 拷贝构造函数需要特定类型的简历 - &引用类型作为第一个参数的类型。(通常应该是const合格的。)
    • 移动构造函数需要特定类型的简历 - &&引用类型作为第一个参数的类型。(通常应该没有限定符。)
    • 运算符的特定重载需要引用或非引用类型。例如:
      • operator=作为特殊成员函数重载时,需要引用类型类似于copy / move构造函数的第一个参数。
      • 后缀++需要虚拟int
      • ...
  • 如果您知道值传递(即使用非引用类型)就足够了,请直接使用它,尤其是在使用支持C ++ 17强制复制省略的实现时。(警告:但是,要详尽地论证这种必要性可能会非常复杂。)
  • 如果要使用所有权来处理某些句柄,请使用unique_ptrshared_ptr,或者(如果要求它们不透明,也可以单独使用自制的)智能指针,而不要使用原始指针。
  • 如果您要在某个范围内进行某些迭代,请使用迭代器(或标准库尚未提供的某些范围),而不要使用原始指针,除非您确信原始指针在非常特定的情况下会做得更好(例如,减少标头依赖性)案件。
  • 如果您知道传递值就足够了,并且想要一些显式的可为空的语义,请使用wrapper之类的std::optional,而不要使用原始指针。
  • 如果您知道由于上述原因传递值不理想,并且您不希望使用可为空的语义,请使用{lvalue,rvalue,forwarding} -references。
  • 即使您确实想要像传统指针这样的语义,也经常有更合适的东西,例如observer_ptr在Library Fundamental TS中。

唯一的例外无法使用当前语言解决:

  • 当您在上面实现智能指针时,您可能必须处理原始指针。
  • 特定的语言互操作例程需要指针,例如operator new。(然而,CV - void*仍然是相当不同的,比普通的对象指针更安全,因为它排除了意外的指针算术,除非你是依靠一些不符合标准的扩展void*像GNU的。)
  • 可以从lambda表达式转换函数指针而无需捕获,而函数引用则不能。对于这种情况,即使您故意不希望使用可为空的值,也必须在非通用代码中使用函数指针。

因此,在实践中,答案是如此明显:如有疑问,请避免使用指针。仅在出于非常明显的原因没有其他更合适的理由时才需要使用指针。除了上面提到的一些例外情况外,此类选择几乎总是不完全是特定于C ++的(但可能是特定于语言实现的)。这样的实例可以是:

  • 您必须使用旧式(C)API。
  • 您必须满足特定C ++实现的ABI要求。
  • 您必须基于特定实现的假设,在运行时与不同的语言实现(包括各种程序集,语言运行时和某些高级客户端语言的FFI)进行互操作。
  • 在某些极端情况下,您必须提高翻译(编译和链接)的效率。
  • 在某些极端情况下,您必须避免符号膨胀。

语言中立警告

如果您通过某些Google搜索结果(不是特定于C ++)来查看问题,则很可能是错误的位置。

在C ++中引用是很“奇怪”,因为它本质上不是一流的:他们将被视为对对象或功能被称为所以他们就没有机会来支持喜欢成为左操作数的一些一流的操作的成员访问运算符独立于所引用对象的类型。其他语言对其参考文献可能有也可能没有类似的限制。

C ++中的引用可能不会保留不同语言之间的含义。例如,引用通常并不暗示像C ++那样的值具有非null属性,因此此类假设可能不适用于某些其他语言(并且您会很容易找到反例,例如Java,C#,...)。

通常,在不同编程语言中的引用之间仍然存在一些公共属性,但让我们将其留给SO中的其他问题。

(附带说明:这个问题可能比涉及任何“ C样”语言的时间要早​​,例如ALGOL 68 vs. PL / I。

作者: FrankHB 发布者: 2019 年 2 月 17 日
32x32