首页 文章

何时使用引用与指针

提问于
浏览
339

我理解指针与引用的语法和一般语义,但是我应该如何决定何时在API中使用引用或指针?或多或少是合适的?

当然有些情况需要一个或另一个( operator++ 需要一个引用参数),但总的来说我发现我更喜欢使用指针(和const指针),因为语法清楚地表明变量是破坏性地传递的 .

例如 . 在以下代码中:

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++ )?是否有任何性能问题?

编辑(已完成):

除了允许NULL值和处理原始数组之外,似乎选择归结为个人偏好 . 我接受了下面引用Google's C++ Style Guide的答案,因为它们提供了"References can be confusing, as they have value syntax but pointer semantics."的观点 .

由于清理不应该为NULL的指针参数所需的额外工作(例如 add_one(0) 将调用指针版本并在运行时中断),从可维护性的角度来看,使用必须存在对象的引用是有意义的,尽管它是一个羞于失去句法的清晰度 .

19 回答

  • 7

    复制自wiki -

    其结果是,在许多实现中,通过引用对具有自动或静态生命周期的变量进行操作,尽管在语法上类似于直接访问它,但可能涉及隐藏的取消引用操作,这是昂贵的 . 引用是C语法上有争议的特性,因为它们模糊了标识符的间接级别;也就是说,与C代码不同,其中指针通常在语法上突出,在C的大块中如果被访问的对象被定义为本地或全局变量,或者它是否是某个其他位置的引用(隐式指针),特别是如果代码混合引用和指针,则代码可能不会立即显而易见 . 这方面可能会使写得不好的C代码更难以读取和调试(参见别名) .

    我同意100%这一点,这就是为什么我认为你应该只在你有充分理由这样做时使用参考 .

  • 12

    像其他人已经回答的那样:除非 NULL / nullptr 变量确实是有效状态,否则始终使用引用 .

    约翰卡马克关于这个问题的观点是相似的:

    NULL指针是C / C中最大的问题,至少在我们的代码中是这样 . 单个值作为标志和地址的双重使用会导致令人难以置信的致命问题 . 只要有可能,C引用应该优于指针;虽然引用“真的”只是一个指针,但它具有非NULL的隐式 Contract . 当指针变为引用时执行NULL检查,然后您可以忽略此问题 .

    http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

    编辑2012-03-13

    用户Bret Kuhns正确评论:

    C 11标准已经定稿 . 我认为现在是时候在这个帖子中提到大多数代码都应该完美地使用引用,shared_ptr和unique_ptr的组合 .

    确实如此,但问题仍然存在,即使用智能指针替换原始指针也是如此 .

    例如, std::unique_ptrstd::shared_ptr 都可以通过其默认构造函数构造为"empty"指针:

    ......意味着使用它们而不验证它们不是空的可能会导致崩溃,这正是J. Carmack讨论的全部内容 .

    然后,我们有一个有趣的问题:“我们如何将智能指针作为函数参数传递?”

    Jonanswer问题C++ - passing references to boost::shared_ptr,以下评论显示,即便如此,通过复制或引用传递智能指针并不像人们想的那样明确(我默认_181422_,但我可能是错的) .

  • 3

    指针和引用具有相同的速度,指针可以做所有引用都可以和更多 . 它主要基于个人意见而使用 . 除非你真的需要指针的某些功能,否则通常使用引用 .

  • 0

    通常,成员变量永远不应该是引用,因为没有任何意义 . 如果您不提供赋值运算符,则会导致类不可赋值 . 此外,一旦将成员引用设置为引用某个对象,就无法更改该成员以引用其他对象 . 参考的最合适用法是使用函数参数,该参数允许通过引用传递 .

  • 12

    尽可能使用参考,指向任何地方的指针 .

    避免指针,直到你不能 .

    原因是指针比其他任何结构都更难以跟踪/阅读,更不安全和更危险的操作 .

    所以经验法则是只有在没有其他选择时才使用指针 .

    例如,当函数在某些情况下可以返回nullptr并且假设它将返回时,返回指向对象的指针是一个有效选项 . 也就是说,更好的选择是使用类似 boost::optional 的东西 .

    另一个例子是使用指向原始内存的指针来进行特定的内存操作 . 这应隐藏并本地化在代码的非常狭窄的部分,以帮助限制整个代码库的危险部分 .

    在您的示例中,使用指针作为参数没有意义,因为:

    • 如果你提供 nullptr 作为参数,你将进入undefined-behavior-land;

    • 引用属性版本不允许(不容易发现技巧)1的问题 .

    • 引用属性版本对于用户来说更容易理解:您必须提供有效的对象,而不是可以为null的对象 .

    如果函数的行为必须使用或不使用给定对象,那么使用指针作为属性表明您可以传递 nullptr 作为参数,它对函数来说很好 . 这是用户和实现之间的 Contract .

  • 31

    性能完全相同,因为引用在内部实现为指针 . 因此,您无需担心这一点 .

    关于何时使用引用和指针,没有普遍接受的约定 . 在少数情况下,您必须返回或接受引用(例如,复制构造函数),但除此之外,您可以根据需要自由地执行 . 我遇到的一个相当常见的约定是当参数必须在NULL值为ok时引用现有对象和指针时使用引用 .

    一些编码约定(如Google's)规定应始终使用指针或const引用,因为引用有一些不清楚的语法:它们具有引用行为但值语法 .

  • 0

    我的经验法则是:

    • 使用指针输出或输入/输出参数 . 因此可以看出 Value 将会发生变化 . (你必须使用 &

    • 如果NULL参数是可接受的值,则使用指针 . (如果是传入参数,请确保它是 const

    • 如果传入参数不能为NULL且不是基本类型( const T& ),请使用对该参数的引用 .

    • 返回新创建的对象时使用指针或智能指针 .

    • 使用指针或智能指针作为结构或类成员而不是引用 .

    • 使用别名参考(例如 int &current = someArray[i]

    无论您使用哪一个,如果它们不明显,请不要忘记记录您的功能及其参数的含义 .

  • -1

    使用引用作为最后的手段 . 在堆栈或堆上分配实例,使用它们 .

    使用参数范围的引用来获得最小的影响 . 如果您使用引用,因为指针对您来说太难了,那么请转到另一种语言 .

  • 256

    只是把我的一角钱 . 我只是进行了测试 . 一个偷偷摸摸的人 . 我只是让使用指针创建相同迷你程序的程序集文件与使用引用相比 . 在查看输出时,它们完全相同 . 符号除了符号 . 所以看一下性能(在一个简单的例子中)没有问题 .

    现在关于指针与引用的主题 . 恕我直言,我认为清晰度高于一切 . 一旦我读到隐含的行为,我的脚趾开始卷曲 . 我同意引用不能为NULL是一种很好的隐式行为 .

    取消引用NULL指针不是问题 . 它会使您的应用程序崩溃,并且易于调试 . 更大的问题是包含无效值的未初始化指针 . 这很可能导致内存损坏,导致未定义的行为,而没有明确的来源 .

    这是我认为引用比指针更安全的地方 . 我同意之前的声明,即接口(应该清楚地记录,参见 Contract 设计,Bertrand Meyer)定义函数参数的结果 . 现在考虑到这一点,我的偏好在任何地方/尽可能使用参考 .

  • 0

    以下是一些指导原则 .

    函数使用传递的数据而不修改它:

    • 如果数据对象很小,例如内置数据类型或小型结构,则按值传递 .

    • 如果数据对象是数组,请使用指针,因为这是您唯一的选择 . 使指针成为指向const的指针 .

    • 如果数据对象是一个大小合适的结构,请使用const指针或const引用来提高程序效率 . 您可以节省复制结构或类设计所需的时间和空间 . 使指针或引用const .

    • 如果数据对象是类对象,则使用const引用 . 类设计的语义通常需要使用引用,这是C添加此功能的主要原因 . 因此,传递类对象参数的标准方法是引用 .

    函数修改调用函数中的数据:

    1.如果数据对象是内置数据类型,请使用指针 . 如果你发现像fixit(&x)这样的代码,其中x是一个int,很明显这个函数打算修改x .

    2.如果数据对象是数组,请使用您唯一的选择:指针 .

    3.如果数据对象是结构,请使用引用或指针 .

    4.如果数据对象是类对象,请使用引用 .

    当然,这些只是指导方针,可能有理由做出不同的选择 . 例如,cin使用基本类型的引用,以便您可以使用cin >> n而不是cin >>&n .

  • 0

    引用更清晰,更易于使用,并且它们可以更好地隐藏信息 . 但是,参考不能重新分配 . 如果需要先指向一个对象然后指向另一个对象,则必须使用指针 . 引用不能为null,因此如果存在相关对象可能为null的任何可能性,则不得使用引用 . 你必须使用指针 . 如果你想自己处理对象操作,即如果你想为堆上的对象而不是堆栈上的对象分配内存空间,你必须使用指针

    int *pInt = new int; // allocates *pInt on the Heap
    
  • 55

    Points to keep in mind:

    • 指针可以 NULL ,引用不能 NULL .

    • 参考更容易使用, const 可以在我们不想改变 Value 时用作参考只需要一个函数中的引用 .

    • 指针与 * 一起使用,而引用与 & 一起使用 .

    • 需要指针算术运算时使用指针 .

    • 您可以指向void类型 int a=5; void *p = &a; ,但不能引用void类型 .

    Pointer Vs Reference

    void fun(int *a)
    {
        cout<<a<<'\n'; // address of a = 0x7fff79f83eac
        cout<<*a<<'\n'; // value at a = 5
        cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
        cout<<*(a+1)<<'\n'; // value here is by default = 0
    }
    void fun(int &a)
    {
        cout<<a<<'\n'; // reference of original a passed a = 5
    }
    int a=5;
    fun(&a);
    fun(a);
    

    Verdict when to use what

    Pointer :用于数组,链表,树实现和指针算法 .

    Reference :在函数参数和返回类型中 .

  • 1

    这不是品味问题 . 以下是一些明确的规则 .

    如果要在声明它的范围内引用静态声明的变量,那么使用C引用,它将是完全安全的 . 这同样适用于静态声明的智能指针 . 通过引用传递参数是此用法的示例 .

    如果你想引用范围比声明范围更宽的范围,那么你应该使用引用计数智能指针来保证它的安全性 .

    您可以引用集合的元素,并提供语法方便的参考,但它并不安全;该元素可以随时删除 .

    要安全地保存对集合元素的引用,必须使用引用计数的智能指针 .

  • -5

    免责声明:除了引用不能为NULL或“反弹”(意味着不能改变它们的别名的对象)这一事实,它真的归结为品味问题,所以我不会说“这个更好” .

    也就是说,我不同意你在帖子中的最后一句话,因为我不认为代码在参考文献中失去了清晰度 . 在你的例子中,

    add_one(&a);
    

    可能比清楚的更清楚

    add_one(a);
    

    因为你知道a的 Value 很可能会发生变化 . 另一方面,功能的签名

    void add_one(int* const n);
    

    有点不清楚:是n将是一个整数还是一个数组?有时您只能访问(记录不良) Headers 和签名

    foo(int* const a, int b);
    

    乍一看并不容易解释 .

    Imho,当没有(重新)分配和重新绑定(在前面解释的意义上)需要时,引用和指针一样好 . 此外,如果开发人员只使用指针作为数组,那么函数签名就不那么模糊了 . 更不用说运算符语法对引用更具可读性的事实 .

  • 14

    “尽可能使用参考”规则有问题,如果想继续使用的话请出现 . 为了举例说明这一点,假设您有以下类 .

    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) 构造函数中的参数似乎是个好主意,因为它可以防止将错误的/ null指针传递给构造函数 . 它以某种方式鼓励在堆栈上分配变量并从RAII中获益 .

    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
    

    但是,临时工会来破坏你的幸福世界 .

    RefPhone tempPhone(SimCard(666));   //evil temporary
    //function referring to destroyed object
    tempPhone.getSimId();    //this can happen
    

    因此,如果你盲目地坚持引用,你就可以通过传递无效指针的可能性来存储对被破坏对象的引用,这具有基本相同的效果 .

    edit: Note that I sticked to the rule "Use reference wherever you can, pointers wherever you must. Avoid pointers until you can't." from the most upvoted and accepted answer (other answers also suggest so). Though it should be obvious, example is not to show that references as such are bad. They can be misused however, just like pointers and they can bring their own threats to the code.


    指针和引用之间存在以下差异 .

    • 当涉及到传递变量时,按引用传递看起来像是按值传递,但是具有指针语义(就像指针一样) .

    • 无法将引用直接初始化为0(null) .

    • 无法修改引用(引用,未引用的对象)(相当于"* const"指针) .

    • const引用可以接受临时参数 .

    • Local const references prolong the lifetime of temporary objects

    考虑到这些,我目前的规则如下 .

    • Use references for parameters that will be used locally within a function scope.

    • 当0(null)是可接受的参数值时使用指针,或者您需要存储参数以供进一步使用 . 如果0(null)是可接受的,我将"_n"后缀添加到参数,使用保护指针(如Qt中的QPointer)或只记录它 . 您还可以使用智能指针 . 对于共享指针而言,你必须比使用普通指针更加小心(否则你最终可能因设计内存泄漏和责任混乱) .

  • 3

    对于指针,您需要它们指向某些东西,因此指针会占用内存空间 .

    例如,采用整数指针的函数不会采用整数变量 . 所以你需要先创建一个指针,然后传递给函数 .

    至于参考,它不会花费内存 . 您有一个整数变量,您可以将其作为引用变量传递 . 而已 . 您不需要专门为它创建引用变量 .

  • -13

    来自C++ FAQ Lite -

    尽可能使用引用,并在必要时使用引用 . 每当您不需要“重新安装”时,引用通常优先于指针 . 这通常意味着引用在类的公共接口中最有用 . 引用通常出现在对象的皮肤上,而指针则出现在内部 . 上面的例外是函数的参数或返回值需要“sentinel”引用 - 一个不引用对象的引用 . 这通常最好通过返回/获取指针,并给出NULL指针这一特殊意义(引用必须始终为别名对象,而不是取消引用的NULL指针) . 注意:旧行C程序员有时不喜欢引用,因为它们提供了调用者代码中不明确的引用语义 . 然而,在经历了一些C经验之后,人们很快意识到这是一种信息隐藏形式,这是一种资产而非负债 . 例如,程序员应该用问题的语言而不是机器的语言编写代码 .

  • 2

    任何性能差异都会很小,以至于无法使用不太清楚的方法 .

    首先,一个未提及引用通常优越的案例是 const 引用 . 对于非简单类型,传递 const reference 避免了创建临时和不关心的问题(因为值很重要,因为查看地址并将其传递给函数可能会让您认为值已更改 .

    无论如何,我基本上同意你的观点 . 我不喜欢引用修改它们的值的函数,因为这不是函数正在做的事情 . 在这种情况下,我也更喜欢使用指针 .

    当您需要返回复杂类型的值时,我倾向于更喜欢引用 . 例如:

    bool GetFooArray(array &foo); // my preference
    bool GetFooArray(array *foo); // alternative
    

    这里,函数名称清楚地表明您正在获取数组中的信息 . 所以没有混乱 .

    引用的主要优点是它们总是包含有效值,比指针更清晰,并且支持多态而无需任何额外的语法 . 如果这些优点都不适用,则没有理由更喜欢对指针的引用 .

  • 5

    我更喜欢使用指针 . 至少你很清楚你在做什么 . 我觉得由于STL及其对代码的语法含义,主要使用引用 . 正因为如此,许多C标准库新奇,如std :: move .....,以获得你想要的,而不是你直觉所想到的 .

相关问题