前言
什么是指针?
在计算机科学中,指针就是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需要的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
指针和指针类型
指针的存放:
int num=10;
int* p = #// p 是就是一个指针变量,将&num (num的地址) 保存到P 中。
指针类型
上面提到的int* 的指针就是为了存放 int 类型变量的地址
我们知道,C语言中有很多类型,如 char , short , long 等等
所以我们指针的定义方式和类型定义方式一样:
type + * 例如(int*)
int* 对应着int 类型的,其它类型指针同int*。
野指针
什么是野指针?
野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)
野指针的成因
1.指针未初始化
#include<stdio.h>
int main()
{
int* p;//局部变量指针未初始化,默认为随机值
*p =20;
//通过P的随机值作为地址找到一个空间,这个空间并不属于我们的程序,这就造成了非法访问,p就是野指针
return 0;
}
2.指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};//arr[10]数组被划分的空间有10个地址大小的空间
int* p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.指针指向的空间释放
#include<stdio.h>
int* test()
{
int a = 10;
return &a;
}
int test1()
{
int a=10;
return a;
}
int main()
{
int* p = test();
//这里的指针就是一个野指针
//因为它指向的空间都已经被释放掉了
//你用这个地址去找原来的空间是找不到的
//更何况你还要去修改那个空间里面存放的值
printf("%d\n",*p);
int c=test1();
printf("%d\n",c);
return 0;
}
可能有人会问,那为什么一个函数可以返回局部变量而不可以返回地址,博主前面的文章讲到过,它的函数返回值是存放在寄存器中的,寄存器不会因为内存的释放而被清空,详情请看函数栈帧的创建与销毁,那么局部变量的值就会被赋值给主函数里面的用来接受的局部变量,但是你如果返回的是地址,在前面那个空间被释放的情况下,你是没有办法找到原来的空间。
如何规避野指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放即使置NULL
#include <stdio.h>
int main()
{
int* p = NULL;
//指针初始化为NULL,只要指针指向的空间被释放,那么它就会变成NULL
//从而避免因为指向空间被释放而产生野指针
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
4.避免返回局部变量的地址
5.指针使用之前检查有效性
指针运算
指针±整数
举个比较常见的例子
int main()
{
int arr[10]=0;
int* p=arr;//这里的arr 指的是数组收元素的地址
for(p=&arr[0];p<&arr[10];)//for循环的条件,在一定条件下是可以省略不写的
{
*p++ = 0;
//++的优先级高于*所以vp应该先++,但是因为是后置++ ,所以它应该在被使用后再++,先被*调用再被=赋值,最后++
}
return 0;
}
指针-指针
相信大家都知道 日期-日期=天数(得到的数的绝对值) ,所以我们这里的 指针-指针 也是同样的道理,指针-指针 得到的数的绝对值是中间的元素个数
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
可能有小伙伴会问 指针+指针 是什么,这个是没有意义的,指针+指针 就和 日期+日期 一样,没有任何意义
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针和数组
数组名是什么?我们看个例子:
#include<stdio.h>
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,0};
printf("%d",arr);//1
printf("%d",&arr[0]);//1
return 0;
}
从这个例子我们可以得到,数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址
那么,我们这样写就是可行的:
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,0};
int* p=arr;//p存放的是数组首元素的地址
return 0;
}
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个数组就成为可能。
例如:
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
运行结果:
所以 p+i 其实计算的是数组 arr 下标为i的地址。
数组名除了两种情况以外,都是作为首元素地址:
- 1.sizeof(数组名),计算的是整个数组的大小,此时的数组名代表 的是整个数组
- 2.&数组名,取出的是整个数组的地址,此时的数组名代表的是整个数组
(详情请看初级C语言数组)
二级指针
所谓二级指针,其实就是套娃,指针变量也是指针,是变量就有地址,用来存放指针变量的指针,就是二级指针。
三级指针就是用来存放二级指针变量的指针,四级指针,五级指针同理。
对于二级指针的运算有:
#include<stdio.h>
int main()
{
int a=10;
int* p = &a;
int** pp=&p;
int b=20;
*pp = &b;//等价于 p=&b
**pp=30;//等价于 *p =30;
//等价于 a=30
return 0;
}
指针数组
什么叫指针数组?就是存放指针的数组。
数组我们已经知道了整形数组和字符数组
例如:
#include<stdio.h>
int main()
{
int a=10;
int b=20;
int c=30;
int* arr[3] = {&a,&b,&c};
int i=0;
for(i=0;i<3;i++)
{
printf("%d\n",*(arr[i]));
}
return 0;
}