
二维数组在概念上是二维的,但在内存中是连续存放的;换句话说,二维数组的各个元素是相互挨着的,彼此之间没有缝隙。
在C语言中,二维数组是按行排列的。
将二维数组当作参数的时候,必须指明所有维数大小或者省略第一维的,但是不能省略第二维或者更高维的大小,这是由编译器原理限制的。
事实上,编译器是这样处理数组的:
设有数组int a[m][n],如果要访问a[i][j]的值,编译器的寻址方式为:
&a[i][j]=&a[0][0]+i*sizeof(int)*n+j*sizeof(int); //注意n为第二维的维数,
因此,可以省略第一维的维数,不能省略其他维的维数。
在定义二维数组的时候对其进行初始化,可以省略第一维,编译器会根据你的初始化语句自动决定第一维度。
int bin_arr[][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //至少需要4行, 4 * 3 * 4 = 48
//int bin_arr[][3] = {1, 2, 3, 4 }; //两行, 2 * 3 * 4 = 24; 不满,有空间浪费
cout << sizeof(bin_arr) << endl;
二维、多维数组,除了第一维大小可以省略,其余的维度大小都不能省略
=====
实际上,C语言并没有什么“二维数组”的概念,C语言中只有数组!
我们先明确下数组的知识:
声明数组的语法是:在名字后跟一个[],这表明这个名字将是个数组,括号中有数字则表示数组的大小。 剩下的那个类型则代表数组中元素的类型。
所以int a[]; 这一句的意思是:a是个数组,其储存的元素是int类型。
同时,在定义数组时必须要能够确定数组大小,这分两种情况:
|
1
2
3
4
5
6
|
// 声明数组时可以指明数组大小int a[10]; // a是一个可含10个int的数组// 也可以不写出大小,但给出初始化列表// 编译器会根据初始化列表来自动确定数组的大小int a[] = {1, 2, 3, 4}; // 编译器能确定数组a的大小是4 |
可以看出,定义数组时是可以省略大小的,只要编译器能推断出它的size就可以。 编译器怎样推断? 首先编译器得知道单个元素的大小,再根据初始化列表的大小,就自然能算出a能存几个元素了(做除法而已嘛)。
下面看看“二维数组”到底是什么: 所谓二维数组就是一个存储了数组的数组!
比如 int a[2][3]; 这代表 a 是个含有2个元素的数组。
// 数组的名字是a,数组的长度是2,数组的元素是int [3]
看上面的讲解“剩下的类型代表数组中元素的类型”,那么剩下的就只有 int [3],这是一个含有3个int的数组。 所以a就是一个含有2个“含有3个int数组”的数组。
==== 上面这些可以理解吗?====
那么“定义数组时可省略大小”的规则对于这样复杂些的数组也是成立的。
如 int a[][3] = { ... };
紧挨着 a 后面的括号中的数才表示a数组的大小(也就是所谓的二维数组的行数),它当然是可以省略的,只要编译器能推断出它应有的大小。
a的元素(就是int [3])是个有3个int的数组,这个元素的大小是可以确定的(就是3个int嘛),而初始化列表的大小也是确定的,所以编译器就可以确定a数组的大小(所以你才可以省略不写)。
那么 int a[][] = { ... }; 这样写是错误的,原因也就知道了吧? a数组大小不确定,a的元素也是个大小不确定的数组,编译器就没法算出到底a能有多少个元素了。
所以“只能省略行数”这一说法的真正原因是:只有那个紧挨数组名的括号代表该数组的大小,后面那些括号代表的是这个数组的元素。
元素的大小必须是确定的,所以后面那些括号中的数字才不能省略!
==========
https://blog.csdn.net/weixin_40539125/article/details/83025410
https://blog.csdn.net/wokaowokaowokao12345/article/details/52999502
直接将返回值定义为二级指针,将二维数组与二级指针转化,想了想发现根本不可行。二级指针和二维数组并不等价。
查找了下资料,发现了论坛https://bbs.csdn.net/topics/360158391大家的讨论。
我们先看下二维数组与二级指针问题:cannot convert from 'double [10][10]' to 'double ** '
例如:
double arry[5][10] = {0};
double** pp;
pp=arry;
直接的赋值会提示错误:cannot convert from 'double [10][10]' to 'double ** '
解决方法:
constexpr int kRow = 3; constexpr int kColumn = 10; double* d1 = new double(1); // 单个元素 double* d2 = new double[10]; // 数组 double** d3 = new double*[kRow]; // 动态分配二维数组 for (int i = 0; i < kRow; ++i) { d3[i] = new double[kColumn]; } cout << sizeof(d3) << endl; // 8 是个指针 double d4[kRow][kColumn]; // 静态分配的二维数组 cout << sizeof(d4) << endl; // 240 // double (*d41)[kColumn] = d4[0]; // error: cannot convert 'double*' to 'double (*)[10]' double (*d42)[kColumn] = &d4[0]; // OK for (int i = 0; i < kRow; ++i) { double (*d43)[kColumn] = &d4[i]; for (int j = 0; j < kColumn; ++j) { // d43[j] = (double)i * 10 + j; // error: incompatible types in assignment of 'double' to 'double [10]' *d43[j] = (double)i * 10 + j; // d43是个指向数组的指针;*d43才是数组,才可以用下标操作符。 } }
一个函数形如:
void f(float **p){
/* 想要在函数体中按二维数组的方式访问*/
p[1][1] = 0; //c++用vc编译ok,运行出错(非法访问)
}
float **p; //其实这里的p并不是一个二维数组的指针,只不过是一个指向指针的指针
像你这样访问肯定是会出问题的。
float a[2][2]={0, 1, 2, 3}; float **p=(float**)a; cout << "a, &a, p: " << endl; cout << a << endl; cout << &a << endl; cout << p << endl << endl; cout << "p+1, p+2, p+3: " << endl; cout << (p+1) << endl; cout << (p+2) << endl; cout << (p+3) << endl << endl; cout << "&a[0][0], 01, 10, 11:" << endl; cout << &a[0][0] << endl << &a[0][1] << endl << &a[1][0] << endl << &a[1][1] << endl << endl; 输出: a, &a, p: 0x7ffd387fda00 0x7ffd387fda00 0x7ffd387fda00 p+1, p+2, p+3: 0x7ffd387fda08 0x7ffd387fda10 0x7ffd387fda18 &a[0][0], 01, 10, 11: 0x7ffd387fda00 0x7ffd387fda04 0x7ffd387fda08 0x7ffd387fda0c
cout << *p << endl; // 输出的是一个无意义的值 0x3f80000000000000
cout << **p << endl; // segment falutl,对地址0x3f80000000000000进行解引用,必挂无疑
cout << *((float*)p) << endl; // 0
cout << *((float*)p+1) << endl; // 1

只能 cout << *((float*)p) << endl; 这样使用,强制把float** p 转成一维指针float*,不好,这得程序员牢牢记住p的真实类型。
或者更好的办法是使用指向数组的指针:
float (*px)[2] = a;
cout << px[1][0] << endl;
cout << *(*(px + 1) + 1); // 这样也是OK的
所以说,二位数组并不能简单的转换成指向指针的指针。
正确的指向二维数组的指针应该是:
float a[5][10];
float (*p)[10];//只需要定义为指向第二维的指针,忽略第一维
p=a;
p[0][1]=a[0][1];
二级指针和二维数组并不等价。
二级指针是一个指向指针的指针
而二维数组其实就是一个指针,char a[3][4]; a是指向整个二维数组的首地址。它相当于(char *)[4],并不是char **;
所以不能直接:t=a;
要这样:t = (char **)a;
我们知道char array[]=”abcdef”; array是数组的首地址,
那么在二维数组中array当然也是数组的首地址,
看看这个定义char Array[][3] ={“ab“,“cd“,“ef“};
怎么知道的呢?定义这样一个数组,在vc调试窗口中
我们看到:
Array ---------0x64324234
|------Array[0]---0x64324234 “ab“
|------Array[1]---0x64324337 “cd“
|------Array[2]---0x6432433A “ef”
已经很明白了,实际编译器是这样实现二维数组的,实际上Array是“一维指针数组“的首地址,其中每一个元素指针都对应一个字符串,那么好我们来看看是否可以这样来使用Array二维数组。
char **pArray = Array;编译器提示出错,怎么办呢?加个(char **)试试,仍然出错。
设断看一下pArray的值和Array的值是相等的【值相等,但含义不同】,但我们是否可以象使用Array那样来同样输出字符串呢?
很明显是不行的,编译器不会把pArray+i处理成pArray+i*3寻找到第i个指针的地址,而只是简单的加了一个i.
这说明编译器只做了很简单的将地址值赋给了pArray,而它实际没有任何意义. 我们不能用它来访问任何数据. 很奇怪吗?
再来看看这样定义char *p[] = {“ab“, “cd“, “ef“};定义了一个指针数组.
char **sp = p;这样的用法经常看到,为什么这样就可以使用sp来访问字符串了呢,
的确编译器在编译的时候识别出了sp是一个指向一维数组的指针的指针, 那么我们就可以把它做为数组名来操纵整个数组了.
解决一:将二维数组定义为全局变量。
全局变量尽量少用,做项目时,可能会导致其他问题。
解决二:由于数组是不可修改的左值,所以在C/C++中函数的返回值不能是数组,但可以返回一个指向二维数组首元素的指针。例如:
————————————————
二维数组存放方式
二维数组中元素排列的顺序是按行存放的,即在内存中先顺序存放第一行的元素,再存放第二行的元素…
二维数组作为函数参数
二维数组作为函数参数,实参可以直接使用二维数组名,在被调用函数中对形参数组定义可以指定所有维数的大小,也可以省略第一维的大小说明,如:
void f(int a[3][4]);
void f(int a[][4]);
它们是合法且等价,也可以使用如下形式:
void f(int (*a)[4]); //a是一个指向数组的指针,数组长4
但不能省略第二维的大小,如下面的定义是不合法的,编译时会出错:
void f(int a[][]);
void f(int a[3][]);
因为从实参传递来的是数组的起始地址,如果在形参中不说明列数,编译器将无法定位元素的的位置。
各个维数不固定的二维数组
如果二维数组的各个维数不固定,我们将不能使用以上定义方法,可以通过以下2种简单的方法实现。
- 将二维数组当一维数组操作
被调用函数定义:
void f(int *a,int n);
实参传递:
int a[3][4];
f(*a,12);
- 手工转变寻址方式
被调用函数定义:
void f(int **a,int m,int n);
实参传递:
int a[3][4];
f((int **)a,3,4);
这样在被调用数组中对对元素a[i][j]的访问可以使用如下形式:
*((int *)a+n*i+j);
注意不能使用a[i][j]来直接访问,因为编译器无法为其定位。
代码
#include <stdio.h>
/*********************************
* 方法1: 第一维的长度可以不指定 *
* 但必须指定第二维的长度 *
*********************************/
void print_a(int a[][5], int n, int m)
{
int i, j;
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
printf("%d ", a[i][j]);
printf("\n");
}
}
/*****************************************
*方法2: 指向一个有5个元素一维数组的指针 *
*****************************************/
void print_b(int (*a)[5], int n, int m) //数组元素的类型
{
int i, j;
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
printf("%d ", a[i][j]);
printf("\n");
}
}
/***********************************
*方法3: 利用数组是顺序存储的特性, *
* 通过降维来访问原数组! *
***********************************/
void print_c(int *a, int n, int m)
{
int i, j;
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
printf("%d ", *(a + i*m + j));
printf("\n");
}
}
int main(void)
{
int a[5][5] = {{1, 2}, {3, 4, 5}, {6}, {7}, {0, 8}};
printf("\n方法1:\n");
print_a(a, 5, 5);
printf("\n方法2:\n");
print_b(a, 5, 5);
printf("\n方法3:\n");
print_c(&a[0][0], 5, 5);
getch();
return 0;
}
参考
http://guoyiqi.iteye.com/blog/1626922
C++:多维数组的动态分配(new)和释放(delete)
分配一个元素,和分配一个数组,都是用int* 来保存。
int *a = new int(5);
int *arr = new int[5];
对于简单的一维数组动态内存分配和释放,相信大家都是知道的,不过还是举个例子吧:
1 int *array1D;
2 //假定数组长度为m
3 //动态分配空间
4 array1D = new int [m];
5 //释放
6 delete [] array1D;
但是,对于多维数组动态分配,大家可能不太熟悉。下面以常见的二维和三维数组为例来说明:
1. 二维数组的动态分配和释放
int **array2D;
//假定数组第一维长度为m, 第二维长度为n
//动态分配空间
array2D = new int * [m];
for( int i=0; i<m; i++ )
{
array2D[i] = new int [n] ;
}
//释放
for( int i=0; i<m; i++ )
{
delete [] arrar2D[i];
}
delete [] array2D;
三维数组的动态分配和释放
int ***array3D;
//假定数组第一维为m, 第二维为n, 第三维为h
//动态分配空间
array3D = new int ** [m];
for( int i=0; i<m; i++ )
{
array3D[i] = new int *[n];
for( int j=0; j<n; j++ )
{
array3D[i][j] = new int [h];
}
}
//释放
for( int i=0; i<m; i++ )
{
for( int j=0; j<n; j++ )
{
delete []array3D[i][j];
}
delete []array3D[i];
}
delete []array3D;
————————————————
1.
A (*ga)[n] = new A[m][n];
...
delete []ga;
缺点:n必须是已知
优点:调用直观,连续储存,程序简洁(经过测试,析构函数能正确调用)
2. A** ga = new A*[m];
for(int i = 0; i < m; i++)
ga[i] = new A[n];
...
for(int i = 0; i < m; i++)
delete []ga[i];
delete []ga;
缺点:非连续储存,程序烦琐,ga为A**类型
优点:调用直观,n可以不是已知
指针数组:指针数组可以说成是”指针的数组”,首先这个变量是一个数组,其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型,在32位系统中,指针占四个字节。
数组指针:数组指针可以说成是”数组的指针”,首先这个变量是一个指针,其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。
2.1指针数组
char *arr[4] = {"hello", "world", "shannxi", "xian"};
//arr就是我定义的一个指针数组,它有四个元素,每个元素是一个char *类型的指针,这些指针存放着其对应字符串的首地址。
2.2数组指针
首先来定义一个数组指针,既然是指针,名字就叫pa
char (*pa)[4];
pa是一个指针, 指向一个char [4]的数组,每个数组元素是一个char类型的变量
既然pa是一个指针,存放一个数组的地址,那么在我们定义一个数组时,数组名称就是这个数组的首地址,那么这二者有什么区别和联系呢?
char a[4];,
a是一个长度为4的字符数组,a是这个数组的首元素首地址。
既然a是地址,pa是指向数组的指针,那么能将a赋值给pa吗?答案是不行的!
因为a是数组首元素首地址,pa存放的却是数组首地址,a是char 类型,a+1,a的值会实实在在的加1;而pa是char[4]类型的,pa+1,pa则会加4,
虽然数组的首地址和首元素首地址的值相同,但是两者操作不同,所以类型不匹配不能直接赋值,
但是可以这样:pa = &a,pa相当与二维数组的行指针,现在它指向a[4]的地址。
http://c.biancheng.net/view/2022.html
二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。以下面的二维数组 a 为例:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
从概念上理解,a 的分布像一个矩阵:
0 1 2 3 4 5 6 7 8 9 10 11
但在内存中,二维数组a 的分布是一维线性的,整个数组占用一块连续的内存:

C语言中的二维数组是按行排列的,也就是先存放 a[0] 行,再存放 a[1] 行,最后存放 a[2] 行;每行中的 4 个元素也是依次存放。数组 a 为 int 类型,每个元素占用 4 个字节,整个数组共占用 4×(3×4) = 48 个字节。
C语言允许把一个二维数组分解成多个一维数组来处理。对于数组 a,它可以分解成三个一维数组,即 a[0]、a[1]、a[2]。每一个一维数组又包含了 4 个元素,例如 a[0] 包含 a[0][0]、a[0][1]、a[0][2]、a[0][3]。
假设数组 a 中第 0 个元素的地址为 1000,那么每个一维数组的首地址如下图所示:

为了更好的理解指针和二维数组的关系,我们先来定义一个指向 a 的指针变量 p:
int (*p)[4] = a;
括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。[ ]的优先级高于*,( )是必须要加的,如果赤裸裸地写作int *p[4],那么应该理解为int *(p[4]),p 就成了一个指针数组,而不是二维数组指针,这在《C语言指针数组》中已经讲到。
对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节,这正好是数组 a 所包含的每个一维数组的长度。也就是说,p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。
数组名 a 在表达式中也会被转换为和 p 等价的指针!
下面我们就来探索一下如何使用指针 p 来访问二维数组中的每个元素。按照上面的定义:
1) p指向数组 a 的开头,也即第 0 行;p+1前进一行,指向第 1 行。
2) *(p+1)表示取地址上的数据,也就是整个第 1 行数据。注意是一行数据,是多个数据,不是第 1 行中的第 0 个元素,下面的运行结果有力地证明了这一点:
#include <stdio.h>
int main(){
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
int (*p)[4] = a;
printf("%d\n", sizeof(*(p+1)));
return 0;
}
运行结果:
16
3) *(p+1)+1表示第 1 行第 1 个元素的地址。如何理解呢?*(p+1)单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;
就像一维数组的名字,在定义时或者和 sizeof、&一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。
4) *(*(p+1)+1)表示第 1 行第 1 个元素的值。很明显,增加一个 * 表示取地址上的数据。
根据上面的结论,可以很容易推出以下的等价关系:
a+i == p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)
【实例】使用指针遍历二维数组。
#include <stdio.h>
int main(){
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int(*p)[4]; // 没有使用int **p2这样的方式,这种方式下无法用p2进行有效的数组变量,会越界,见上面图
int i,j;
p=a;
for(i=0; i<3; i++){
for(j=0; j<4; j++) printf("%2d ",*(*(p+i)+j));
printf("\n");
}
return 0;
}
运行结果:
0 1 2 3 4 5 6 7 8 9 10 11
指针数组和二维数组指针的区别
指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:
- int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
- int (*p2)[5]; //二维数组指针,不能去掉括号
指针数组和二维数组指针有着本质上的区别:
指针数组是一个数组,只是每个元素保存的都是指针,以上面的 p1 为例,在32位环境下它占用 4×5 = 20 个字节的内存。
二维数组指针是一个指针,它指向一个二维数组,以上面的 p2 为例,它占用 4 个字节的内存。
浙公网安备 33010602011771号