我了解指针与引用之间的语法和一般语义,但是我应该如何决定在API中使用引用或指针是更合适还是更不合适?
当然,有些情况需要一个或另一个(operator++需要一个引用参数),但总的来说,我发现我更喜欢使用指针(和const指针),因为语法清楚地表明变量正在被破坏性地传递。
例如,在以下代码中:
1 2 3 4 5 6 7
| void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
int a = 0;
add_one(a); // Not clear that a may be modified
add_one(&a); // 'a' is clearly being passed destructively
} |
对于指针,它总是(更)明显的发生了什么,所以对于API和类似的问题,清晰性是一个大问题,指针是否比引用更合适?这是否意味着只有在必要时才应使用引用(如operator++)?其中一个或另一个存在性能问题吗?
编辑(过时):
除了允许空值和处理原始数组之外,似乎选择取决于个人偏好。我接受了下面的答案,引用谷歌的C++风格指南,因为他们提出"引用可能会混淆,因为它们有值语法但指针语义。"
由于对不应为空的指针参数进行消毒所需的额外工作(例如,add_one(0)将在运行时调用指针版本并中断),因此从可维护性的角度来看,在必须存在对象的地方使用引用是有意义的,尽管丢失语法清晰性是一种耻辱。
- 很明显,当它是一个非常量引用时,它可能会被修改。另一方面,一个常量引用清楚地表明它不能被修改。它们在调用时看起来是一样的,但大多数时候IntelliSense(或类似的东西)会告诉您发生了什么,您可以从中推断出来。否则,您只需记录您的API;)
- 似乎你已经决定了什么时候用哪一个了。就我个人而言,我更喜欢传递我所处理的对象,不管我是否在修改它。如果一个函数接受一个指针,它会告诉我它是在指针上执行的,也就是说,将它们用作数组中的迭代器。
- @Schnommus:只有在查看方法声明时才清楚,在读取代码时查看每个被调用方法的定义并不总是很方便的。
- 本杰明:我倾向于避免C++中的指针运算,因为有更清晰的接口来避免它(STL容器等)。
- @Connec-我从来没有使用过一个没有IntelliSense的IDE或类似的东西来处理任何严重的问题-所以我从来没有真正需要查看头定义,当我在中键入函数时,vs只是向我显示它。
- @施诺缪:够公平的,我主要用短信。不过,我认为最好是从一眼就能看出它的含义。
- @康纳克:我也是,这就是为什么我几乎没有理由写一个接受指针的函数。
- @本杰明:呵呵,如果没有必要的话,我是不会用的。)我现在看到的情况是一棵树和add_child方法(例如node1.add_child(&node2)方法),必须通过引用传递子对象来更新其父对象。我想我可以做两个函数(add_child和set_parent),但这看起来有点夸张。
- 关于add_one(a);的情况如何?不清楚a会被修改吗?它在代码中说:添加一个。
- 谷歌的C++风格指南不被认为是一个很好的C++风格指南。这是一个风格指南,使用谷歌的旧C++代码库(即对他们的东西很好)。接受这样的回答对任何人都没有帮助。只是阅读你的评论和解释,你带着一个已经确定的观点来到这个问题上,只是在寻找其他人来证实你的观点。因此,你是根据你想要/期望听到的问题和答案。
- @马丁:我现在已经改变了我的答案,不过从我对它的仔细研究来看,他们倾向于指出什么时候是一个兼容性决定(例如,例外情况),在本例中他们没有这样做(而是强调语法/语义不一致,我仍然认为这是一个有效的观点)。
- 通过命名方法addOneTo(...)可以简单地修复这个问题。如果你不想这样做,那就看看声明吧。
- Trolltech似乎支持您的视图-doc.qt.digia.com/qq/qq13 apis.html指针相关引用
尽可能使用引用,必要时使用指针。
避免使用指针,直到不能使用为止。
原因是指针使操作变得比任何其他构造都更难执行/读取,更不安全,更危险。
所以经验法则是只有在没有其他选择的情况下才使用指针。
例如,当函数在某些情况下可以返回nullptr时,返回指向对象的指针是一个有效的选项,并且假定它会返回nullptr。也就是说,更好的选择是使用类似于boost::optional的东西。
另一个例子是使用指向原始内存的指针进行特定的内存操作。这应该隐藏并定位在代码的非常狭窄的部分,以帮助限制整个代码库的危险部分。
在您的示例中,使用指针作为参数没有意义,因为:
如果你提供nullptr作为论据,你将进入一个不明确的行为领域;
引用属性版本不允许(没有容易发现的技巧)1出现问题。
对于用户来说,引用属性版本更容易理解:必须提供有效的对象,而不是可能为空的对象。
如果函数的行为必须使用或不使用给定的对象,那么使用指针作为属性建议您可以将nullptr作为参数传递,这对函数来说是很好的。这是用户和实现之间的一种契约。
- 我不确定指针是否会使阅读变得困难?这是一个相当简单的概念,当某些东西可能被修改时,它会很清楚。如果我想说的话,当没有任何迹象表明发生了什么事情时,很难阅读,为什么add_one(a)不返回结果,而不是通过引用来设置结果呢?
- @康纳克:如果add_one(a)是混淆的,那是因为它的名字不正确。add_one(&a)会有同样的混乱,只是现在您可能在增加指针而不是对象。add_one_inplace(a)将避免所有的混淆。
- 有一点,引用可以引用那些可以像指针一样容易丢失的内存。所以它们不一定比指针更安全。持久化和传递引用和指针一样危险。
- 结构简单,容易理解,但在上下文中很难理解。当你在C++中使用指针时,如果你不添加大量的断言,那么你只需保证什么是指向的。
- 将在可能的情况下添加C++使用引用,并依赖于异常模型来踢出任何未定义的情况。指针应该只在实现专门的数据结构或寻址数组时使用。
- @实际上,用户805547目前建议使用指针作为"不拥有"指针,因为它比其他替代方法便宜。一个更明确的标准指针类型对于使其更清晰是有用的,但现在可以使用原始指针来表示所有权的缺失,至少在现代C++代码库中是这样。
- 好。。。当我们已经有了一个"可选"对象时,导入一个完整的数百兆字节库…我觉得有点夸张。指针也有它们的用途。
- @h2co3通常不是关于"指针",而是关于"原始指针",它们太危险了,但有时还是必要的。现在有一个建议将其他智能指针添加到C++中,比如最简单的智能指针,它将取代大多数"非拥有"指针:ISOCPP.Org/Fiels/Prss/N370.PDF
- @我指的是原始指针。我的意思是C++有指针,EDOCX1,4,EDCX1,5,这是有原因的。而给出"永远不要使用指针"和/或"永远不要使用空值,总是使用boost::optional"的建议,并不是一个经过深思熟虑甚至现实的建议。太疯狂了。不要误会我的意思,C++中的原始指针比C中少一些,但它们仍然是有用的,它们并不像某些C++用户所声称的那样"危险"(这也太夸张了),而且,当使用指针和EDCOX1(7)来表示缺失值时更容易……为什么要导入整个增强?
- @ H2CO3第一,STD::可选的已经被投票入C++ 14 -这不是关于具体的提升,其他的实现存在,你也可以DIY。第二,所有这些建议都是来自标准的人所说的,他们有强有力的论据支持他们。第三,我没有说总是使用可选的而不是指针,我说这是一个选项(特别是对于小的返回类型)。根据具体情况,还有很多其他的选项可以让您无需花费任何代价来避免指针,这有助于编写大量的代码文档,使代码更易于维护。我最后一点…
- @ H2CO3第四,我完全不知道你的C++经验,我真的不想不尊重你,但是说"指针是危险的"是一个"外泄",对我来说,也许你从未有过一个满是指针的大代码库的经验。如果您这样做了(甚至是在较小的代码基中),那么您就知道了这样一个问题:不知道指针是否拥有数据,是否存在销毁该数据的特殊方法,以及发生异常(泄漏)时该怎么做。
- @最后一件事:说指针不危险是相当大胆和不可辩驳的断言今天。即使有时,我们也无法避免。如果你还没有这样做,我建议阅读有关相关问题提出的C++命令书,它们都说明了我在这里所说的内容。基本上,如果你不同意的话,你就必须对那些C++编写的专家提供了论点,并建立了当前的标准(这也避免了指针太多)。最后一点:使用NULL在C++ 11中被认为是不好的练习。
- @Klaim"使用空值是不好的做法"-现在这太荒谬了。而且if已经过时了,应该用while() { break; }来代替,对吗?另外,不用担心,我已经看到并使用了大量的代码库,是的,如果您不小心,那么所有权就是一个问题。但是,如果您坚持惯例,就不会这样做,而是一致地使用它们,并对代码进行注释和文档记录。但毕竟,我应该使用C,因为我对C++太笨了,对吧?
- @h2co3我认为你误解了我的说法:我的意思是不建议使用空值,因为现在我们有了空指针。它们基本上做了相同的事情(将指针设置为NULL并不坏…)但是在可以使用int的地方可以使用NULL,因此这通常是一个问题,正如所指出的那样:thenewcpp.wordpress.com/2011/11/03/nullptr同样,不要外推。约定和注释很好,但编译器不检查它们。错误是人,假设每个人都不会粗心大意,这是完全错误的。不,你不是傻子,但你似乎不知道我观点背后的论点。
- @Klaim确实,我认为您认为整个空指针概念已经过时了。当然,总会有粗心的人,但他们会写一些没有意义的代码。
- @H2CO3其实不,这些粗心的人是你和我,任何人,因为任何人都不会犯错误。
- @h2co3如果你还没有看它,你可能想看channel9.msdn.com/events/goingnative/2013-此时只有第一天发生,但他们已经用更完整的方式解释了一整天我说的话。特别是STL和肖恩的谈话。我希望这比我在这里的评论更清楚,让你看到我的观点。
- @Klaim谢谢你的链接,这看起来很有用!
- @尼可博拉斯,嗯,我觉得埃多克斯1〔2〕不好看。如果你有更多的里程计呢?EDOCX1?3?
- @gblomqvist:从函数调用来看,返回类型并不明显。呼叫者可能正在放弃返回。而名称总是可见的。
- @尼古拉斯对不起,那部分是给用户医生的,我的错。我同意好的名称是至关重要的,只是想补充一点,函数的返回类型可能会对函数有很大的影响。但是,从函数调用的角度来看,返回类型并不明显,这是正确的。
性能完全相同,因为引用是作为指针在内部实现的。所以你不必担心。
关于何时使用引用和指针,没有公认的惯例。在一些情况下,您必须返回或接受引用(例如,复制构造函数),但除此之外,您可以自由地执行您希望的操作。我遇到的一个非常常见的约定是,当参数必须引用现有对象时使用引用,当空值正常时使用指针。
有些编码约定(如Google)规定应该始终使用指针或常量引用,因为引用的语法有点不清楚:它们有引用行为,但有值语法。
- 在&和* const之间,不允许使用带引用的空似乎是唯一的语义差异。另外,如果谷歌这么做,肯定是对的,对吧?:)
- 为了增加一点内容,谷歌的风格指南说,函数的输入参数应该是常量引用,输出应该是指针。我喜欢这个,因为当你读到一个函数签名时,它非常清楚什么是输入,什么是输出。
- @康纳克:试着用AdWords API编码……它将完全改变你对谷歌的看法。
- @丹:谷歌风格指南是针对谷歌的旧代码,不应该用于现代编码。事实上,对于一个新项目来说,这是一种相当糟糕的编码风格。
- @康纳克:让我这样说:空是一个完全有效的指针值。任何有指针的地方,我都可以给它一个空值。因此,你的第二个版本的add_one被打破了:add_one(0); // passing a perfectly valid pointer value,kaboom。你需要检查它是否为空。有些人会反驳说:"好吧,我只是记录下我的函数不适用于空值"。这很好,但随后您就违背了问题的目的:如果您要查看文档以查看是否可以使用空值,那么您还将看到函数声明。
- 如果这是一个参考,你会看到情况就是这样。然而,这样的反驳忽略了一点:引用在一个语言级别上强制执行,它引用了一个现有的对象,不可能是空的,而指针没有这样的限制。我认为很明显,语言级别的强制比文档级别的强制更强大,更不容易出错。有些人会试图反驳这一点,说:"看,空引用:int& i = *((int*)0);"。这不是一个有效的反驳。前一个代码中的问题在于指针的使用,而不是引用。引用从不为空,句点。
- 你好,我在评论中看到了语言律师的缺乏,所以让我来补救一下:引用通常是由指针实现的,但标准中没有这样的规定。使用其他机制的实施将是100%投诉。
- 我不同意没有公认的惯例。始终使用引用,除非需要传递对象不存在,否则传递一个空的智能指针。实际上,你在C++中从来没有传递过一个指针。有些人可能会说,theta数组是通过指针传递的。同样,我不同意,在C++中,你应该传递一个对标准容器的引用(或者更优选的是两个迭代器)。只有C通过指针传递数组。
- @GMAN:这当然是一个很好的观点…但是,不得不牺牲句法的清晰性是一件很遗憾的事情:(我仍然有点心烦意乱,在大多数情况下,很明显,这个论点不应该是空的(在我的例子中,说"添加子项"(空),尽管这样做会破坏一切),但我认为从可维护性的角度来看,最好使用最健壮的方法。我一直在学习为什么这么多人认为C++是一种语言XD的混乱。
- 如果你认为你在和不懂C++的人交谈。它是一门复杂的语言。但美的本质是简单的使用。问题是,来自其他语言的程序员带来了这样的期望:C++应该像他们的旧语言一样工作(C/Java开发人员脑子里),当它没有时,会陷入巨大的粘性漏洞。
- @康纳克:这是一个相当个人的观点。你只不过是路过…地址。再也没有了。你自己的个人约定说,你的foo会修改bar,但就我所知,阅读代码,我只是感到困惑,担心你对对象地址(指针算术)的实际操作。删除它?)。如果不知道函数对其参数的作用,就不能期望使用函数。如果你知道一个函数在做什么,那么你就不需要&的东西。
- @connec:现在,c有一个ref和out参数限定符。在某些方面,函数对参数的作用更为清晰(如果您通过ref传递一个对象引用,则信息的意义不大)。经过几个月的编写C代码,EDOCX1 8和EDOCX1,9是没有C语言的语法糖,不喜欢在C++中使用。另一方面,我仍然非常想念friend和const,没有它们,我的C代码就不那么安全了。你确定你在关注这个重要的问题吗?
- @谁有时间去阅读他们用来了解它的每一个功能?在某些情况下,这甚至是不可能的(如果您使用的是库SAN源代码)。作为一个有效的程序员,经常需要对行为进行假设,或者缩小你需要调查的范围。一个获取常量引用的函数有助于减少我必须做的无关工作。
- @iheanyi:一个函数有一个契约,即:它做什么,它的前置条件和后置条件是什么。您是对的:参数是由const-ref传递的这一事实是一个很好的提示。我们同意这一点。
- @你所说的关于ref和out的paercebal是不正确的,它们绝对不是句法上的糖。参数传递方式不同。引用类型的引用参数与引用类型的值参数的行为仍然不同。您可以更改引用引用的内容。至于它是作为论点传递的句法甜头,那也是不正确的。在IL和机器代码级别,这是一个不同的调用。在反射时,它实际上是一个不同的类型(虽然ref和out是相同的类型),因此您甚至可以在上面重载方法。
- @艾迪亚卡皮:我想你误解了我。我完全知道C中的out和ref是什么。它们的映射(或者至少是EDCOX1×0映射)在C++中没有太大的差别(包括重载时)。我所说的是,在C语言中,必须总是添加EDCOX1,1或EDCOX1,0,它对我来说不是积极的(有利弊),我很高兴在C++中调用一个引用参数的函数时不必这样做。
- PauleCeBar实际上与C++引用有很大的不同。没有EDCOX1(0)的值类型参数将类似于C++中的EDCOX1(10)。没有ref的引用类型参数类似于MyClass&,带有ref的值类型参数也映射到MyClass&。带有ref的引用类型参数最接近于MyClass*&。当然有细微差别。然而,最重要的是,在大多数情况下,当处理classes时,您将通过引用传递,而不将其标记为ref。类似于C++中引用的传递方式。
- @艾迪亚卡皮:再说一次,你没有告诉我我不知道的任何事情(所以我不会费心评论你的评论),而且似乎忽略了我的观点,即:在C中,在呼叫站点,你必须添加ref(resp)。当被调用的函数需要一个ref时,out关键字会出现在变量中。(1)。在C++中,在调用站点中,无论参数是按值传递还是引用引用,都不需要特定的关键字。对我来说,调用站点的C句法符号很有趣,但是与一般符号相比太繁琐了。因此,对我来说,缺点超过了调用站点C样式符号的优点。
- @你完全无视我的观点。在调用站点中,传递引用具有与C++相同的语法,并且不需要任何语法符号。在调用站点的C++中,还需要特殊的语法传递给C中的什么是EDCOX1×0参数。处理引用类型时。唯一不同的时间是通过引用传递值类型。你说了两件事:"如果你通过引用传递一个对象引用,那么这个信息就没有什么意义","ref和out是句法上的"。两者都是完全错误的。我只是希望你不要误传读过这篇文章的人。
- 嗨,你的意思是pass-by引用实际上使用了一个指向被引用变量的指针吗?如果是这样,那么为什么我们要创建两个不同的语法来实际执行相同的操作呢?
- @leonovelkaren编译器可以自由地实现它想要的引用;它不需要使用指针,并且您不应该关心大多数编译器做的偶然事实。唯一重要的是,指针和引用实际上具有非常不同的语义和强度,正如您从阅读本线程的其余部分所看到的那样。
- 我喜欢你这么简洁地说:"它们有引用行为,但有值语法。"
来自C++ FAQ Lite——
Use references when you can, and pointers when you have to.
References are usually preferred over pointers whenever you don't need
"reseating". This usually means that references are most useful in a
class's public interface. References typically appear on the skin of
an object, and pointers on the inside.
The exception to the above is where a function's parameter or return
value needs a"sentinel" reference — a reference that does not refer
to an object. This is usually best done by returning/taking a pointer,
and giving the NULL pointer this special significance (references must
always alias objects, not a dereferenced NULL pointer).
Note: Old line C programmers sometimes don't like references since
they provide reference semantics that isn't explicit in the caller's
code. After some C++ experience, however, one quickly realizes this is
a form of information hiding, which is an asset rather than a
liability. E.g., programmers should write code in the language of the
problem rather than the language of the machine.
- 我想您可能会争辩说,如果您使用的是API,那么您应该熟悉它的功能,并且知道传递的参数是否被修改了…有点需要考虑,但我发现自己同意C程序员的观点(尽管我自己几乎没有C经验)。不过,我还要补充一点,更清晰的语法对程序员和机器都有好处。
- @康纳克:当然,C程序设计员的语言是正确的。但不要犯把C++当作C语言的错误,它是一种完全不同的语言。如果你把C++当作C,那么你就结束写被引用为EDCOX1(0)(这不是C++)。
我的经验法则是:
- 将指针用于传出或输入/输出参数。所以可以看出,这个值将会改变。(您必须使用&)
- 如果可以接受空参数值,则使用指针。(如果是传入参数,请确保它是const)
- 如果传入参数不能为空且不是基元类型(const T&),则对其使用引用。
- 返回新创建的对象时使用指针或智能指针。
- 使用指针或智能指针作为结构或类成员,而不是引用。
- 使用引用作为别名(例如int ¤t = someArray[i])
无论您使用哪一个,如果您的函数及其参数不明显,不要忘记记录它们的意义。
像其他人已经回答过的那样:总是使用引用,除非变量为NULL/nullptr确实是有效状态。
约翰·卡马克对这个问题的看法类似:
NULL pointers are the biggest problem in C/C++, at least in our code. The dual use of a single value as both a flag and an address causes an incredible number of fatal issues. C++ references should be favored over pointers whenever possible; while a reference is"really" just a pointer, it has the implicit contract of being not-NULL. Perform NULL checks when pointers are turned into references, then you can ignore the issue thereafter.
http://www.altdevblogaday.com/2011/12/24/static-code-analysis/
编辑2012-03-13
用户Bret Kuhns正确评论:
The C++11 standard has been finalized. I think it's time in this thread to mention that most code should do perfectly fine with a combination of references, shared_ptr, and unique_ptr.
确实如此,但即使用智能指针替换原始指针,问题仍然存在。
例如,std::unique_ptr和std::shared_ptr都可以通过其默认构造函数构造为"空"指针:
- http://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr
- http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
…也就是说,在不验证它们是否为空的情况下使用它们会有撞车的风险,这正是J.Carmack所讨论的。
然后,我们有一个有趣的问题,"如何将智能指针作为函数参数传递?"
乔恩对C++问题的回答——通过对Booo::SydDypTR的引用,以及下面的注释显示,即使在那时,通过复制或引用传递智能指针并不像人们所希望的那样清晰(我默认自己的"引用",但我可能错了)。
- C++ 11标准已经确定。我认为是时候在这个线程中提到,大多数代码应该通过引用(shared_ptr和unique_ptr的组合)做得非常好了。所有权语义和输入/输出参数约定由这三个部分和常量的组合来处理。除了处理遗留代码和非常优化的算法之外,C++中几乎不需要原始指针。那些使用它们的区域应该尽可能地封装起来,并将任何原始指针转换为语义上适当的"现代"等价物。
- 很多时候,智能指针不应该被传递,但应该测试是否为空,然后通过引用传递其包含的对象。您真正应该传递智能指针的唯一时间是与另一个对象传输(唯一指针)或共享(共享指针)所有权时。
- @povman:我完全同意:如果所有权不是接口的一部分(除非它将要被修改,否则它不应该被修改),那么我们不应该将智能指针作为参数(或返回值)传递。当所有权是接口的一部分时,事情会变得更加复杂。例如,Sutter/Meyers关于如何将唯一的ptr作为参数传递的争论:通过copy(sutter)还是通过r-value引用(meyers)?反模式依赖于传递指向全局共享指针的指针,而该指针有失效的风险(解决方案是在堆栈上复制智能指针)。
免责声明:除了引用不能为空或"反弹"(意思是它们不能改变它们的别名对象)之外,它实际上是一个品味问题,所以我不会说"这更好"。
也就是说,我不同意你在文章中最后的陈述,因为我认为代码不会因为引用而失去清晰度。在你的例子中,
可能比
因为你知道A的值很可能会改变。另一方面,函数的签名
1
| void add_one(int* const n); |
也有点不清楚:n是一个整数还是一个数组?有时您只能访问(文档记录不好)头文件和签名,例如
1
| foo(int* const a, int b); |
一眼就不容易理解。
imho,当不需要(重新)分配或重新绑定(在前面解释的意义上)时,引用和指针一样好。此外,如果开发人员只对数组使用指针,那么函数签名就不那么模棱两可了。更不用说运算符语法对引用的可读性更高了。
- 感谢您清楚地展示了这两种解决方案的优势和劣势。我最初是在投手训练营,但这很有意义。
这不是品味问题。以下是一些明确的规则。
如果您想在声明它的范围内引用静态声明变量,那么使用C++引用,它将是完全安全的。这同样适用于静态声明的智能指针。通过引用传递参数就是这种用法的一个例子。
如果要引用范围中比声明范围更宽的任何内容,则应使用引用计数的智能指针,使其完全安全。
为了方便语法,可以引用集合中的某个元素,但这是不安全的;可以随时删除该元素。
要安全地保存对集合元素的引用,必须使用引用计数的智能指针。
任何性能差异都是如此之小,以至于使用不那么清晰的方法是不合理的。
首先,一个没有提到的案例是const参考文献,其中参考文献通常更优。对于非简单类型,传递const reference可以避免创建临时类型,并且不会引起您所关心的混乱(因为值没有被修改)。在这里,强制一个人传递一个指针会引起你担心的非常混乱,因为看到获取的地址并将其传递给一个函数可能会让你认为值发生了变化。
无论如何,我基本同意你的看法。我不喜欢函数引用来修改它们的值,因为函数的作用并不明显。在这种情况下,我也更喜欢使用指针。
当需要返回复杂类型的值时,我倾向于使用引用。例如:
1 2
| bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative |
在这里,函数名清楚地表明您正在从数组中获取信息。所以没有混淆。
引用的主要优点是,它们总是包含有效值,比指针更干净,并且支持多态性,而不需要任何额外的语法。如果这些优点都不适用,就没有理由比指针更喜欢引用。
从wiki复制-
A consequence of this is that in many implementations, operating on a variable with automatic or static lifetime through a reference, although syntactically similar to accessing it directly, can involve hidden dereference operations that are costly. References are a syntactically controversial feature of C++ because they obscure an identifier's level of indirection; that is, unlike C code where pointers usually stand out syntactically, in a large block of C++ code it may not be immediately obvious if the object being accessed is defined as a local or global variable or whether it is a reference (implicit pointer) to some other location, especially if the code mixes references and pointers. This aspect can make poorly written C++ code harder to read and debug (see Aliasing).
我百分之百同意这一点,这就是为什么我认为只有当你有充分的理由这样做的时候,你才应该使用一个参考。
- 我在很大程度上也同意这种观点,但是我现在又回到了这样的观点:对于纯粹的句法问题来说,丢失对空指针的内置保护有点太昂贵了,特别是因为——尽管更明确——指针语法还是相当难看。
- 我想情况也是一个重要因素。我认为当当前的代码基主要使用指针时尝试使用引用是一个坏主意。如果你期望他们是参考,那么事实上,他们的如此含蓄是不重要的,也许……
"尽可能使用引用"规则存在问题,如果要保留引用以供进一步使用,则会出现此问题。为了用示例说明这一点,假设您有以下类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class SimCard
{
public:
explicit SimCard(int id):
m_id(id)
{
}
int getId() const
{
return m_id;
}
private:
int m_id;
};
class RefPhone
{
public:
explicit RefPhone(const SimCard & card):
m_card(card)
{
}
int getSimId()
{
return m_card.getId();
}
private:
const SimCard & m_card;
}; |
起初,在RefPhone(const SimCard & card)构造函数中通过引用传递参数似乎是一个好主意,因为它可以防止向构造函数传递错误/空指针。它以某种方式鼓励在堆栈上分配变量,并从RAII中获益。
1 2 3 4
| PtrPhone nullPhone(0); //this will not happen that easily
SimCard * cardPtr = new SimCard(666); //evil pointer
delete cardPtr; //muahaha
PtrPhone uninitPhone(cardPtr); //this will not happen that easily |
但是,暂时性的东西会摧毁你的幸福世界。
1 2 3
| RefPhone tempPhone(SimCard(666)); //evil temporary
//function referring to destroyed object
tempPhone.getSimId(); //this can happen |
因此,如果您盲目地坚持引用,那么就权衡了传递无效指针的可能性,以换取存储对已销毁对象的引用的可能性,这基本上具有相同的效果。
编辑:请注意,我坚持"尽你所能地使用引用,尽你所能地使用指针"的原则。避免使用指针,直到你不能这样做。"从最乐观和接受的答案(其他答案也建议这样)。尽管这应该是显而易见的,但示例并没有显示这样的引用是不好的。但是,它们可能被误用,就像指针一样,它们会给代码带来自己的威胁。
指针和引用之间存在以下差异。
当涉及到传递变量时,传递引用看起来像传递值,但具有指针语义(类似于指针)。
引用不能直接初始化为0(空)。
不能修改引用(引用,不引用对象)(相当于"*const"指针)。
常量引用不能接受临时参数。
局部常量引用延长了临时对象的生存期
考虑到这些,我目前的规则如下。
- 对将在函数范围内本地使用的参数使用引用。
- 当0(空)是可接受的参数值或需要存储参数以供进一步使用时,请使用指针。如果0(空)是可接受的,我将向参数添加"_n"后缀,请使用受保护的指针(如qt中的qpointer),或者只记录它。您还可以使用智能指针。与普通指针相比,您必须更小心地使用共享指针(否则,最终可能会导致设计内存泄漏和责任混乱)。
- 您的示例的问题不是引用是不安全的,而是您依赖对象实例范围之外的某个内容来保持私有成员的活动状态。const SimCard & m_card;只是一个写得不好的代码。
- @普拉门科,我很生气,你不明白这个例子的目的。const SimCard & m_card是否正确取决于上下文。这篇文章中的信息并不是说引用是不安全的(如果你努力尝试的话,它们可能是不安全的)。信息是你不应该盲目地坚持"尽可能使用参考"的咒语。示例是"尽可能使用参考"原则的积极使用的结果。这应该是清楚的。
- 你的回答有两件事困扰着我,因为我认为这可能误导一些人试图了解更多的事情。1。这篇文章是单向的,很容易让人觉得参考资料不好。您只提供了一个如何不使用引用的示例。2。在你的例子中,你不清楚它出了什么问题。是的,临时会得到破坏者,但不是那行错了,而是类的实现。
- 你几乎不应该有像const SimCard & m_card这样的成员。如果您想在临时性方面提高效率,请添加explicit RefPhone(const SimCard&& card)构造函数。
- @如果你不能用基本的理解来阅读,那么你就有比被我的帖子误导更大的问题。我不知道怎么才能更清楚。看第一句话。"尽可能使用引用"的咒语有问题!在我的帖子里,你在哪里找到了一个引用错误的陈述?在我的文章末尾,你已经写下了在哪里使用参考文献,那么你是如何得出这样的结论的?这不是问题的直接答案?
- @Plamenko " Yes, temporary will get destroyet, but it was not that line that was wrong, it is the implementation of the class"你可以通过任何东西,只是暂时的。你是在自相矛盾,如果你说推荐人不应该正常地作为一个班级成员出现,以此来证明投反对票是正当的,因为这正是我要说的。如果可能,使用"引用"规则是完全错误的,并且const SimCard &成员是该规则的结果。没关系,哪一行是错的,因为任何一行都可能是错的。这与效率无关。
- @plamenko是一个接受const引用的构造函数,这很重要,而不是类成员(coliru.stacked crooked.com/a/aefa11254995a7ab)。"You only provided a single example of how not to use references."有很多关于如何使用引用的例子,所以如果我写了10000行典型的例子,我的答案会增加什么?我在为他人的答案做贡献,并专注于未涉及的话题。此外,这不是关于如何使用引用的问题,而是关于何时使用引用与指针的问题。
要记住的要点:
指针可以是NULL,引用不能是NULL。
引用更容易使用,当我们不想更改值,只需要函数中的引用时,可以使用const作为引用。
用于*的指针,而用于&的引用。
需要指针算术运算时使用指针。
可以有指向void类型int a=5; void *p = &a;的指针,但不能有对void类型的引用。
指针与引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void fun(int *a)
{
cout<<a<<'
'; // address of a = 0x7fff79f83eac
cout<<*a<<'
'; // value at a = 5
cout<<a+1<<'
'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
cout<<*(a+1)<<'
'; // value here is by default = 0
}
void fun(int &a)
{
cout<<a<<'
'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a); |
判决何时使用什么
指针:用于数组、链接列表、树实现和指针算法。
引用:在函数参数和返回类型中。
以下是一些指导原则。
函数使用传递的数据而不修改它:
如果数据对象很小,例如内置数据类型或小型结构,则按值传递。
如果数据对象是数组,请使用指针,因为这是您唯一的选择。使指针成为指向常量的指针。
如果数据对象是大小合适的结构,请使用常量指针或常量参考以提高程序效率。您可以节省所需的时间和空间复制结构或类设计。使指针或引用变为常量。
如果数据对象是类对象,则使用const引用。类设计的语义通常需要使用引用,这是C++添加的主要原因。因此,传递类对象参数的标准方法是通过引用。
函数修改调用函数中的数据:
1.如果数据对象是内置数据类型,请使用指针。如果你点代码就像fixit(&x),其中x是一个int,很明显这个函数打算修改x。
2.如果数据对象是数组,请使用唯一的选择:指针。
3.如果数据对象是结构,请使用引用或指针。
4.如果数据对象是类对象,请使用引用。
当然,这些只是指导方针,可能有理由使选择。例如,CIN使用基本类型的引用,这样您就可以使用CIN>>n而不是CIN>>&N。
引用更清晰、更容易使用,而且它们可以更好地隐藏信息。但是,不能重新分配引用。如果需要先指向一个对象,然后指向另一个对象,则必须使用指针。引用不能为空,因此,如果有任何可能存在相关对象可能为空,则不能使用引用。必须使用指针。如果要自己处理对象操作,即如果要为堆上而不是堆栈上的对象分配内存空间,则必须使用指针
1
| int *pInt = new int; // allocates *pInt on the Heap |
把我的一角钱放进去。我刚做了一个测试。对那件事嗤之以鼻。我只是让g++使用指针创建同一个小程序的汇编文件,而不是使用引用。当查看输出时,它们完全相同。除了符号命名。所以看性能(在一个简单的例子中)没有问题。
现在讨论指针和引用的主题。我觉得清晰是最重要的。我一看到暗示行为,我的脚趾就开始弯曲。我同意引用不能为空是一种很好的隐式行为。
取消对空指针的引用不是问题。它将使您的应用程序崩溃,并且易于调试。更大的问题是未初始化的指针包含无效值。这很可能导致内存损坏,导致未定义的行为,而没有明确的来源。
这就是我认为引用比指针更安全的地方。我同意前面的一个声明,即接口(应该清楚记录,见合同设计,BertrandMeyer)定义了函数参数的结果。考虑到这一切,我的偏好转到尽可能使用参考资料。
指针和引用具有相同的速度,指针可以做引用可以做的任何事情。它在很大程度上是基于个人的意见。使用引用是很常见的,除非您确实需要指针的一些特性。
对于指针,您需要它们指向某个对象,因此指针占用内存空间。
例如,采用整数指针的函数不会采用整数变量。所以您需要创建一个指针,让它首先传递给函数。
作为参考,它不会占用内存。您有一个整数变量,可以将其作为引用变量传递。就是这样。您不需要专门为它创建引用变量。
- 不。接受指针的函数不需要分配指针变量:您可以传递一个临时的&address。如果引用是一个对象的成员,那么它肯定会占用内存,而且,所有现存的编译器实际上都将引用实现为地址,因此您也不会在参数传递或取消引用方面保存任何内容。