首页 > 代码库 > C++数组解析

C++数组解析

C++数组:常类型数组和类对象数组

 

常类型数组:

1. 数组的定义和初始化:

一维数组:

1>

       //stack--------------------------------------------

       int a[10];

       //cout<<a[0]<<endl;  error 未初始化不能访问

       int a[10]={1,2}; //a[0]==1,a[1]==2,a[3]==0,a[3~9]==0

       int b[]={1,3};  //length of b is 2.

       //heap----------------------------------------------

       int d=3;

       int *c=new int[d];

       delete c;//  防止内存泄漏

2> 动态数组的含义

       const int i=3;

       int a[i];// OK

       int j=3;

       int b[j];// error

       int *c=new int[j];// OK

在定义int[]时,编译器必须知道要在栈中分配多少内存,所以[]内的必须是常数或常量。在定义int *a=new的时候,由于是动态分配内存,所以编译的时候不实际分配,在运行才会指定内存分配的大小单元。

3> 求数组的长度通用做法

       int a[]={1,2,3,4,5,6};

       int length=sizeof(a)/sizeof(a[0]);

因为这里sizeof(a)==sizeof(int)*n;而sizeof(a[0])==sizeof(int)。所以可以求得数组的长度。

注意不能在其他函数中使用,因为数组在传参过程中会退化成指针,使得sizeof(a)==sizeof(int*):

void foo(int a[])

{

       cout<<sizeof(a)/sizeof(a[0])<<endl;

}

int _tmain(int argc, _TCHAR* argv[])

{

       int a[]={1,2,3,4,5,6};

       foo(a);

       system("pause");

       return 0;

}

在window 64bit结果是2。注意如果编译64bit程序,vs必须调成64bit编译。Vs默认的是32bit编译

 

二维数组:

       //stack------------------------------------------------------------

       int a[5][5];

       int b[5][5]={{1,2},{5}};

       int c[][4]={{1,2},{6,9}};

       //int c[][]={{1,2},{6,9}}; error

       //heap--------------------------------------------------------------

       //1

       int m=2;const int n=3;

       int (*a)[n]=new int[m][n];//n必须是常量,m可以是变量

       a[0][0]=1;

       delete[] a;//因为申请的空间是连续的,所以可以一下子释放

       //2

       int i;

       int **d=newint*[m];//m可以是变量

       for(i=0;i<m;i++) d[i]=newint[i+1];//之后才能使用d[i][j]

       d[0][0]=2;

 

       for(i=0;i<m;i++) delete[]d[i];//因为申请的空间不连续,所以要一个一个释放

       delete[] d;//注意最后别忘了这句,感觉new的数量应该始终和delete一样。

1> 注意:new和delete的数量应该始终是一样的。因为无论是delete还是delete[],它一次都只能释放一块连续的内存(对于普通类型来说)。而每次new出来的内存一般都是和上次new出来的内存是不连续的,所以要分别delete掉new出来的内存。

2> New是动态申请数组,后面申请的大小一般都可以是变量。而”=new”之前的,例如上面的int (*a)[n]=new。这里编译器需要知道n的大小,所以=new之前的一般必须都是常量。

3> 指针数组和数组指针

      int (*a)[10]=newint[10][10];//数组指针,a就是一个指针,它指向一个数组。

       int *p[10];//指针数组,其实应该这么写:int* p[10];这样看就是一个一维数组,数组的每个元素都是一个指针。p是数组名。而不是指针,所以不能delete p

       for(inti=0;i<10;i++)

              p[i]=newint[10];

       p[0][0]=1;

       for(inti=0;i<10;i++)

              delete p[i];

4> 二维数组的访问

       int (*a)[10]=new int[10][10];

       a[3][3]=2;

       cout<<(*(a+3))[3]<<endl;

       cout<<*(a[3]+3)<<endl;

       cout<<*(*(a+3)+3)<<endl;//3个其实都是访问的a[3][3]

解释一下:首先a可以看作该二维数组的首地址。而编译器把二维数组a看成一个一维数组,只是这个一维数组的每个元素又是一个一维数组。所以*(a+3)其实是访问a一维数组的第4个元素(也就是a[3])。因为a[3]又是一个一维数组,所以*(a[3]+3)就是访问a[3]这个数组中的第4个元素。即a[3][3]。

标准写法:*(*(a+i)+j)    ==  a[i][j]

 

2. 数组和指针的关系

1. 数组名只是表示一种数据结构。而指向数组的指针,表示它存放的是数组的地址。

       char *a="123456";//编译器会在常量区存放“123456”,而会在栈区存放一个指针aa指向这个常量区的内容。

                       //也就是说是绝不能去改a指向的内容的。不可以a[0]==‘9‘.

       char a[]="123456";//编译器只是在栈区开辟一个连续的空间,就是数组。每个空间内分别存放‘1‘,‘2‘,‘3‘,‘4‘,‘5‘,‘6‘,‘\0‘

                      //该空间大小为7,且这些存放的值是可以去改的。例如a[0]=‘9‘;

 

2. 指向数组的指针,是可以自加,自减,重新指向别的内容,可以deletep;

数组名是不可以修改的,有点像常量指针。更不可以delete a;

       char *a="123";

       a++;

       cout<<*a<<endl;

       char b[]="123";

       //b++;   error

 

3. 当数组名作为实参传递时,无论形参的形式怎样,编译器都是用指针来接收实参的。

void foo(char a[])

{

       a++;

       cout<<*a<<endl;

}

int _tmain(int argc, _TCHAR* argv[])

{

       char b[]="123";

       foo(b);

       system("pause");

       return 0;

}

也就是说,在函数foo内,a就是一个指针,不再是函数名了。它可以自加,自减。完全就是一个指针了。

所以在函数foo内sizeof(a)是等于一个指针的大小的。

 

3. 数组的存储格式

多维数组的在内存中的存储是按照最低维连续的格式来存储的。(注意和mat的区别,在mat中是按列储存的)

比如:a[3][3]内存中的存储是:

a[0][0]  a[1][0]  a[2][0]

a[0][1]  a[1][1]  a[2][1]

a[0][2]  a[1][2]  a[2][2]

 

类对象数组

1. 栈存储,局部变量数组。

class A{};

class B

{

public: B(int i){}

};

class C

{

public: C(int i,int j){}

};

int _tmain(int argc, _TCHAR* argv[])

{

       Aa[10];//构造函数可以无参的。

       Bb[5]={1,2,3,4,5};//构造函数只有一个参数的,其实是利用了隐式转换

       Cc[3]={C(0,1),C(1,2),C(2,3)};//构造函数有两个以上的参数的,对象数组只能这样定义。

       system("pause");

       return 0;

}

 

2. 堆存储。

class A{};

class B

{

public: B(int i){}

              void foo(){}

};

int _tmain(int argc, _TCHAR* argv[])

{

       A*a=new A[10];

       delete[] a;//这里只能是delete[] a,不能用delete adelete[] a调用a[0~9]的所有析构。而delete a只调用可一个a[0]的析构

                  //注意!!这里由于A中没有动态分配内存,所以delete a,这里也不会产生内存泄漏。只是没有调用全部的析构函数而已。

                    //如果Anew了堆内存,而在析构中释放这些内存,就必须要delete[] a了。不然就会引发内存泄漏。

       B*b[10];  //有参的构造函数,想要在堆中分配,就要用到指针数组。

       for(inti=0;i<10;i++)

              b[i]=new B(i);

       b[0]->foo();

 

       for(inti=0;i<10;i++)

              delete b[i];//由于分开new的,所以要一个一个delete。否则一定会产生内存泄漏。

       _CrtDumpMemoryLeaks();//检测内存是否泄漏函数

       system("pause");

       return 0;

}

总结一下:就是说delete和delete[]的本质区别,就是调用了一个析构函数还是调用一组析构函数。

 

引申总结:delete

1. delete和delete[]的本质区别?

众所周知在普通类型对象中,delete和delete[]是没有区别的。

                在类对象数组中是有区别的,区别是调用了析构函数的多少。

                delete只调用了一个析构。Delete[]调用了一组析构。

2. 对于数组的释放,是一下子delete,还是要分开一个一个delete?

delete使用的次数取决于new的个数。

如果数组是连续的,一下子new出来的,只需要delete/delete[]一次即可。

如果数组是不连续的,分开new出来的,则需要一个一个delete/delete[]。

C++数组解析