什么是指针
在生活中,我们应邀去朋友家是不是首先要知道他家的地址,而他家的门牌号就可以让我找到一个在某个地方内唯一存在的地址,当我看到了这个门牌号,我就明白了我已经到了。在C语言中,指针与此类似,它是一个储存内存地址的变量,它指向一个内存位置,该位置存储了数据或另一个内存地址。
怎么来定义一个指针
下面这个程序定义了一个指针变量,*是在说明p是一个指针变量,int是指明了其类型,让我们来看一下它的运行结果
#include <stdio.h>
int main()
{
int a=10;
int*p=&a;
printf("p = %d\n",p);
printf("a = %d\n",a);
}
它们输出的的值居然是一样的,那么它们的地址是否也是一样呢,让我们来试一下
运行后我们发现它们的地址也是相同的,那它们的值是相同的也就可以说得通了,这也就和我开头写的“它是一个储存内存地址的变量,它指向一个内存位置,该位置存储了数据或另一个内存地址”相呼应了。
既然指针是一个储存内存地址的变量,那么它就具有变量的属性——可以修改,那么我们怎么来修改它呢,这里就要用到解引用操作符了,也就是(*),下面这个程序可以修改指针变量p的值,方便起见,就不运行了,读者想要尝试的话,可以手敲实践一下。
#include <stdio.h>
int main()
{
int a=10;
int*p=&a;
printf("修改前p = %d \n",*p);
*p=20;
printf("修改后p = %d \n",*p);
}
指针也是有大小的,不过是取决于地址的大小。例如在32位平台下地址是32个比特位,即4个字节
在64位平台下地址是64个比特位,即8个字节。
指针的类型
一个变量可以有多种类型,比如浮点型,字符型,既然指针也是一种变量,那么它也应该具有类型,比如整数指针,浮点型指针,字符型指针。阅读了上面的文字后,你可能会问既然指针的大小取决于地址,那么指针类型有什么用呢?我们可以通过调试来观察一下其内存的变化。
//代码1
#include <stdio.h>
int main()
{
int n = 0x11223344;
int* pi = &n;
*pi = 0;
return 0;
}
我们可以观察到n的内存(图中红色数字)从0x11223344变为了0x00000000,至于图中数字为什么倒着放,是因为vs的是小端存储,感兴趣的可以自行了解。现在我来给这个程序做一点修改。
//代码2
#include <stdio.h>
int main()
{
int n = 0x11223344;
char* pi = &n;
*pi = 0;
return 0;
}
我们可以代码1中n的4个字节全部被改为0,而代码2中n只有一个字节被改为0,回到我们开始的问题,指针类型的作用就在于指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节),int*的解引用就可以访问4个字节,而char*的解引用就只能访问1个字节。
指针运算
指针的基本运算有三种运算,分别是指针+-整数,指针-指针,指针的关系运算。为了减少篇幅,后续代码除必要外不展示运行结果截图。
(一)指针+-整数
因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素,我们可以用它来访问数组的元素
#include <stdio.h>
int main()
{
int arr[5]={1,2,3,4,5};
int *p=&arr[0];
int sz=sizeof(arr)/sizeof(arr[0]);
for(int i=0;i<sz;i++)
{
printf("%d ",*(p+i));//这里就是指针加减整数
}
printf("\n");
return 0;
}
我们再来写一个程序来观察指针+-整数的地址的变化
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("&p = %p\n", p);
printf("&a = %p\n", &a);
printf("&(p+1) = %p\n", p + 1);
char* pa = (char)&a;
printf("&pa = %p\n", pa);
printf("&a = %p\n", &a);
printf("&(pa+1) = %p\n", pa + 1);
}
我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
(二)指针-指针
可以用指针-指针来计算字符串的长度
#include <stdio.h>
int my_strlen(char *s)
{
char*p=s;
while(*p!='\0')
{
p++;
}
return p-s; //指针-指针
}
int main()
{
printf("%d\n",my_strlen("abcde"));
return 0;
}
(三)指针的关系运算
1.相等性比较:可以使用相等运算符(==)和不等运算符(!=)来比较两个指针的值,这将比较它们是否指向相同的内存地址。
这里输出不相等。
#include <stdio.h>
int main()
{
int arr[3]={1,2,3};
int *ptr1=&arr[0];
int *ptr2=&arr[1];
if(*ptr1==*ptr2)
{
printf("相等\n");
}
else
printf("不相等\n");
}
大小比较:指针也可以进行大小比较,但是这需要注意指针指向的类型。指针的大小比较是根据指向的内存地址而言的,指向地址较小的指针被认为是“较小”的。
这里输出*ptr1<*ptr2。
#include <stdio.h>
int main()
{
int arr[3]={1,2,3};
int *ptr1=&arr[0];
int *ptr2=&arr[1];
if(*ptr1<*ptr2)
{
printf("*ptr1<*ptr2\n");
}
else if(*ptr1>*ptr2)
{
printf("*ptr1>*ptr2\n");
}
else
printf("*ptr1=*ptr2\n");
}
野指针
野指针概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因:
(一)指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
(二)数组越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
(三)指针指向的空间释放
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
不要犯以上错误可以避免野指针的出现。
二级指针
#include <stdio.h>
int main()
{
int a=10;
int *p=&a;
int**pp=&p;
return 0;
}
这就是二级指针