我搜索并搜索了这个问题的答案,但找不到任何我实际上得到的东西#34;
我对c ++非常陌生,无法理解使用双指针,三指针等等。他们有什么意义?
任何人都可以启发我
答案 0 :(得分:47)
老实说,在编写良好的C ++中,你应该很少在库代码之外看到T**
。事实上,你拥有的星星越多,你就越接近获胜an award of a certain nature。
这并不是说永远不会要求指向指针的指针;您可能需要构造指向指针的指针,原因与您需要构造指向任何其他类型对象的指针相同。
特别是,当你在动态分配的节点周围进行混乱时,我可能期望在数据结构或算法实现中看到这样的事情吗?
通常,在此上下文之外, if 你需要传递对指针的引用,你就是这样做(即T*&
)而不是加倍指点,甚至那应该是相当罕见的。
在Stack Overflow上你会看到人们做着可怕的事情,指向动态分配数据指针的数组,试图实现效率最低的" 2D向量"他们可以想到。请不要受他们的启发。
总而言之,你的直觉并非毫无根据。
答案 1 :(得分:16)
你应该/必须知道指向指针指针的一个重要原因是你有时必须通过某种API(例如Windows API)与其他语言(例如C)进行交互。 / p>
这些API通常具有带有返回指针的输出参数的函数。但是,其他语言通常没有引用或兼容(使用C ++)引用。这是指向指针指针的情况。
答案 2 :(得分:12)
我对c ++非常陌生,无法理解使用双指针,三指针等等。他们有什么意义?
理解C中的指针的诀窍就是回到基础知识,你可能从未教过它。他们是:
x
是T
类型的变量,则&x
是T*
类型的值。x
评估为T*
类型的值,则*x
是T
类型的变量。更具体地说...... x
评估类型T*
的值等于&a
某个类型为a
的变量T
,则{ {1}}是*x
的别名。现在一切都如下:
a
int x = 123;
是x
类型的变量。其值为int
。
123
int* y = &x;
是y
类型的变量。 int*
是x
类型的变量。因此int
是&x
类型的值。因此,我们可以将int*
存储在&x
。
y
*y = 456;
评估变量y
的内容。这是类型y
的值。将int*
应用于*
类型的值会提供类型为int*
的变量。因此我们可以为它分配456。什么是int
?它是*y
的别名。因此,我们刚刚将{45}分配给x
。
x
什么是int** z = &y;
?它是z
类型的变量。什么是int**
?由于&y
是y
类型的变量,因此int*
必须是&y
类型的值。因此,我们可以将其分配给int**
。
z
什么是**z = 789;
?从内到外工作。 **z
评估为z
。因此int**
是*z
类型的变量。它是int*
的别名。因此,这与y
相同,我们已经知道那是什么了;它是*y
的别名。
不,真的,重点是什么?
在这里,我有一张纸。它说x
。那是房子吗?不,这是一张纸,上面写着房子的地址。但是我们可以使用那张纸找到房子。
在这里,我有一千万张纸,都是编号。论文编号123456说1600 Pennsylvania Avenue Washington DC
。 123456是一所房子吗?不,这是一张纸吗?不。但是我仍然有足够的信息可以找到房子。
这就是重点:通常我们需要通过多个间接层来引用实体以方便。
也就是说,双指针令人困惑,并且表明你的算法不够抽象。尽量使用好的设计技巧来避免它们。
答案 3 :(得分:8)
在c ++中使用较少。但是,在C中,它可能非常有用。假设你有一个函数将malloc一些随机数量的内存并用一些东西填充内存。如果必须调用函数来获取分配所需的大小,然后调用另一个将填充内存的函数,那将是一件痛苦的事。相反,你可以使用双指针。双指针允许函数将指针设置为内存位置。还有一些其他的东西可以用来,但这是我能想到的最好的东西。
int func(char** mem){
*mem = malloc(50);
return 50;
}
int main(){
char* mem = NULL;
int size = func(&mem);
free(mem);
}
答案 4 :(得分:5)
双指针,只是指向指针的指针。常见用法是字符串数组。想象一下几乎所有C / C ++程序中的第一个函数:
int main(int argc, char *argv[])
{
...
}
也可以写
int main(int argc, char **argv)
{
...
}
变量 argv 是指向char的指针数组的指针。这是传递C“字符串”数组的标准方法。为什么这样?我已经看到它用于多语言支持,错误字符串块等等。
不要忘记指针只是一个数字 - 计算机内存“slot”的索引。就是这样,仅此而已。所以双指针是一块内存的索引,恰好将另一个索引保存到其他地方。如果你愿意,可以使用数学连接点。
这就是我向孩子们解释指针的方法:
想象一下,计算机内存是一系列的盒子。每个盒子上都写有一个数字,从零开始,向上增加1,到存在多少字节的内存。假设你有一个指向内存中某个地方的指针。这个指针只是方框号。我的指针是,比如说4.我看着方框#4。里面是另一个数字,这次它是6.所以现在我们看看#6框,并得到我们想要的最终结果。我的原始指针(表示“4”)是一个双指针,因为它的框的内容是另一个框的索引,而不是最终结果。
近来,指针本身已成为编程的贱民。回到不久的过去,传递指针指针是完全正常的。但随着Java的普及,以及在C ++中越来越多地使用pass-by-reference,对指针的基本理解有所下降 - 尤其是当Java成为第一年的计算机科学初学者语言时,就像Pascal和C. p>
我认为关于指针的很多毒液是因为人们根本不理解它们。人们不理解的事情受到嘲笑。所以他们变得“太难”和“太危险”。我想,即使据说有学识的人提倡Smart Pointers等,这些想法也是可以预期的。但实际上有一个非常强大的编程工具。老实说,指针是编程的魔力,而且总之,它们只是一个数字。
答案 5 :(得分:1)
在许多情况下,Foo*&
代替Foo**
。在这两种情况下,您都有一个可以修改其地址的指针。
假设您有一个抽象的非值类型,并且需要返回它,但返回值由错误代码占用:
error_code get_foo( Foo** ppfoo )
或
error_code get_foo( Foo*& pfoo_out )
现在一个函数参数是可变的很少有用,所以改变最外面的指针ppFoo
指向的位置的能力很少有用。但是,指针是 nullable - 所以如果get_foo
的参数是可选,则指针就像一个可选的引用。
在这种情况下,返回值是原始指针。如果它返回一个拥有的资源,它通常应该是std::unique_ptr<Foo>*
- 在该间接级别的智能指针。
如果相反,它返回一个指向它不共享所有权的东西的指针,那么原始指针就更有意义了。
Foo**
除了这些&#34;原油输出参数&#34;之外还有其他用途。如果您具有多态非值类型,则非拥有句柄为Foo*
,并且您希望拥有int*
的原因与您希望拥有Foo**
的原因相同}。
然后引导您询问&#34;为什么要int*
?&#34;在现代C ++中,int*
是对int
的非拥有可空可变引用。当存储在struct
而不是引用时,它的行为会更好(结构中的引用会在赋值和复制周围生成令人困惑的语义,尤其是在与非引用混合时)。
您有时可以将int*
替换为std::reference_wrapper<int>
,std::optional<std::reference_wrapper<int>>
,但请注意,这将是简单int*
的2倍。
因此有正当理由使用int*
。完成后,如果需要指向非值类型的指针,则可以合法地使用Foo**
。你甚至可以通过拥有一个你想要操作的int**
连续数组来到达int*
。
合法地接触三星级程序员变得更加困难。现在你需要一个合理的理由(比方说)希望通过间接传递Foo**
。通常在您达到这一点之前很久,您应该考虑抽象和/或简化您的代码结构。
所有这一切都忽略了最常见的原因;与C API交互。 C没有unique_ptr
,它没有span
。它倾向于使用原始类型而不是结构,因为结构需要基于函数的笨拙访问(没有运算符重载)。
因此,当C ++与C交互时,有时会比同等的C ++代码多出0到3 {/ 1}}。
答案 6 :(得分:0)
用法是指向指针,例如,如果你想通过引用将指针传递给方法。
答案 7 :(得分:0)
在C ++中,如果要将指针作为out或in / out参数传递,则通过引用传递它:
int x;
void f(int *&p) { p = &x; }
但是,引用不能(“合法地”)是nullptr
,因此,如果指针是可选的,则需要指向指针的指针:
void g(int **p) { if (p) *p = &x; }
当然,既然C ++ 17你有std::optional
,但是,“双指针”几十年来一直是惯用的C / C ++代码,所以应该没问题。此外,使用情况不是很好,你要么:
void h(std::optional<int*> &p) { if (p) *p = &x) }
在通话网站上有点难看,除非你已经有std::optional
,或者:
void u(std::optional<std::reference_wrapper<int*>> p) { if (p) p->get() = &x; }
本身并不是那么好看。
另外,有些人可能会争辩说g
在通话网站上读得更好:
f(p);
g(&p); // `&` indicates that `p` might change, to some folks
答案 8 :(得分:0)
双指针有什么实际用途?
这是一个实际的例子。假设你有一个函数,并且你想向它发送一个字符串参数数组(也许你有一个你想要传递params的DLL)。这看起来像这样:
#include <iostream>
void printParams(const char **params, int size)
{
for (int i = 0; i < size; ++i)
{
std::cout << params[i] << std::endl;
}
}
int main()
{
const char *params[] = { "param1", "param2", "param3" };
printParams(params, 3);
return 0;
}
您将发送一个const char
指针数组,每个指针指向一个空终止的C字符串的开头。编译器会将你的数组衰减到函数参数的指针,因此得到的是const char **
指向const char
指针数组的第一个指针的指针。由于此时数组大小已丢失,因此您需要将其作为第二个参数传递。
答案 9 :(得分:0)
我使用它的一个案例是在C中操作链表的函数。
有
struct node { struct node *next; ... };
表示列表节点,
struct node *first;
指向第一个元素。所有的操作函数都需要struct node **
,因为即使列表为空,我也能保证这个指针不是NULL
,而且我不需要任何特殊的插入和删除情况:
void link(struct node *new_node, struct node **list)
{
new_node->next = *list;
*list = new_node;
}
void unlink(struct node **prev_ptr)
{
*prev_ptr = (*prev_ptr)->next;
}
要插入列表的开头,只需将指针传递给first
指针,即使first
的值为NULL
,它也会做正确的事。< / p>
struct node *new_node = (struct node *)malloc(sizeof *new_node);
link(new_node, &first);
答案 10 :(得分:0)
多个间接很大程度上是C的保留(既没有引用也没有容器类型)。你不应该在编写良好的C ++中看到多个间接,除非你正在处理遗留的C库或类似的东西。
话虽如此,多个间接不属于一些相当常见的用例。
在C和C ++中,在大多数情况下,数组表达式将从“T
的N元素数组”“衰减”为“指向T
” 1 。因此,假设数组定义如
T *a[N]; // for any type T
将a
传递给函数时,如下所示:
foo( a );
表达式 a
将从“T *
的N元素数组”转换为“指向T *
的指针”或T **
,那么函数实际收到的是
void foo( T **a ) { ... }
它们弹出的第二个位置是你想要一个函数来修改指针类型的参数,比如
void foo( T **ptr )
{
*ptr = new_value();
}
void bar( void )
{
T *val;
foo( &val );
}
由于C ++引入了引用,你可能不会经常看到它。您通常只会在使用基于C的API时看到它。
您也可以使用多个间接来设置“锯齿状”数组,但是您可以使用C ++容器实现相同的功能,以便更多减少痛苦。但如果你感到自虐:
T **arr;
try
{
arr = new T *[rows];
for ( size_t i = 0; i < rows; i++ )
arr[i] = new T [size_for_row(i)];
}
catch ( std::bad_alloc& e )
{
...
}
但是大多数时候在C ++中,你应该看到多个间接的唯一时间是指针数组“衰减”到指针表达式本身。
<小时/>
sizeof
或一元&
运算符的操作数时,或者是用于初始化声明中另一个数组的字符串文字时,会出现此规则的例外情况。