我对C中指针和数组的问题感到困惑。
让我们先看一段代码:
//case 1
int **a;
a = (int **)malloc(sizeof(int*)*m);
for(i = 0; i < m; i++)
a[i] = (int *)malloc(sizeof(int)*n);
//case 2
int b[m][n];
然后,我们知道,内存中b
的布局如下:
b[0][0] b[0][1] ... ... b[m-1][n-1]
并且内存的布局如下:
a
\
_\|
a[0] a[1] ... a[m-1]
| |
| |
| \|/
| a[1][0] a[1][a] ... a[1][n-1]
\|/
a[0][1] a[0][2] ... a[0][n-1]
所以,我的问题是:由于a[0..m-1][0..n-1]
非常连续地存储在内存中,为什么我们可以在[]
上使用下标运算符a
?换句话说,为什么a[i][j]
可以获得正确的元素,就像b[i][j]
一样?
答案 0 :(得分:3)
那是因为a[i][j]
等于*(*(a+i) + j)
。即使a
未连续存储(可能),指针*(a+i)
也会精确指向正确的位置(存储a[i][0]
的位置)。
答案 1 :(得分:2)
因为a
的元素连续存储在内存中,并且每个a[n]
的元素都连续存储在内存中。执行int n = a[i][j]
基本上等同于执行int * p = a[i]; int n = p[j];
这两个维度在a
的内存中可能与b
的方式不相关并不重要。
答案 2 :(得分:2)
它们都有效,因为编译器会针对每种情况生成不同的内容。虽然确实单个维数的数组和指针以及类似的,双重维数的数组和指针指针都有相似之处。
因此,对于b
的情况,因为编译器知道b
是一个数组并且是一个连续的内存块,所以它生成的代码为:
// int c = b[i][j];
int c = *(b + (i*m+j);
但它为a
生成不同的代码,因为它是指向指针的指针而不是连续的内存块,每个括号运算符都是一个解除引用运算符:
// int c = a[i][j];
int c = *(*(a+i)+j);
答案 3 :(得分:2)
编译器分别处理表达式的每个部分。 a[i][j]
和b[i][j]
之间的主要区别在于,由于a
是指向int
的指针,因此a[i]
的地址计算会计算多个指针但是,由于b
是int
的数组数组,因此b[i]
的地址计算会计算多个数组。 C使用表达式每个部分的类型来确定如何评估它。
解释a[i][j]
的步骤如下:
a
是指向int
的指针。a[i]
定义为*(a + i)
。a+i
是除i
分之外的a
个元素。由于a
指向指向int
的指针,因此我们会将i
指针指向int
以确定新地址。int
指针的指针,因此将*
应用于它会产生指向int
的指针。a[i][j]
定义为*(a[i] + j)
。我们已经评估了a[i]
部分。a[i] + j
是除j
分之外的a[i]
个元素。由于a[i]
指向int
个对象,我们会计算j
int
个对象以确定新地址。int
的指针,因此将*
应用于int
会产生b[i][j]
。解释b
的步骤如下:
m
是n
int
b
数组的数组。n
是一个数组,因此会将其转换为指向其第一个元素的指针,即int
b[i]
的数组。*(b + i)
定义为b + i
。i
是除b
分之外的b
个元素。由于n
已转换为指向int
i
数组的指针,因此我们会计算n
int
n
个数组以确定新地址int
*
数组的指针,因此将n
应用于它会产生一个int
int
的数组。b[i][j]
。*(b[i] + j)
定义为b[i]
。我们已经评估了b[i] + j
部分。j
是除b[i]
分之外的int
个元素。由于它指向j
,我们会计算int
int
以确定新地址。*
的指针,因此将int
应用于{{1}}会产生{{1}}。答案 4 :(得分:0)
a[i][j]
基本上我们说:
我们需要a
指向的第i个指针,然后我们需要该内部指针指向的第j个int。
只使用这个概念我们可以写
foo(int **a, int rows, int cols){
//...
//acess a[i][j] ; 0<=i<rows, 0<=j<cols
}