【C语言】指针详解

指针是什么

指针是什么?
指针理解的2个要点

  • 指针是内存中一个最小单元的编号,也就是地址
  • 平时口语说的指针,通常指的是指针变量,是用来存放内存地址的变量

总结:指针就是地址,口语中说的指针通常是指针变量,指针里面存的是地址。
举个例子
int* p这是一个指向int类型的指针

int main()
{
	int a = 10;
	int* p = &a;
	printf("p里面存的地址是%p\n      a的地址是%p",p,&a);
}

image.png
image.png
可以发现p里面存的地址就是变量a的地址。

指针类型

那我们怎么看指针指向的是什么类型呢:

其实很简单,只要我们把声明语句里面的指针名字去掉,剩下的部分就是指针的类型

  1. int* p //整型类型的指针,指向的是int类型
  2. char* p //字符类型的指针,指向的是char类型
  3. double* p //浮点类型的指针,指向的是double类型
  4. int *p[5] //指针数组,其实这是一个数组,数组的每个元素是int类型的指针
  5. int (*p)[5] //数组指针,指向的是一个数组,数值有5个元素,每个人元素的类型是int
  6. int** p //整型类型的二级指针,指向int*类型的指针

  1. int* p //首先p先和*结合,说明p是一个指针,再和int结合,说明指向的内容是int类型,所以p是一个int类型的指针
  2. char* p //首先p先和*结合,说明p是一个指针,再和char结合,说明指向的内容是char类型,所以p是一个char类型的指针
  3. double* p // 首先p先和*结合,说明p是一个指针,再和double结合,说明指向的内容是double类型,所以p是一个double类型的指针
  4. int p[5] // **首先p先和[]结合,因为[]优先级比高,所以p现在是一个数组,然后再和*结合,说明数组里的每个元素都是指针类型,然后再和int结合,说明p数组里面的指针指向的都是int类型,所以p是一个数组,数组里面的每个元素是int类型的指针。**
  5. int (p)[5] // **首先p先和结合,然后再和[]结合,说明p指向的内容是一个数组,再和int结合,说明p指向的数组里面的每个元素是int类型。**
  6. 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位
image.png

int main()
{
	int* p;
	printf("在64位机器上的大小为:%d", sizeof(p)); //输出8
}

image.png

int main()
{
	int* p;
	printf("在32位机器上的大小为:%d", sizeof(p)); //输出4
}

image.png

指针的使用

通过上面的了解,我们知道指针是个啥,还有指针的类型,但就是不知道他咋用。
指针是通过*来使用,简称解引用,
指针里面存的是地址,赋值的时候得用&获得变量的地址。
举个例子
int *p 这是一个指向int类型的指针

int main()
{
	int a = 10;
	int* p = &a; //赋值的话得两边类型相同,&a==取出来变量的a地址,相当于int*类型,所以能够赋值
	printf("指针p通过解引用获得a地址里面的值:%d\ta的值为:%d", *p, a);
}

image.png
**指针里面存的地址就像一个门牌号,编译器就像外卖员,比如你在酒店点外卖,外卖员(编译器)通过p指针里面存的门牌号(内存地址)找到你,然后把外卖给你,解引用就像外卖员把外卖给你的那个操作,没有解引用的话就像是,外卖员(编译器)知道你的门牌号(内存地址),但没有把外卖给你的那个操作。 **

int类型的指针解引用是一次访问四个字节,那是不是所有的指针解引用都访问四个字节吗,其实不是的,解引用访问多少字节是看你指针指向的类型。比如:

  1. int* p //指向int类型的指针,他解引用是访问4个字节 。
  2. char* p //指向char类型的指针,他解引用是访问1个字节。
  3. float* p //指向float类型的指针,他解引用是访问4个字节。
  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);
	}
}

image.pngimage.png
可以看出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);
	}

image.png

指针解引用加减

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],[]也相当一次解引用
	}
}

image.png
先将(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);
}

image.png
为啥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);
}

image.png
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);
}

image.png
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]);
}

image.png
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,这样才能赋值。
image.png
所以能不强转还是别强转了,不然值可能会出错。

结构体指针

结构体指针就是指向结构体类型的指针,本来结构体就已经够复杂了,还来个指针。
结构体变量访问成员用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);
}

image.png

结构体加减运算

#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);
}

image.png
可以看出结构体指针p+1就是跳过一整个结构体。

野指针

野指针:访问一个已销毁或者访问受限的内存区域的指针,野指针不能判断是否为NULL来避免
垂悬指针:指针正常初始化,曾指向一个对象,该对象被销毁了,但是指针未制空,那么就成了悬空指针。
野指针的危害
野指针指向的就是随机的空间,那个空间可能是非常重要的数据,你对他进行解引用修改,这样会造成非常严重的后果。
野指针规避方法

  • 对指针进行初始化,置为NULL。
  • 释放指针的时候,把指针置为NULL。

image.png

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值