首页 文章

为什么要使用指针? [关闭]

提问于
浏览
296

我知道这是一个非常基本的问题,但是在用高级语言编写一些项目之后,我刚开始使用一些基本的C编程 .

基本上我有三个问题:

  • 为什么使用指针超过正常变量?

  • 我应该何时何地使用指针?

  • 如何使用带数组的指针?

18 回答

  • 6
    • 为什么使用指针超过正常变量?

    简短的回答是:不要 . ;-)指针用于不能使用其他任何东西的地方 . 这可能是因为缺乏适当的功能,缺少数据类型或纯粹的性能 . 更多信息......

    • 我应该何时何地使用指针?

    这里简短的回答是:你不能使用任何其他东西 . 在C中,您不支持复杂的数据类型,例如字符串 . 也没有办法将“通过引用”变量传递给函数 . 那是你必须使用指针的地方 . 此外,您可以让它们指向几乎任何东西,链接列表,结构成员等 . 但是我们不要在这里讨论 .

    • 如何使用带数组的指针?

    只需很少的努力和困惑 . ;-)如果我们谈论简单的数据类型,如int和char,那么数组和指针之间几乎没有区别 . 这些声明非常相似(但不相同 - 例如, sizeof 将返回不同的值):

    char* a = "Hello";
    char a[] = "Hello";
    

    您可以像这样到达数组中的任何元素

    printf("Second char is: %c", a[1]);
    

    索引1,因为数组以元素0开头 . :-)

    或者你也可以这样做

    printf("Second char is: %c", *(a+1));
    

    因为我们告诉printf我们要打印一个字符,所以需要指针运算符() . 如果没有,将打印内存地址本身的字符表示 . 现在我们正在使用角色本身 . 如果我们使用%s而不是%c,我们会要求printf打印'a'指向的内存地址的内容加上一个(在上面的例子中),我们不必放*在前:

    printf("Second char is: %s", (a+1)); /* WRONG */
    

    但是这不会只打印第二个字符,而是打印下一个内存地址中的所有字符,直到找到空字符(\ 0) . 这就是事情开始变得危险的地方 . 如果您不小心尝试使用%s格式化程序打印整数类型的变量而不是char指针,该怎么办?

    char* a = "Hello";
    int b = 120;
    printf("Second char is: %s", b);
    

    这将打印在内存地址120上找到的任何内容并继续打印,直到找到空字符 . 执行这个printf语句是错误的和非法的,但它可能会起作用,因为在许多环境中指针实际上是int类型 . 想象一下,如果您使用sprintf()而将这种方式分配给另一个变量的“char数组”,只会得到一定的有限空间 . 您很可能最终会在内存中写下其他内容并导致程序崩溃(如果您很幸运) .

    哦,如果你在声明它时没有为char数组/指针赋值,你必须在给它一个值之前为它分配足够的内存量 . 使用malloc,calloc或类似的 . 这是因为您只在数组中声明了一个元素/单个内存地址指向 . 所以这里有一些例子:

    char* x;
    /* Allocate 6 bytes of memory for me and point x to the first of them. */
    x = (char*) malloc(6);
    x[0] = 'H';
    x[1] = 'e';
    x[2] = 'l';
    x[3] = 'l';
    x[4] = 'o';
    x[5] = '\0';
    printf("String \"%s\" at address: %d\n", x, x);
    /* Delete the allocation (reservation) of the memory. */
    /* The char pointer x is still pointing to this address in memory though! */
    free(x);
    /* Same as malloc but here the allocated space is filled with null characters!*/
    x = (char *) calloc(6, sizeof(x));
    x[0] = 'H';
    x[1] = 'e';
    x[2] = 'l';
    x[3] = 'l';
    x[4] = 'o';
    x[5] = '\0';
    printf("String \"%s\" at address: %d\n", x, x);
    /* And delete the allocation again... */
    free(x);
    /* We can set the size at declaration time as well */
    char xx[6];
    xx[0] = 'H';
    xx[1] = 'e';
    xx[2] = 'l';
    xx[3] = 'l';
    xx[4] = 'o';
    xx[5] = '\0';
    printf("String \"%s\" at address: %d\n", xx, xx);
    

    请注意,在执行已分配内存的free()之后仍然可以使用变量x,但是您不知道其中的内容 . 另外请注意两个printf()可能会给你不同的地址,因为不能保证第二次内存分配在与第一个相同的空间中执行 .

  • 24

    使用指针的一个原因是可以在被调用的函数中修改变量或对象 .

    在C中,使用引用而不是指针是更好的做法 . 尽管引用本质上是指针,但C在某种程度上隐藏了这一事实并使其看起来好像是在传递值 . 这使得更改调用函数接收值的方式变得容易,而无需修改传递它的语义 .

    请考虑以下示例:

    使用参考:

    public void doSomething()
    {
        int i = 10;
        doSomethingElse(i);  // passes i by references since doSomethingElse() receives it
                             // by reference, but the syntax makes it appear as if i is passed
                             // by value
    }
    
    public void doSomethingElse(int& i)  // receives i as a reference
    {
        cout << i << endl;
    }
    

    使用指针:

    public void doSomething()
    {
        int i = 10;
        doSomethingElse(&i);
    }
    
    public void doSomethingElse(int* i)
    {
        cout << *i << endl;
    }
    
  • 21
    • 指针允许您从多个位置引用内存中的相同空间 . 这意味着您可以在一个位置更新内存,并且可以从程序中的其他位置查看更改 . 您还可以通过共享数据结构中的组件来节省空间 .

    • 您应该在需要获取的任何地方使用指针,并将地址传递到内存中的特定位置 . 您还可以使用指针来导航数组:

    • 数组是已使用特定类型分配的连续内存块 . 数组的名称包含数组起始点的值 . 当你添加1时,会带你到第二个位置 . 这允许您编写循环,增加一个向下滑动数组的指针,而没有用于访问数组的显式计数器 .

    这是C中的一个例子:

    char hello[] = "hello";
    
    char *p = hello;
    
    while (*p)
    {
        *p += 1; // increase the character by one
    
        p += 1; // move to the next spot
    }
    
    printf(hello);
    

    版画

    ifmmp
    

    因为它需要每个字符的值,并将其递增1 .

  • 4

    指针是获取间接引用另一个变量的一种方法 . 他们不是保留变量的值,而是告诉你它的地址 . 这在处理数组时特别有用,因为使用指向数组中第一个元素的指针(其地址),您可以通过递增指针(到下一个地址位置)快速找到下一个元素 .

    指针和指针算法的最佳解释是've read is in K & R' s The C Programming Language . 一本开始学习C的好书是C++ Primer .

  • 12

    让我试着回答这个问题 .

    指针与参考文献类似 . 换句话说,它们不是副本,而是一种引用原始值的方法 .

    在其他任何事情之前,当你处理 with embedded hardware 时,一个地方 you will typically have to use pointers 很多 . 也许您需要切换数字IO引脚的状态 . 也许你're processing an interrupt and need to store a value at a specific location. You get the picture. However, if you'没有直接处理硬件而只是想知道要使用哪些类型,请继续阅读 .

    为什么使用指针而不是普通变量?当您处理复杂类型(如类,结构和数组)时,答案会变得更清晰 . 如果你要使用普通变量,你最终可能会制作一个副本(编译器足够聪明,在某些情况下可以防止这种情况,C 11也有帮助,但我们暂时不会讨论这个问题) .

    现在如果要修改原始值会发生什么?你可以使用这样的东西:

    MyType a; //let's ignore what MyType actually is right now.
    a = modify(a);
    

    这将工作得很好,如果你不确切知道为什么使用指针,你不应该使用它们 . 要小心“他们可能更快”的原因 . 运行您自己的测试,如果它们实际上更快,那么使用它们 .

    但是,让's say you'解决您需要分配内存的问题 . 分配内存时,需要取消分配内存 . 内存分配可能成功也可能不成功 . 这是 pointers 有用的地方 - 它们已经分配了它们,它们允许您通过取消引用指针来访问分配内存的对象 .

    MyType *p = NULL; //empty pointer
    if(p)
    {
        //we never reach here, because the pointer points to nothing
    }
    //now, let's allocate some memory
    p = new MyType[50000];
    if(p) //if the memory was allocated, this test will pass
    {
        //we can do something with our allocated array
        for(size_t i=0; i!=50000; i++)
        {
            MyType &v = *(p+i); //get a reference to the ith object
            //do something with it
            //...
        }
        delete[] p; //we're done. de-allocate the memory
    }
    

    这是你使用指针的关键-- references assume the element you're referencing exists already . 指针不会 .

    您使用指针(或至少最终必须处理它们)的另一个原因是因为它们是引用之前存在的数据类型 . 因此,如果您最终使用库来执行您知道他们更擅长的事情,您会发现很多这些库在整个地方使用指针,只是因为它们存在了多长时间(很多)他们是在C)之前写的 .

    如果你没有使用任何库,你可以设计你的代码,使你可以远离指针,但鉴于指针是语言的基本类型之一,你越习惯使用它们,越多便携式你的C技能 .

    从可维护性的角度来看,我还应该提一下,当你使用指针时,你要么必须测试它们的有效性并在它们无效时处理它,或者只是假设它们是有效的并接受你的事实 . 当假设被打破时,程序将崩溃或更糟 . 换句话说, your choice with pointers is to either introduce code complexity or more maintenance effort when something breaks 并且您正在尝试追踪属于指针引入的整类错误的错误,例如内存损坏 .

    因此,如果您控制所有代码,请远离指针,而是使用引用,尽可能保持const . 这将迫使您考虑对象的生命周期,并最终使代码更容易理解 .

    记住这个区别: A reference is essentially a valid pointer. A pointer is not always valid.

    所以我说不可能创建无效的引用?不,它完全有可能,因为C可以让你做任何事情 . 这是无意中做的更难,你会惊讶于有多少错误是无意的:)

  • 145

    以下是对C的许多功能有意义的一个略微不同但有见地的看法:http://steve.yegge.googlepages.com/tour-de-babel#C

    基本上,标准CPU架构是Von Neumann架构,能够在这样的机器上引用数据项在内存中的位置并对其进行算术运算非常有用 . 如果您知道汇编语言的任何变体,您将很快看到它在低级别上的重要性 .

    C使指针有点混乱,因为它有时会为你管理它们并以“引用”的形式隐藏它们的效果 . 如果你使用直接C,对指针的需求就更明显了:没有其他方法可以通过引用进行调用,这是存储字符串的最佳方式,它是迭代数组的最佳方式,等等 .

  • 5

    指针的一种用法(我不会提及其他人的帖子中已经涵盖的内容)是访问您尚未分配的内存 . 这对于PC编程来说没有多大用处,但它在嵌入式编程中用于访问内存映射的硬件设备 .

    回到DOS的旧时代,您曾经可以通过声明指向以下内容的指针直接访问视频卡的视频内存:

    unsigned char *pVideoMemory = (unsigned char *)0xA0000000;
    

    许多嵌入式设备仍然使用它技术 .

  • 1

    在很大程度上,指针是数组(在C / C中) - 它们是内存中的地址,并且如果需要可以像数组一样被访问(在“正常”情况下) .

    由于它们是项目的地址,因此它们很小:它们只占用地址的空间 . 因为它们很小,所以发送它们很便宜 . 然后他们允许该函数处理实际项目而不是副本 .

    如果要进行动态存储分配(例如链接列表),则必须使用指针,因为它们是从堆中获取内存的唯一方法 .

  • 13

    指针在许多数据结构中很重要,其设计需要能够有效地将一个“节点”链接或链接到另一个“节点” . 你不会“选择”一个指针而不是像浮点数这样的普通数据类型,它们只是有不同的用途 .

    在需要高性能和/或紧凑内存占用的情况下,指针非常有用 .

    可以将数组中第一个元素的地址分配给指针 . 然后,这允许您直接访问基础分配的字节 . 数组的重点是避免你需要这样做 .

  • 44

    在变量上使用指针的一种方法是消除所需的重复内存 . 例如,如果您有一些大型复杂对象,则可以使用指针指向您所做的每个引用的该变量 . 使用变量,您需要为每个副本复制内存 .

  • 9

    在C中,如果要使用子类型polymorphism,则 have 为用户指针 . 看这篇文章:C++ Polymorphism without pointers .

    真的,当你想到它时,这是有道理的 . 当您使用子类型多态时,最终,您不会提前调用哪个类或子类的方法实现,因为您不知道实际的类是什么 .

    拥有一个包含未知类对象的变量的想法与C在堆栈上存储对象的默认(非指针)模式不兼容,其中分配的空间量直接对应于类 . 注意:如果一个类有5个实例字段而不是3个,则需要分配更多空间 .


    请注意,如果您使用'&'通过引用传递参数,则间接(即指针)仍然涉及幕后 . '&'只是语法糖,(1)省去了使用指针语法的麻烦,(2)允许编译器更严格(例如禁止空指针) .

  • 3

    因为在整个地方复制大对象会浪费时间和内存 .

  • 1

    这是我的anwser,我不会成为一名专家,但我发现我的一个图书馆中的指针很棒 . 在这个库中(它是一个带有OpenGL的图形API :-))你可以创建一个三角形,顶点对象传入它们 . draw方法接受这些三角形对象,并且......根据我创建的顶点对象绘制它们 . 好吧,好吧 .

    但是,如果我改变顶点坐标怎么办?在顶点类中使用moveX()移动它或其他东西?嗯,好吧,现在我必须更新三角形,添加更多方法和性能被浪费,因为我必须在每次顶点移动时更新三角形 . 仍然不是什么大不了的事,但并不是那么好 .

    现在,如果我有一个具有大量顶点和大量三角形的网格,并且网格正在旋转,并且移动等等 . 我将不得不更新使用这些顶点的每个三角形,并且可能更新场景中的每个三角形,因为我不知道哪些使用哪个顶点 . 这是计算机密集型的,如果我在景观上有几个网格,哦,天啊!我遇到了麻烦,因为我几乎每一帧都更新每个三角形,因为这些顶点正在变化!

    使用指针,您不必更新三角形 .

    如果我每个三角形类有三个*顶点对象,不仅节省了空间,因为一个巨大的三角形没有三个本身很大的顶点对象,而且这些指针总是指向它们所要求的顶点,无论如何顶点变化的频率 . 由于指针仍然指向同一个顶点,因此三角形不会改变,更新过程更容易处理 . 如果我困惑你,我不会怀疑它,我不会假装成为专家,只是把我的两分钱投入到讨论中 .

  • 3

    在java和C#中,所有对象引用都是指针,c的东西是你可以更好地控制指针指向的位置 . 记住以强大的力量来承担巨大责任 .

  • -4

    需要使用C语言指针here

    基本思想是可以通过操纵数据的内存位置来消除语言中的许多限制(如使用数组,字符串和修改函数中的多个变量) . 为了克服这些限制,在C中引入了指针 .

    此外,还可以看到使用指针,有时您可以更快地运行代码并节省内存(在将大型数据类型(如结构)传递给函数的情况下 . 在传递之前,这些数据类型的副本需要时间并且会消耗内存) . 这是程序员更喜欢大数据类型指针的另一个原因 .

    PS:有关示例代码的详细说明,请参阅link provided .

  • 10

    关于你的第二个问题,通常你不需要在编程时使用指针,但是有一个例外,那就是当你创建一个公共API时 .

    人们通常用来替换指针的C构造的问题非常依赖于您使用的工具集,当您拥有源代码所需的所有控件时,这很好,但是如果您使用visual studio 2008编译静态库并且尝试在visual studio 2010中使用它会导致大量链接器错误,因为新项目与更新版本的STL链接,后者不向后兼容 . 如果您编译DLL并提供人们在不同工具集中使用的导入库,事情变得更加糟糕,因为在这种情况下,您的程序迟早会因为没有明显原因而崩溃 .

    因此,为了将大型数据集从一个库移动到另一个库,您可以考虑将一个指向数组的指针提供给应该复制数据的函数,如果您不想强制其他人使用您使用的相同工具 . 关于这一点的好处是它甚至不必是C风格的数组,你可以使用std :: vector并通过给出第一个元素&vector [0]的地址来给出指针,并使用std :: vector在内部管理数组 .

    在C中再次使用指针的另一个好理由与库相关,考虑在程序运行时有一个无法加载的DLL,所以如果使用导入库则不满足依赖性并且程序崩溃 . 例如,当您在应用程序旁边的dll中提供公共API并希望从其他应用程序访问它时就是这种情况 . 在这种情况下,为了使用API,您需要从其位置加载dll(通常是在注册表项中),然后您需要使用函数指针来调用DLL中的函数 . 有时制作API的人很好,可以为你提供一个包含辅助函数的.h文件来自动执行这个过程,并为你提供所需的所有函数指针,但如果没有,你可以在windows和dlopen上使用LoadLibrary和GetProcAddress . unix上的dlsym来获取它们(考虑到你知道函数的整个签名) .

  • 9
    • 在某些情况下,函数指针需要使用共享库(.DLL或.so)中的函数 . 这包括跨语言执行的东西,通常提供DLL接口 .

    • 制作编译器

    • 制作科学计算器,你有一个数组或向量或函数指针的字符串映射?

    • 尝试直接修改视频内存 - 制作自己的图形包

    • 制作API!

    • 数据结构 - 您正在制作的特殊树的节点链接指针

    指针有很多原因 . 如果要保持跨语言兼容性,那么在DLL中特别重要的是C名称 .

  • 33

    使用指针是因为在动态堆区域中分配和撤回数据时性能会提高 .

相关问题