静态数组和动态数组

概念

数组在程序设计中应用十分广泛,可以用不同类型的数组来存储大量相同类型的数据。创建数组的方式一般有三种:

全局/静态范围的数组、局部变量数组、申请堆空间创建的数组。其中,全局/静态范围的数组以及局部变量数组都属于静态数组,从堆中申请空间建立的数组为动态数组。

静态数组和动态数组的区别

1、静态数组的大小是在编译期间就确定,并且分配的,其内存在使用结束后由计算机自动释放,效率高;动态数组是在程序运行时,由程序员根据实际需要从堆内存中动态申请的,使用结束后由程序员进行释放,效率低。

2、对静态数组进行sizeof运算时,结果是整个数组的大小,而对动态数组进行sizeof运算时,结果为常数4.

因此可以用sizeof(数组名)/sizeof(*数组名)或者sizeof(数组名)/sizeof(元素类型)来获取数组的长度。

int a[5]; 则sizeof(a)=20,sizeof(*a)=4.因为整个数组共占20字节,首个元素(int型)占4字节。
int *a=new int[4];则sizeof(a)=sizeof(*a)=4,因为地址位数为4字节,int型也占4字节。

3、静态数组作为函数参数时,在函数内对数组名进行sizeof运算,其结果为4。因为此时数组名是一个数组指针,即一个地址,占用4个字节的内存(因为在传递数组名的参数时,编译器对数组的长度不做检查。对动态数组的函数名,无论何时进行sizeof运算,得到的结果都是4。

4、在一个函数内声明的静态数组不可能通过函数返回,因为生存期的问题,函数调用完其内部变量占用的内存就被释放了。如果想通过函数返回一个数组,可以在函数中用new动态创建该数组,然后返回其首地址。静态数组是在栈中申请的,而函数中的局部变量也是在栈中的,而new动态数组是在堆中的分配的,所以函数返回后,栈中的申请的内存被自动释放,而堆中申请的内存如果没有delete就不会自动释放。

一个程序来说明静态数组和动态数组的区别

//首先声明几个数组:
int g_a[10];    // 全局变量数组

int main(void)
{
    int a[10];    // 局部变量数组
    static int s_a[10];    // 静态局部变量数组
    int *p1_a, *p2_a;    // 数组指针

    // 为动态数组申请空间
    p1_a = (int*)malloc(sizeof(int) * 10);
    p2_a = new int[10];

    // 为数组赋值
    a[7] = 0;
    s_a[7] = 0;
    g_a[7] = 0;
    p1_a[7] = 0;
    p2_a[7] = 0;

    // 释放空间,并且将指针置0
    delete[] p2_a;
    free(p1_a);
    p1_a = p2_a = 0;
}

上述程序中,5个数组的在赋值的时候除了变量名以外几乎都是一模一样的,是不是他们的实现也一样了呢?
答案是否定的,动态数组和静态数组虽然在使用时看起来没有什么差别,但他们实现是不一样的。
反汇编看一下他们的代码。

数组类型C/C++代码汇编实现简略说明
局部变量a[7] = 0;MOV DWORD PTR SS:[EBP-C], 0采用EBP在堆栈定位变量
[EBP - 28] a[0]
...
[EBP - 4] a[9]
静态局部变量s_a[7] = 0;MOV DWORD PTR DS:[4C5E5C], 0静态变量会被放到数据.data段中
全局变量g_a[7] = 0;MOV DWORD PTR DS:[4C5E84], 0全局变量和静态变量一样,
会被放到数据.data段中
数组指针
(malloc)
p1_a[7] = 0;MOV EAX, DWORD PTR SS:[EBP-2C]
MOV DWORD PTR DS:[EAX+1C], 0
对于数组指针,要进行两次寻址
0x1C / 4 = 7
数组指针
(new)
p2_a[7] = 0;MOV EAX, DWORD PTR SS:[EBP-30]
MOV DWORD PTR DS:[EAX+1C], 0
同上

 

从寻址的角度来说,静态数组采用的是直接寻址,而动态数组都是两次寻址,这和动态数组本身实现是有关系的。静态数组的变量本身就是数组第一个元素的地址。动态数组的变量存放的是一根指向到申请空间的首址指针。
即使对于静态二维数组或者多维数组来说,其真实也是多个地址连续的一位数组组成的。

例如:int a[35]和int b[7][5]对于前者,就是包含35个int类型值的数组而对于后者,可以理解为b是一个拥有7个数组类型的数组,每个数组类型又是一个拥有5个int类型值的数组。可以得出的结果就是两个数组在内存中的分布其实是一样的。

动态数组的创建方式:

TYPE (*p)[N] = new TYPE [][N];

其中,TYPE是某种类型,N是二维数组的列数。采用这种格式,列数必须指出,而行数无需指定。在这里,p的类型是TYPE*[N],即是指向一个有N列元素数组的指针。

还有一种方法,可以不指定数组的列数:

int **p;
p = new int*[10];    //注意,int*[10]表示一个有10个元素的指针数组
for (int i = 0; i != 10; ++i)
{
    p[i] = new int[5];
}

这里是将p作为一个指向指针的指针,它指向一个包含10个元素的指针数组,并且每个元素指向一个有5个元素的数组,这样就构建了一个10行5列的数组。每一行元素的地址之间都是连续的,但是行与行之间的地址是不连续的。例如p[0][9] 和 p[1][0]两个元素的地址是不连续的。

当数组使用完毕,释放空间的代码是:

for(int i = 0; i != 5; i++)
{
    delete[] p[i];
}
 delete[] p;

数组与指针

int a[10];
int *pa = a;

声明完这两个变量后,我们可以:
a[0] = 0;
同样我们可以:
*a = 0; // 一次寻址
*pa = 0; // 两次寻址
pa[0] = 0; // 两次寻址

对于*a和*pa来说,其实原型为:
*(a + 0)
*(pa + 0)

动态数组(数组指针)

动态数组虽然也有局部的——它的指针,
但一旦new过了之后,它所指向的实际分配空间是在堆里面的。

在堆中申请空间有两种方式:C的malloc和C++的new。
对于基本数据类型的数组来说,这两种申请空间的方式没有什么太大的区别。

int *pa, *pb;
pa = (int *)malloc(sizeof(int) * 10); // 正确
pb = new int[10]; // 正确

但是,如果类数组的话,一定要用new来分配空间,而不是malloc。

MyClass *pa, *pb;
pa = (MyClass *)malloc(sizeof(MyClass) * 10); // 错误
pb = new MyClass[10]; // 正确

采用malloc调用的只是分配了一块sizeof(MyClass) * 10大小的空间,其他什么事情都没做。
采用new调用,不但分配了空间(自动计算),而且还调用了每个MyClass的构造函数。
对于类来说,构造函数是很重要的,如果没有调用构造函数而使用该类变量的话,可能会出现预想不到的结果。
同样,在用new []申请空间后,需要用delete []释放空间。
为什么不是delete,而是delete []?
对于基本数据类型的数组来说,delete只释放了pa[0]的空间,而delete []正确地释放了所有的空间。
对于类的数组来说,delete只调用了pa[0]的析构函数,并是放了空间,
而delete []调用了所有元素的析构函数,并且正确地释放了所有的空间。


因为存在这样一个事实:
在指针上加上一个值后,并不是单纯在地址上加上该值,
而是表示一个偏移量,根据类型不同,偏移单位量也是不同的。

posted on 2015-11-27 16:39  RunningSnail 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/tgycoder/p/5001035.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值