为什么和什么时候值得使用指向指针的指针?

8

可能是重复问题:
C语言中指向指针的指针是如何工作的?

你好,

虽然我认为我已经通过编程的新手阶段,但我仍然有一些问题,这些问题在某种程度上似乎无法得到解释。是的,有很多“如何”教程,但几乎没有人解释为什么和/或何时使用某种技术。

在我的情况下,我发现在C++中有时会使用指向指针的指针。一个指向对象的指针不够吗?它的好处是什么?什么时候或者在哪里应该使用指向指针的指针?我在这个问题上感到有点迷茫。

我希望有经验的专家能回答这些问题,这些问题也希望其他不那么有经验的程序员能分享。;-)

谢谢大家。

朱伦。


http://stackoverflow.com//questions/897366/how-do-pointer-to-pointers-work-in-c - tster
1
这不是重复的问题。原始问题是关于C语言的,而这个问题是关于C++的。 - Daniel Daranas
7个回答

10

嗯,对于这样一个普遍的问题,回答有点困难。

C++程序员的第一个答案肯定是:不要在C++中使用指针!因为你有很多比指针更安全的处理问题的方法,你的目标应该是尽量避免使用它们 :)

因此,在C++中很少使用指向指针的指针。它们主要用于C语言中。首先,因为在C语言中,字符串是“char *”,所以当你需要一个“指向C字符串的指针”时,你最终会得到一个“char **”。其次,由于在C语言中没有引用,当你需要一个函数修改指针或将指针作为输出值时,你需要给出一个指向指针的参数。通常在分配内存的函数中找到它们,因为它们会给你一个指向已分配内存的指针。

如果你选择使用C++,尽量避免使用指针,通常有更好的方法。

我的建议


2
“不要在C++中使用指针”。认真的吗?那我是不是应该忘记DirectX编程、OpenGL、com对象、直接API调用?没有指针的编程有什么意义呢?这对我来说听起来像是无用的语法糖。 - SigTerm
@SigTerm:对于那些建议,我给予肯定。需要时降级到低级API(比如著名的&v[0])要容易得多,而不是用低级代码完成所有任务。在接近十年的时间里,我编写并维护了一个几百万行代码的C++应用程序的一部分(顺便说一句,它很可能已经安装在你的计算机上了)。最后,我的部分代码有超过100,000行。然而,在其中只有大约三个delete语句,主要是出于历史原因。 - sbi
@SigTerm:首先感谢您解释您的投票理由。其次,我不是您所提到的API方面的专家,但我认为它们是C API,对吗?当然,在C++中使用指针的一个用例是使用C API :) 我也许应该说“在设计C++时尽量不要使用指针”。当您使用API时,您别无选择。此外,由于OP的问题涉及指向指针的指针,因此在设计C++代码时使用指向指针的指针似乎并不是一种理想的方式。实际上,当我写指针时,我假设我在使用原始指针,因为我相当广泛地使用智能指针 ;) - neuro
@sbi:使用指针并不等同于使用new/delete。指针在动态内存分配例程之外有很多用途。 - SigTerm
@neuro:你的第一个问题是你没有解释为什么你认为指针不好,并声称“C++程序员不应该使用指针”,却没有引用任何人或给出合理的解释。这已经值得被点踩了。你也没有提到任何指针问题。任何没有合理解释的建议都应该被忽略。像这样的信仰会让你写额外的代码(使其看起来漂亮),而实际上并没有写任何有用的东西。包装器和智能指针在需要良好性能的情况下并不好,因为它们会增加开销。 - SigTerm
显示剩余3条评论

6
在C语言中,通过指针将参数传递给会改变它的函数。对于旧的或遗留代码(int main(int argc, char** argv)),在需要从C(COM / XPCOM)访问代码或由习惯使用C(或C风格)的人编写的代码中也会看到相同情况。
从“纯C ++”角度来看,在大多数情况下,使用指向指针的指针是一种糟糕的编码风格,因为大多数需要 ** 结构的情况都可以(而且应该)重构为使用更安全的替代方案(如 std :: 容器或 *& 参数)。

4

我能想到两种用例。

一种是继承自C的数组。在许多情况下,数组会自动降解为指向它们第一个元素的指针。如果你恰好有一个指向指针的数组,那么你就会得到一个指向指针的指针。
(顺便说一句,当你有一个指向指针的std::vector时,类似的事情也会发生:指针是完美的随机访问迭代器,我确实见过使用指针作为std::vector<>::iterator的std库实现。对于一个指向指针的std::vectorv.begin()将返回一个指向指针的指针。)

另一个用例是函数参数。对于函数参数,以指针方式传递某些东西表示调用者可能即使没有要传递的对象也会调用该函数;他们可以传递NULL。 (否则为什么要取一个指针而不是一个引用?在此处查看更多详细信息:here)。
对于指针的非const引用,表示被调用函数可能会为该指针分配一个新的地址。
因此,取一个指向指针的指针表示如果调用者传递了一个指向它的指针,则该函数可能会为指针分配一个新的地址,但也可以使用NULL进行调用。 例如:

void f(int** ppi);
void g(int i);

void h()
{
   f(NULL); // call `f()` only for its side-effects

   int* pi = NULL;
   f(&pi);  // call `f()` and get some object in *pi
   if(pi) 
     g(*pi); // use result
}

3

指向指针的指针应该在什么时候或何处使用?

在一个函数中,如果调用者要求返回指针,则可以选择性地将指针返回给调用者。这在系统API、某些COM对象等中经常使用。

int f(void** p = 0){
    //.......
}

如果调用者提供了p,则函数通过p传递指针。如果调用者没有提供p,则不返回指针。在某些情况下,这可能对调试有用。
“这个技术有哪些好处?”这个问题太宽泛了。看,这是一种非常简单的技术,没有什么神秘的好处。当你需要使用它时,就会用到它。这就是全部。这个概念没有隐藏的意义和秘密优势——这相当于问“在英语中使用字母'e'的好处是什么”。

+1 这是如此主流,然而许多其他答案都在说“你不应该这样做!” - Will

3
指向指针在C中具有最大的相关性。在C++中很少需要它们,因为你可以使用标准库中的容器或引用来工作。不过,在C语言中有两个流行的用例:
1. 指针数组,最广泛地用于main()函数的argv:指针给出了参数字符串数组(类型为char *)的地址。在C语言中,[]运算符适用于任何指针,因为将指针视为数组的等价物。
2. 一个函数参数,表示存储某个东西的地址的位置。用例:在C中,返回值经常用于错误处理或状态信息。参数用于传递有效负载。例如:一个函数“返回”新分配的内存的地址,但是使用一个参数。你将指向应该指向数据的指针的指针传递给函数。函数覆盖那个空间,所以它需要一个指向指针的指针。

2

只有在使用手动内存管理时才会用到它们,而在C++中这是非常罕见的,因此它们几乎没什么用处。即使是普通指针在大多数情况下也存在疑问。


1

实际上有两种用例。

首先,当您调用必须返回多个值的函数时,其中一些是指针。然后,您将提供指针的地址,以便它可以填充指向指针的指针:

int *p1 = 0, *p2 = 0, *p3 = 0;
multi_malloc(&p1, &p2, &p3); // Allocates three pointers

第二种情况是当您想要处理稀疏的二维数组:

int main(int argc, char **argv)

这是一个指向指针的指针。 argv[i] 是一个指向字符的指针。这样,argv[i][j] 就是第 i 行中的第 j 个字符。

您会高兴地听到,几乎没有使用指向指针的指针的用途。 int ***p; 几乎从不有用。


1
这非常C。在C++中,您可以使用指针引用,并以更简单(因此更少出错)的方式完成所有这些操作。 - Daniel Daranas
1
呵呵。int ***p 看起来像是你被审查了什么内容。 - detly
2
据我所知,根据C++标准,您最多可以进行8个级别的解引用。例如:int ********p。 - Aamir
@Aamir:我很确定没有这样的限制。你有参考资料吗? - Mike Seymour

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