标签:
概念
数组在程序设计中应用十分广泛,可以用不同类型的数组来存储大量相同类型的数据。创建数组的方式一般有三种:
全局/静态范围的数组、局部变量数组、申请堆空间创建的数组。其中,全局/静态范围的数组以及局部变量数组都属于静态数组,从堆中申请空间建立的数组为动态数组。
静态数组和动态数组的区别
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 []调用了所有元素的析构函数,并且正确地释放了所有的空间。
因为存在这样一个事实:
在指针上加上一个值后,并不是单纯在地址上加上该值,
而是表示一个偏移量,根据类型不同,偏移单位量也是不同的。
标签:
原文地址:http://www.cnblogs.com/tgycoder/p/5001035.html