提问



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


但有什么区别?





以下答案和链接摘要:



  1. 指针可以重新分配任意次数,而在绑定后无法重新分配引用。

  2. 指针可以指向任何地方(NULL),而引用总是指对象。

  3. 你不能用指针来获取引用的地址。

  4. 没有参考算术(但是您可以获取引用所指向的对象的地址,并在&obj + 5中对其执行指针算法。)



澄清一个误解:



   C ++标准非常谨慎,以避免规定编译器可能如何
  实现引用,但每个C ++编译器都实现
  引用作为指针。也就是说,声明如下:


int &ri = i;

  
   如果没有完全优化 ,会分配相同数量的存储空间
  作为指针,并放置地址
  i中的i



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


作为基本规则,



  • 使用函数参数和返回类型中的引用来提供有用的自我文档接口。

  • 使用指针实现算法和数据结构。



有趣的读物:



  • 我最喜欢的C ++ FAQ lite。

  • 参考与指针。

  • 参考资料简介。

  • 参考文献和常量。


最佳参考



  1. 可以重新指定指针:[97] [98] [99] [100]


    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转到第5个元素。无论指针指向的对象是什么大小。

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

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

  8. 引用不能填充到数组中,而指针可以是(由用户@litb提及)

  9. Const引用可以绑定到临时对象。指针不能(不是没有一些间接):


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


    这使得const&更安全地用于参数列表等等。


其它参考1


什么是C ++参考( for C programmers )



引用可以被认为是一个常量指针(不要与指向常量值的指针混淆!)和自动间接,即编译器将应用*运营商为您服务。


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


C程序员可能不喜欢C ++引用,因为当间接发生时,或者如果参数通过值或指针传递而不查看函数签名,它将不再是显而易见的。


C ++程序员可能不喜欢使用指针,因为它们被认为是不安全的 - 虽然引用并不比常量指针更安全,除了在最微不足道的情况下 - 缺乏自动间接的便利性并带有不同的语义含义。


请考虑 C ++ FAQ 中的以下声明:[102]



  即使引用通常使用地址中的地址来实现
  基础汇编语言,请不将引用视为一个
  有趣的看指向一个对象。引用是对象。它是
  不是指向对象的指针,也不是对象的副本。它是
  目的。



但是如果引用真的是对象,那么怎么会有悬空引用呢?在非托管语言中,引用不可能比指针更安全 - 通常不是跨范围边界可靠地对值进行别名的方法!


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



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


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

其它参考2


如果你想变得非常迂腐,你可以使用一个你可以用指针做的引用来做一件事:延长临时对象的生命周期。在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的情况下尝试此操作,则无法编译。您不能将非const引用绑定到临时对象,也不能为此处获取其地址。

其它参考3


与流行的观点相反,可能有一个NULL引用。


int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)


当然,使用引用要困难得多 - 但是如果你管理它,你会把你的头发撕掉,试图找到它。在C ++中,引用 本身就是安全的!


从技术上讲,这是无效引用,而不是空引用。 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);


我想重申,获取空引用的唯一方法是通过格式错误的代码,一旦你拥有它,你就会得到未定义的行为。 never 检查空引用是有意义的;例如你可以尝试if(&bar==NULL)...,但编译器可能会优化语句!有效的引用永远不会是NULL,所以从编译器的视图来看,比较总是错误的,并且可以自由地消除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。[104]


有关解除引用空指针的危险的另一个示例,请参阅Raymond Chen尝试将代码移植到另一个平台时暴露未定义的行为。[105]

其它参考4


你忘记了最重要的部分:


使用指针的成员访问使用->

带引用的成员访问使用.


foo.bar 明显优于foo->bar,就像vi 明显优于Emacs一样: - )[106] [107]]]

其它参考5


除了语法糖之外,引用是const指针(不是指向const的指针)。您必须在声明引用变量时确定它所引用的内容,并且以后不能更改它。


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


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


参考的目标不能以任何方式替换UB。


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

其它参考6


实际上,引用并不像指针。


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


创建引用时,只告诉编译器为指针变量指定另一个名称;这就是为什么引用不能指向null,因为变量不能,而不是。


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


现在对实际代码的一些解释:


int a = 0;
int& b = a;


在这里,您不是要创建另一个指向a的变量;你只是在保存a值的内存内容中添加另一个名称。此内存现在有两个名称,ab,可以使用任一名称进行寻址。


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

int a;
increment(a);


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


现在可能存在编译器在编译时可能无法知道引用的情况,例如使用extern变量时。因此,引用可能会也可能不会被实现为底层代码中的指针。但是在我给你的例子中,它很可能不会用指针实现。

其它参考7


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



  • 引用的设计使编译器更容易跟踪哪个引用别名哪个变量。两个主要特征非常重要:没有参考算术,也没有重新分配参考文献。这些允许编译器在编译时找出哪些引用别名是哪些变量。

  • 允许引用引用没有内存地址的变量,例如编译器选择放入寄存器的变量。如果你取一个局部变量的地址,编译器就很难把它放在一个寄存器中。



举个例子:


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


优化编译器可能会意识到我们正在访问

其它参考8

int &ri = i;
相当多的一组。它希望优化算法:


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
}


为了进行这样的优化,需要证明在调用期间没有任何东西可以改变数组
int &ri = i;
。这很容易做到。我永远不会少于2,所以array [[i]]永远不会引用数组
int &ri = i;
。 maybeModify()被赋予a0作为参考(别名数组

其它参考9

)。因为没有引用算法,编译器只需要证明maybeModify永远不会得到x的地址,并且它已经证明没有任何改变数组
int &ri = i;



它还必须证明,当我们在a0中有一个临时寄存器副本时,未来的调用没有办法读/写

其它参考10

。这通常是微不足道的,因为在很多情况下很明显,引用永远不会存储在像类实例这样的永久结构中。


现在用指针做同样的事情


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


行为是一样的;只是现在更难以证明maybeModify不会修改数组
int &ri = i;
,因为我们已经给它一个指针;这只猫已经不在了。现在它必须做更加困难的证明:对maybeModify进行静态分析以证明它永远不会写入& x + 1.它还必须证明它永远不会保存可以引用数组

其它参考11

的指针,同样棘手。


现代编译器在静态分析方面越来越好,但总是很好地帮助它们并使用引用。


当然,除非进行这种巧妙的优化,否则编译器确实会在需要时将引用转换为指针。

其它参考12


引用永远不会是NULL

其它参考13


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


考虑这两个程序片段。在第一个中,我们将一个指针指向另一个:


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引用的值,而不是引用本身。在赋值之后,两个引用仍然引用它们的原始对象,并且这些对象的值现在也是相同的。

其它参考14


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


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


除此之外,指针和引用之间当然存在一些实际差异。使用它们的语法明显不同,你不能重新定位引用,引用虚无,或指向引用。

其它参考15


引用是另一个变量的别名,而指针保存变量的内存地址。引用通常用作函数参数,以便传递的对象不是副本而是对象本身。


    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. 

其它参考16


这是基于本教程。写的更清楚:[108]


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


简单地记住,


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


更重要的是,因为我们可以参考几乎任何指针教程,指针是指针算法支持的对象,它使指针类似于数组。


看下面的陈述,


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


alias_Tom可以理解为alias of a variable(与typedef不同,alias of a type)Tom。也可以忘记这样的陈述的术语是创建Tom的引用。

其它参考17


它占用多少空间并不重要,因为你实际上看不到它将占用的任何空间的任何副作用(不执行代码)。


另一方面,引用和指针之间的一个主要区别是分配给const引用的临时值存在,直到const引用超出范围。


例如:


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

...

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


将打印:


in scope
scope_test done!


这是允许ScopeGuard工作的语言机制。

其它参考18


引用不是给予某些内存的另一个名称。它是一个不可变的指针,在使用时会自动取消引用。基本上它归结为:


int& j = i;


它在内部成为


int* const j = &i;

其它参考19


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


有关指针引用的更多信息,请访问以下内容:



  • C ++:对指针的引用

  • 指针指针和指针指针



正如我所说,指向引用的指针是不可能的。请尝试以下程序:[109] [110]


#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}

其它参考20


指针和引用之间有一个根本区别,我没有看到任何人提到过:引用在函数参数中启用了引用语义。指针,尽管它最初不可见但它们只提供传递 - 值语义。本文对此进行了很好的描述。[111]


问候,
&安培; rzej

其它参考21


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


因此,引用是引擎盖下的指针,它们都只是存储一个内存地址,地址所指向的是无关紧要的,如果我调用std :: cout<< str_ref;在调用delete& str_ref之后?嗯,显然它编译得很好,但是在运行时导致分段错误,因为它不再指向有效变量,我们基本上有一个仍然存在的破坏引用(直到它掉出来o范围),但没用。


换句话说,引用只不过是一个指针,它将指针机制抽象掉,使其更安全,更容易使用(没有意外的指针数学,没有混合。和 - >等),假设你不要像我上面的例子那样尝试任何废话;)


现在无论编译器如何处理引用,它始终都有某种指针,因为引用必须引用特定的在特定的内存地址变量,使其按预期工作,没有解决这个问题(因此称为参考)。


唯一要记住的重要规则是必须在声明时定义它们(除了标题中的引用之外,在这种情况下它必须在构造函数中定义,在对象之后)包含在其中的构造为时已晚,无法定义它。


请记住,我上面的示例就是这样,展示参考资料的示例,您永远不会想要以这些方式使用参考!为了正确使用参考文献,这里已经有很多答案可以解决问题了

其它参考22


我使用引用,除非我需要以下任何一个:



  • 空指针可以用作a
    哨兵价值,往往是一种廉价的方式
    避免函数重载或使用
    布尔。

  • 你可以对指针进行算术运算。
    例如,p += offset;


其它参考23


引用和指针都可用于更改另一个函数内的一个函数的局部变量。当它们作为函数的参数传递或从函数返回时,它们都可用于保存大对象的复制,以获得效率增益。
尽管有上述相似之处,但引用和指针之间存在以下差异。


引用功能不如指针


1)创建引用后,以后不能引用另一个引用;它不能重新安置。这通常用指针完成。


2)引用不能为NULL。指针通常为NULL,表示它们没有指向任何有效的东西。


3)必须在声明时初始化引用。指针没有这样的限制


由于上述限制,C ++中的引用不能用于实现链接列表,树等数据结构。在Java中,引用没有上述限制,可用于实现所有数据结构。引用在Java中更强大,是Java不需要指针的主要原因。


参考文献更安全,更易于使用:


1)更安全:由于必须初始化引用,因此不太可能存在像野生指针这样的野蛮引用。仍然可以使引用不引用有效位置


2)易于使用:引用不需要解除引用操作符来访问该值。它们可以像普通变量一样使用。只有在申报时才需要'&'运营商。此外,可以使用点运算符('。')访问对象引用的成员,而不像需要箭头运算符( - >)来访问成员的指针。


与上述原因一起,很少有地方像复制构造函数参数,其中指针不能使用。必须使用引用传递复制构造函数中的参数。类似地,引用必须用于重载某些运算符,如++

其它参考24


该计划可能有助于理解问题的答案。这是引用j和指向变量x的指针ptr的简单程序。


#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 [112]

其它参考25


另一个区别是你可以有一个指向void类型的指针(它意味着指向任何东西的指针)但禁止引用void。


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


我不能说我对这种特殊的差异非常满意。我更倾向于允许带有地址的含义引用,以及引用的相同行为。它允许使用引用定义一些C库函数的等价物,如memcpy。

其它参考26


此外,作为内联函数的参数的引用可以与指针不同地处理。


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


在内联指针版本1时,许多编译器实际上会强制写入内存(我们正在明确地获取地址)。但是,他们会将参考文献保留在更优化的寄存器中。


当然,对于没有内联的函数,指针和引用生成相同的代码,如果它们未被函数修改并返回,则通过值传递内在函数而不是引用它总是更好。

其它参考27


我觉得还有另外一点,这里没有涉及。


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


虽然这可能看起来很肤浅,但我相信这个属性对于许多C ++特性至关重要,例如:



  • 模板的。由于模板参数是鸭型的,因此类型的语法属性都很重要,因此通常相同的模板可以与TT&一起使用。

    (或std::reference_wrapper<T>仍然依赖隐式演员
    T&)

    涵盖T&T&&的模板更为常见。

  • 左值。考虑语句str[0] = 'X';没有引用它只适用于c-strings(char* str)。通过引用返回字符允许用户定义的类具有相同的表示法。

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

  • 运算符重载。通过引用,可以将间接引入操作员调用 - 比如说,operator+(const T& a, const T& b),同时保留相同的中缀符号。这也适用于常规的重载功能。



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

其它参考28


引用的另一个有趣用途是提供用户定义类型的默认参数:


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


默认flavor使用绑定const引用到临时引用方面。

其它参考29


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



  • 参考要求您指定实际窗口。

  • 指针需要屏幕上一块空间的位置,确保它包含零个或多个该窗口类型的实例。


其它参考30


指针和引用之间的区别



指针可以初始化为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模板的模板时,这种类模板将始终返回引用而不是指针,以便使用operator [[]]轻松读取或分配新值:


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 "="

其它参考31


区别在于非常量指针变量(不要与指向常量的指针混淆)可能在程序执行期间的某个时间发生变化,需要使用指针语义(&,*)运算符,而引用可以设置为只是初始化(这就是为什么你只能在构造函数初始化列表中设置它们,但不能以其他方式设置它们)并使用普通值访问语义。基本上引用了一些允许支持运算符重载的内容,正如我在一本非常古老的书中所读到的那样。有人在这个线程中声明 - 指针可以设置为0或任何你想要的值.0(NULL,nullptr)意味着指针初始化没有任何东西。取消引用空指针是一个错误。但实际上指针可能包含一个值这并没有指向一些正确的内存位置。引用依次尝试不允许用户初始化对无法引用的引用的引用,因为您始终为其提供正确类型的rvalue。虽然有很多方法可以将引用变量初始化为错误的内存位置 - 但最好不要深入研究细节。在机器级别,指针和引用均匀地工作 - 通过指针。让我们在必要的引用中说是语法糖。右值引用与此不同 - 它们自然是堆栈/堆对象。

其它参考32


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


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引用,即使函数从不期望nullptr。也就是说,那些人认为不应该允许像fn3()这样的功能签名。 Google的C ++风格指南就是一个例子。[113]

其它参考33


我总是根据C ++核心指南的规则来决定:[114]



  喜欢T& T&当无论证是一个有效的选择