指针的好处?

我最近对C编程感兴趣,所以我给自己写了一本书(K&R)并开始学习。

来自Java的大学课程(基础知识),指针是一个全新的篇章,从我在网上阅读的内容来看,这是一个相当困难的概念。 在进入指针章节之前,我的印象是指针是C的主要部分,并提供了很大的好处。

在阅读本章并了解指针是什么以及它们如何工作时,这些好处对我来说并不明显。

例如(请纠正我,如果我完全错了)在K&R书中引用指针它说,因为我们通过值调用,当在函数调用中传递变量时,我们几乎传递了变量的副本函数处理,因此函数不能对原始变量做任何事情,我们可以用指针克服它。

在稍后使用char指针的示例中,该书说增加char指针是合法的,因为该函数具有指针的私有副本。 “私人副本”不是使用指针的理由吗?

我想我对整个指针的使用有点困惑。 如果被问到我可以使用指针而不是使用数组下标,但我怀疑这是指针的主要用途。

Linux和开源编程是我进入C的主要原因。我得到了一个C项目的源代码(Geany IDE),我可以看到在整个源代码中都使用了指针。

我也在论坛上做了一些搜索,发现了几个有类似问题的post。 答案是(我引用):

如果您不知道何时应该使用指针,请不要使用它们。

当您需要使用它们时,每种情况都会有所不同。

我是否可以安全地避免在目前使用指针并且仅在特定情况下使用它们(指针的需求将显而易见?)

突出显示的规则非常明智。 它会让你摆脱困境,但迟早你必须学习指针。

那么我们为什么要使用指针呢?

假设我打开了一个文本文件并将其读入一个巨大的字符串。 我不能通过值传递巨大的字符串,因为它太大而不适合堆栈(比如10mb)。 所以我告诉你字符串的位置并说“看看我的字符串”。

数组是指针(几乎是)。

int []和int *略有不同,但大多数情况下可以互换。

int[] i = new int[5]; // garbage data int* j = new int[5] // more garbage data but does the same thing std::cout << i[3] == i + 3 * sizeof(int); // different syntax for the same thing 

更高级的指针使用是非常有用的是使用函数指针。 在C和C ++中,函数不是第一类数据类型,而是指针。 因此,您可以将指针传递给您想要调用的函数,并且可以执行此操作。

希望这有所帮助,但更可能会令人困惑。

指针的一个好处是,当您在函数参数中使用它们时,您不需要复制大块内存,也可以通过取消引用指针来更改状态。

例如,您可能有一个巨大的struct MyStruct ,并且您有一个函数a()

 void a (struct MyStruct* b) { // You didn't copy the whole `b` around, just passed a pointer. } 

来自Java,你的观点与K&R中的观点略有不同(K&R并不认为读者知道任何其他现代编程语言)。

C中的指针就像Java中引用的稍微强大的版本。 您可以通过名为NullPointerException的Javaexception看到这种相似性。 C中指针的一个重要方面是你可以通过递增和递减来改变它们所指向的内容。

在C中,你可以将一堆东西存储在一个数组的内存中,你知道它们在内存中并排放置。 如果你有一个指向其中一个的指针,你可以通过递增指针使指针指向“下一个”指针。 例如:

 int a[5]; int *p = a; // make p point to a[0] for (int i = 0; i < 5; i++) { printf("element %d is %d\n", i, *p); p++; // make `p` point to the next element } 

上面的代码使用指针p依次指向数组a中的每个连续元素,并将它们打印出来。

(注意:上面的代码只是一个说明性的例子,你通常不会用这种方式编写一个简单的循环。 a[i]访问数组的元素会更容易,而不是在那里使用指针。)

在稍后使用char指针的示例中,该书说增加char指针是合法的,因为该函数具有指针的私有副本。

我要说这意味着它们正在递增指针本身,这意味着更改地址(因此使其指向不同的值)。 如果将它们传递给数组的第一项并希望在数组中继续,但不更改值,则这可能很有用。

请记住,C(以及K&R书籍)非常陈旧,可能比您以前学到的任何东西都要老(绝对比Java年龄大)。 指针不是C的额外function,它们是计算机工作方式的基本部分。

指针理论并不是特别难以掌握,只是它们非常强大,因此错误很可能会使应用程序崩溃,并且编译器很难尝试捕获与指针相关的错误。 关于Java的一个重大新颖之处就是拥有“几乎”没有指针的C的function。

因此,在我看来,尝试编写C避免指针就像试着没有一个踏板骑自行车。 是的,这是可行的,但你会努力工作两倍。

请忽略那个答案。

如果您不知道如何使用指针,请了解如何使用它们。 简单。

如你所说,指针允许你通过指向它的指针将多个变量传递给函数,正如你正确观察到的那样。 指针的另一个用途是引用数据数组,您可以使用指针算法逐步完成。 最后,指针允许您动态分配内存。 因此,建议您不要使用指针会严重限制您可以做的事情。

指针是C讨论内存地址的方式。 因此,它们至关重要。 如果您有K&R,请阅读。

有关使用的一个示例,请参阅我对此的回答。 正如我在那个答案中所说,鉴于这个问题,你不一定会这样做。

但是,这种技术正是像MPIR和GMP这样的库的工作原理。 libgmp如果你还没有遇到它,那就是mathematica,maple等,它是任意精度算术库。 你会发现mpn_t是指针的typedef; 取决于操作系统取决于它指向的内容。 你还会在这段代码的慢速C版本中找到很多指针算法。

最后,我提到了内存管理。 如果你想分配一个你需要mallocfree的数组来处理指向内存空间的指针; 特别是malloc在分配后返回指向某个内存的指针,或者在失败时返回NULL

我最喜欢使用的指针之一是使用win32 API使C ++类成员函数充当Windows上的线程,即让类包含一个线程。 不幸的是,由于显而易见的原因,Windows上的CreateThread不接受C ++类函数 – CreateThread是一个不理解类实例的C函数; 所以你需要传递一个静态函数。 这是诀窍:

 DWORD WINAPI CLASSNAME::STATICFUNC(PVOID pvParam) { return ((CLASSNAME*)pvParam)->ThreadFunc(); } 

(PVOID void *

会发生什么呢?它返回ThreadFunc ,它被执行“而不是”(实际上是STATICFUNC调用它) STATICFUNC并且确实可以访问STATICFUNC所有私有成员变量。 巧妙,是吗?

如果这还不足以说服你指点有点C,我不知道是什么。 或者在没有指针的情况下,C中没有任何意义。 要么…

考虑到你来自Java背景,这是了解使用指针的最简单方法。

假设你在Java中有这样的类:

 public class MyClass { private int myValue; public int getMyValue() { return myValue; } public void setMyValue(int value) { myValue = value; } } 

这是您的主要function,它创建您的一个对象并在其上调用一个函数。

 public static void Main(String[] args) { MyClass myInstance = new MyClass(); myInstance.setMyValue(1); System.out.printLn(myInstance.getMyValue()); // prints 1 DoSomething(myInstance); System.out.printLn(myInstance.getMyValue()); // prints 2 } public static void DoSomething(MyClass instance) { instance.setMyValue(2); } 

您在Main声明的myInstance变量是一个引用。 它基本上是JVM用来保持对象实例的标签的句柄。 现在,让我们在C中做同样的事情。

 typedef struct _MyClass { int myValue; } MyClass; void DoSomething(MyClass *); int main() { MyClass myInstance; myInstance.myValue = 1; printf("i", myInstance.myValue); // prints 1 MyClass *myPointer = &myInstance; // creates a pointer to the address of myInstance DoSomething(myPointer); printf("i", myInstance.myValue); // prints 2 return 0; } void DoSomething(MyClass *instance) { instance->myValue = 2; } 

当然,指针在C中更灵活,但这是它们如何以Java方式工作的要点。

如果要处理动态分配的内存,则必须使用指针来访问分配的空间。 许多程序处理已分配的内存,因此许多程序必须使用指针。

在这个例子中:

 int f1(int i); f1(x); 

参数i通过值传递,因此函数f1无法更改调用者的变量x的值。

但在这种情况下:

 int f2(int* i); int x; int* px = &x; f2(px); 

这里我们仍然按值传递px参数,但同时我们通过refference传递x ! 因此,如果被调用者( f2 )将改变其int* i ,它将对调用者中的px没有影响。 但是,通过更改*i ,被调用者将更改调用者中的x值。

让我用Java引用来解释这个问题(正如@Greg的答案所指出的那样)

在Java中,有引用类型(即对类的引用)和值类型(即int )。 与C一样,Java只是按值传递。 如果将基本类型传递给函数,则实际传递值的值(毕竟,它是“值类型”),因此对该函数内的该值的任何修改都不会反映在调用代码中。 如果将引用类型传递给函数,则可以修改该对象的值,因为在传递引用类型时,会按值将引用传递给该引用类型。

指针可以在给定条件或程序状态的情况下动态分派代码。 理解这个概念的一种简单方法是考虑树结构,其中每个节点表示函数调用,变量或指向次级节点的指针。 一旦你理解了这一点,你就可以使用指针来指向程序可以随意引用的已建立的内存位置,以便了解初始状态,从而了解第一个取消引用和偏移。 然后每个节点将包含它自己的指针,以进一步了解状态,从而可以进行进一步的解引用,可以调用函数或抓取值。

当然,这只是可视化指针如何使用的一种方式,因为指针只不过是内存位置的地址。 您还可以使用指针进行消息传递,虚函数调用,垃圾收集等。事实上,我已经使用它们来重新创建c ++样式的虚函数调用。 结果是c虚函数和c ++虚函数以完全相同的速度运行。 然而,c实现的尺寸要小得多(KB减少66%)并且稍微便携一些。 但是从C中复制其他语言的function并不总是有利的,显然b / c其他语言可以更好地收集编译器可用于优化该数据结构的信息。

总之,使用指针可以做很多事情。 现在,C语言和指针被贬值b / c大多数高级语言都配备了更常用的数据结构/实现,你必须使用指针自己构建。 但是,程序员总是需要实现一个复杂的例程,在这种情况下,知道如何使用指针是一件非常好的事情。