【问题标题】:Dynamic Function Memory? C++动态功能记忆? C++
【发布时间】:2011-01-21 17:16:02
【问题描述】:

我一直在阅读一些书籍,当谈到使用指针/动态内存(或堆或他们称之为 w/e)的类/函数时,我开始感到困惑。

有没有人有一个简单的....像他们可以展示的简单示例,因为我使用的书籍使用了过于复杂的示例(大型类或多个函数)并且很难理解。无论如何,指针一直是我的弱点,但我了解 BASIC 指针,只是使用它们的类/函数有点令人困惑。

另外......你什么时候使用它们是另一个问题。

【问题讨论】:

  • BASIC 不支持指针:P
  • @Thomas:我认为他的意思是对指针的基本理解,而不是 BASIC。
  • Alf P. Steinbach 曾经有一本他正在写的关于这个主题的书,但现在我能找到的所有链接都是死胡同。
  • @Chan 我认为 Sauron 知道,我也认为 Thomas 知道这一点(因此是笑脸)。 @Sauron:当你的意思是“基本”时,请不要使用“基本”(如果你想强调一个词,请在它周围加上“*”)
  • 嗯,在某种程度上,它确实支持他们——只是在更受限制的幕后,假装他们不在那里。至少,VB.Net 区分了按值传递和按引用传递。一些程序员仍然想说那些不是真正的指针,因为语言和使用该语言的人表现得好像它没有任何指针;但实际上,这正是 ByRef 变量的含义。

标签: c++ class pointers dynamic-memory-allocation


【解决方案1】:

堆栈分配:

char buffer[1000];

这里的 1000 必须是一个常数。当buffer 超出范围时,内存会自动释放。

堆分配:

int bufsz = 1000;
char* buffer = new char[bufsz];
//...
delete [] buffer;

这里的 bufsz 可以是一个变量。必须显式释放内存。

何时使用堆:

  • 您不知道编译时需要多少空间。
  • 您希望内存/对象在当前范围之外持续存在。
  • 您需要大量内存(堆栈空间比堆空间更有限)

【讨论】:

  • 虽然这里的所有答案都是正确的,但我相信这个答案最准确地回答了 Asker 的问题。
  • 我看不到这一点,但不是因为答案不好 - 它是! - 但因为这个问题相当不清楚。具体来说,它没有询问分配的差异,而是询问如何使用动态内存的示例。很好的猜测来找出这是索伦真正需要的。
【解决方案2】:

您计算机的 RAM 是一大堆依次排列的字节,每个字节都可以通过其地址独立访问:从零开始向上的整数。指针只是一个变量,用于保存内存中单个位置的地址。

由于 RAM 是一大块字节,CPU 通常将一大堆字节分成几个块。最重要的是:

  1. 代码
  2. 堆栈

代码块是汇编代码所在的位置。 Heap 是一个用于分配的大字节池:

  • 全局变量
  • 动态数据,通过 C++ 上的 new 操作,或 C 上的 malloc()

堆栈是用来存储的内存块:

  • 局部变量
  • 函数参数
  • 返回值(C/C++ 上的返回语句)。

堆栈和堆之间的主要区别在于它的使用方式。虽然堆是一个大字节池,但堆栈“增长”就像一堆盘子:你不能删除底部的盘子,除非它的顶部没有更多的盘子。

这就是递归的实现方式:每次递归调用函数时,内存都会在堆栈上增长,分配参数、局部变量并存储返回函数的返回值,一个在另一个之上,就像一堆盘子一样。

堆栈中的数据与堆中的数据具有不同的“寿命”。一旦函数退出,局部变量的数据就会丢失。

但是,如果您在堆上分配数据,则该数据不会丢失,除非您使用 deletefree() 操作明确释放该数据。

【讨论】:

    【解决方案3】:

    指针基本上是一个变量,它包含另一个变量的内存地址(或者在其他情况下指向函数,但让我们关注第一个)。

    这意味着如果我声明int[] x = {5,32,82,45,-7,0,123,8};,该变量将被分配到某个地址的内存,假设它被分配到地址0x000001000x0000011F 但是我们可以有一个变量来指示某个内存地址我们可以使用它来访问它。

    所以,我们的数组看起来像这样

    Address           Contents
    0x00000100        1
    0x00000104        32
    0x00000108        82
    0x0000010B        45
    0x00000110        -7
    0x00000114        0
    0x00000118        123
    0x0000011B        8
    

    例如,如果我们要创建一个指向数组开头的指针,我们可以这样做:int* p = &x; 想象这个指针变量被创建了一个内存地址0x00000120,这样该地址处的内存将包含数组开头的内存位置x

    Address           Contents
    0x00000120        0x00000100
    

    然后您可以通过取消引用指针来通过指针访问该地址的内容,这样int y = *p 将导致y = 1。我们也可以移动指针,如果我们要执行p += 3;,指针将向前移动 3 个地址(但是请注意,它移动的大小是它所指向的对象类型的 3 倍,这里我举个例子对于 32 位系统,其中 int 为 32 位或 4 个字节长,因此地址将移动 4 个字节,每次增量或总共 12 个字节,因此指针最终将指向 0x0000010B),如果我们要通过执行y = *p; 再次取消引用p,然后我们最终将拥有y = 45。这只是一个开始,你可以用指针做很多事情。

    其他主要用途之一是将指针作为参数传递给函数,以便它可以对内存中的某些值进行操作,而无需复制所有这些值或进行将在函数范围之外持续存在的更改.

    【讨论】:

      【解决方案4】:

      警告:不要这样做。这就是我们有向量的原因。

      如果你想创建一个数据数组,然后从函数中返回,你会怎么做?

      显然,这不起作用:

      int [10] makeArray(int val)
      {
          int arr[10];
          for(int i=0; i<10; ++i)
              arr[i] = val;
          return arr;
      }
      

      您不能从函数返回数组。我们可以使用指针来引用数组的第一个元素,如下所示:

      int * makeArray(int val)
      {
          int arr[10];
          for(int i=0; i<10; ++i)
              arr[i] = val;
          return &(arr[0]);  // Return the address of the first element.
                             // Not strictly necessary, but I don't want to confuse.
      }
      

      然而,这也失败了。 arr 是一个局部变量,它进入堆栈。当函数返回时,数据不再有效,现在你有了一个指向无效数据的指针。

      我们需要做的是声明一个即使在函数退出后仍然存在的数组。为此,我们使用关键字 new 来创建该数组,并将地址返回给我们,该地址需要存储在一个指针中。

      int * makeArray(int val)
      {
          int * arr = new int[10];
          for(int i=0; i<10; ++i)
              arr[i] = val;
          return arr;
      }
      

      然后您可以调用该函数并像这样使用该数组:

      int * a = makeArray(7);
      
      for(int i=0; i<10; ++i)
          std::cout << a[i] << std::endl;
      
      delete [] a; // never forget this.  Obviously you wouldn't do it right
                   // away like this, but you need to do it sometime.
      

      将指针与 new 一起使用还可以让您在运行时确定数组的大小,这是本地静态数组无法做到的(尽管在 C 中可以):

      int * makeArray(int size, int val)
      {
          int * arr = new int[size];
          for(int i=0; i<size; ++i)
              arr[i] = val;
          return arr;
      }
      

      曾经是指针的主要用途之一。但就像我在顶部所说的那样,我们不再这样做了。我们使用vector

      指针的最后遗迹之一不适用于动态数组。我唯一一次使用它们是在我希望一个对象可以访问另一个对象的类中,而不给它该对象的所有权。因此,对象 A 需要了解对象 B,但即使对象 A 消失了,这也不会影响对象 B。您也可以为此使用引用,但如果您需要为对象 A 提供更改哪个对象的选项,则不需要它可以访问。

      【讨论】:

      • 不要拘泥于语言特性的使用/不使用。
      【解决方案5】:

      (未经测试,只是写下来。并按要求有意保持原始状态。)

      int* oneInt  = new int;  // allocate
      *oneInt = 10;            // use: assign a value
      cout << *oneInt << endl; // use: retrieve (and print) the value
      delete oneInt;           // free the memory
      

      现在是一个整数数组:

      int* tenInts = new int[10];  // allocate (consecutive) memory for 10 ints
      tenInts[0] = 4353;           // use: assign a value to the first entry in the array.
      tenInts[1] = 5756;           // ditto for second entry
      //... do more stuff with the ints
      delete [] tenInts;           // free the memory
      

      现在有了类/对象:

      MyClass* object = new MyClass();  // allocate memory and call class constructor
      object->memberFunction("test");   // call a member function of the object
      delete object;                    // free the object, calling the destructor
      

      这就是你想要的吗?希望对你有帮助。

      【讨论】:

        【解决方案6】:

        我想这就是你要问的:

        基本上 C++ 不允许可变大小的数组。 C++ 中的任何数组都必须指定一个非常具体的大小。但是您可以使用指针来解决这个问题。考虑以下代码:

        int *arry = new int[10];
        

        这只是创建了一个包含 10 个元素的整数数组,并且与此几乎完全相同:

        int arry[] = int[10];
        

        唯一的区别是每个人将使用一组不同的语法。但是想象一下尝试这样做:

        Class class:
        {
        public:
            void initArry(int size);
        
        private:
            int arry[];
        };
        
        void class::initArry(int size)
        {
            arry = int[size]; // bad code
        }
        

        无论出于何种原因,C++ 都被设计为不允许为常规数组分配在运行时确定的大小。相反,它们必须在编码时分配大小。然而,在 C++ 中创建数组的另一种方法 - 使用指针 - 没有这个问题:

        Class class:
        {
        public:
            ~class();
            void initArry(int size);
        
        private:
            int *arry;
        };
        
        class::~class()
        {
            delete []arry;
        }
        
        void class::initArry(int size)
        {
            arry = new int[size]; // good code
        }
        

        您必须在第二个示例中进行一些内存清理,因此我包含了析构函数,但是通过使用指针,您可以在运行时调整数组的大小(大小可变)。这叫做动态数组,据说这里的内存是动态分配的。另一种是静态数组。

        就二维数组而言,您可以像这样处理它:

        Class class:
        {
        public:
            ~class();
            void initArrays(int size1, int size2);
        
        private:
            int **arry;
        };
        
        class::~class()
        {
            delete [] arry[0];
            delete [] arry[1];
            delete [] arry;
        }
        
        void class::initArrays(int size1, int size2)
        {
            arry = new int*[2];
            arry[0] = new int[size1];
            arry[1] = new int[size2];
        }
        

        免责声明:我已经有一段时间没有使用这种语言做了很多工作了,所以我在某些语法上可能略有错误。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-08-14
          • 1970-01-01
          • 2018-05-02
          • 2015-08-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多