new 与 delete


到目前为止,变量创建后会配置内存空间,这类资源是配置在内存的栈区(Stack),生命周期局限于函数运行期间,也就是函数执行过后,配置的空间就会自动清除。

若要将函数执行结果返回,不能直接返回这类被自动配置空间的地址,因为函数执行过后,该空间就会释出,函数调用者后续若透过地址取用这些资源,会发生不可预期的结果,例如,不能直接将局部创建的变量地址或数组地址返回。

然而程序运行后,资源之间的互用关系错综复杂,有些资源「无法预期」被使用的生命周期,因为无法预期,也就有赖于开发者自行管理内存资源,也就是开发者得自行在需要的时候配置内存,这些内存会被配置在堆区(Heap),不会自动清除,开发者得在不使用资源时自行删除内存。

要自行配置或删除内存,可以使用newdelete运算符。举例来说,可以在程序中以动态方式配置一个int类型大小的内存:

int *p = new int;

在这段程序中,new运算符会配置int需要的空间,并返回该空间的地址,可以使用指针p来存储地址,这段程序只配置空间但不初始化空间的值,若要在配置完成后指定存储值,可以如此定义:

int *p = new int(100);

这段程序在配置空间之后,会将空间中的存储值设定为 100,以下使用一个简单的程序来示范动态配置的使用:

#include <iostream> 
using namespace std; 

int main() {
    int *p = new int(100); 

    cout << "空间地址:" << p << endl 
         << "存储的值:" << *p << endl; 

    *p = 200; 

    cout << "空间地址:" << p << endl 
         << "存储的值:" << *p << endl; 

    delete p;

    return 0; 
}

执行结果:

空间地址:0x787a88
存储的值:100
空间地址:0x787a88
存储的值:200

使用new动态配置的空间,在程序结束前不会自动归还,必须使用delete将空间归还,若大量使用new而没有适当使用delete的话,由于空间一直没有归还,最后将导致整个内存空间用尽。

如果想配置连续个指定类型的空间,可以如下:

int *p = new int[1000];

这段代码动态配置了 1000 个int大小的空间,并返回空间的第一个地址,配置后的空间数据是未知的,[]中指定的长度可以是来自于表达式,不必是编译时期就得决定的值,这个值必须自行存储下来,因为没有任何方式,可以从p得知到底配置的长度是多少。

如果想在配置连续空间后指定每个空间的初值,可以使用{},例如:

int *p = new int[3]{10, 20, 30};

如果要全部设定为类型的空值,可以如下:

int *p = new int[3]();

连续配置得来的空间,不使用时要使用delete[]归还给内存,必须加上[],表示归还的是整个连续空间:

delete [] p;

之前在谈数组时说过,数组具有指针性质,因此上面的方式,会被用来克服数组大小必须事先决定的问题,也就是可以用来动态地配置连续空间,并当成数组来操作,例如底下是个简单的示范:

#include <iostream> 
using namespace std; 

int main() {
    int size = 0; 

    cout << "输入长度:"; 
    cin >> size; 
    int *arr = new int[size]{0}; 

    cout << "指定元素:" << endl; 
    for(int i = 0; i < size; i++) { 
        cout << "arr[" << i << "] = "; 
        cin >> arr[i]; 
    } 

    cout << "显示元素值:" << endl; 
    for(int i = 0; i < size; i++) {
        cout << "arr[" << i << "] = " << arr[i]
             << endl; 
    } 

    delete [] arr; 

    return 0; 
}

执行结果:

输入长度:3 
指定元素:
arr[0] = 10
arr[1] = 20
arr[2] = 30
显示元素值:
arr[0] = 10
arr[1] = 20
arr[2] = 30

若要动态配置连续空间,并当成二维数组来操作,就记得二维(或多维)数组,就是以数组的数组来实现,二维数组就是多段一维数组,如果你的二维数组有两段一维数组,那就是如下:

int **arr = new int*[2];

现在arr[0]arr[1]可以分别存储一维数组地址,目前尚未初始化,若每段一维数组的长度是 3,可以如下动态配置,并将一维数组每个元素初始化设为 0 :

for(int i = 0; i < 2; i++) {
    arr[i] = new int[3]{0};
}

来看一下简单的范例:

#include <iostream> 
using namespace std; 

int main() {
    int **arr = new int*[2];

    for(int i = 0; i < 2; i++) {
        arr[i] = new int[3]{0};
    }

    for(int i = 0; i < 2; i++) {
        for(int j = 0; j < 3; j++) {
            cout << arr[i][j] << " ";
        }
        cout << endl;
    }

    for(int i = 0; i < 2; i++) {
        delete [] arr[i];
    }
    delete [] arr; 

    return 0; 
}

记得最后要删除配置的空间时,也要如以上范例逐一删除,执行结果如下:

0 0 0
0 0 0

既然可以动态配置,那每段一维数组长度当然可以不一样啰!

#include <iostream> 
using namespace std; 

int main() {
    int **arr = new int*[2];

    arr[0] = new int[3]{0};
    arr[1] = new int[5]{0};

    for(int i = 0; i < 3; i++) {
        cout << arr[0][i] << " ";
    }
    cout << endl;

    for(int i = 0; i < 5; i++) {
        cout << arr[1][i] << " ";
    }
    cout << endl;

    for(int i = 0; i < 2; i++) {
        delete [] arr[i];
    }
    delete [] arr; 

    return 0; 
}

执行结果:

0 0 0
0 0 0 0 0

当然,动态配置的方式,会令程序变得不易理解,在需要动态创建长度不定的容器时,建议考虑使用vector之类的容器。

C++ 11 提供了unique_ptrshared_ptr等类,可以协助管理动态配置的资源,这之后再来谈。


展开阅读全文