为什么在C++中使用指针

Why use pointers in C++?

本文关键字:指针 C++ 为什么      更新时间:2023-10-16

我从游戏开发的角度学习C++,这源于C#中与游戏无关的长期开发,但我很难掌握指针的概念/使用以及去引用。我已经把我现在课堂课本上的这两章读了三遍,甚至在谷歌上搜索了一些与之相关的不同页面,但似乎并没有很好地结合在一起。

我想我得到了这个部分:

#include <iostream>
int main()
{
    int myValue   = 5;
    int* myPointer = nullptr;
    std::cout << "My value: " << myValue << std::endl; // Returns value of 5.
    std::cout << "My Pointer: " << &myValue << std::endl; // Returns some hex address.
    myPointer  = &myValue; // This would set it to the address of memory.
    *myPointer = 10; // Essentially sets myValue to 10.
    std::cout << "My value: " << myValue << std::endl; // Returns value of 10.
    std::cout << "My Pointer: " << &myValue << std::endl; // Returns same hex address.
}

我想我没有得到的是,为什么?为什么不说myValue=5,然后说myValue=10呢?为另一个变量或指针遍历添加的层的目的是什么?任何有助于理解这一点的有用输入、现实生活中的使用或阅读链接都将不胜感激!

指针的用途是你在第一次真正需要它们之前不会完全意识到的。您提供的示例是一种不需要指针但可以使用指针的情况。这实际上只是为了展示它们是如何工作的。指针是一种记住内存所在位置的方法,而不必复制它所指向的所有内容。阅读本教程,因为它可能会给你一个与课堂书不同的视图:

http://www.cplusplus.com/doc/tutorial/pointers/

示例:如果你有一个像这样定义的游戏实体数组:

std::vector<Entity*> entities;

你有一个相机类,可以"跟踪"一个特定的实体:

class Camera
{
private:
   Entity *mTarget;  //Entity to track
public:
   void setTarget(Entity *target) { mTarget = target; }
}

在这种情况下,相机引用实体的唯一方法是使用指针。

entities.push_back(new Entity());
Camera camera;
camera.setTarget(entities.front());

现在,每当实体在游戏世界中的位置发生变化时,相机将自动访问渲染到屏幕上的最新位置。如果没有使用指向实体的指针并传递副本,则渲染摄影机的位置将过期。

TL;DR:当多个位置需要访问相同的信息时,指针很有用

在你的例子中,它们并没有做什么,就像你说的那样,只是展示了如何使用它们。指针的作用之一是像树一样连接节点。如果你有这样的节点结构…

struct myNode
{
    myNode *next;
    int someData;
};

您可以创建多个节点,并将每个节点链接到上一个myNode的next成员。你可以在没有指针的情况下完成这项工作,但有指针的好处是,它们都链接在一起,当你在myNode列表中传递时,你只需要传递第一个(根)节点。

指针最酷的地方是,如果两个指针引用同一个内存地址,那么对内存地址的任何更改都会被引用该内存地址的所有内容所识别。如果你这样做了:

int a = 5; // set a to 5
int *b = &a; // tell b to point to a
int *c = b; // tell c to point to b (which points to a)
*b = 3; // set the value at 'a' to 3
cout << c << endl; // this would print '3' because c points to the same place as b

这有一些实际用途。假设您有一个链接在一起的节点列表。每个节点中的数据定义了需要由某个函数处理的某种任务。当新任务被添加到列表中时,它们会被附加到末尾。由于函数有一个指向节点列表的指针,因此在添加任务时,它也会接收这些任务。另一方面,该函数还可以在完成任务时删除任务,然后将这些任务反映回查看节点列表的任何其他指针。

指针也用于动态内存。假设你想让用户输入一系列数字,他们会告诉你他们想使用多少数字。您可以定义一个由100个元素组成的数组,以允许最多100个数字,也可以使用动态内存。

int count = 0;
cout << "How many numbers do you want?n> ";
cin >> count;
// Create a dynamic array with size 'count'
int *myArray = new int[count];
for(int i = 0; i < count; i++)
{
    // Ask for numbers here
}
// Make sure to delete it afterwars
delete[] myArray;

如果按值传递int,则无法更改调用者的值。但如果你把一个指针传给int,你就可以改变它。这就是C改变参数的方式。C++可以通过引用传递值,所以这不太有用。

f(int i)
{
  i= 10;
  std::cout << "f value: " << i << std::endl;
}
f2(int *pi)
{
    *pi = 10;
    std::cout << "f2 value: " << pi << std::endl;
}
main()
{
    i = 5
    f(i)
    std::cout << "main f value: " << i << std::endl;
    f2(&i)
    std::cout << "main f2 value: " << i << std::endl;
}

总的来说,第一次打印应该仍然是5。第二个应该是10。

为另一个变量或指针遍历添加的层的目的是什么?

没有。这是一个刻意设计的例子,向你展示了这个机制是如何工作的。

事实上,对象经常被存储,或者从代码库的遥远部分访问,或者动态分配,或者不能被范围绑定。在这些场景中,您可能会发现自己需要间接引用对象,这是通过使用指针和/或引用(取决于您的需要)实现的。

例如,一些对象没有名称。它可以是一个已分配的内存或从函数返回的地址,也可以是迭代器。当然,在您的简单示例中,不需要声明指针。然而,在许多情况下,例如,当您处理C字符串函数时,您需要使用指针。的一个简单例子

char s[] = "It is pointer?";
if ( char *p = std::strchr( s, '?' ) ) *p = '!';  

我们主要在需要动态分配内存时使用指针。例如,要实现一些数据结构,如链表、树等。

从C#的角度来看,指针与C#中的对象引用完全相同——它只是内存中存储实际数据的地址,通过取消引用它,您可以用这些数据进行操作。

示例中的第一个非指针数据(如int)是在堆栈上分配的。这意味着,当它超出其使用范围时,内存将被释放。另一方面,使用运算符new分配的数据将被放置在堆中(就像在C#中创建任何对象一样),导致这些数据不会被释放,从而导致指针丢失。因此,使用堆内存中的数据可以执行以下操作之一:

  • 稍后使用垃圾收集器删除数据(如C#中所做的)
  • 手动释放内存,然后您就不再需要它了(以C++的方式带有操作员删除)

为什么需要它?基本上有三个用例:

  1. 堆栈内存很快,但有限,因此如果您需要存储大量内存必须使用堆的数据量
  2. 复制大数据是昂贵的然后在堆栈上的函数之间传递简单值它进行复制。然后你传递指针,唯一复制的东西就是它是地址(就像C#中一样)
  3. C++中的一些对象可能是不可复制,例如线程,因为它们的性质

以一个指向类的指针为例。

struct A
{
    int thing;
    double other;
    A() {
        thing = 4;
        other = 7.2;
    }
};

假设我们有一种方法,它取"a":

void otherMethod()
{
    int num = 12;
    A mine;
    doMethod(num, mine);
    std::cout << "doobie " << mine.thing;
}
void doMethod(int num, A foo)
{
    for(int i = 0; i < num; ++i)
        std::cout << "blargh " << foo.other;
    foo.thing--;
}

当调用doMethod时,会按值传递A对象。这意味着将创建一个新的A对象(作为副本)。foo.thing--行根本不会修改mine对象,因为它们是两个独立的对象。

您需要做的是传入一个指向原始对象的指针。当您传入指针时,foo.thing--将修改原始对象,而不是将旧对象的副本创建为新对象。

指针(或引用)对于C++中动态多态性的使用至关重要。它们是如何使用类层次结构的。

Shape * myShape = new Circle();
myShape->Draw(); // this draws a circle
// in fact there is likely no implementation for Shape::Draw

尝试通过基类的值(而不是指针或引用)使用派生类通常会导致切片并丢失对象的派生数据部分。

当您将指针传递给函数时,它更有意义,请参见以下示例:

void setNumber(int *number, int value) {
    *number = value;
}
int aNumber = 5;
setNumber(&aNumber, 10);
// aNumber is now 10

我们在这里所做的是设置*number的值,如果不使用指针,这是不可能的。

如果你这样定义它:

void setNumber(int number, int value) {
    number = value;
}
int aNumber = 5;
setNumber(aNumber, 10);
// aNumber is still 5 since you're only copying its value

它还提供了更好的性能,并且在将对较大对象(如类)的引用传递给函数时,而不是传递整个对象,不会浪费那么多内存。

在编程中使用指针是一个很好的概念。为了动态分配内存,必须使用指针来存储我们保留的内存的第一个位置的地址,释放内存也是如此,我们需要指针。正如上面的答案中有人所说的那样,除非你需要,否则你无法理解一点形的使用。一个例子是,你可以使用指针和动态内存分配来创建一个可变大小的数组。重要的一点是,使用指针,我们可以更改位置的实际值,因为我们是间接访问该位置的。更重要的是,当我们需要通过引用传递我们的值时,有时引用不起作用,所以我们需要指针。

而你写的代码是使用解引用运算符。正如我所说,我们通过使用指针间接访问内存的作用,因此它会像引用对象一样更改位置的实际值,这就是它打印10的原因。