C++对象:何时应使用指针或引用

12

我可以使用一个对象的指针或引用。我理解它们之间的区别在于指针需要手动删除,而引用仅在超出其作用域时才消失。

那么何时使用它们?它们之间有什么实际差异?

这些问题都没有回答我的疑虑:


你不需要删除指针本身,同时引用也不会自动删除所引用的对象。你确定你没有把引用和值/对象搞混了吗? - DaVinci
我对值和引用之间的区别非常清楚。我的问题是何时将对象声明为指向它的指针并传递指针,或者将其声明为值并传递其引用。 - rafaelxy
9个回答

19

引用基本上是带有限制的指针(必须在创建时进行绑定,不能重新绑定/为空)。如果您的代码使用这些限制很有意义,那么使用引用而不是指针允许编译器警告您意外违反它们。

这很像 const 修饰符:语言可以存在没有它,它只是一种额外的功能,使开发安全代码更容易。


你的意思是说,如果我有一个常量指针,它的行为就类似于引用? - rafaelxy
1
并不是说const的比较与指针本身有关:const指针和引用并不相同(更多信息请参考这里:http://www.parashift.com/c++-faq-lite/references.html#faq-8.5 )。这只是一个强制实施特定语义的语言特性的示例,让你不必自己去做。 - suszterpatt
3
“the language could exist without it” 的意思是C++语言即使没有引用也可以存在,但那样就会有一些事情你今天可以做却不能再做了(所以它不会是相同的语言)。例如,某些运算符重载结构只能使用引用而非指针来完成。比如,如果想要使用赋值运算符进行操作链接时就只能使用引用。 - Dan

8

我需要删除指针并在作用域结束前保留引用。

不,那是完全错误的。

使用new分配的对象必须使用delete来删除[*]。未使用new分配的对象不应被删除。可以有一个指向未使用new分配的对象的指针,也可以有一个指向使用new分配的对象的引用。

指针或引用是访问对象的方式,但它们本身不是对象,并且与对象的创建方式无关。概念上的区别在于,引用是对象的名称,而指针是包含另一个对象地址的对象。实际上,选择使用哪种方法的差异包括每种方法的语法以及引用不能为null且不可重新设置。

[*] 使用delete。使用new[]分配的数组必须使用delete[]删除。有一些工具可以帮助跟踪已分配的资源并为您进行这些调用,称为智能指针,因此直接调用应该很少见,而只需安排进行即可,但是仍然必须执行。


4

suszterpatt已经给出了很好的解释。如果你想要一个易记的经验法则,我建议如下:

如果可以使用引用,就尽量使用引用,只有当你无法避免时才使用指针。

更简短的说:优先选择使用引用而非指针。


4
这里有另一个答案(也许我应该编辑第一个答案,但由于它的重点不同,我认为将它们分开会更好)。使用new创建指针时,会保留其内存并持久化直到您调用delete函数来释放内存。但是,标识符的生命周期仍限于代码块的结尾。如果在函数中创建对象并将它们附加到外部列表中,则这些对象可能会在函数返回后安全地留在内存中,并且您仍然可以引用它们而无需标识符。
以下是我正在开发的 C++ 框架 Umbra 的简化示例。引擎中有一个模块列表(对象指针),引擎可以将对象追加到该列表中:
void UmbraEngine::addModule (UmbraModule * module) {
    modules.push(module);
    module->id = modules.size() - 1;
}

获取一个:

UmbraModule * UmbraEngine::getModule (int id) {
    for (UmbraModule **it=modules.begin(); it != modules.end(); it++) {
        if ((*it)->id == id) return *it;
    }
}

现在,我可以添加和获取模块,而不必知道它们的标识符:
int main() {
    UmbraEngine e;
    for (int i = 0; i < 10; i++) {
        e.addModule(new UmbraModule());
    }
    UmbraModule * m = e.getModule(5); //OK
    cout << m << endl; //"0x127f10" or whatever
    for (int j = 0; k < 10; j++) {
        UmbraModule mm; //not a pointer
        e.addModule(&mm);
    }
    m = e.getModule(15);
    cout << m << endl; //{null}
}

模块列表在整个程序运行期间都会保留,如果使用new实例化模块,我不需要关心模块的寿命 :). 这就是它的基本含义-通过指针,你可以拥有长寿的对象,而不需要标识符(或名称)来引用它们 :).

另一个简单而美好的例子如下:

void getVal (int * a) {
    *a = 10;
}
int main() {
    int b;
    getVal(&b);
    return b;
}

3
你会遇到很多情况,其中参数不存在或无效,这取决于代码的运行时语义。在这种情况下,你可以使用指针并将其设置为NULL(0)来表示此状态。除此之外,
- 指针可以重新分配到一个新的状态,而引用则不能。在某些情况下,这是可取的。 - 指针有助于传递所有权语义。在多线程环境中,如果参数状态用于在单独的线程中执行,并且你通常不轮询直到线程退出。现在线程可以删除它。

2

让我们先回答最后一个问题。然后第一个问题会更有意义。

问: "指针和引用之间的实际区别是什么?"

答:引用只是另一个变量的本地别名。如果您通过引用传递参数,则该参数与调用语句中列出的变量完全相同。但是,在内部,指针和引用通常没有区别。引用提供了“语法糖”,使您可以减少输入的数量,当您真正想要访问给定变量的单个实例时。

问:“我应该在何时使用它们?”

答:这将是个人偏好的问题。这是我遵循的基本规则。如果我需要在另一个范围内操作变量,并且该变量是一个内置类型,应该像内置类型一样使用的类(即std :: string等),或者是const类实例,则我通过引用传递。否则,我将通过指针传递。


2

嗯...不完全正确。作用域的是标识符。当你使用new创建一个对象时,但其标识符的作用域结束后,你可能会遇到内存泄漏(或者不会 - 这取决于你想要实现什么) - 对象仍然在内存中,但你已经无法引用它了。

区别在于指针是内存中的地址,因此如果你有以下代码:

int * a = new int;

a 是一个指针。你可以打印它 - 你会得到类似 "0x0023F1" 的东西 - 它只是一个地址。它没有值(尽管一些值存储在该地址的内存中)。

int b = 10;

b 是一个变量,它的值为 10。如果你打印它,你会得到 10

现在,如果你想让 a 指向 b 的地址,你可以这样做:

a = &b; //a points to b's address

或者,如果您想让a指向的地址具有b的值:

*a = b; //value of b is assigned to the address pointed by a

请编译此示例,并注释/取消注释第13行和第14行以查看差异(注意标识符指向的位置和值)。我希望输出结果是自说明的。
#include <iostream>

using namespace std;

int main()
{
    int * a = new int;
    int b = 10;
    cout << "address of a: " << a << endl;
    cout << "address of b: " << &b << endl;
    cout << "value of a: " << *a << endl;
    cout << "value of b: " << b << endl;
    a = &b; //comment/uncomment
    //*a = b; //comment/uncomment
    cout << "address of a: " << a << endl;
    cout << "address of b: " << &b << endl;
    cout << "value of a: " << *a << endl;
    cout << "value of b: " << b << endl;
}

0
事实上,您无法将引用重新绑定到另一个对象。 引用在编译时绑定,并且不能为null或重新绑定。 因此,如果您怀疑指针是否多余,则不是的 :)

好的,您的答案有所帮助,但并没有解决所有问题。我仍然不确定何时使用对对象值的引用或使用指向它的指针。 - rafaelxy

0

正如我的C++老师所说,指针指向内存位置,而引用是别名。因此,它们的主要优点是可以像对象名称一样在作用域中使用,即使该对象不可用也可以通过引用传递。

虽然指针可以重定向到其他位置,但引用就像常量指针一样,不能被重定向。因此,在函数中不能使用引用来遍历数组等。

然而,指针作为一个独立的实体占用一些内存,而引用与所引用的对象相同,不占用任何额外的空间。这是它的优点之一。

我还读到过引用的处理时间更短,

例如:

int & i = b ;

i++ ; 比

int * j = b ;

(*j) ++ ;

花费的时间更少,但我还需要确认这一点。如果有人能够证实这个说法,那就太好了。

欢迎留言 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接