系列文章目录
速通C语言系列
速通C语言第一站 一篇博客带你初识C语言 http://t.csdn.cn/N57xl
速通C语言第二站 一篇博客带你搞定分支循环 http://t.csdn.cn/Uwn7W
速通C语言第三站 一篇博客带你搞定函数 http://t.csdn.cn/bfrUM
速通C语言第四站 一篇博客带你学会数组 http://t.csdn.cn/Ol3lz
速通C语言第五站 一篇博客带你详解操作符 http://t.csdn.cn/OOUBr
感谢佬们支持!
文章目录
前言
一、指针类型
int*pa;
char*pc;
struct stu*;
……
以上均为不同类型指针
虽然类型不同,但是指针的本质是存储地址,所以我们认为任何类型的指针均为内置类型
但是指针的不同类型还是有用的
指针类型的意义
1 指针解引用的权限有多大
例;
我们给到一个16进制的数
int a = 0x11223344;
先用int*类型的指针对其解引用
通过调试之后
这个二进制位的每个字节都被改成了0.
再用char*的指针解引用下
char* pa = &a;
*pa = 0;
进过调试之后
只改了第一个字节。
在后期我们模拟实现string类函数时,为了能对数据逐字节操作,就会将指针强转为char*类型,就是利用了不同指针类型解引用权限的不同。
2 指针的类型决定了指针一步走多远
例:
int arr[10] = { 0 };
//p和pc均放的是一个地址
int* p = arr;
char* pc=arr;
printf("%p\n", p);
printf("%p\n", p+1);
printf("\n");
printf("%p\n", pc);
printf("%p\n", pc+1);
打印后发现,p+1跳过了一个整型(4字节),pc+1跳过了一个字符(1字节)
二、野指针
1 定义
如果一个指针指向的位置是不可知的,这个指针就是个野指针。
2 成因
1 指针未初始化
例:
int* p;
//p是一个局部变量,没初始化默认是随机值
*p = 20;
2 指针越界访问
例
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 10; i++)
//从i=0到i<=10,循环了11次
{
*p = i;
p++;
}
由于循环了11次,所以这波会越界访问
3 指针指向的空间释放
指针指向的空间被释放主要有两种:动态内存开辟(这个放在之后再讲),我们先来介绍下面这种情况:函数
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
*p = 20;
return 0;
}
在这段代码中,test函数返回了a的地址,但是我们知道出了test函数的作用域,a就会销毁,也就是a所在空间被释放了,这个时候p就成了野指针。
为了让大家好理解,我们举一个生活中的例子。
一钢在酒店开了一个房间302,给松小打电话让他明天来住,结果第一天晚上一钢退了房了,所以第二天松小来酒店想住这个房间就住不了了。
野指针是个很麻烦的东西,我们应该避免它
3 如何避免野指针?
1 指针初始化
//当前不知道指针应该初始化为什么地址时,直接给他初始化为NULL
int* p = NULL;
2 小心越界
由于编译器并不会检查越界,所以我们需要自己注意,或加上assert断言
注:
理论上来说,越界是可以的,但是越界后不能对其访问、修改。在某些情况下,我们需要越界一下(当然,我们不会对其访问、修改)。
3 指针指向的空间释放时及时将其置成NULL
这个等我们讲到动态内存后再向大家介绍。
4 指针使用前进行有效性检查
由于我们不能对空指针解引用,所以在使用指针前,一定要进行有效性检查
例:
if (p != NULL)
{
*p = 10;
}
三、指针运算
1 指针减指针
例:
int arr[10] = { 0,1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);
我们画波图给大家演示一下
得到了中间元素的个数
注:指针-指针的前提是两个指针需指向同一块空间。
例:
int arr[10] = { 1,2,3,4,5,6,7,8,9 ,10};
char c[5];
printf("%d\n", &arr[9] - &c[0]);
由于起始地址不同,所以这波得到的是随机值。
2 指针的关系运算 、 3 指针加/减整数
我们先上例子
例:
#define N 5
int main()
{
float value[N];
float* vp;
for (vp = &value[N]; vp > &value[0];)
{
//先--,再赋0
*--vp=0;
}
}
这段代码中,刚开始vp=5,5>0,vp会将value[4] 赋成0,之后vp--变成4
然后4>0,vp会将value[3] 赋成0,之后vp--变成2
……
0=0时,vp停下
在上述过程中,vp--为指针+/-整数的运算,
vp>value[0]为指针的关系运算。
但是我们觉得上面这个式子似乎不太符合平时写的风格,所以对其改良一下
float value[N];
float* vp;
for (vp = &value[N]; vp >= &value[0]; vp--)
{
*vp = 0;
}
对应于画图上:
这样写确实更好理解,也在大多数编译器上可行。 但是并不符合标准。
标准规定,允许指向数组元素的指针和指向数组最后面的·那个内存位置的指针比较,
但不允许与指向第一个元素之前的那个内存位置的指针进行比较
总结就是,两种方法都是越界而不访问,但是
四、指针和数组
众所周知,数组名是数组首元素地址,那每个元素的地址呢?
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%p-%p\n", &arr[i], p + i);
}
}
打印的地址相同
所以我们可知 p+i 产生的地址等价于 arr[i] 产生的地址(即下标为i的地址)
我们可以通过p+i对其进行赋值
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
(成功赋值)
拓展
拓展一点东西
刚才我们知道了*(p+i)等价于arr[i]
所以可推得p和arr是一个东西
所以 arr[2] 等价于 (arr+2)等价于 *(p+2)
由于加法有交换律
我们又可推得 *(p+2)等价于*(2+p)等价于*(2+arr) 等价于 2[arr]
那么这个看起来三观炸裂的东西到底对不对呢?
我们浅测一下
printf("%d\n", 2[arr]);
printf("%d\n", arr[2]);
结果为……
(真的一样)
其实,从操作符的角度理解,[ ] 是下标引用操作符,所以2和arr均为它的两个操作数,就像a+b=b+a, 2和arr也可交换一下位置。
注:在底层上arr【2】会转化为*(arr+2)来处理哦
五、二级指针
前面我们知道,我们可以用指针存一个变量的地址
int a = 10;
int* pa = &a;
pa是个指针,也就是个指针变量,既然是变量,就有存放的地址,我们可以类似地“套娃”出
二级指针的概念
int** ppa = &pa;
为什么pa的类型为int? 因为他存的是int类型的地址
所以我们可推得ppa的类型为int*,因为他存的是int*类型的地址
此时,ppa就是一个二级指针变量。
我们用打印来验证一波
printf("%p %p %p %p",&a,pa,&pa,ppa);
即证pa存的是a的地址,ppa存的是pa的地址
二级指针将在我们学习链表时用到
在其基础上,我们可以再次进行套娃
int***pppa=&ppa;
……
但是二级指针之后就很少用到了
六、指针数组
指针数组,即为存放指针的数组,其每个元素为一个指针
例:
//整形数组-》存放整形的数组
int arr[10];
//字符数组-》存放字符的数组
char ch[5];
//整形指针数组->每个元素是int*
int* parr[5];
//字符指针数组->每个元素是char*
char* pch[5];
我们简单的应用一下
先给5个值,再用一个数组分别存储这五个值的指针,用for循环将其打印出来
//给5个值
int num1 = 10;
int num2 = 20;
int num3 = 30;
int num4 = 40;
int num5 = 50;
//用个指针数组给它存起来
int* arr[5] = { &num1,&num2,&num3,&num4,&num5 };
//for循环遍历打印
for (int i = 0; i < 5; i++)
{
printf("%d\n", *arr[i]);
}
(成功打印)
指针和数组还有很多指针数组,数组指针,回调函数……之类的套娃操作,我们放到指针进阶在做讲解。
总结
做总结, 这篇博客带大家学习了指针初阶,相信大家对指针又有了新的理解,其实指针远不止这么点内容,更多深的、难的内容将放至指针进阶再为大家讲解。
水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。