【C语言期末不挂科——指针初阶篇】(上)https://developer.aliyun.com/article/1386228
指针运算
我们已经理解指针的基本功能了,除了上面的基本功能,指针还有一个很重要的东西———指针运算。
1)指针 + - 整数
我们前面已经学过,指针加上整数就是跳过整数倍指针类型个字节,就像:
#include<stdio.h> int main() { int a = 0x44332211; char *p = (char *)&a; printf("%x\n", *p); printf("%x\n", *(p + 1)); printf("%x\n", *(p + 2)); printf("%x", *(p + 3)); return 0; }
指针加上整数除了可以进行读取数据以外,还可以连续的存储数据,我们看下面代码:
#include<stdio.h> #define N 5//数组元素个数 int main() { int arr[N] = { 0 }; int *parr = NULL;//指针用来保存数组首元素地址 for(parr = &arr[0] ; parr < &arr[N] ; )//将数组首元素地址赋值、小于判断条件就一直执行 { *parr++ = 1;//指针解引用对内存空间进行赋值,随后+1指向下一个位置 } int i = 0; for(i = 0 ; i < N ; i++)//打印出来数组里的值看看是否改变 { printf("%d ", arr[i]); } return 0; }
我们用指针解引用访问对应的内存空间从而完成了赋值操作。这种是指针parr的位置一直在变化,如果不想要指针的位置,我们可以这样写:
#include<stdio.h> #define N 5 int main() { int arr[N]; int *parr = &arr[0]; int sz = sizeof(arr) / sizeof(arr[0]);//sizeof(数组名)求出整个数组的字节大小 //然后再除上一个元素的大小,就是数组元素的个数。sizeof(arr) == N int i = 0; for(i = 0 ; i < sz ; i++) { *(parr + i) = 1; } for(i = 0 ; i < N ; i++) { printf("%d ", arr[i]); } return 0; }
这样就能控制指针的地址不变,而完成数组元素的赋值了。
总结:
指针加减整数的意义就是指针跳过了 指针类型大小*整数 个字节,进而访问对应的内存空间。
2)指针-指针
我们来看下面的代码:
#include<stdio.h> int main() { int arr[10] = { 0 }; printf("%d\n", &arr[9] - &arr[0]); return 0; }
这里两个指针相减,你来思考一下,得出的结果是多少?相信聪明的你能很快的得出正确的答案。我们直接来看结果:
答案是9,不知道你想对了没有,我们取了数组元素的第10个元素地址,与第一个元素地址作差,得出来的结果是9,正好就是两个数组元素的距离。
但是如果是这两种情况:
#include<stdio.h> int main() { int arr[10]; double a = 1; int *ptr1 = &arr[0]; double *ptr2 = &a; printf("%d", ptr1 - ptr2); }
如果采用了不用类型的参数进行相减,就会报错,而且最好是像数组这种连续的内存空间使用指针相减,否则相减出来的值是几乎没有什么意义的。
总结:
1、指针-指针得到的就是指针和指针之间元素的个数。
2、两个指针相减的前提是他们的类型必须相同。
3、指针相减的到时元素的个数,所以在连续的内存空间下相减是比较有意义的,不推荐两个毫不相关的指针相减,因为几乎没什么意义。
3)指针的关系运算
我们的地址是有大小的,有高低地址之分,而指针的关系运算就是比较指针的大小。我们来看下面的代码:
#include<stdio.h> #define N 5 int main() { int arr[N] = { 1 }; int *parr = NULL; for(parr = &arr[5] ; parr > &arr[0] ; ) { *--parr = 0; } int i = 0; for(i = 0 ; i < N ; i++) { printf("%d ", arr[i]); } return 0; }
数组一共有5个元素,将数组元素全部初始化为1,随后我们将数组的最后一个元素的地址放进指针变量parr里面,我们准备使数组中的元素从后往前进行赋值,将数组中的元素全部赋值为0。
详细的工作原理如下:
当然你的for循环里也可以这样写:
for(parr = &arr[5] ; parr >= &arr[0] ; parr--)//这两种方式都是相同的 { *parr = 0; }
得到的结果同样是:
指针的比较还有一个要点:就是只能向后比较,但是不能向前比较,如下图:
C语言规定了:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
数组和指针
指针和数组是什么关系呢?我们前面也使用了数组名作为首元素地址,那么数组与指针究竟有着什么样的渊源呢?大型纪录片之《指针与数组的故事》持续为您播出…
指针变量就是指针变量,不是数组,指针变量的大小为4/8个字节,专门用来存放地址的。数组也就是数组,不是指针,数组有一块连续的内存空间,可以存放1个或多个类型相同的数据。
我们来看下面的代码:
#include<stdio.h> int main() { int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int len = sizeof(arr)/sizeof(arr[0]); int *p = arr;//指向数组首元素地址 int i = 0; for(i = 0 ; i < len ; i++) { printf("%p == %p\n",p + i, &arr[i]); } return 0; }
我们可以看到,数组与指针的地址全部都是对应的,一模一样,所以说,数组名就是首元素地址,那么以后你就可以不使用[]来访问数组的内容了,可以使用指针 + 偏移量 的方式来访问数组元素:
int a[1000]; int *p = arr; for(int i = 0 ; i < sizeof(arr)/sizeof(arr[0]) ; i++) { *(p + i) = 1;//i就为偏移量 }
数组与指针的联系:
前面我们也用到了,数组名就是首元素地址,数组名 == 地址 == 指针 ,当我们知道数组首元素的地址的时候,又因为数组是连续存放的,所以通过指针就可以遍历访问数组,前面也演示过了,数组可以通过指针来访问。
数组名就是数组的首元素地址,但是在这两个情况下是例外的:
//sizeof(数组名) sizeof数组名是直接得到整个数组的字节大小 //& 数组名 &数组名 如果进行+1操作是直接跳过一整个数组
其余的情况数组名就是首元素地址。
二级指针
指针的基本用法我们大概了解了,但是我们了解的是 “一级指针” 的用法,其实还存在着二级指针、三级指针…多级指针,因为二级指针用的最多,所以我们在这里主要阐述二级指针,其他指针的情况类比就行了。
那么究竟什么是二级指针呢?我们先来看我们日常所说的一级指针:
int a = 0; int *pa = &a;//这里的指针pa就是一级指针
我们再来看看二级指针:
int a = 0; int *pa = &a;//这里的指针pa就是一级指针 int **ppa = &pa;//这里为二级指针
我们要理解一个东西,指针变量也是变量 啊,既然是变量,那么就一定有内存空间来存储指针变量,而二级指针就是取一级指针变量的地址 的指针。如下图:
这里二级指针的两个*可以这么来理解:int **是种类型,而我们可以把int ** 看成int* * 前面的int*是指向变量的类型,也就是一级指针(一级指针的类型为int *),而你本身是二级指针,指针的类型必须是int *,加上一级指针的类型int* 就是int ** 。
同样,三级整形指针的返回类型就为int ***,多级指针以此类推…我们来看下面代码:
int a = 0; int *p = &a;//一级指针 int **pp = &p;//二级指针
现在我们想通过二级指针来修改变量a的值,我们该如何做?我们是如何由一级指针访问变量内存的?使用解引用来访问:
*p = 1;//以上面代码为续接
那我们二级指针解引用就找到了一级指针的地址,然后我们在解引用一次,不就可以访问变量a了吗?
* *pp = 100;//两次解引用,第一次解引用找一级指针内存,第二次解引用就访问到变量a了
以上就是二级指针的具体用法了,多级指针以此类推。
指针数组
在我们C语言中存在着这样一个东西————指针数组 ,那么请你思考一下,指针数组究竟是指针呢还是数组呢?
答:是一个数组,用来存放指针的数组。
我们知道,数组有不同的类型,有int型数组,double型数组、char型数组…
那么我们的指针数组呢?刚才我们也回答了,指针数组里面存放的都是指针变量,那这里的数组名其实就是二级指针了。
我们来看下面代码:
#include<stdio.h> int main() { char arr1[] = "abcdef"; char arr2[] = "talk is cheap"; char arr3[] = "show me code"; char *parr[] = { arr1, arr2, arr3 }; char **p = parr; return 0; }
我们可以通过指针p来访问数组中的元素的指向。
字符指针数组的每个值的类型都是char*,而数组名就为数组的首元素地址,首元素为指针,所以数组名就是二级指针。
; 这里我们用二维数组的方式打印出各个数组里的字符串,我们只需要:
#include<stdio.h> #include<stdlib.h> #include<string.h> int main() { char arr1[] = "abcdef "; char arr2[] = "talk is cheap"; char arr3[] = "show me code "; char *parr[] = { arr1, arr2, arr3 }; char **p = parr; int len = strlen(arr1); int i = 0; for(i = 0 ; i < 3 ; i++) { int j = 0; for(j = 0 ; j < len ; j++) { printf("%c",parr[i][j]);//通过[]来访问下标 } printf("\n"); } return 0; }
我们可以看到,完全可以用二级指针来模拟二维数组。其实在第二层的for循环里面我们可以这样改:
for(j = 0 ; j < len ; j++) { printf("%c",*(parr[i] + j)); } printf("\n");
先由数组名可以访问每个元素,而每个元素的类型都是char*所以我们可以使用偏移量来对每个指针变量所指向的数组进行访问。当然,这里还有其他的写法可以支持访问,大家可以自由的探索。
感谢你能看到这,这期就到这里啦,如果还想看后续内容就给博主点点关注!C语言指针篇正在持续更新~~