目录
本篇文章适用于对c语言有一定了解的同学,文章详细介绍了初阶指针学习的所有知识点,欢迎各位阅读。
指针基本概念
什么是指针
1, 指针是内存中一个最小单元的编号,也就是地址。
2,平时口语中说的指针,通常指的是指针变量,是用来存放地址的变量。
什么是地址
1,生活地址
2,内存中的地址
地址与内存单元
1,地址如同房间上的门牌号,通过这个门牌号我们可以找到所对应的房间。
2,内存单元如同房间,房间里面存储的是数据。
变量地址
系统分配给"变量"的"内存单元"的起始地址。
int num = 6; // 占用4个字节
//那么变量num的地址为: 0ff06
char c = 'a'; // 占用1个字节
//那么变量c的地址为:0ff05
什么是指针变量
1,在C语言中,允许用一个变量来存放其它变量的地址, 这种专门用于存储其它变量地址的变量, 我们称之为指针变量。
int main()
{
int a = 10;//在内存中开辟一块空间
int* p = &a;//int占4个字节,这里是将a的4个字节的第一个字节放到
p当中去,而这时的p就是一个指针变量
return 0;
}
总结
1,指针变量是用来存放地址的,地址是唯一标识一个内存单元的。
2,指针的大小在32位系统是4个字节,在64位系统是8个字节。
指针和指针类型
指针类型
1,我们大家都知道变量都是有类型,比如整型,浮点型,字符型,那么指针有没有类型呢?
准确的来说是有的。
比如:我们要把一个整形放在个指针变量当中去,肯定是要用一个整形指针来接收。
int num = 10;
p = #
有以上代码,我们取num的地址放在p中去,p肯定是一个指针,那么他的类型是怎么样呢?
指针类型的定义
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
我们可以看到指针类型的定义方法是:type+*的方式。
指针类型的意义
1,在不同的编译器下,所用指针虽然占用的内存空间一样,但不同的变量类型所占用的字节空间不同。
2,int占4个字节,char占1个字节,double占8个字节,那我们怎么知道从起始地址开始向后访问多少个字节呢?
3,指针变量所指向的指针类型,决定了你向后访问一次要走多少个字节,通俗的说也就是步长,比如int是4个字节,你往后走一步,也就是走了4个字节。
指针+-整数
- 指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。
- 指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。
#include <stdio.h>
//演示实例
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
指针关系运算
假设有变量px,py
- px > py 表示 px 指向的存储地址是否大于 py 指向的地址
- px == py 表示 px 和 py 是否指向同一个存储单元
- px == 0 和 px != 0 表示 px 是否为空指针
//定义一个数组,数组中相邻元素地址间隔一个单元
int num[2] = {1, 3};
//将数组中第一个元素地址和第二个元素的地址赋值给 px、py
int *px = &num[0], *py = &num[1];
int *pz = &num[0];
int *pn;
//则 py > px
if(py > px){
printf("py 指向的存储地址大于 px 所指向的存储地址");
}
//pz 和 px 都指向 num[0]
if(pz == px){
printf("px 和 pz 指向同一个地址");
}
//pn 没有初始化
if(pn == NULL || pn == 0){
printf("pn 是一个空指针");
}
指针解引用
1,指针解引用重点实在内存中去观察它的变化。
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0;
*pi = 0;
return 0;
}
野指针
野指针形成原因
1,没有进行指针初始化
int main()
{
int *p;
*p=20;
return 0;
}
这里的局部变量指针没有初始化,那么它默认的就是随机值,那么他此时就是一个野指针。
2,指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
这里指针的指向范围超出了数组arr的范围,就是越界访问,而此时指针p的值已经不知道了,所以他是野指针。
如何规避野指针
1,指针初始化
2,小心指针越界
3,指针指向释放空间,及时置NULL
4,避免返回局部变量的地址
5,指针使用之前检查指针的有效性
指针和数组
1,一个变量有地址,一个数组包含若干元素,每个数组元素也有相应的地址, 指针变量也可以保存数组元素的地址。
2,只要一个指针保存了数组元素的地址,我们就称之为数组元素指针。
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
注意: 可见数组名表示的就是数组首元素的地址。
既然我们可以把数组元素放到指针中去,那么我们也可以通过指针去访问数组元素。
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
所以p+i实在计算数组arr小标位i的地址,那么我们就直接可以通过指针来访问数组
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
二级指针
什么是二级指针
1,如果一个指针变量存放的是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量,那我们就称这个为二级指针。
对于二级指针的运算有:
1,*ppa通过对ppa中的地址解引用,这样找的是pa,*ppa访问的其实就是pa。
int b = 20;
*ppa = &b;//等价于 pa = &b;
2,**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a 。
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
多级指针的取值规则
int ***m1; //取值***m1
int *****m2; //取值*****m2
指针数组
1,首先我们要明白,指针数组不是指针而是数组。
2,指针数组里面所有元素都是指针。
在指针向下访问允许以下运算:
- 加一个整数(用+或+=),如p+1
- 减一个整数(用-或-=),如p-1
- 自加运算,如p++,++p
- 自减运算,如p–,--p
注意:数组名虽然是首元素地址,但是数组名所保存的数组首地址是不允许随便修改的。
int x[10];
x++; //错误
int* p = x;
p++; //正确
- 结论: 访问数组元素,可用下面两种方法:
- 下标法, 如a[i]形式
- 指针法, *(p+i)形式