文章目录
一、指针的运算
(1)指针加整数
例子,我们打印一组数字:
#include<stdio.h>
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int* pa = &arr[0];
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
printf("%d ", *(pa + i));
}
return 0;
}
这里的pa + i
就是指针变量加上一个整数,pa + i
是地址,*
是对这个地址的解引用。
(2)指针减指针(指针关系运算)
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
int* pa = &arr[0];
printf("%d ", &arr[9] - pa);
return 0;
}
上面的代码我们也可以用指针关系运算来写:(这里我们需要用到循环)
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = &arr[0];
int sz = sizeof(arr) / sizeof(arr[0]);
while (pa < arr+sz)
{
printf("%d ", *pa);
pa++;
}
return 0;
}
这里的pa < arr+sz
是指针的比较,pa就是第一个元素开始的地址,而arr+sz就是最后一个元素结束的地址:
这里的&arr[9] - pa
也可以写成&arr[9]-&arr[0]
,而得到的结果是 9 。由此我们可以得到一个结论:
指针的关系运算,得到的是指针和指针之间元素的个数。
二、野指针
(1)野指针的成因
(1.1)指针未初始化
示范一个错误例子:
#include<stdio.h>
int main()
{
int* pa;
*pa = 1;
printf("%d ", *pa);
return 0;
}
局部变量指针未初始化,默认为随机值。
(1.2)指针的越界访问
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* pa = &arr[0];
for (int i = 0; i <= 11; i++)
{
*(pa++) = i;
printf("%d ", *pa);
}
return 0;
}
指针指向的范围超出数组arr的范围,pa就是野指针(指针指向的范围有11个,而数组arr的范围只有10个)
(1.3)指针指向的空间释放
#include<stdio.h>
int Print()
{
int a = 90;
return &a;
}
int main()
{
int* pa = Print();
printf("%d ", *pa);
return 0;
}
我们写一个函数,将a赋值为90,然后把a的空间返回到主函数中,*pa
可以接受到a的地址,但是出了Print()
函数,空间就被回收了,此时*pa
带着地址去访问该空间,不会得到任何数字,这就是空间的释放。
(2)如何避免野指针
(2.1)指针需要初始化
- 如果我们明确知道指针指向哪里就直接赋值地址:
#include<stdio.h>
int main()
{
int a = 20;
int* pa = &a;
return 0;
}
这里前面就是知道pa指针指向的是a的地址,所以我们直接:int* pa = &a
。
- 如果我们不知道指针应该指向哪里,可以给指针赋值NULL。NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。
#include<stdio.h>
int main()
{
int* p = NULL;
*p = 20; //err
printf("%d ", *p);
return 0;
}
小心指针的越界:
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。(可以见上面的(1.2))
(2.2)指针变量不再使用时,及时置NULL,指针使用之前检查有效性
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = &arr[0];
for (int i = 0; i < 10; i++)
{
*(pa++) = i;
}
pa= NULL;
pa = &arr[0];
if (pa != NULL)
{
for (int i = 0; i < 10; i++)
{
printf("%d ", *(pa + i));
}
}
return 0;
}
当 *(pa++) = i;
循环结束后,pa是超出了arr的范围的,此时可以把pa重置;当我们要重新用到pa时,可以让pa重新获得地址,重新获得地址后,我们需要判断pa是不是空指针,以确保代码的安全性。
- 避免返回局部变量的地址(可见(1.3))
三、 strlen的模拟实现
库函数strlen
的功能是求字符串长度,统计的是字符串中 \0
之前的字符的个数。
如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是 \0
字符,计数器就+1,这样直到 \0
就停止。
#include<stdio.h>
#include<string.h>
int my_strlen(char* pc)
{
int count = 0;
while (*pc != '\0')
{
count++;
pc++;
}
return count;
}
int main()
{
char arr[] = { "abcdf" };
int len = my_strlen(arr);
printf("%d ", len);
return 0;
}
上面的代码,我们可以发现有很多的不足之处,不可以确保代码完全的安全性:
-
如果用户不小心把数组传成了空指针
-
int是有符号的整形,它可以为负数,但是我们统计数字不可能是负数
-
char *pc
只是用于遍历的,不能对*pc
进行修改
所以最后的代码应该改为:
#include<assert.h>
#include<stdio.h>
#include<string.h>
size_t my_strlen( const char* pc)
{
assert(pc != NULL);
size_t count = 0;
while (*pc != '\0')
{
count++;
pc++;
}
return count;
}
int main()
{
char arr[] = { "abcdf" };
size_t len = my_strlen(arr);
printf("%zd ", len);
return 0;
}
四、总结
指针需要理解的东西有很多,希望大家可以自己慢慢去消化,指针这一章的内容还没有结束哟,希望与大家下一次再见。ԅ(¯ㅂ¯ԅ)