C++: What are scenarios where using pointers is a “Good Idea”(TM)?
Possible Duplicate:
Common Uses For Pointers?
我仍然在学习C++的基础知识,但是我已经知道足够做一些有用的小程序了。
我理解指针的概念,并且在教程中看到的示例对我来说是有意义的。然而,在实际层面上,作为一个(以前的)PHP开发人员,我还没有信心在我的程序中实际使用它们。
事实上,到目前为止,我还没有觉得有必要使用任何指针。我有我的类和函数,而且我似乎在没有使用任何指针的情况下做得非常好(更不用说指针指向指针了)。我禁不住为我的小节目感到骄傲。
不过,我知道我在C++的一个最重要的功能之一,一个双刃剑失踪:指针和内存管理可以造成破坏,看似随机崩溃,很难找到错误和安全漏洞…但同时,如果使用得当,它们必须允许巧妙而高效的编程。
所以:不要用指针告诉我遗漏了什么。
什么是必须使用指针的好场景?他们允许你做那些你不能做的事情?他们以什么方式使你的程序更有效率?
那么指向指针的指针呢????
[编辑:所有的答案都很有用。一个问题是我们不能"接受"一个以上的答案。我经常希望我能。事实上,所有的答案结合起来有助于更好地理解整个情况。谢谢。
当我想给一个类访问一个对象的权限时,我使用指针,而不给它该对象的所有权。即使这样,我也可以使用引用,除非我需要能够更改我访问的对象和/或不需要对象选项,在这种情况下,指针将为空。
这个问题以前就有人问过。我的回答是:
我使用的指针大约是我写的C++代码中每六行的一个指针。从我的头顶上看,这些是最常见的用途:
- 当我需要动态地创建一个生存期超过创建它的范围的对象时。
- 当我需要分配一个在编译时大小未知的对象时。
- 当我需要将一个对象的所有权从一个对象转移到另一个对象而不实际复制它时(就像在一个链表/堆/任何真正大的、昂贵的结构中一样)
- 当我需要从两个不同的地方引用同一个对象时。
- 当我需要切片一个数组而不复制它时。
- 当我需要使用编译器内部函数来生成特定于CPU的指令时,或者解决编译器发出次优或幼稚代码的情况。
- 当我需要直接写入特定的内存区域时(因为它有内存映射IO)。
指针通常在C++中使用。熟悉它们,将有助于您理解更广泛的代码范围。这就是说,如果你能避开它们,那就太好了。然而,随着你的程序变得越来越复杂,你很可能需要它们,即使只是与其他库接口。
主要指针用于引用动态分配的内存(由
new 返回)。它们允许函数接受由于太大或无法复制而无法复制到堆栈上的参数,例如系统调用返回的对象。(我也认为堆栈对齐,可能是一个问题,但太模糊,无法自信。)
在嵌入式程序设计中,它们被用来指硬件寄存器之类的东西,这些寄存器要求代码写入内存中非常特定的地址。
指针还用于通过其基类接口访问对象。也就是说,如果我有一个类B,它是从类A
class B : public A {} 派生出来的。这是对象B的一个实例,通过向指向类A的指针(即:A *a = &b_obj; )提供地址,可以像访问类A一样访问它。使用指针作为数组的迭代器是C语言的一个习惯用法。在旧的C++代码中,这可能仍然是常见的,但可能被认为是STL迭代器对象的一个可怜的表兄弟。
如果需要与C代码接口,您将始终需要处理用于引用动态分配对象的指针,因为没有引用。C字符串只是指向由nul' '字符终止的字符数组的指针。
一旦你对指针感到舒服,指向指针的指针就不会那么糟糕了。最明显的例子是
声明是C样式的,但它说我有指向char的指针数组。它被解释为C样式字符串(以nul''0'字符结尾的字符数组)的任意大小的数组(大小由argc携带)。
如果你没有感觉到需要指针,我不会花很多时间去担心它们,直到需要出现为止。
也就是说,指针可以帮助更高效地编程的主要方法之一是避免实际数据的副本。例如,假设您正在编写一个网络堆栈。您将收到一个要处理的以太网数据包。您依次将数据从"原始"以太网驱动程序向上传递到IP驱动程序,再传递到TCP驱动程序,例如,将HTTP驱动程序传递到处理它所包含的HTML的对象。
如果您要为每一个内容制作一个新的副本,那么在实际开始渲染之前,您至少要制作四个数据副本。
使用指针可以避免很多这样的情况——您不必复制数据本身,只需传递一个指向数据的指针。网络堆栈的每一个连续层都会查看自己的头,并向上传递一个指针,指向它认为的"有效负载",指向堆栈中的下一个更高层。下一层查看自己的头,修改指针以显示它所认为的有效负载,并将其传递到堆栈上。所有四个层都使用一个真实数据的副本,而不是数据的四个副本。
有几个场景需要指针:
- 如果将抽象基类与虚拟方法一起使用。您可以持有一个std::vector并在所有这些对象中循环,然后调用一个虚拟方法。这需要指针。
- 您可以将指向缓冲区的指针传递给从文件等读取的方法。
- 您需要在堆上分配大量内存。
从一开始就关心记忆问题是件好事。所以,如果你开始使用指针,你不妨看看智能指针,比如Boost的共享指针。
指针的一个主要用途是动态调整数组大小。当您在编译时不知道数组的大小时,您需要在运行时分配它。
1 | int *array = new int[dynamicSize]; |
如果这个问题的解决方案是使用STL中的
我只想说我很少使用指针。我使用引用和STL对象(deque、list、map等)。
一个好主意是当您需要返回一个对象时,调用函数应该是空闲的,或者当您不想按值返回时。
1 2 3 4 | List<char*>* fileToList(char*filename) { //dont want to pass list by value ClassName* DataToMyClass(DbConnectionOrSomeType& data) { //alternatively you can do the below which doesnt require pointers void DataToMyClass(DbConnectionOrSomeType& data, ClassName& myClass) { |
这几乎是我唯一使用的情况,但我不想那么难。另外,如果我想要一个函数修改一个变量并且不能使用返回值(比如我需要一个以上的函数)
1 | bool SetToFiveIfPositive(int**v) { |
指针有一个特殊的值,即
一般来说,指针是有用的,因为它们可以保存内存块的地址。它们在一些低级别的驱动程序中特别有用,在这些驱动程序中,它们被有效地逐字节操作一段内存。它们是C++从C继承的最强大的发明。
关于指向指针的指针,这里有一个"hello world"示例,演示如何使用它。
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 | #include <iostream> void main() { int i = 1; int j = 2; int *pInt = &i; //"pInt" points to"i" std::cout<<*pInt<<std::endl; // prints: 1 *pInt = 6; // modify i, i = 6 std::cout<<i<<std::endl; // prints: 6 int **ppInt = &pInt; //"ppInt" points to"pInt" std::cout<<**ppInt<<std::endl; // prints: 6 **ppInt = 8; // modify i, i = 8 std::cout<<i<<std::endl; // prints: 8 *ppInt = &j; // now pInt points to j *pInt = 10; // modify j, j = 10 std::cout<<j<<std::endl; // prints: 10 } |
如我们所见,"pint"是一个指向整数的指针,在开头指向"i"。使用它,您可以修改"i"。"ppint"是指向指向"pint"的指针的指针。有了它,您可以修改"pint",它恰好是一个地址。结果,"*ppint=&j"使"pint"现在指向"j"。所以我们有上面所有的结果。
您可以将它们用于链接列表、树等。它们是非常重要的数据结构。