【程序猿必备:指针与数组的高级技能秘籍】(上):https://developer.aliyun.com/article/1424712
多维数组和多级指针
二维数组
基本概念
- 几乎大部分书中所画的二维数组,都是矩阵样子,具体可以参考书中的图。
- 但是,现在我们要在这里澄清,书中的图,最多只能称之为示意图,并非真的内存布局图。
- 可以想象一些问题:如果按照书中矩阵样子画二维数组的话,那么三维数组,四维数组又该如何画呢?
二维数组的基本内存布局
#include <stdio.h> int main() { char a[3][4] = { 0 }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("a[%d][%d] : %p\n", i, j, &a[i][j]); } } return 0; }
结论:二维数组在内存地址空间排布上,也是线性连续且递增的。
二维数组如何画图
- 只有能够正确画出二维数组的布局图,才算真正能深刻理解二维数组的空间布局
- 以它为例:char a[3][4] = { 0 };
- 数组的定义是:具有相同数据元素类型的集合,特征是,数组中可以保存任意类型。
- 那么数组中可以保存数组吗? 答案是可以!
- 在理解上,我们甚至可以理解所有的数组都可以当成"一维数组"!(这样理解的好处,我们后面就说)
- 就二维数组来说,我们认为二维数组,可以被看做“一维数组”,只不过内部“元素”也是一维数组
- 那么内部一维数组是在内存中布局是“线性连续且递增”的,多个该一维数组构成另一个“一维数组”,那么整体便也是线性连续且递增的
- 这也就解释了,上述地址为何是连续的。
- 在强调一遍,我们认为:二维数组可以被看做内部元素是一维数组的一维数组。
#include <stdio.h> int main() { char a[3][4] = { 0 }; //char* p = a;//a首元素(是一个数组,char[3]的地址) //a的类型是int(*p)[3] /* A:&a -> 二维数组的地址 B:a -> 第一个一维数组的地址 C:&a[0][0] -> 第一个数组的第一个元素的地址 地址值都是:005BFAA4 printf("%p\n", &a); printf("%p\n", &a + 1);005BFAB0 printf("%p\n", a); printf("%p\n", a+1);005BFAA8 printf("%p\n", &a[0][0]); printf("%p\n", &a[0][0] + 1);005BFAA5 */ //所以这里需要强制类型转换 char* p = (char*)a; for (int i = 0; i < 3 * 4; i++) { //printf("%p\n", &p[i]); printf("%p\n", p + i); } //用来对比 for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("a[%d][%d] : %p\n", i, j, &a[i][j]); } } return 0; }
#include<stdio.h> 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):这里a必须单独出现,不能包含操作符的运算。
- &a
#include<stdio.h> int main() { /* 数组名补充:两种情况代表整个数组,其他都是首元素地址 sizeof(a) 这里a必须单独出现 & a */ //所有的数组都可以看做成为"一维数组" int a[3][4] = { 0 }; printf("%d\n", sizeof(a)); //a代表整个数组,二维数组一共有3*4=12个元素,每个元素4个字节 - 48 printf("%d\n", sizeof(a[0][0])); //a[0][0]代表二维数组的第一个一维数组元素内的第一个整形 - 4 printf("%d\n", sizeof(a[0])); //a[0]代表二维数组的第一个一维数组,这个一维数组有4个元素 - 16 printf("%d\n", sizeof(a[0] + 1)); //a[0]是第一个一维数组, 由于只有两种情况代表整个数组,其他都是首元素地址,\ a[0]+1这里进行了+1的操作,所以这里的a[0]就是一维数组的首元素地址,\ +1跳过sizeof(int)个字节,获取的是&a[0][1]的地址 - 4 printf("%d\n", sizeof(*(a[0] + 1))); //由上一个题目可以得出来,这里的解引用获取的是a[0][1] - 4 printf("%d\n", sizeof(a + 1)); //这里是由于+1的操作,a就是首元素的地址,+1后就是第二个一维数组的地址 - 4 printf("%d\n", sizeof(*(a + 1))); //由上一个题目可以得出来,这里的解引用获取的是a[1]的所有元素 - 16 printf("%d\n", sizeof(&a[0] + 1)); //&a[0]第一个数组的地址,+1就是第二个数组的地址 - 4 printf("%d\n", sizeof(*(&a[0] + 1))); //由上一个题目可以得出来,这里的解引用获取的是a[1]的所有元素 - 16 printf("%d\n", sizeof(*a)); //这里由于*操作,a就是首元素的地址,就是第一个一维数组的地址,*a - 16 printf("%d\n", sizeof(a[3])); //这里越界,代表二维数组的第四个一维数组 - 16 return 0; }
数组a的定义为: int a[3][4];下面哪个不能表示a[1][1] ?
- (A) 、*(&a[0][0]+5)
- (B) 、*(*(a+1)+1)
- (C)、*(&a[1]+1)
- (D)、*(a[1]+1)
提示:
A:&a -> 二维数组的地址
B:a -> 第一个一维数组的地址
C:&a[0][0] -> 第一个数组的第一个元素的地址
what is the value of & p[4][2] - &a[4][2]?
#include<stdio.h> int main() { int a[5][5];//二维数组 int(*p)[4];//数组指针 p = (int(*)[4])a;//a是首元素地址 printf("a_ptr = %p,p_ptr = %p\n", &a[4][2], &p[4][2]); printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); return 0; }
二级指针
- 指针变量是变量吗?是的
- 变量有地址吗?有的
- 地址是数据吗?是滴
- 数据可以被其他变量保存吗? 当然可以
结论:指针变量也有地址。
#include <stdio.h> int main() { int a = 10; int* p = &a; int** pp = &p; p = 100; //什么意思 *p = 100; //什么意思 pp = 100; //什么意思 *pp = 100; //什么意思 **pp = 100; //什么意思 return 0; }
- 将指针p的值赋为100,这意味着指针p现在指向内存地址100处的值。
- 将指针p指向的内存地址的值赋为100,即将a的值赋为100。
- 将指针pp的值赋为100,这意味着指针pp现在指向内存地址100处的值。
- 将指针pp指向的指针的值赋为100,这意味着指针p的值被修改为内存地址100。
- 将指针pp指向的指针所指向的内存地址的值赋为100,即将a的值赋为100。
【程序猿必备:指针与数组的高级技能秘籍】(下):https://developer.aliyun.com/article/1424717