C++ 为什么引用不能在C++;

C++ 为什么引用不能在C++;,c++,pointers,reference,language-design,C++,Pointers,Reference,Language Design,C++引用有两个属性: 它们总是指向同一个对象 它们不能是0 指针则相反: 它们可以指向不同的对象 它们可以是0 为什么C++中没有“不可空的、可引用的或指针”?我想不出一个很好的理由来解释为什么引用不应该被重新密封 编辑: 这个问题经常出现,因为当我想确保一个“关联”(我在这里避免使用“引用”或“指针”)永远不会无效时,我通常使用引用 我不认为我曾经认为“这个参考总是指同一个对象很好”。如果引用是可重设的,仍然可以得到如下当前行为: int i = 3; int& const j =

C++引用有两个属性:

  • 它们总是指向同一个对象
  • 它们不能是0
指针则相反:

  • 它们可以指向不同的对象
  • 它们可以是0
<>为什么C++中没有“不可空的、可引用的或指针”?我想不出一个很好的理由来解释为什么引用不应该被重新密封

编辑: 这个问题经常出现,因为当我想确保一个“关联”(我在这里避免使用“引用”或“指针”)永远不会无效时,我通常使用引用

我不认为我曾经认为“这个参考总是指同一个对象很好”。如果引用是可重设的,仍然可以得到如下当前行为:

int i = 3;
int& const j = i;

这已经是合法的C++,但是没有意义。 我重申我的问题如下:“引用就是对象”设计背后的基本原理是什么?为什么认为引用总是同一个对象而不是仅当声明为常量时才有用?”

干杯,Felix

C++引用有时会在某些编译器中被强制为0(这样做是个坏主意*,它违反了标准*)


编辑:根据比我更了解该标准的不同人士的说法,上述代码会产生“未定义的行为”。在GCC和VisualStudio的至少一些版本中,我看到了这样做的预期:等价于将指针设置为NULL(并在访问时导致空指针异常)。C++中的

,经常被称为“引用是对象”。从某种意义上说,这是正确的:尽管在编译源代码时引用被当作指针处理,但引用的目的是表示调用函数时未复制的对象。由于引用不可直接寻址(例如,引用没有地址,&返回对象的地址),因此从语义上讲,重新分配它们是没有意义的。此外,C++已经有指针,它处理重新设置的语义。

我想它与优化有关。p>


当您可以清楚地知道变量的内存意味着什么时,静态优化就容易多了。指针打破了这个条件,可重新设置的引用也会。

< P> > C++引用“别名”可能不会那么混乱了。正如其他人提到的,C++中的引用应该是作为变量引用的,而不是作为变量的指针/引用。因此,我想不出一个好的理由,他们应该重新设置


在处理指针时,允许null作为值通常是有意义的(否则,您可能需要一个引用)。如果您特别不允许保留null,您可以编写自己的智能指针类型;)

> P> Stroustrup在C++的设计和演化中给出的C++不允许引用引用的原因:

初始化后不可能更改引用引用的内容。也就是说,一旦C++引用被初始化,就不能稍后引用不同的对象;它不能被重新绑定。我过去曾被Algol68引用咬过,
r1=r2
可以通过
r1
分配给所引用的对象,或者根据
r2
的类型为
r1
分配一个新的引用值(重新绑定
r1
)。我想避免C++中的这些问题。
因为有时候事情不应该是可重复的。(例如,对单例的引用。)

因为在函数中知道你的参数不能为null是很好的

但最重要的是,因为它允许用户拥有真正的指针,但其行为类似于本地值对象。C++试图引用Stroustrup,使类实例“做为int”。通过vaue传递int很便宜,因为int适合于机器寄存器。类通常比int大,按值传递它们会带来很大的开销

能够传递一个“看起来”像值对象的指针(通常是一个int的大小,或者两个int的大小),允许我们编写更干净的代码,而不需要解引用的“实现细节”。而且,除了运算符重载之外,它还允许我们使用与int类似的语法编写类。特别是,它允许我们使用语法编写模板类,这些语法可以同样应用于基本类(如int)和类(如复数类)

特别是在运算符重载的情况下,有些地方我们应该返回一个对象,但同样,返回指针要便宜得多。再一次,返回引用是我们的“出局”

指针是很难的。也许对你来说不是,对任何意识到指针只是内存地址的值的人来说也不是。但是回想我的CS 101课,他们绊倒了很多学生

char* p = s; *p = *s; *p++ = *s++; i = ++*p;
这可能令人困惑

见鬼,在使用C语言40年后,人们甚至不能同意指针声明是否应该:

char* p;


因为这样你就不会有不能为0的可重置类型。除非,你包含了3种类型的引用/指针。这只会使语言复杂化,只会获得很少的收益(那么为什么不添加第4种类型呢?可以为0的不可重置引用?)

一个更好的问题可能是,为什么您希望引用是可重设的?如果它们是可重设的,那么在很多情况下,它们将变得不那么有用。这将使编译器更难进行别名分析

Java或C#中的引用之所以可以重新放置,主要原因似乎是它们执行指针的工作。它们指向对象。它们不是对象的别名

以下情况的影响应该是什么

int i = 42;
int& j = i;
j = 43;
在今天的C++中,使用不可恢复的引用,它是简单的。J是I的别名,我以值43结尾。

如果引用已重新设置
char *p;
int i = 42;
int& j = i;
j = 43;
int i = 42;
int k = 43;
int& j = i;
j = k;
struct null_pointer_exception { ... };

template<typename T>
struct non_null_pointer {
    // No default ctor as it could only sensibly produce a NULL pointer
    non_null_pointer(T* p) : _p(p) { die_if_null(); }
    non_null_pointer(non_null_pointer const& nnp) : _p(nnp._p) {}
    non_null_pointer& operator=(T* p) { _p = p; die_if_null(); }
    non_null_pointer& operator=(non_null_pointer const& nnp) { _p = nnp._p; }

    T& operator*() { return *_p; }
    T const& operator*() const { return *_p; }
    T* operator->() { return _p; }

    // Allow implicit conversion to T* for convenience
    operator T*() const { return _p; }

    // You also need to implement operators for +, -, +=, -=, ++, --

private:
    T* _p;
    void die_if_null() const {
        if (!_p) { throw null_pointer_exception(); }
    }
};
int theInt = 0;
int& refToTheInt = theInt;

int otherInt = 42;
refToTheInt = otherInt;
int firstInt = 1;
int secondInt = 2;
secondInt = firstInt;
firstInt = 3;

assert( firstInt != secondInt );
MyClass & c = *new MyClass();
c = *new MyClass("other")
MyClass * a =  new MyClass();
MyClass & b = *new MyClass();
a =  new MyClass("other");
b = *new MyClass("another");
struct A{
    int y;
    int& x;
     A():y(0),x(y){}
};

int main(){
  A a;
  const A& ar=a;
  ar.x++;
}
int i = 42;
int k = 43;
int& j = i;
//change i, or change j?
j = k;
int i = 42;
int k = 43;
int& j = i;
//change i, or change j?
//change j!
{
    int& j = k;
    //do what ever with j's new meaning
}
int a;
int * p = &a;
int a = 10;
int b = 20;
int & r = a;
r = b; // re-set r to b, or set a to 20?
void foo(int & r)
{
    int b = 20;
    r = b; // re-set r to a? or set a to 20?
}
void main()
{
    int a = 10;
    foo(a);
}
#include <iostream>

struct Field_a_t
{
    int& a_;
    Field_a_t(int& a)
        : a_(a) {}
    Field_a_t& operator=(int& a)
    {
        // a_.~int(); // do this if you have a non-trivial destructor
        new(this)Field_a_t(a);
    }
};

struct MyType : Field_a_t
{
    char c_;
    MyType(int& a, char c)
        : Field_a_t(a)
        , c_(c) {}
};

int main()
{
    int i = 1;
    int j = 2;
    MyType x(i, 'x');
    std::cout << x.a_;
    x.a_ = 3;
    std::cout << i;
    ((Field_a_t&)x) = j;
    std::cout << x.a_;
    x.a_ = 4;
    std::cout << j;
}