指针变量和引用变量之间的区别是什么?
当前回答
引用是常量指针。int*const a=&b与int&a=b相同。这就是为什么没有const引用,因为它已经是const,而const的引用是const int*consta。当使用-O0编译时,编译器将在这两种情况下将b的地址放在堆栈上,并且作为类的成员,它也将出现在堆栈/堆上的对象中,与您声明了常量指针时相同。使用-Ofast,可以免费优化此功能。常量指针和引用都被优化了。
与常量指针不同,无法获取引用本身的地址,因为它将被解释为它引用的变量的地址。因此,在Ofast上,表示引用的常量指针(被引用变量的地址)将始终在堆栈外进行优化,但如果程序绝对需要实际常量指针的地址(指针本身的地址,而不是指针指向的地址),即您打印常量指针的位置,那么const指针将被放置在堆栈上,以便它有一个地址。
否则它是相同的,即当您打印它指向的地址时:
#include <iostream>
int main() {
int a =1;
int* b = &a;
std::cout << b ;
}
int main() {
int a =1;
int& b = a;
std::cout << &b ;
}
they both have the same assembly output
-Ofast:
main:
sub rsp, 24
mov edi, OFFSET FLAT:_ZSt4cout
lea rsi, [rsp+12]
mov DWORD PTR [rsp+12], 1
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<void const*>(void const*)
xor eax, eax
add rsp, 24
ret
--------------------------------------------------------------------
-O0:
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-12], 1
lea rax, [rbp-12]
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-8]
mov rsi, rax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*)
mov eax, 0
leave
ret
指针已经在堆栈外进行了优化,在这两种情况下,指针甚至都没有在-Ofast上取消引用,而是使用编译时值。
作为对象的成员,它们在-O0到-Ofast上是相同的。
#include <iostream>
int b=1;
struct A {int* i=&b; int& j=b;};
A a;
int main() {
std::cout << &a.j << &a.i;
}
The address of b is stored twice in the object.
a:
.quad b
.quad b
mov rax, QWORD PTR a[rip+8] //&a.j
mov esi, OFFSET FLAT:a //&a.i
当通过引用传递时,在-O0上,传递被引用变量的地址,因此它与通过指针传递相同,即常量指针包含的地址。On Ofast如果函数可以内联,则编译器会在内联调用中对其进行优化,因为动态范围是已知的,但在函数定义中,参数总是作为指针(期望引用引用的变量的地址)被解引用,其中它可能被另一个转换单元使用,而编译器不知道动态范围,当然,除非函数声明为静态函数,否则它不能在转换单元之外使用,然后它通过值传递,只要它没有在函数中通过引用进行修改,那么它将传递您传递的引用所引用的变量的地址,如果调用约定中有足够多的易失性寄存器,则将在一个寄存器中传递,并保持在堆栈之外。
其他回答
在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++:指针引用指向指针的指针和指向指针的引用
正如我所说,指向引用的指针是不可能的。尝试以下程序:
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int *ptr = &x;
int &*ptr1 = ptr;
}
什么是C++参考(针对C程序员)
引用可以被视为具有自动间接寻址的常量指针(不要与指向常量值的指针混淆!),即编译器将为您应用*运算符。
所有引用都必须使用非空值初始化,否则编译将失败。既不可能获得引用的地址——地址运算符将返回被引用值的地址——也不可能对引用进行算术运算。
C程序员可能不喜欢C++引用,因为当发生间接寻址时,或者当参数通过值或指针传递而不查看函数签名时,C++引用将不再明显。
C++程序员可能不喜欢使用指针,因为它们被认为是不安全的——尽管引用实际上并不比常量指针更安全,但在大多数情况下除外——缺乏自动间接寻址的便利性,并且具有不同的语义内涵。
考虑C++常见问题解答中的以下语句:
即使引用通常使用底层汇编语言,请不要将引用视为指向对象的有趣指针。引用是对象。它是不是指向对象的指针,也不是对象的副本。它是对象
但如果引用真的是对象,那么怎么会有悬空引用呢?在非托管语言中,引用不可能比指针更“安全”——通常没有办法跨范围可靠地别名值!
为什么我认为C++引用有用
来自C背景的C++引用可能看起来有点傻,但在可能的情况下,仍应使用它们而不是指针:自动间接寻址很方便,在处理RAII时引用变得特别有用,但这并不是因为任何已知的安全优势,而是因为它们使编写惯用代码不那么困难。
RAII是C++的核心概念之一,但它与复制语义进行了非平凡的交互。通过引用传递对象可以避免这些问题,因为不需要复制。如果语言中没有引用,则必须使用指针,这样使用起来更麻烦,从而违反了语言设计原则,即最佳实践解决方案应该比替代方案更容易。
你忘记了最重要的部分:
带指针的成员访问使用->使用引用的成员访问。
foo.bar明显优于foo->bar,就像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];
}
}
优化编译器可能会意识到,我们正在访问一个[0]和一个[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
}
要进行这样的优化,需要证明在调用期间没有任何东西可以改变数组[1]。这很容易做到。i永远不小于2,所以array[i]永远不能引用array[1]。maybeModify()被给定a0作为引用(别名数组[0])。因为没有“引用”算法,编译器只需要证明maybeModify永远不会得到x的地址,并且它已经证明没有任何东西会改变数组[1]。
它还必须证明,当我们在a0中有一个[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];
}
}
行为是相同的;直到现在,要证明maybeModify从未修改过数组[1]要困难得多,因为我们已经给了它一个指针;猫从袋子里出来了。现在它必须做更困难的证明:对maybeModify进行静态分析,以证明它从未写入&x+1。它还必须证明它从未保存过可以引用数组[0]的指针,这同样棘手。
现代编译器在静态分析方面越来越好,但帮助他们并使用引用总是很好的。
当然,除非进行这种巧妙的优化,编译器确实会在需要时将引用转换为指针。
编辑:在发布这个答案五年后,我发现了一个实际的技术差异,即引用不同于看待相同寻址概念的不同方式。引用可以以指针无法修改的方式修改临时对象的寿命。
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
通常,临时对象(例如通过调用createF(5)创建的对象)会在表达式末尾被销毁。然而,通过将该对象绑定到引用ref,C++将延长该临时对象的寿命,直到ref超出范围。
塔林♦ 说:
不能像使用指针那样获取引用的地址。
事实上你可以。
我引用了另一个问题的答案:
C++常见问题解答说得最好:与指针不同,一旦引用绑定到对象,就不能将其“重新放置”到另一个对象。引用本身不是一个对象(它没有标识;获取引用的地址可以获得引用的地址;记住:引用是它的引用)。
推荐文章
- cplusplus.com给出的错误、误解或坏建议是什么?
- 找出质数最快的算法是什么?
- c++枚举类可以有方法吗?
- 格式化IO函数(*printf / *scanf)中的转换说明符%i和%d之间的区别是什么?
- 将析构函数设为私有有什么用?
- main()中的Return语句vs exit()
- 为什么c#不提供c++风格的'friend'关键字?
- 如果不是内存地址,C指针到底是什么?
- 在函数的签名中添加关键字
- 我如何在Visual Studio中预处理后看到C/ c++源文件?
- 为什么在标准容器中使用std::auto_ptr<>是错误的?
- 用比较double和0
- 在c#中创建一个对象的副本
- 保护可执行文件不受逆向工程的影响?
- 在c++中字符串前面的“L”是什么意思?