二维数组🦑
这就是数组的最后一道题。需要仔细的讲讲
int main() { int a[3][4] = { 0 }; printf("%d\n", sizeof(a)); printf("%d\n", sizeof(a[0][0])); printf("%d\n", sizeof(a[0])); printf("%d\n", sizeof(a[0] + 1)); printf("%d\n", sizeof(*(a[0] + 1))); printf("%d\n", sizeof(a + 1)); printf("%d\n", sizeof(*(a + 1))); printf("%d\n", sizeof(&a[0] + 1)); printf("%d\n", sizeof(*(&a[0] + 1))); printf("%d\n", sizeof(*a)); printf("%d\n", sizeof(a[3])); return 0; }
- sizeof(a):这里是sizeof中出现数组名的情况,求的是整个数组的大小,数组3*4的个数是12个元素,类型都为int,12 *4 =
48 。
- sizeof(a[0][0]):这是就是二维数组中第一个元素的写法,第一个元素类型为int,大小就是
4。
- sizeof(a[0]):这个就不一样了,我们说
a[0]
其实也可以写成这样*(a+0)
,a
为数组名,数组名代表首元素地址,但是在二维数组中数组名代表第一行的地址,
也就是说这个地址+1的话会跳过一行,而不是一个元素。但是这里只是+0就还是不变,我们知道a代表第一行的地址(把第一行看成是一维数组
),和我们的一维数组的&arr是一样的
。都是拿到整个一维数组的地址
。那么其实a+0可以看成就是&arr,为什么呢?因为都是拿到整个数组的地址,并且都是一维数组。那么*(a+0),就可以看成是 * (&arr),
而*又可以与&抵消就得到arr
,这个arr我们就直接看成是第一行的数组名****就行。那么这道题的意思就是求第一行的总大小,是[4]*4=16。
其实你也可以这样记忆,sizeof(a[0])代表求第一行的所有元素大小。
那么a[0]就可以看成是第一行的数组名,并且是第一行的地址。 - sizeof(a[0] + 1):a[0]作为第一行的数组名,并没有单独放在sizeof内部,也没有被取地址,按上面的分析方法a[0]代表第一行的数组名也是第一行的地址,所以a[0]就是数组首元素的地址,就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址,是地址那么在
32位下是4,64位下是8。
- sizeof(*(a[0] + 1)):a[0]+1就是第一行第二个元素的地址,解引用得到第二行第一个元素,大小为
4。
- sizeof(a + 1):a代表第一行地址,+1跳过第一行,但是还是地址,是地址那么在
32位下是4,64位下是8。
- sizeof(*(a + 1)):对第二行的地址解引用得到第二行,也可以写成这样:a[1],根据我所理解的,a[1]看成是第二行的数组名,并且是第二行的地址。解引用就得到第二行。大小为
16。
- sizeof(&a[0] + 1):a[0]我们知道是第一行的数组名对数组名取地址相当于拿到整个数组,然后+1跳过一行,但也还是地址,是地址那么在
32位下是4,64位下是8。
- sizeof(*(&a[0] + 1)):与多加了一个 *,代表对第二行的地址解引用,拿到第二行,对第二行求大小得
16。
- sizeof(*a):a代表二维数组数组名,也是第一行的地址,对第一行的地址解引用得到第一行。第一行大小为
16。
- sizeof(a[3]):最后是这个,我们看到我们行数最大也为2,为什么这里写3呢?
这个代码是错误的吗?
int a[3][4] = { 0 }; • 1
其实不是,我们要知道一个知识点
, sizeof在进行计算类型的大小时,它不会去看你括号里写的东西存不存在而是直接根据类型推得大小,a[3]就是第三行的数组名,即使没有第三行也会计算,数组名放在sizeof里得到第三行的大小,也是16。
小总结
二维数组中 :
a +1的+1是要跳过一行。(a是第一行的地址+1跳过一行,并且a是相对于二维数组而言)
a[0] +1的+1是要跳过一个元素。(a[0]是第一行的数组名,一维数组的数组名+1就跳过一个元素)
&a[0]+1的+1是要跳过一行。(&是拿到整个一维数组的地址再+1,肯定就跳过一行了)。
指针试题(共8道)
一二题
一 :
int main() { int a[5] = { 1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf( "%d,%d", *(a + 1), *(ptr - 1)); return 0; }
程序的结果是什么?
分析
- a是整型数组5个元素,
- int *ptr = (int *)(&a + 1);我们先说&a+1,我们知道&a得到的是整个数组的地址,那么+1就相当于跳过一个数组,为什么还要强制转换(int *)呢?因为&a相当于数组指针,与int *ptr 的类型不一样,所以要强制类型转换。现在已经分析完了,输出部分请看图。
答案:
二 :
//结构体的大小是20个字节 struct Test { int Num; char *pcName; short sDate; char cha[2]; short sBa[4]; }*p; //假设p 的值为0x100000。 如下表表达式的值分别为多少? //已知,结构体Test类型的变量大小是20个字节 int main() { printf("%p\n", p + 0x1); printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1); return 0; }
输出的结果是什么?
分析
我们知道p的值是0x100000,p+0x1
呢? (unsigned long)p + 0x1); (unsigned int*)p + 0x1)
又是多少?
- 我们不要被吓到了,0x1其实就是1,而p是一个结构体指针变量,所以这个指针应该是这样的,
struct Test *p;
。
我们知道指针的类型决定指针的步长,那么现在结构体大小为20个字节,所以结构体指针+1就应该+20的地址。
相当于0x100000+20。20用十进制表示就14。
最后结果就是0x100014。
(unsigned long)p + 0x1);
这里就改变的p的类型,之前是结构体指针现在强转为u long了,而u long咱们就把p不再看作一个指针变量了,就是一个普通的变量,相当于 int i = 0; i + 1。- 所以结果就是
0x100001。
(unsigned int*)p + 0x1)
,与上面一样,都是改变了指针的类型,u int *的大小也是 4个字节(32位),那么+1也是跳过4个字节。也相当于0x100000+0x4。
结果就为0x100004。
三四题
int main() { int a[4] = { 1, 2, 3, 4 }; int *ptr1 = (int *)(&a + 1); int *ptr2 = (int *)((int)a + 1); printf( "%x,%x", ptr1[-1], *ptr2); return 0; }
输出的结果是什么?
分析:
- 我们a是4个元素的整型数组, ptr1指向的是
&a+1的地址处
,就是跳过一个数组,这里强转是因为&a的类型相当于int (*)[4],
最后将数组指针转为int *。
- ptr2就不一样的了,a是数组名也是首元素地址,也可看作是一个
int *类型
,但是这里强转了,转为int 就相当于变成普通的变量加减
,而int *+1跳过4个字节
,int 类型+1,只跳过一个字节,
最后再把类型转为int*就行了。 - 图
四
int main() { int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int *p; p = a[0]; printf( "%d", p[0]); return 0; }
输出的结果是什么?
分析:
这个就比上面的简单一些了,考察二维数组的知识点,
首先这个题目有个坑,
在二维数组初始化的时候,用(),就相当于逗号表达式,
实际上应该是这样的, int a[3][2] = { 1, 3, 5};
我们知道a[0]就是代表第一行的数组名,数组名也是首元素地址,p[0]就相当于*(p +0),
对p 解引用得到1。