【问题标题】:In C++, how "heavy" is a data structure when compared to a data structure pointer?在 C++ 中,与数据结构指针相比,数据结构有多“重”?
【发布时间】:2021-06-15 14:08:02
【问题描述】:

让我们举个具体的例子:

我想使用一个字符串向量,并且可以选择将该向量定义为以下之一:

std::vector<string> myVector;

std::vector<string>* myPtrVector = new vector<string>;

我知道 myPtrVector 是一个内存地址,但是 myVector 是什么?当我调用myVector.push_back(someString)时,它是否也使用内存地址?

巩固我的问题:

1)。我想知道计算机如何处理普通(非指针)变量以及它们与指针变量有何不同。

2)。我还想知道将数据结构(向量、映射、堆栈等)声明为指针是否有任何优势。

提前感谢大家的回答!我是新来的,如果您对我应该如何以不同的方式提出问题有任何建议,我很乐意听到。

【问题讨论】:

  • std::vector&lt;string&gt;* myPtrVector = new vector&lt;string&gt;; -- 谁清理内存?为此,C++ 中没有自动垃圾收集器。
  • std::vector 基本上只是 3 个指针,或者一个指针和 2 个整数。通常没有充分的理由使用std::vector&lt;string&gt;* myPtrVector = new vector&lt;string&gt;;。我总是将这样的表达式标记为错误,除非其原因被清楚且令人信服地记录在案。
  • @OP std::vector&lt;string&gt;* myPtrVector = new vector&lt;string&gt;; -- 在大多数优秀的代码审查中,你会被问到“你为什么要这样做?”,你最好有一个好的答案。
  • @PaulMcKenzie 和“这就是我们在 Java 和 C# 中的做法”不是一个好的答案。

标签: c++ pointers data-structures


【解决方案1】:

myVector 是一个 本地 变量。根据其使用的上下文,它可能存储在不同的位置,但重要的是它会在其作用域结束时自动销毁(例如,如果函数是本地的,则当函数返回时)。在内部vector 从空闲存储中分配,这意味着当您push_back 某些内容时,它会将其添加到非本地内存(并在需要时分配更多内存)。在内部,它将使用一个指针来获取该内存,但您不必担心这一点,因为它保证它会自行清理。

最后一点,通过指针访问向量几乎肯定比移动它或类似的方式要慢。通过指针访问意味着您可能会遇到缓存未命中并且您还必须处理管理它的生命周期

【讨论】:

    【解决方案2】:

    我将使用这个简单结构的示例。我们假设 int 的大小是 4 个字节 - 这个结构的大小将是 8 个字节。

    struct Point {
      int x;
      int y;
    
      Point(char x, int y){
        this->x = x;
        this->y = y;
      }
    }
    

    现在我们将在 nice 函数中创建 2 个变量。一个是普通变量,另一个是指针

    void niceFunction(){
        Point  normalVariable(10, 5);
        Point* pointer = new Point(10, 5);
    }
    niceFunction(); // we call our function
    

    有什么区别? 第一个变量包含我们的整个结构。如果你使用sizeof(normalVariable),你会得到8个字节的结果。

    第二个包含我们结构的地址。当您使用 sizeof(pointer) 时,您将获得 4 个字节 - 我们的结构位于动态内存中的某个位置,并且该指针仅包含指向我们结构的地址。

    分配

    分配是当我们为我们的结构请求内存时的行为。系统必须找到空闲内存并将其提供给我们。我们将用我们的东西填充这个内存(在我们的例子中,我们使用数字 10 和 5)。

    在第一种情况下(普通变量),分配更快,因为系统使用 stack 来存放普通变量,因此知道你的东西存储在哪里以及空闲区域在哪里。这有一个问题 - 堆栈的大小有限,如果您分配给其中的许多结构,内存将被耗尽并且您的程序将被操作系统终止 - 称为 Stack Overflow

    的错误

    在第二种情况下(指针),系统必须在动态内存中找到空闲区域并在那里注入你的结构。这需要一些时间。你也有无限的内存(你只受你的内存大小和来自操作系统的限制)

    解除定位

    解除分配是将内存归还给系统的行为。返回的内存将在需要时再次使用

    在第一种情况下,解除分配是自动。因为局部变量只存在于函数内部,所以一旦我们的函数结束,就没有任何理由从堆栈中释放它们。

    在第二种情况(指针)中,我们的结构位于内存中的某个位置,因此系统不知道如何释放它。我们必须明确声明我们想要释放我们的结构

    void niceFunction(){
        Point  normalVariable(10, 5);
        Point* pointer = new Point(10, 5);
    
        delete pointer; // this will dealocate our structure
    }
    

    如果我们忘记释放动态内存中的结构,它会一直留在这里直到程序结束。

    参数传递/复制

    想象一下有 2 个参数(点 a 和 b)的函数,它会返回它们的距离。我们将在第一个使用普通变量,在第二个使用指针。

    float distanceNormal(Point a, Point b){
      // body of function is irelevant for us
    }
    float distancePointer(Point* a, Point* b){
      // body of function is irelevant for us
    }
    

    在第一种情况下,当我们将点传递给函数时,系统会复制它们。这意味着它将在函数内部的堆栈中分配新内存,并将整个结构复制到分配的内存中。如果您的结构很大或者它们的构造器正在执行昂贵的操作,这可能是无效的。这也意味着两个普通变量不能共享一个结构 - 它们可以具有相同的值,但它们不是相同的结构。

    在第二种情况下,不会复制任何内容。相反,系统只会将地址传递给动态内存中的结构。这也会导致一种效果 - 当您使用指针修改结构时。它会改变原来的结构,因为它们都有相同的地址

    Point* pointer = new Point(10, 5); // values of our point are (10, 5)
    
    void changeX(Point* point){
        point->x = 50;
    }
    changeX(pointer); // after this call, values of our point are (50, 5)
    

    这被结构/对象的方法所利用。你在构造函数中看到了那个叫做 this 的奇怪变量吗?这也是指针* - 它是指向我们结构的指针。这允许我们直接操作结构的值。

    结论

    你可以看到他们都有优点和缺点。什么时候使用它们?

    在以下情况下使用普通变量:

    • 您不会在函数之外使用其内容。当你有一些只在 function 内部使用的东西时,没有任何理由在动态内存中分配它。 Stack 是更好的选择

    • 你想要自动解除分配:在我们的例子中,普通变量将被自动解除分配。第二个(指针)必须手动释放。

    • 您不想将其共享到函数中:当您使用指针时,您正在使用原始文件,并且更改将应用​​于原始文件。当您使用普通变量时,您正在使用副本,原始结构将不受影响

    在以下情况下使用指针:

    • 您想使用原始结构:如果您想在函数内部操作原始结构,请使用指针。
    • 你想避免复制:如果你有结构并且你有被调用1000次的函数,使用普通变量是无效的。使用指针避免复制。
    • 您想避免自动分配:有时,自动分配并不好。就像您为具有数百个字节的游戏分配的结构一样,在函数结束后,它将被释放。指针从未发生过这种情况,您必须使用 delete 手动释放它们。

    黄金法则是:尽可能使用普通变量,必须使用指针

    【讨论】:

    • 这非常有用!非常感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-28
    • 2019-05-09
    相关资源
    最近更新 更多