我知道引用是语法糖,因此代码更易于读写。
但是有什么区别呢?
int &x = *(int*)0;
在gcc上。引用确实可以指向NULL。
我知道引用是语法糖,因此代码更易于读写。
但是有什么区别呢?
int &x = *(int*)0;
在gcc上。引用确实可以指向NULL。
Answers:
可以重新分配一个指针:
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;
指针在堆栈上有自己的内存地址和大小(x86上为4字节),而引用共享相同的内存地址(带有原始变量),但也占用了堆栈上的一些空间。由于引用具有与原始变量本身相同的地址,因此可以将引用视为同一变量的另一个名称。注意:指针指向的内容可以在堆栈或堆上。同上一个参考。我在此声明中的主张不是指针必须指向堆栈。指针只是保存内存地址的变量。此变量在堆栈上。由于引用在堆栈上有其自己的空间,并且地址与引用的变量相同。堆栈与堆的更多信息。这意味着编译器不会告诉您引用的真实地址。
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
assert(p == p2);
您可以使用指向提供额外级别间接功能的指针。而引用仅提供一种间接级别。
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);
可以直接分配指针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
指针可以遍历数组。您可以++
用来转到指针指向的下一个项目,并+ 4
转到第五个元素。无论指针指向的对象大小是多少。
指针需要取消引用*
才能访问其指向的内存位置,而引用可以直接使用。指向类/结构的指针用于->
访问其成员,而引用则使用.
。
引用不能填充到数组中,而指针可以(由用户@litb提及)
常量引用可以绑定到临时对象。指针不能(并非没有间接):
const int &x = int(12); //legal C++
int *y = &int(12); //illegal to dereference a temporary.
这样可以const&
更安全地在参数列表等中使用。
一个参考,可以看作是一个常量指针自动间接(不要用一个指针指向一个恒定值混淆!),即编译器将应用*
运营商为您服务。
必须使用非null值初始化所有引用,否则编译将失败。既不可能获得引用的地址-地址运算符将返回引用值的地址-也不可能对引用进行算术运算。
C程序员可能不喜欢C ++引用,因为在发生间接调用时,或者如果通过值或指针传递参数而不查看函数签名,它将不再显而易见。
C ++程序员可能不喜欢使用指针,因为它们被认为是不安全的-尽管除了在大多数琐碎的情况下,引用实际上并没有比常量指针更安全-缺少自动间接的便利,并且具有不同的语义含义。
请考虑以下C ++常见问题解答:
即使参考使用底层汇编语言的地址经常被实现,请不要不认为引用作为好笑的看着指针指向的对象。引用是对象。它不是指向对象的指针,也不是对象的副本。它是对象。
但是,如果引用确实是对象,那么怎么会有悬挂的引用呢?在非托管语言中,引用不可能比指针更“安全”-通常只有一种方法才能可靠地跨作用域边界使用别名!
来自C的背景,C ++引用可能看起来有点愚蠢,但是在可能的情况下,仍然应该使用它们而不是指针:自动间接访问很方便,并且引用在处理RAII时特别有用-但不是因为任何可察觉的安全性优势,而是因为它们使编写惯用代码变得不那么尴尬。
RAII是C ++的核心概念之一,但它与复制语义非常重要地交互。通过引用传递对象避免了这些问题,因为不涉及复制。如果在语言中没有引用,则必须使用指针,因为指针使用起来比较麻烦,因此违反了语言设计原则,即最佳实践的解决方案应该比替代方法更容易。
如果您想成为真正的书呆子,则可以使用引用做某件事,而不能使用指针做这件事:延长临时对象的寿命。在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 &
绑定的事实延长了对象的生存期,并且只有当引用超出范围时,才调用实际引用类型的析构函数(与引用类型相比,可以是基本类型)。由于它是参考,因此在它们之间不会进行切片。
Animal x = fast ? getHare() : getTortoise()
那时x
将面临经典的切片问题,则Animal& x = ...
可以正常工作。
除语法糖外,引用是一个const
指针(不是指向a的指针const
)。您必须在声明引用变量时建立它所引用的内容,并且以后不能更改它。
更新:现在我考虑了更多,有一个重要的区别。
可以通过获取其地址并使用const强制替换const指针的目标。
引用目标不能用UB以外的任何方式替换。
这应该允许编译器对参考进行更多优化。
T* const
带有不同语法糖的(这恰好消除了代码中的*和&)。
int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);
可以。
与流行观点相反,可能有一个为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 试图将代码移植到另一个平台时公开未定义的行为。
->
提供对指针的引用的方式,就像指针本身一样。
.
并且->
与vi和emacs有关:)
.
优于使用->
,但就像vi与emacs一样,它完全是主观的,您无法证明任何内容
引用与指针非常相似,但是它们经过专门设计,有助于优化编译器。
举个例子:
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]。
还必须证明,当我们在a0中有一个临时寄存器副本时,将来的调用将无法读取/写入a [0]。通常很难证明这一点,因为在许多情况下,很明显,引用从未存储在像类实例这样的永久性结构中。
现在用指针做同样的事情
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];
}
}
行为是相同的。只是现在很难证明,也许Modify永远不会修改array [1],因为我们已经给了它一个指针。那只猫从书包里掏出来。现在,它必须做更加困难的证明:对maymayModify的静态分析,以证明它永远不会写入&x +1。它还必须证明,它永远不会保存可以引用array [0]的指针。一样棘手。
现代编译器在静态分析方面越来越好,但是帮助他们并使用引用总是很高兴。
当然,除非进行此类巧妙的优化,否则编译器的确会在需要时将引用转换为指针。
编辑:发布此答案的五年后,我发现了实际的技术差异,其中参考文献有所不同,而不仅仅是查看同一寻址概念的不同方式。引用可以通过指针无法修改的方式来修改临时对象的寿命。
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
通常,临时对象(例如通过调用创建的对象)createF(5)
会在表达式末尾销毁。但是,通过将该对象绑定到引用ref
C,C ++将延长该临时对象的寿命,直到ref
超出范围。
maybeModify
不占用任何与之相关的地址的过程x
比证明没有发生一堆指针算术要容易得多。
void maybeModify(int& x) { 1[&x]++; }
,上面的其他评论正在讨论
实际上,引用不是真的像指针。
编译器保留对变量的“引用”,将名称与内存地址相关联。这是在编译时将任何变量名转换为内存地址的工作。
创建引用时,仅告诉编译器您已为指针变量分配了另一个名称。这就是为什么引用不能“指向null”的原因,因为变量不能是,也不能是。
指针是变量;它们包含其他一些变量的地址,或者可以为null。重要的是,指针具有一个值,而引用仅具有它所引用的变量。
现在对真实代码进行一些解释:
int a = 0;
int& b = a;
在这里,您没有创建另一个指向的变量a
;您只是将另一个名称添加到具有值的内存内容中a
。该内存现在有两个名称,a
和b
,并且可以使用任一名称进行寻址。
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
调用函数时,编译器通常会为要复制到的参数生成内存空间。函数签名定义应创建的空间,并提供应用于这些空间的名称。将参数声明为引用只是告诉编译器在方法调用期间使用输入变量存储空间,而不是分配新的存储空间。说您的函数将直接操作在调用范围中声明的变量似乎很奇怪,但是请记住,在执行编译的代码时,不再有范围了。只有普通的平面内存,您的功能代码可以操纵任何变量。
现在,在某些情况下,例如在使用extern变量时,编译器可能无法知道引用。因此,引用可以或可以不被实现为基础代码中的指针。但是在我给您的示例中,很可能不会使用指针来实现它。
引用永远不可能NULL
。
void Foo::bar() { virtual_baz(); }
。如果您不知道引用可能为null,则无法将null追溯到其起源。
int &r=*p;
是未定义的行为。在这一点上,你没有一个“参考指向NULL,”你有一个程序可以不再思索可言。
虽然引用和指针都用于间接访问另一个值,但是引用和指针之间有两个重要区别。首先是引用始终引用对象:定义引用而不初始化它是错误的。分配的行为是第二个重要区别:分配给引用会更改引用所绑定到的对象;它不会将引用重新绑定到另一个对象。初始化后,引用始终引用相同的基础对象。
考虑这两个程序片段。首先,我们将一个指针分配给另一个指针:
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引用的值,而不是引用本身。分配后,两个引用仍引用其原始对象,并且这些对象的值现在也相同。
占用多少空间都没有关系,因为您实际上看不到它会占用任何空间的任何副作用(不执行代码)。
另一方面,引用和指针之间的主要区别是分配给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运行的语言机制。
这是基于教程的。所写的内容更加清楚:
>>> 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
。
nullptr
?您是否实际阅读过该线程的其他部分,或者...?
引用不是给某些内存提供的另一个名称。这是一个不变的指针,在使用时会自动取消引用。基本上可以归结为:
int& j = i;
在内部成为
int* const j = &i;
const
指针的有效方式。这种灵活性并不能证明引用和指针之间存在差异。
C ++中的参考是什么?类型的某些特定实例不是对象类型。
C ++中的指针是什么?类型的某些特定实例是对象类型。
一个对象类型是(可能CV -qualified)类型不函数类型,不是引用类型,而不是CV空隙。
可能很重要的一点是要知道,对象类型是C ++中Universe类型的顶级类别。参考也是顶级类别。但是指针不是。
在复合类型的上下文中一起提到了指针和引用。这基本上是由于声明语语法的性质所继承的,该声明语语法是从(并扩展的)C继承的,它没有引用。(此外,自C ++ 11起,引用的声明符不止一种,而指针仍是“ unityped”的:&
+ &&
vs *
。)因此,在这种情况下,用类似C的样式起草“扩展”特定的语言在某种程度上是合理的。(我仍然会争论说,声明符的语法在新的语言设计中浪费了很多,使人类用户和实现都感到沮丧。因此,它们都不具备内置的条件。语法表达能力。不过,这是关于PL设计的完全不同的主题。)
否则,将指针与引用一起限定为特定类型的指针就无关紧要了。除了语法相似性以外,它们仅共享很少的公共属性,因此在大多数情况下无需将它们放在一起。
请注意,以上声明仅提及“指针”和“引用”作为类型。关于它们的实例(例如变量),存在一些有趣的问题。还有太多的误解。
顶级类别的差异已经可以揭示许多不直接与指针相关的具体差异:
cv
限定符。参考不能。关于引用的其他一些特殊规则:
&&
在模板参数推导过程中基于参考折叠的参数特殊规则(作为“转发参考”)允许参数“完美转发”。std::initializer_list
一些类似初始化的上下文也遵循一些类似的参考生存期扩展规则。这是另一种蠕虫。我知道引用是语法糖,因此代码更易于读写。
从技术上讲,这是完全错误的。引用不是C ++中任何其他功能的语法糖,因为在没有任何语义差异的情况下,引用不能被其他功能完全替代。
(类似地,λ-表达 s为未在c任何其他特征++,因为它不能被精确地与像“未指定”属性模拟语法糖所捕获的变量的声明顺序,这可能是重要的,因为这样的变量的初始化顺序可以是重大。)
从严格的意义上讲,C ++只有几种语法糖。一个实例(从C继承)是内置的(非重载)运算符[]
,它的定义与内置运算符unary *
和binary 完全具有特定组合形式的相同语义属性+
。
因此,指针和引用都使用相同数量的内存。
上面的陈述是完全错误的。为避免这种误解,请改用ISO C ++规则:
...物体在其构造期间,整个生命周期和破坏期间都占据一个存储区域。...
未确定参考是否需要存储。
请注意,这些是语义属性。
即使在语言设计的意义上说指针没有足够的资格与引用放在一起,仍然有一些论点使其在其他上下文中(例如,在对参数类型进行选择时)在它们之间进行选择值得商bat。
但这还不是全部。我的意思是,除了要考虑的指针还是引用之外,还有更多的事情需要考虑。
如果您不必坚持这种过分具体的选择,那么在大多数情况下,答案很简单:您不必使用指针,而不必使用指针。指针通常很糟糕,因为它们暗示了太多您不期望的事情,并且它们将依赖太多隐式假设,从而破坏了代码的可维护性和(甚至)可移植性。不必要地依赖指针绝对是一种不好的样式,从现代C ++的意义上应该避免这种情况。重新考虑您的目的,您最终会发现在大多数情况下指针是最后一种功能。
&
引用类型作为第一个参数的类型。(通常应该是const
合格的。)&&
引用类型作为第一个参数的类型。(通常应该没有限定符。)operator=
作为特殊成员函数重载时,需要引用类型类似于copy / move构造函数的第一个参数。++
需要虚拟int
。unique_ptr
和shared_ptr
,或者使用自制的智能指针(如果您要求它们不透明,也可以单独使用自制指针),而不要使用原始指针。std::optional
,而不是原始指针。observer_ptr
在图书馆基础TS中。唯一的例外无法使用当前语言解决:
operator new
。(然而,CV - void*
仍然是相当不同的,比普通的对象指针更安全,因为它排除了意外的指针算术,除非你是靠一些非符合扩展上void*
像GNU的。)因此,在实践中,答案是如此明显:如有疑问,请避免使用指针。仅在出于非常明显的原因没有其他更合适的理由时才需要使用指针。除了上面提到的一些例外情况外,此类选择几乎总是不完全是特定于C ++的(但可能是特定于语言实现的)。这样的实例可以是:
如果您通过某些Google搜索结果(不是特定于C ++)来查看问题,则很可能是错误的位置。
在C ++中引用是很“奇怪”,因为它本质上不是一流的:他们将被视为对对象或功能被称为所以他们就没有机会来支持喜欢成为左操作数的一些一流的操作的成员访问运算符独立于所引用对象的类型。其他语言对其参考文献可能有也可能没有类似的限制。
C ++中的引用可能不会保留不同语言之间的含义。例如,引用通常并不暗示像C ++那样的值具有非null属性,因此此类假设可能不适用于某些其他语言(并且您会很容易找到反例,例如Java,C#,...)。
通常,在不同编程语言中的引用之间仍然存在一些公共属性,但让我们将其留给SO中的其他问题。
(附带说明:这个问题可能比涉及任何“ C样”语言的问题早,例如ALGOL 68 vs. PL / I。)
在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;
}
指针和引用之间有一个根本的区别,我没有看到有人提到过:引用在函数参数中启用了按引用传递语义。指针虽然一开始不可见,但它却没有:它们仅提供按值传递语义。这已经非常漂亮描述这篇文章。
问候,&rzej
冒着增加混乱的风险,我想输入一些信息,我确定它主要取决于编译器如何实现引用,但是在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之后?好吧,显然它可以很好地编译,但是在运行时会导致分段错误,因为它不再指向有效变量,我们实质上有一个残破的引用仍然存在(直到超出范围),但是没有用。
换句话说,引用不过是一种指针,它抽象了指针机制,使其更安全,更易于使用(没有意外的指针数学,不会混淆'。'和'->'等),假设您不要像我上面的例子一样尝试任何废话;)
现在,无论编译器如何处理引用,它都将始终具有某种内在的指针,因为引用必须引用特定内存地址处的特定变量才能使其按预期工作,因此无法避免(因此)术语“参考”)。
使用引用要记住的唯一重要规则是,必须在声明时定义它们(除了标头中的引用之外,在这种情况下,必须在构造函数中定义引用,然后将其包含在其中)构造,现在定义它为时已晚)。
请记住,我上面的示例仅仅是说明引用是什么的示例,您永远都不想以这种方式使用引用!为了正确使用参考,这里已经有很多答案,直指头绪
另外,可以以不同于指针的方式处理作为内联函数的参数的引用。
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时实际上会强制写入内存(我们正在显式获取地址)。但是,它们会将参考保留在最佳寄存器中。
当然,对于未内联的函数,指针和引用会生成相同的代码,并且如果函数未修改和返回内联函数,则按值传递内在函数总是比按引用传递内在函数更好。
引用的另一种有趣用法是提供用户定义类型的默认参数:
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;
}
默认风格使用引用的“将const引用绑定到临时”方面。
该程序可能有助于理解问题的答案。这是引用“ 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
我觉得这里还没有提到另一点。
与指针不同,引用在语法上等同于它们引用的对象,即,可以应用于对象的任何操作都可以使用引用进行操作,并且语法完全相同(当然是初始化)。
尽管这可能看起来很肤浅,但我相信此属性对于许多C ++功能至关重要,例如:
模板。由于模板参数是鸭子类型的,因此所有类型的语法属性都很重要,因此通常同一模板可以与T
和一起使用T&
。
(或std::reference_wrapper<T>
仍然依赖于隐式转换为T&
)
模板盖二者T&
和T&&
甚至更常见。
左值。考虑一下str[0] = 'X';
没有引用的语句,该语句仅适用于c字符串(char* str
)。通过引用返回字符允许用户定义的类具有相同的符号。
复制构造函数。从语法上讲,将对象传递给复制构造函数是有意义的,而不是将指针传递给对象。但是复制构造函数无法按值获取对象-它将导致对同一复制构造函数的递归调用。这将引用作为唯一的选择。
操作员重载。通过引用,可以将间接引入到操作员调用中,例如,operator+(const T& a, const T& b)
同时保留相同的中缀表示法。这也适用于常规的重载函数。
这些要点使C ++和标准库有相当大的一部分,因此这是引用的主要属性。
指针和引用之间存在非常重要的非技术区别:通过指针传递给函数的参数比通过非常量引用传递给函数的参数更明显。例如:
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 ++风格指南就是一个例子。
指针可以初始化为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 "="
const int& i = 0
。
不同之处在于,非常量指针变量(不要与指向常量的指针混淆)可以在程序执行期间的某个时间更改,需要使用指针语义(&,*)运算符,而引用可以在初始化时设置仅(这就是为什么您只能在构造函数初始值设定项列表中设置它们,而不能以其他方式设置它们)并使用普通的值访问语义。基本上引入了引用,以支持操作员重载,就像我在一本非常古老的书中所读到的那样。正如该线程中有人指出的那样-指针可以设置为0或任何您想要的值。0(NULL,nullptr)表示指针不进行任何初始化。取消引用空指针是错误的。但是实际上指针可能包含不指向某个正确内存位置的值。反过来,引用也尝试不允许用户初始化对无法引用的内容的引用,这是因为您总是向其提供正确类型的右值。尽管有很多方法可以将引用变量初始化为错误的内存位置-最好不要将其深入了解细节。在机器级别,指针和引用均通过指针统一工作。假设在必不可少的参考文献中是句法糖。右值引用与此不同-它们自然是堆栈/堆对象。尽管有很多方法可以将引用变量初始化为错误的内存位置-最好不要将其深入了解细节。在机器级别,指针和引用均通过指针统一工作。假设在必不可少的参考文献中是句法糖。右值引用与此不同-它们自然是堆栈/堆对象。尽管有很多方法可以将引用变量初始化为错误的内存位置-最好不要将其深入了解细节。在机器级别,指针和引用均通过指针统一工作。假设在必不可少的参考文献中是句法糖。右值引用与此不同-它们自然是堆栈/堆对象。
简而言之,我们可以说引用是变量的替代名称,而指针是保存另一个变量地址的变量。例如
int a = 20;
int &r = a;
r = 40; /* now the value of a is changed to 40 */
int b =20;
int *ptr;
ptr = &b; /*assigns address of b to ptr not the value */