二重指针和二维数组
1、二重指针
(1)二重指针的概念
- 二重指针和一重指针的本质都是指针变量,指针变量的本质就是变量。
- 一重指针变量和二重指针变量本身都占4字节内存空间。
- 二重指针和一重指针的差别就是它指向的变量类型不同,二重指针的指向必须是个一重指针。
- 二重指针其实也是一种数据类型,编译器在编译时会根据二重指针的数据类型来做静态类型检查,一旦发现运算时数据类型不匹配编译器就会报警告。
- C语言中如果没有二重指针行不行?
- 其实是可以的。一重指针完全可以做二重指针做的事情,之所以要发明二重指针(函数指针、数组指针),就是为了让编译器了解这个指针被定义时定义它的程序员希望这个指针被用来指向什么东西,编译器知道指针类型之后可以帮我们做静态类型检查。编译器的这种静态类型检查可以辅助程序员发现一些隐含性的编程错误,这是C语言给程序员提供的一种编译时的查错机制。
- 为什么C语言需要发明二重指针?
- 指针本身是一个变量,它有时也需要存储地址,二重指针可以用来存储指针的地址。
- 传递指针地址以便修改指针本身。
(实践编程中有时在函数传参时为了通过函数内部改变外部的一个指针变量,会传这个指针变量的地址(也就是二重指针)进去)
(2)二重指针的用法
- 二重指针指向一重指针的地址
- 二重指针指向指针数组的数组名
#include "stdio.h"int main() {int *p1[10] = {0}; /* 指针数组,数组中的元素类型是int *的 */int **p2 = p1; /* p1是数组名,表示数组首元素的首地址,元素类型int *的 */return 0; }
- 实践编程中二重指针用的比较少,大部分时候就是和指针数组纠结起来用的。
(3)指针总结
- 二重指针、数组指针、结构体指针、一重指针、普通变量的本质都是相同的,都是变量。
- 所有的指针变量本质都是相同的,都是4个字节,都是用来指向别的东西的,不同类型的指针变量只是可以指向的变量类型不同。
2、二维数组
2.1、二维数组得内存映像
(1)一维数组在内存中是连续分布的多个内存单元组成的,而二维数组在内存中也是连续分布的多个内存单元组成的。从内存角度来看,一维数组和二维数组没有本质差别。
(2)二维数组int a[2][5]和一维数组int b[10]其实没有任何本质差别。我们可以把两者的同一单元的对应关系写下来。
a[0][0] b[0] a[0][1] b[1] a[0][2] b[2]a[0][3] b[3]a[0][4] b[4] a[1][0] b[5]a[1][1] b[6] a[1][2] b[7]a[1][3] b[8]a[1][4] b[9]
2.2、为什么需要二维数组?
(1)既然二维数组都可以用一维数组来表示,那二维数组存在的意义和价值在哪里?
- 二维数组a和一维数组b在内存使用效率、访问效率上是完全一样的(或者说差异是忽略不计的)。
- 在某种情况下用二维数组而不用一维数组,原因在于二维数组好理解、代码好写、利于组织。
2.3、第一维和第二维
(1)二维数组int a[2][5]。
- 2是第一维,5是第二维。
- 首先第一维是最外面一层的数组,所以第一维有2个元素;其中每一个元素又是一个含有5个元素的一维数组。
- 第二维是里面的那一层,第二维本身是个一维数组,数组中存的元素是普通元素,而不是数组。第二维这个一维数组本身作为第一维数组的元素。
2.4、二维数组的下标式访问和指针式访问
(1)一维数组的两种访问方式。以 int b[10]; int *p = b; 为例。
- b[0] 等同于 *(p+0);
- b[9] 等同于 *(p+9); b[i] 等同于 *(p+i)
(2)二维数组的两种访问方式:以 int a[2][5]; int (*p)[5] = a; 为例。
- 数组名做为右值,表示数组首元素的首地址。a的首元素是第一维数组的元素,元素为一维数组,而一维数组的地址需要用数组指针。(指向数组的指针)
- a[0][0]等同于 *(*(p+0)+0); a[i][j]等同于 *(*(p+i)+j);
- *(p+i)得到的是第一维数组的元素,即得到了一维数组(一维数组的数组名表示首元素的首地址)
2.5、二维数组的应用和更多维数组
(1)最简单情况,有10个学生成绩要统计;如果这10个学生没有差别的一组,就用b[10];如果这10个学生天然就分为2组,每组5个,就适合用int a[2][5]来管理。
(2)最常用情况:一维数组用来表示直线,二维数组用来描述平面。数学上,用平面直角坐标系来比拟二维数组就很好理解了。
(3)三维数组和三维坐标系来比拟理解。三维数组其实就是立体空间。
(4)四维数组也是可以存在的,但是数学上有意义,现在空间中没有对应(因为人类生存的宇宙是三维的)。
总结:一般常用最多就到二维数组,三维数组除了做一些特殊与数学运算有关的之外基本用不到。(四轴飞行器中运算飞行器角度、姿态时就要用到三维数组)
3、二维数组的运算与指针
3.1、指针指向二维数组的数组名
(1)二维数组的数组名做右值:
- 表示二维数组的第一维数组中首元素的的首地址。
- 二维数组的数组名a等同于&a[0],这个和一维数组的符号含义是相符的。
- 用数组指针来指向二维数组的数组名是类型匹配的。
(2)示例代码
#include "stdio.h"int main(void)
{int a[2][5] = {{1, 2, 3, 4, 5},{6, 7, 8, 9, 10}};/* 二维数组元素的访问 */printf("a[1][3] = %d.\n", a[1][3]);printf("a[1][3] = %d.\n", *(*(a+1)+3));/* 错误指针赋值方式: a是数组,需要用数组指针 *///int *p1 = a; // 类型不匹配//int **p2 = a; // 类型不匹配/* 指针指向二维数组的数组名 */int (*p)[5] = a; // 使用数组指针,指向一个数组,数组有5个int类型的元素printf("a[1][3] = %d.\n", *(*(p+1)+3));p = &a[0]; // 二维数组的数组名a等同于&a[0]printf("a[1][3] = %d.\n", *(*(p+1)+3));return 0;
}
3.2、指针指向二维数组的第一维
(1)用int *p来指向二维数组的第一维
- int a[2][5] = {{1, 2, 3, 4, 5},{6, 7, 8, 9, 10}}; int *p = a[1]; printf("a[1][3] = %d.\n", *(p+3));
- a[1]表示二维数组的第一维的元素,也就是二维数组的第二维数组的整体,一个具有5个int元素的数组。
- a[1]做右值,又是一个具有5个int元素的数组。a[1]就是第二维数组的数组名,所以就相当于第二维数组的首元素的首地址。
- int *p = a[1]; 等价于 int *p = &a[1][0];
(2)示例代码
#include "stdio.h"int main(void)
{int a[2][5] = {{1, 2, 3, 4, 5},{6, 7, 8, 9, 10}};/* 二维数组元素的访问 */printf("a[1][3] = %d.\n", a[1][3]);printf("a[1][3] = %d.\n", *(*(a+1)+3));/* 指针指向二维数组的第一维 */int *p = a[1];printf("a[1][3] = %d.\n", *(p+3));p = &a[1][0];printf("a[1][3] = %d.\n", *(p+3));return 0;
}
3.3、指针指向二维数组的第二维
(1)二维数组的第二维元素其实就是普通变量,已经不能用指针类型和它相互赋值了。
(2)a[1][1]其实就是int类型的7。
(3)指针访问二维数组的第二维的元素
- int *p = &a[i][j];