指针的好处?

时间:2010-12-18 23:58:54

标签: c pointers

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

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

阅读本章并了解指针是什么以及它们如何工作的基本概念,对我来说效益并不明显。

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

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

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

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

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

  

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

     

当你需要使用它们时会很明显,每种情况都不同。

我是否可以安全地避免暂时使用指针并仅在特定情况下使用它们(指针需要明显?)

11 个答案:

答案 0 :(得分:9)

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

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

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

答案 1 :(得分:8)

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

C中的指针就像Java中一个稍微强大的引用版本。您可以通过名为NullPointerException的Java异常看到这种相似性。 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],不要在那里使用指针。)

答案 2 :(得分:4)

  

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

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

答案 3 :(得分:3)

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

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

说我打开了一个文本文件并将其读成一个巨大的字符串。我无法通过值传递巨大的字符串,因为它太大而无法放在堆栈上(例如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 ++中,函数不是第一类数据类型,而是指针。因此,您可以将指针传递给您想要调用的函数,并且可以执行此操作。

希望这会有所帮助,但很可能会让人感到困惑。

答案 4 :(得分:3)

请忽略那个答案。

如果您不知道如何使用指针,学习如何使用它们。简单。

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

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

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

但是,这种技术正是像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并且确实可以访问CLASSNAME的所有私有成员变量。巧妙,嗯?

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

答案 5 :(得分:3)

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

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

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

答案 6 :(得分:2)

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

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

public class MyClass {
    private int myValue;

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

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

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);
}

您在myInstance中声明的Main变量是一个参考。它基本上是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方式工作的要点。

答案 7 :(得分:1)

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

答案 8 :(得分:1)

在这个例子中:

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

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

但在这种情况下:

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

此处我们仍然按值传递px参数,但同时我们通过引用传递x!因此,如果被叫方(f2)将更改其int* i,则它对调用方中的px无效。但是,通过更改*i,被调用方将更改调用者中x的值。

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

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

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

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