目录
前言
在本篇中我们讲述指针的基本概念与用法,知识点中涉及的堆区,栈区,静态区的概念我们在本篇中暂时忽略,后面会写一篇文章简要的讲述一下相关的内容。
一.什么是指针
我么知道数据是在内存中存放的,当我们想要访问内存中的某一特定区域时就需要用到了指针,例如我创建了一个局部变量int a=4而我如果想直接操作这个数字所在的内存空间将其更改。而指针又是如何实现的呢?
我们知道内存中是存在地址这个概念的,什么是地址呢,地址呢就是地址。
当然这里并不是说一些废话,而是地址我们可以简单的理解为字面意思。假设内存是一个正在建设的小镇,这个小镇里面按一个字节的大小整齐的划分成了一个一个的区域,我们给这些区域按顺序编上了号,那么这些编号就是对应区域的地址。而地址就是指针
当我们需要一片空间来盖房子或者 干其他事就需要向内存申请一片空间。
既然有了指针我们又该怎么去用呢,这个时候我们便需要一个指针变量去存储我们的指针。
二.什么是指针变量
int main()
{
int a=8;
int* p=&a;
return 0;
}
这个p就是我们用来存放指针a的指针变量。
当我们想要修改a的时候只要使用我们的解引用操作符*对p进行解引用就可以直接操作其中的内容了
int main()
{
int a=8;
int* p=&a;
*p=2;
return 0;
}
此时我们的a中存放的数值就改成了2。
简单的来说就是我们假设a的地址是0x175864BF,我们在内存中开辟的这片内存空间就是一个宝箱
而地址就是这个宝箱的编号
(宝箱是我自己随手画的,很丑,还请见谅)
其中指针p里边就是存放的就是这个编号,通过这个编号我们就找到了这个宝箱。
当我们在p前面加上*写成*p就可以找到这个宝箱,拿走或者放入东西了。
三.二级指针
我们既然提到了指针变量本身也是存放的一组数据(地址),那么指针变量它本身也是有地址的。
那么这个地址又能不能存放到一个指针变量之中呢,当然是可以的。
那么这个存放指针变量的的指针也就是二级指针。
以上面那个宝箱为例,既然宝箱有编号,那么我们就可以把这张编号写在纸上,将这张纸放入另一个宝箱,那么存放这张纸的宝箱肯定也有自己的编号(地址),这个存放宝箱编号的宝箱的编号(地址)就是二级指针。
当然这里只是为了更形象的说明才举了这样一个例子,实际存储中并没有纸这个介质,数据是直接存在内存空间中的。
int main()
{
int a=8;
int* b=&a;
int* c=&b;
**c=2;
printf("%d",a);
return 0;
}
当我们解引用一次的时候,找到c宝箱的位置里边放的是一个地址,也就是另一个宝箱的编号。在次解引用找到名为a的宝箱,将里边放的2换成了8。
当然这些描述对于比较熟悉C语言的人来说可能会感到相当糟糕,因为实际上指针也就是地址实际上是一片空间的首位(或者说低地址处)的地址,根据指针的类型来决定它的访问权限的,这时候就要提到我们的指针类型的意义了。
四.指针类型的意义
1.指针的访问权限
我们已经知道了一个int开辟的是一个4个字节的空间,而我们每一个字节都是有自己的地址的,那么当我们使用一个int*的指针变量时它其中又是怎么存放的地址呢
假设我们如图中所示开辟了一片空间用来存放数据a,如果此时我们给了一个p的指针变量去存放a的地址,那么p中存放的就是4个字节中3这个位置的地址。
而类型就决定了我们会从3开始往后访问几个字节,如int*我们会从3的最左端开始往后访问4个字节,而使用char*的话只会从左端开始访问一个字节也就是3这个字节。
2指针的运算
1.指针类型也决定了我们指针的运算
以下面这段代码为例
int main()
{
char ch = 5;
int a = 5;
int* pa = &a;
char* pc = &ch;
printf("%p\n", pa);
printf("%p\n", pa+1);
printf("%p\n", pc);
printf("%p\n", pc+1);
}
我们可以看到pa+1跳过了一个int的大小4个字节,而pa+1跳过了一个char 类型的大小1个字节。
2.如果指针减指针会发生什么
首先指针减指针的话,两个指针必须指向的是一片连续的空间例如arr[6],里面arr[0]到arr[6]就是一片连续的空间
关于指针减指针我们可以通过下面一段代码进行分析
#include<stdio.h>
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
printf("%d\n", &arr[0] - &arr[9]);
printf("%d\n", &arr[9] - &arr[0]);
}
五.指针变量的大小
既然p是存放我们地址的,那么p本身也就是一组数据,那么自然也就会在内存中开辟一片空间。
而这片空间的大小在32位平台和64位平台环境下是有所差别的
这是因为32位系统的最大寻址空间是2的32次方=4294967296(bit)= 4(GB)左右,64位系统的最大寻址空间为2的64次方方。所以对指针的大小也就有所改变,在32位x86环境下指针变量的大小是4个字节(32个比特位),而在x64环境下就是8个字节。
六.野指针
1.什么是野指针
int main()
{
int *p;
return 0;
}
在实际编写代码中如果意外使用了野指针可能会导致难以发现的bug。
2.如何规避野指针
1.首先注意指针的初始化
避免出现未完全初始化的指针
2.避免指针越界
int main()
{
int arr[5]={0,1,2,3,4};
int* p=arr;
*(arr+5)=1;
return 0;
}
注意此时就出现了指针越界。
3.指针指向的空间释放时及时的将其置为NULL空指针
#include<stdlib.h>
int main()
{
int* p=malloc(40);
free(p);
p=NULL;
}
malloc是动态内存分配,当我们给他一个40时他就会开辟一块40字节的空间给我们,返回值是指向这片空间开头的指针,当然我们在这里就不详细讲解malloc了;
free的作用释放内存,当我们free(p)时这个块的内存空间就释放了,此时指针就没有了明确指向,所以我们使用p=NULL;将其置为一个空指针避免其成为野指针。
4.避免返回局部变量的地址
int* fun()
{
int a=8;
return &a;
}
int main()
{
int* p=fun();
}
像这种里面的指针p也是一个野指针, 我们知道a是一个局部变量,当离开它的作用域之后,它的生命周期也就结束了,此时指向a的空间已经释放。
这些大概就是指针的部分基本概念与注意事项了,下一篇文章再带大家更多的认识一下指针和它们能做什么。