目录
- 什么是指针?
- 指针变量
- 指针类型
- 指针类型的意义
- 在数组中举例
- 野指针
- 概念
- 野指针成因
- 如何规避野指针
- 指针运算
- 指针+-整数
- 指针关系运算
- 指针-指针
- 应用(求字符串长度)
- 结语
什么是指针?
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑储存器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过他能找到以他为地址的内存单元。
指针变量
指针变量,用来存放地址的变量。(存放在指针中的值被当成地址处理)
以32位机器为例,通电自供,将会产生正负两种电信号,再转化为数字信号后其实就是由32个0和1组成的二进制序列。那么这里就有2的32次方个地址。那地址就得用4个字节的空间来存储,所以
一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地
址。
指针类型
普通的变量,有整型,浮点型等等,那么类比普通变量,指针变量也有类型。
我们来举个例子,如图1.
可以看出,不同的指针类型变量都是8个字节(博主的电脑是64位机器,所以是8个字节),那么我们不妨假设一个指针类型ptr,它能代表所有的指针类型,这样不就方便多了吗,然而事实上并不存在这样一个类型,说明区分开不同类型的指针是有它存在的意义的。
指针类型的意义
我们给出以下代码(还是假设为32位机器)
int main()
{
int a=0x11223344;
int* pa=&a;
*pa=0;
return 0;
}
在我们的调试过程中我们会发现16进制的11 22 33 44全部变为了00 00 00 00.
我们再把类型改为字符
int main()
{
int a=0x11223344;
char* pc=&a;
*pc=0;
return 0;
}
这一次我们在调试过程中发现只有一个字节变为了0,即11 22 33 44变为了11 22 33 00(小端是从大到小).
由上面两个例子我们可以得出,整型指针解引用一次可以访问四个字节,而字符指针解引用一次只能访问一个字节。则
指针类型决定了:指针解引用的权限有多大。
我们再给出一个例子
int main()
{
int arr1[10] = { 0 };
char arr2[10] = { 0 };
int* p = arr1;
char* pc = arr2;
printf("%p\n", p);
printf("%p\n", p + 1);
printf("%p\n", pc);
printf("%p\n", pc + 1);
return 0;
}
代码运行结果如图2
我们发现,整形指针加1加了4个字节,相当于是跳过了一个整型(即四个字节),而字符指针加1只加了一个字节,相当于跳过了一个字符(即一个字节)。
所以我们又可以得出一个结论,指针类型决定了:指针走一步能走多远.
综上,指针类型的意义有:
1,指针类型决定了指针解引用的权限有多大。
2,指针类型决定了指针走一步能走多远。
在数组中举例
我们给出一段代码
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
int j = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 1;
}
for (j = 0; j < 10; j++)
{
printf("%d", arr[j]);
}
return 0;
}
运行结果如图3
产生这种结果的原因在于整形指针每次解引用都跳过了一个整形,如图4
但如果我们把指针类型修改一下
int main()
{
int arr[10] = { 0 };
char* p = arr;
int i = 0;
int j = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 1;
}
for (j = 0; j < 10; j++)
{
printf("%d", arr[j]);
}
return 0;
}
代码运行结果就并没有把数组访问完,因为一次只跳过了一个字节。图5
这样区分开的意义就在于如果我们想对一个整型数组的元素逐个字符修改的话,就可以把指针类型惊醒相应的调整。
野指针
概念
野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)
野指针成因
1,指针未初始化
我们来看这样一段代码
int main()
{
int* p;
*p = 0;
return 0;
}
由于p是一个局部的指针变量,局部变量不初始化的话,默认是随机值,那么*p解引用的时候就非法访问内存了。
这里我们举一个比较形象的例子:你到一个酒店去,你钱也没付就找了个303房间睡觉了,很显然,这个303房间并不属于你,房管并没有给你房卡,准许你入住,这样是不是就要好理解多啦。
2,越界访问
我们来看这个代码
int main()
{
int arr[10] = { 0 };
int* p = 0;
int i = 0;
for (i = 0; i < 11; i++)
{
*p = i;
p++;
}
return 0;
}
这个代码会循环十一次,最后一次访问的是arr[10],很明显越界访问了。如图6
3,指针指向的空间释放
大致意思是本来申请了一片空间,我也知道这个空间的地址,但后来这个空间被释放了(或者说成还给系统了),
举个例子:你找了一个女朋友,你也有了她的电话号码,但后来你们分手了,你再给她打电话就是有问题的了,因为她已经和你没有关系了。
我们也来看一段代码
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
*p = 20;
return 0;
}
如图7所示,当我们把地址返回给主函数是,a的生命周期也结束了,所以再对这个指针解引用时,就会非法访问。
如何规避野指针
1,指针初始化
2,小心指针越界
3,指针指向空间释放及时置NULL
4, 使用之前检查有效性。
和初始化变量一样,指针也尽量全部都初始化,否则会是一个随机值。如果当前你不知道应该初始化为什么地址时,就直接初始化为NULL(空指针)。
int* p=NULL;
指针运算
指针±整数
我们来看一段代码
int main()
{
int arr[10];
int* p;
for (p = &arr[0]; p < &arr[10];)
{
*p++ = 0;
}
return 0;
}
这里的p++即为p+1,指的是地址的后移。
指针关系运算
还是看一段代码
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int* pend = arr + 9;
while (p <= pend)
{
printf("%d\n", *p);
p++;
}
return 0;
}
指针-指针
看下面这段代码
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);
return 0;
}
运行结果如图8
由图可得指针减指针得到的是两个指针之间的元素个数。如图10
但要注意的是:指针和指针相间的相减的前提是两个指针指向同一个空间!!!
应用(求字符串长度)
我们想求一个字符串的长度,我们不妨这样想,我们只要找到\0的地址,把这两个指针相减就是中间元素的个数,即为长度。
int my_strlen(char* str)
{
char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
int len = my_strlen("abc");
printf("%d", len);
return 0;
}
结语
好了,关于C语言指针初级的内容就讲到这里了,如有出入,欢迎大佬们指指正。