指针是什么
指针是什么?
指针理解的2个要点
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常是指针变量,指针里面存的是地址。
举个例子
int* p这是一个指向int类型的指针
int main()
{
int a = 10;
int* p = &a;
printf("p里面存的地址是%p\n a的地址是%p",p,&a);
}
可以发现p里面存的地址就是变量a的地址。
指针类型
那我们怎么看指针指向的是什么类型呢:
其实很简单,只要我们把声明语句里面的指针名字去掉,剩下的部分就是指针的类型
- int* p //整型类型的指针,指向的是int类型
- char* p //字符类型的指针,指向的是char类型
- double* p //浮点类型的指针,指向的是double类型
- int *p[5] //指针数组,其实这是一个数组,数组的每个元素是int类型的指针
- int (*p)[5] //数组指针,指向的是一个数组,数值有5个元素,每个人元素的类型是int
- int** p //整型类型的二级指针,指向int*类型的指针
- int* p //首先p先和*结合,说明p是一个指针,再和int结合,说明指向的内容是int类型,所以p是一个int类型的指针
- char* p //首先p先和*结合,说明p是一个指针,再和char结合,说明指向的内容是char类型,所以p是一个char类型的指针
- double* p // 首先p先和*结合,说明p是一个指针,再和double结合,说明指向的内容是double类型,所以p是一个double类型的指针
- int p[5] // **首先p先和[]结合,因为[]优先级比高,所以p现在是一个数组,然后再和*结合,说明数组里的每个元素都是指针类型,然后再和int结合,说明p数组里面的指针指向的都是int类型,所以p是一个数组,数组里面的每个元素是int类型的指针。**
- int (p)[5] // **首先p先和结合,然后再和[]结合,说明p指向的内容是一个数组,再和int结合,说明p指向的数组里面的每个元素是int类型。**
- int* p // **首先p先和结合,说明p是一个指针,然后再和int结合,说明p指向的是int类型,所以p是一个指向int*类型的指针,其实二级指针里面存的就是一级指针的地址,就是是99级指针,里面存的也是98级指针的地址。**
指针的大小
在32位的机器上,地址是32个0或者1组成的二进制序列,那地址就得用4个字节的空间来储存,所以一个指针变量的大小就是四字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量就是8字节。
举个例子
64位和32位在vs最上面切换,x64是64位,x86是32位
int main()
{
int* p;
printf("在64位机器上的大小为:%d", sizeof(p)); //输出8
}
int main()
{
int* p;
printf("在32位机器上的大小为:%d", sizeof(p)); //输出4
}
指针的使用
通过上面的了解,我们知道指针是个啥,还有指针的类型,但就是不知道他咋用。
指针是通过*来使用,简称解引用,
指针里面存的是地址,赋值的时候得用&获得变量的地址。
举个例子
int *p 这是一个指向int类型的指针
int main()
{
int a = 10;
int* p = &a; //赋值的话得两边类型相同,&a==取出来变量的a地址,相当于int*类型,所以能够赋值
printf("指针p通过解引用获得a地址里面的值:%d\ta的值为:%d", *p, a);
}
**指针里面存的地址就像一个门牌号,编译器就像外卖员,比如你在酒店点外卖,外卖员(编译器)通过p指针里面存的门牌号(内存地址)找到你,然后把外卖给你,解引用就像外卖员把外卖给你的那个操作,没有解引用的话就像是,外卖员(编译器)知道你的门牌号(内存地址),但没有把外卖给你的那个操作。 **
int类型的指针解引用是一次访问四个字节,那是不是所有的指针解引用都访问四个字节吗,其实不是的,解引用访问多少字节是看你指针指向的类型。比如:
- int* p //指向int类型的指针,他解引用是访问4个字节 。
- char* p //指向char类型的指针,他解引用是访问1个字节。
- float* p //指向float类型的指针,他解引用是访问4个字节。
- double *p //指向double类型的指针,他解引用是访问8个字节。
可以看出解引用访问多少字节是看指针指向的类型的大小。
指针的±运算
指针±运算一般有两种
- 指针本身加减
- 指针解引用加减
指针本身加减
例子1
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9 };
int* p = a; //因为数组的名字就是数组的首地址,所以不需要加&符号
printf("首地址为:");
for (int i = 0; i < sizeof(a) / sizeof(a[0]);i++)
{
printf("%p ", p + i);
}
}
可以看出p+0就是数组的首地址,p+1就是数组第二个元素的地址,p+2就是数组第三个元素的地址,p+3就是数组第三个元素的地址…以此类推,p+N就是N-1元素的地址。
那为什么指针加1可以刚好走到第二元素的地址呢,这个是看指针指向的元素类型大小,就跟解引用一样,int类型就走4个字节,char类型就走一个字节。(如果是64位的int类型就走8字节)
p++,p–也是一样的,也看指针指向的元素类型大小。
例子2
char str[] = { "hello bit"};
char* p = str;
printf("首地址为:");
for (int i = 0; i < sizeof(str) / sizeof(str[0]);i++)
{
printf("%p ", p+i);
}
指针解引用加减
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9 };
int* p = a;
for (int i = 0; i < sizeof(a) / sizeof(a[0]);i++)
{
printf("%d ", ++*(p + i)); //也等于++a[i],[]也相当一次解引用
}
}
先将(p+i)括起来不然p就和*结合就先解引用了,在加一个解引用符号在(p+i)左边,表示访问第p+i个元素,最后面加上++运算符,就可以实现解引用的加加,减减也是一样的。
指针数组和数组指针
数组指针
int (p)[]-这是一个数组指针,他本质是一个指针,p先和结合,说明p是一个指针,再和[]结合,说明指向的是一个数组,再和int结合,说明p指向的数组里面每个元素是int类型。
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int(*p)[10] = &arr;
printf("%p %p %p", p,*p,arr);
}
为啥p和*p是一样的呢,先看下面这个代码
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int(*p)[10] = &arr;
printf("%p %p %p", p+1,*p+1,arr);
}
p+1是直接跳过了整个数组,说明此时p是数组的地址,不是数组首元素的地址,所以+1会跳过整个数组,而p+1只是走了4个字节,是说明p是数组首元素的地址,+1就跳过四个字节,到达第二个元素的地址,
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int(*p)[10] = &arr;
printf("%p",**p+1);
}
p里面存的是数组的地址,不是数组首元素的地址,所以想要获得数组的元素,得用两次解引用,第一解引用是获得数组首元素的地址,第二次解引用是通过数组元素的地址获得第一个元素的值。
指针数组
int* p[] //p先和[]结合,说明p是一个数组,再和int_结合。说明p数组的每个元素是int类型的指针
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int* p[10] = { arr ,arr+1,arr+2,arr+3,arr+4,arr+5};
printf("%d %d %d",*p[0], *p[1] ,*p[2]);
}
int* p[10] = { arr ,arr+1,arr+2,arr+3,arr+4,arr+5}; //p[0]存的是arr数组元素的首地址,p[1]存的是arr数组第二个元素的地址,以此类推。
p[0]是获得arr首元素的地址,*p[0]就是通过p[0]里面存的地址,找到p[0]里面的内容。
指针类型转换
int main()
{
double temp = 3.14;
double* p = &temp;
int* p = &temp;
}
按道理来讲只能同类型之间才能赋值,但是vs2022没有报错,不知道其他编译器能不能这样,正常来见得先强转才能赋值比如int* p = (int*)&temp,这样才能赋值。
所以能不强转还是别强转了,不然值可能会出错。
结构体指针
结构体指针就是指向结构体类型的指针,本来结构体就已经够复杂了,还来个指针。
结构体变量访问成员用s1.age,结构体指针变量用p->age来访问结构体成员
#include<stdio.h>
typedef struct student
{
int age;
int tel;
}student;
int main()
{
student s1;
student* p = &s1;
p->age = 18;
printf("%d", p->age);
}
结构体加减运算
#include<stdio.h>
typedef struct student
{
int age;
int tel;
}student;
int main()
{
student s1;
student* p = &s1;
p->age = 18;
printf("%p\t%p", p,p+1);
}
可以看出结构体指针p+1就是跳过一整个结构体。
野指针
野指针:访问一个已销毁或者访问受限的内存区域的指针,野指针不能判断是否为NULL来避免
垂悬指针:指针正常初始化,曾指向一个对象,该对象被销毁了,但是指针未制空,那么就成了悬空指针。
野指针的危害
野指针指向的就是随机的空间,那个空间可能是非常重要的数据,你对他进行解引用修改,这样会造成非常严重的后果。
野指针规避方法
- 对指针进行初始化,置为NULL。
- 释放指针的时候,把指针置为NULL。