提前说好,我是小白,欢迎大家来喷我(如果有读者的话)
那么我会从以下几个方面来做一个简单的阐述
- 指针是什么?
- 指针与指针类型
- 野指针
- 指针运算
- 指针和数组
- 二级指针
- 指针数组
1.指针是什么?
理解指针的2个要点:
(1)指针是内存中一个最小单元的编号,就是地址
(2)平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量.
那么这里就引出一个关键点:数据在内存中是怎么存储的?
我们知道,在现实当中有一个东西叫做门牌号,我可以通过省—>市—>区—>门牌号,来找到你具体在哪里,其实内存中的存储和现实中的门牌号差不多.
关键点
1.数据在内存当中存储的其实是这个数据的地址
2.编号—地址—指针
3.&c,取出的是第一个字节的地址,也就是较小字节的地址(在这里就是取出:0X0056ff20)
那么怎么创建一个指针变量呢?
int* pa=&c;
在32位的机器上,一个指针变量的大小是4个字节,在64位的机器上,一个指针变量的大小是8个字节 .
2.指针与指针类型
我们在这里讨论一下指针的类型
我们都知道,变量有不同的类型,整型,浮点型等.那么指针有没有类型呢?准确的说:有的.我们来看以下几个例子
int*类型的例子
我在这里调出了内存,我们可以看到int*类型的指针一次可以访问4个字节
char*类型
我们可以看到,char*类型的指针一次可以访问一个字节
short*类型
我们可以看到short*类型的指针一次可以访问两个字节
那么由以上的规律可以知道
若要访问一个字节,则要用char*类型的指针
若要访问两个字节,则要用short*类型的指针
若要访问四个字节,则要用int*类型的指针
那么此时就有一个问题,float*同样也是会访问四个字节
那么我们究竟需要用哪一个呢?
若我们用int*类型的指针,那么我们会将内存中的数据看为一个整型来处理
若我们用float*类型的指针,那么我们会将内存中的数据看为一个浮点型来处理.
我们现在从地址的角度来看一看这个变化
我们由上图可知
int*类型的指针变量pa,pa+1后会跳过四个字节(int*是访问四个字节的,int类型占据四个字节)
char*类型的指针变量pb,pb+1后会跳过一个字节(char*是访问一个字节,char类型占据一个字节)
下面我们来写一个程序,来向大家更好的理解这个现象
上面这个程序的功能是将一个整型a(4个字节)一个字节一个字节的将它改为0.
注意:这里并不是说int类型的变量就一定要放在int*类型的指针中去,而是要根据你到底想怎么去访问这一组数据来决定的.
指针类型的意义
不同的指针类型,其实提供了不同的视角去观看和访问内存
char*————一次访问一个字节,+1跳过一个字节
int*————一次访问四个字节,+1跳过4个字节
3.野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
容易出现野指针的两种情况
(1)未对指针进行初始化
#include<stdio.h>
int main()
{
int a = 0;
int* p;
printf("%d\n", *p);
*p = 20;
printf("%d\n", a);
printf("%d\n", *p);
return 0;
}
以上那段代码在VS 2013的IDE中会报告这样的一个错误 .
要注意:指针变量它也是一个变量,此时这个变量是一个局部变量,局部变量在未初始化的时候是一个随机值,在这里就是说,我们产生了一个随机值,并在内存中找到了找到了这个随机值所对应的地址,并且我们还将20放入到了这个地址所标注的内存空间中去。注意这样的做法是非常危险的.
这种写法是完全没有问题的,*p在这里有明确的指向.
(2)指针越界访问
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int*a = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
*a = 1;
a++;
//当指针指向的范围超出数组arr的范围时,p就是野指针.
}
return 0;
}
此时编译器就会报错,这种错误的原因就是发生了越界访问
数组越界图解,以此题为例(关于数组越界,我在以后的博客会出)
我们看一下下面这个例子
#include<stdio.h>
int*test(int a)
{
a = 20;
return &a;
}
int main()
{
int a = 0;
int*p = test(a);
printf("%d\n", a);
return 0;
}
这是它的运行结果
对,没错它不是20,为什么?
那么至于为什么打印出的结果是0(这里好像与函数栈帧有关?我下去再看看,求大佬指点)
那么 我们该怎么规避野指针呢?
1.指针初始化
2.小心指针越界
3.指针指向空间释放,及时置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性.
关于NULL
我们转到NULL的定义我们可以知道(如上图所示)
4.指针运算
(1)指针+-整数
(2)指针-指针
(3)指针的关系运算
(1)指针+-整数
其实我们在前面我们讲到指针类型的时候用到了,不过我们这里再看一个例子
#define VP 5
#include<stdio.h>
int main()
{
int arr[VP] = { 0 };
int*i= 0;
for (i = &arr[0]; i < &arr[VP];)
{
*(i++) = 1;
}
return 0;
}
我们可以看到,此程序是修改数组的每一个元素的内容,也从内存中得以看到指针+-整数是决定了这个指针怎么移动.
(2)指针-指针
直接拿代码说话,这里我们会自主实现strlen
int my_strlen(char*p)
{
char*a = p;
while (*p != '\0')
{
p++;
}
return p-a;
}
int main()
{
int sum = 0;
char arr[] = "abcdefg";
sum=my_strlen(arr);
printf("%d\n", sum);
return 0;
}
由此可见:指针-指针得到的是两个指针之间的元素的个数
注意:指针-指针的前提是两个指针必须要指向同一块空间.
(3)指针的关系运算(说白了就是比大小)
直接拿代码说话
#define VP 5
int main()
{
int arr[VP] = { 0 };
int*p = 0;
int i = 0;
for (p = &arr[VP-1];p>=&arr[0];p--)
{
*p = 1;
}
return 0;
}
这里肯定会有人吼:哦哦哦哦,你看你看,越界访问!哦哦哦哦,你看你看,野指针!
可是这里真的越界访问了吗?p真的是野指针了吗?
再次强调:指针会最终指向第一个元素前面的内存空间中去,但请注意,虽然此时p到了这个数组的外面,但是我们并没有对它进行一个解引用的操作,所以并没有进行一个越界访问,所以这里的p并不是一个野指针.
5.指针和数组
直接上代码
在这里我们要实现用指针来访问数组
#include<stdio.h>
int fb(int *p)
{
int i = 0;
for (i = 0; i < 10; i++)
{
*p = 1;
p++;
}
}
int main()
{
int sum = 0;
int arr[10] = { 0 };
fb(arr);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行截图如上.
注意:数组名是数组首元素的地址.
6.二级指针
我们之前讲到,指针变量是用来存放变量地址的,那么指针变量本身身为一个变量有没有地址?
指针变量的地址该怎么表示呢?
此时就要用到我们的二级指针
拿代码说话
#include<stdio.h>
int main()
{
int a = 10;
int*pa = &a;
int**ppa = &pa;
**ppa = 20;
printf("%d\n", a);
return 0;
}
注意*的个数不是随便加的而是有规律的,但最终还是要理解规律的,具体请看上图图解.
7.指针数组
指针数组是指针还是数组?
答案:是数组,是存放指针的数组.
上代码
#include<stdio.h>
int main()
{
int a = 100;
int b = 200;
int c = 300;
int d = 400;
int*arr[5] = { &a, &b, &c, &d };
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *arr[i]);
}
return 0;
}
其实指针数组的使用与一般的数组没什么不同,就是要注意,指针存放的是地址,所以不要将&给忘记,还有,若要打印出指针数组,千万不要忘记*.
指针数组原理图解如下
但是话又说回来,我们若是想要实现上述代码的功能有何必动用指针呢?
接下来我们讲一个实例:用一维数组来模拟一个二维数组
#include<stdio.h>
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6 };
int b[] = { 7, 8, 9, 10, 11, 12 };
int c[] = { 13, 14, 15, 16, 17, 18 };
int d[] = { 19, 20, 21, 22, 23, 24 };
int*k[4] = { &a, &b, &c, &d };
int i = 0;
for (i = 0; i < 4; i++)
{
int j = 0;
for (j = 0; j < 6; j++)
{
printf("%d ", k[i][j]);
}
printf("\n");
}
return 0;
}
代码详解
最后总结
本人萌新一枚,欢迎各路大神前来吐槽,若文章中有什么不对的,也感谢各位的提点.