一、指针的运算
(一)指针+(-)整数
指针加减整数代表的是指针在内存上的移动,结果也是指针。这个时候我们可以观察p+1和*(p+1)的变化。
我们以数组举例,只需要知道其中一个元素的地址,就可以顺藤摸瓜找到所有元素的地址。
#include<stdio.h>
int main() { int a[10] = { 1,2,3,4,5,6,7 }; int sz = sizeof(a) / sizeof(a[0]); int* p = a;//这里的a指的是a的首地址的地址,等价于&a[0] for (int i = 0; i < sz - 1; i++) { p++; printf("%p %d\n", p, *p); } return 0;}>>>0000001F5C4FF86C 20000001F5C4FF870 30000001F5C4FF874 40000001F5C4FF878 50000001F5C4FF87C 60000001F5C4FF880 70000001F5C4FF884 00000001F5C4FF888 00000001F5C4FF88C 0需要标注的是,数组名(如:a)单独出现的时候,代表的是数组首元素的地址,也就是&a[0],对于二维数组也是如此,之后会细说,这里不做展开。
可以发现,p++一次,地址往后移动了4个字节,这是因为int类型的变量占用4个字节,因此地址往后四个字节才会找到下一个元素。换成char*的指针的话,因为char类型的变量只占用一个字节,因此地址只会往后一个字节。
这就是为什么明明在内存上都占用4/8个字节,但是却要分出指针类型的原因。
(二)指针-指针
前面我们知道指针是可以加减整数的,并且结果也是指针,用表达式来看的话就是p1 + n = p2,那么自然的,这样的运算是否可逆?
结论是可以的,我们可以反向得到p2 - p1 = n,其中n为int类型变量。
#include<stdio.h>
int main() { int a[10] = { 1,2,3,4,5,6,7 }; printf("%d\n", &a[9] - &a[0]); return 0;}>>> 9得到的9就是从a[0]到a[9],指针扫过的元素个数。下面我用图像辅助解释一下:
p在&a[0]时,p + 1的时候,指针停在&a[1]上,经过了a[0]这一个元素,因此(p + 1) - 1 = p,同理,p+9时到达&a[9],(p + 9) - p = 9。
(三)指针的关系运算
加减法存在那么自然也存在大小比较,比如我们可以对前面的代码进行改编:
#include<stdio.h>
int main() { int a[10] = { 1,2,3,4,5,6,7 }; int sz = sizeof(a) / sizeof(a[0]); for (int* p = a; p < a + sz ; p++) { printf("%p %d\n", p, *p); } return 0;}
>>>00000028C70FFBC8 100000028C70FFBCC 200000028C70FFBD0 300000028C70FFBD4 400000028C70FFBD8 500000028C70FFBDC 600000028C70FFBE0 700000028C70FFBE4 000000028C70FFBE8 000000028C70FFBEC 0其中就涉及了关系运算p < arr + sz。
(四)指针+-整数的等价形式
如果我们对尝试这样的代码:
#include<stdio.h>
int main() { int a[10] = { 1,2,3,4,5,6,7 }; int sz = sizeof(a) / sizeof(a[0]); for (int i = 0; i < sz ; i++) { printf("%d %d\n", *(a + i), a[i]); } return 0;}>>>>1 12 23 34 45 56 67 70 00 00 0可以发现*(a + i)和a[i]是等价的,也就是说,借用解引用操作符*和指针的运算,我们可以用指针代替数组的下标访问!
再进一步,我们甚至可以得出这样的等价式:*(p + i) = p[i] = *(i + p) = i[p]。
再用取地址操作符我们还可以得到一组恒等式:p + i = &p[i] = i + p = &i[p]。
i[p]可读性太差了之后不会出现,这里只是在说明下标访问和地址访问的等价性。
二、指针的遍历:
有了基础的运算,现在我们就可以用指针访问数组了。
一维数组
#include<stdio.h>
int main() { int arr[10]; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; for (int i = 0; i < sz; i++) { scanf("%d", p + i);//也可以写成arr + i printf("%d ", *(p + i) + 1); } return 0;}>>>1 2 3 4 5 6 7 8 9 102 3 4 5 6 7 8 9 10 11二维数组
二维数组也是一样的,但是我们要注意一下拆解的顺序。
#include<stdio.h>
#include<stdio.h>
int main() { int arr[3][3]; int r = sizeof(arr) / sizeof(arr[0]); int c = sizeof(arr[0]) / sizeof(arr[0][0]); int (*p)[3] = arr;//&arr[0],也就是arr的第一行的地址 for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { scanf("%d", *(p + i) + j);//p + i为第i行的地址,p + i + j代表第i行第j列 printf("%d ", arr[i][j]); } } return 0;}>>>>1 2 3 4 5 6 7 8 91 2 3 4 5 6 7 8 9在解释代码之前补充几个知识点:
1.此时p是数组指针,类型已经不再是int*,而是int (*)[3],表示的是指针指向的数组有三个元素
2.&arr和arr虽然都是数组首元素的地址,但是&arr表示整个数组的地址,因此&arr + 1就会跳过整个数组,对于一维数组arr[9],&arr + 1就会跳过9个元素直接到数组外面,arr + 1则是指向第二个元素。
我们对*(*(p + i)+j)进行分解,p代表第一行的地址,也就等价于&a[0],a[0]是一维数组,因此*(p + 1)就会跳过a[0]整个数组,得到&a[1]的地址并解引用得到a[1],a[1]就是&a[1][0],同理,*(p + i) 得到的就是第i行的数组的首元素地址,*(*(p + i)+j)就是在第i行数组中访问第j列。
倒退的话,arr[i][j] = *(arr[i] + j) = *(*(&arr[0] + i) + j) = *(*(p + i)+j)。
数组指针转换
我们也可以用一些歪门邪道(划掉)来把二维数组拍扁成一维数组。
由于数组是连续存放的,我们可以通过强制类型转换,把类型为int (*)[3]的p强行转换成int*。于是我们就可以用地址*(p + i * r + j)对第i行第j列的元素进行访问和修改。
部分信息可能已经过时











