c++ 二维数组

C++ 的二维数组是一个经常遇到的话题,也是C++程序员的基本功把。

这篇文章,我们就主要谈谈C++ 的二维数组问题。

首先从一维数组谈起


在栈空间上创建一个一维数组

A a[n];

这里的n, 必须是在编译器期间就可以确定的值,常量或者常量表达式,const int n = 2,这样的n应该也可以。在C++ 11中,或许有所改变,这里不是我们的重点(但是很值得了解)。

A: 可以是内置类型(int ,char double ,float 等),也可以是类类型。

那么就有一下几个问题:

1. a[...] 的初值是多少,如果是内置类型的话,初值为0 吗? 如果是类类型的话,是在创建对象的时候是调用默认构造函数吗?(如果类A 没有默认构造函数怎么办?)

2. 如何对 a[...] 进行初始化操作? (请注意初始化操作 和赋值操作的区别)

 

在堆空间上创建一个一维数组:

A * a = new A[n].

n : 这里的 n 不需要在编译期间就确定值。运行时确定即可。

同样会有上述两个问题的

无论在堆上,还是在栈上这些对象都是连续存储的,所以数组可以进行随存取。

在栈空间上分配数组时要小心,因为栈空间是有限的,如果你在栈空间上分配一个很大的数组,那么很可能会造成段溢出,在Linux上是segment fault(段错误)。

数组元素的存取:

1 A b = a[3];
2 A c;
3 a[4] = c;

在编译的时候 a[3] 被看作 *(a + 3*sizeof(A) ),其中a 是数组元素的首地址。(a + 3*sizeof(A) )是第四个元素的首地址, *操作符表示取这个地址中的元素。

编译器对于二维数组的表示同样如此,只是方式略有不同。其中,编译器是不管操作是否越界的。

 

将数组当作函数参数:

1 void traverse(int a[], int n); //#1  正确 遍历数组, 共n哥元素
2 
3 void traverse(int a[10]); // #2 正确,只是无法知道 a 的边界,也就是其中的元素个数。
4 
5 void traverse(int a[10], int n); // #3 正确
6 
7 void traverse(int * a, int n); // #4 正确.
8 void traverse(int a[n]);  // 正确还是错误????

我们知道:当你 写出表达式: a[11] = 3  的时候,编译器是如何将3存入相应的地址中呢? 首先编译器确定 a表示一个地址(找到变量a的声明)而不是其它,然后编译器还必须知道a中的每一个元素的类型 A, 然后就可以采用这样的方式: *(a + 3*sizeof(A) ) = 3 进行 操作了。

int * a; int a[10];  int a[];  都由这样的功能的:声明了每个元素的类型,也表示了a的类型(只是其中略有不同)。所以这三种作为函数的参数声明时起到的作用都是一样的,编译器将这三个表达式都看作 int *a, 都将参数作为一个指针类型的变量。对于 int a[10],忽视掉了其中的10。(私以为编译器这样做的目的是为了实现简单)。

所以如果我们想要确定 数组中的界限,还必须传递一个整形变量,表示a中的元素个数。

 

二维数组


 

定义二维数组:

A a[3][4];   // 栈空间上分配

这样做就是告诉编译器如果看见:a[2][2] 这样的表示就当作 *(a + (2*4+2)*sizeof(A) )。 我们可以看到  这样的声明告诉了编译器很多的信息,它告诉了编译器:

1. 元素的类型 A

2. 起始地址 a

3. 可以按照 a[i][j] 的方式表示元素: *( a + (i*4+j)*sizeof(A) )

4. 或许我们注意到了: 4这个值很重要,在计算a[i][j]的地址的时候:需要知道每一行由多少个元素。

那么我们如何在堆上分配二维数组呢? 大致分为四种方法,方法一:

A (*a)[4];     

a = new A[3][4];

我们为什么要分为两次呢?其实将两个语句合在一起是常规做法,我们这里这样子写可以更清楚一些。

第一句对于 a 的类型的声明很重要: A (*a)[4] 已经完整的告诉了我们刚才所说的四点信息了。这里说明a是一个地址(也就是指针),与平常的指针有什么区别呢?比如 A *b.

区别在于你可以: a[3][2], 但是不可以b[3][2],为什么?因为编译器不知道每一行中元素的个数,就是上述所说的第四点。

存取元素:

a[i][j] = x;

释放堆空间:

delete[] a;

我们或许会觉得奇怪,不要奇怪,仔细想想还是蛮道理的,可以与方法二进行对比。

 

方法二:

1 A * (* a) = new A[3];  // 我们故意加上括号,可以表示的更明确。 a 是一个指针, 其中的元素是A* 也是指针类型。所以a[i]表示一个指针而已。
2 
3 for(int i = 0; i <= 3; i++)
4     a[i] = new A[4];

存取元素:

a[i][j] = x;   //因为 a[i] 表示 A 类型的指针

释放堆内存:

1 for(int i = 0; i < 3; i++)
2     delete[] a[i];
3 
4 delete[] a;   //a 是一个指针,元素类型是 A*

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2014-04-07 18:27  xidianzyh  阅读(404)  评论(0编辑  收藏  举报