前言
相信大家在学习c语言的过程中都被指针所折磨过吧,的确,指针作为c语言学习的一大难点,确实比较难以理解,我在学习的过程中也是跟不上学校老师的思路,哇,这是什么,这地址那地址的,取地址又解引用的,真烦,后来经过一段时间的学习,对指针的理解也更加清晰,之前的疑惑也都解开了,那么本篇文章就我自己的理解给大家介绍一下指针。
内存和地址
讲到指针肯定离不开内存和地址,其实指针就是地址,可以把内存和地址分别理解为一个学校的宿舍,宿舍就是内存,给每一个宿舍标上门牌号,作为地址方便查找,也就是指针。
0XFFFFFFFF也就相当于门牌号,这些地址都被存放在内存单元中,一个内存单元也就是一个字节
取地址操作符(&)和解引用操作符(*)
&就是把内存中的地址拿出来
因为 int 类型占四个字节,我们通过&得到的为四个字节里的较小地址的编号,那么我们把地址取出来干什么呢,肯定是拿来用的,所以我们得先把地址存起来,那么用来存放地址的变量就称为指针变量
#include <stdio.h>
int main(){
int a = 10;
int* p = &a;
*p = 0;
printf("%d\n",a);
return 0;
}
p 就作为了指针变量来存储a的地址,类型为int* ,那么我们又怎么去使用地址呢,就需要用到解引用操作符 * ,代码中 *p就是通过指针变量p里面存放的地址找到指定的空间进行修改。
总结来说呢,取地址& 和 解引用 就是一对相反的操作,&把地址拿出来,解引用 就是顺着地址再找回去。
指针变量
在上面已经提到,指针变量就是用来存放指针的变量,那么既然是变量,大小又是多少呢。
在32位计算机下指针变量的大小为4个字节,64位时指针变量的大小为8个字节。
可以看出,无论前面定义的类型是什么,在X86环境下,指针变量的大小就是4个字节,X64环境下,指针变量的大小就是8个字节。
那么前面定义的类型又有什么意义呢
首先,pa和pc里面都存的是a的地址,所以输出结果相同,又由于指针变量类型不用,后面进行指针运算的时候,同时加一,但增加的字节不同,可以看出,char*类型的加了一个字节,int *类型的加了4个字节。
void*类型指针
在前面我们看到了int *,char *等一些指针类型,还有一种类型比较特殊
#incldue <stdio.h>
int main(){
int a = 0;
float b = 0.0;
void* p1 = &f;
p1 = &a;
}
void* 类型的指针可以存放其他类型的指针,但也因为这个原因,它不能像其他指针那样进行+1 , - 1等一些操作,因为不知道每次加减多少字节。
const 修饰指针变量
const 修饰指针变量时,可以放在*左边也可以放在右边,但所产生的效果是不相同的。
下面我们通过具体的例子来感受一下:
当我们把const 放在* 右边时,指针变量p就不能再指向其他地址了,编译器就会报错
但是我们却可以通过解引用,来修改值,我们再看下面一组例子:
当我们把const放在*的左边时,产生的效果就和上面的情况相反了。
总结:
当const放在右边时,限制的是指针本身(不能改变指针变量指向),可以改变指针指向内容。
当const放在左边时,限制的是指针指向的内容,不能修改,但是可以修改指针变量本身(改变指针变量指向)
其实也好理解,*p表示指向的内容,p就是指针本身const * p限制内容, *const限制指针本身。
指针运算
指针±整数
我i们直接说结论
int a = 0;
int * p = &a;
p+1;
这里的p+1就是在指针p原来表示的地址基础上又加了4个字节,那为什么是4个字节呢,其实是个前面的int 有关的
type * p;
p+n跳过n*sizeof(type)个字节
那么这个有什么用呢,我们可以联想一下数组
我们可以用指针加减整数的方式访问数组元素,操作如下:
int main() {
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(a) / sizeof(a[0]);
int* p = &a[0];//首元素地址
for (int i = 0; i < sz; i++) {
printf("%d ", *p);
p++;//每次加四个字节,正好访问下一个元素
}
return 0;
}
指针 - 指针
我们可以把指针加减数字理解为日期加减天数,得到的就是加减后的日期,那么指针减指针就是日期减日期,就是两个日期之间相差的天数,但是指针加指针是没有意义的。
不过指针相减前提是两个指针指向的是同一块区域,不然相减是没有意义的
指针比较大小
既然指针可以相减,那么指针肯定是有大小的,数组中随着下标的增长就是从低地址到高地址,根据这个我们来看个样例
int main() {
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(a) / sizeof(a[0]);
int* p = a;//数组名就是数组首元素地址
while (p < a + sz) {
printf("%d ", *p);
p++;
}
return 0;
}
根据上面的代码同样可以实现数组的遍历。
野指针
野指针就是指针指向的位置是不可知的,随机的
同时呢,越界访问也会产生野指针,因为数组中内存连续,但出了数组,内存分配又是随机的
还是上面的代码,这次我们改为 i<=sz
int main() {
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(a) / sizeof(a[0]);
int* p = &a[0];//首元素地址
for (int i = 0; i <= sz; i++) {
printf("%d ", *p);
p++;//会越界访问,变成野指针
}
return 0;
}
那么如何规避野指针呢
规范的进行初始化,如果不知道指针指向哪里,可以先给指针赋值NULL,使用的时候检查指针的有效性。
本次分享就先到这里了,感谢支持!