看到指针就头疼?这篇文章让你对指针有更全面的了解!

文章目录

  • 1.什么是指针
  • 2.指针和指针类型
    • 2.1 指针+-整数
    • 2.2 指针的解引用
  • 3.野指针
    • 3.1为什么会有野指针
    • 3.2 如何规避野指针
  • 4.指针运算
    • 4.1 指针+-整数
    • 4.2 指针减指针
    • 4.3 指针的关系运算
  • 5.指针与数组
  • 6.二级指针
  • 7.指针数组

1.什么是指针

指针的两个要点
1.指针是内存中的一个最小单元的编号,也就是地址。
2.平时口语所说的指针,通常指的是指针变量,是用来存放内存地址的变量。

总结
指针就是地址,口语所说的指针通常是指针变量

内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的。
所以为了有效的使用内存,就要把内存划分成一个个小的内存单元,每个内存单元的大小都是一个字节。
为了能够有效的访问到内存的每个单元,就要给内存单元进行编号,这些编号被称为内存单元的地址。
在写程序时,创建的变量、数组等都要在内存上开辟空间。
每个内存都有唯一的编号,这个编号也被称为地址 地址 == 编号
内存
变量是创建内存中的(在内存中分配空间的),每个内存单位都有地址,所以变量也是有地址的。
可以利用&来取出变量的地址。
指针变量

通过&(取地址符)取出变量内存的地址,把地址可以存放在一个变量当中,这个变量就是指针变量。

#include <stdio.h>
int main()
{
	int a = 0;
	int* pa = &a;//这里的pa就是指针变量
	*pa = 10;//*就是根据a的地址取找到a
	//这样我们就可以间接的改变a的值
	printf("%d\n",a);
	return 0;
}
//打印结果:10

总结:

指针变量就是用来存放地址的变量。(存放在指针中的值会被当成地址处理)。

在内存当中是如何编址的呢?
上面我们提到了一个字节对应一个地址,为什么会这样呢?
其实在计算机当中会存在地址线,32位的机器上就存在32根地址线,这些地址线会发出高电压(高电平)和低电压(低电平)就是(1或者0);
那么32根地址线就可以产生2的32次方种情况。

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001

11111111 11111111 11111111 11111111

2的32次方种情况,每种情况就对应着每个地址,就标识着一个字节。这里右2的32次方字节,大概是4G的空间。
同样的方法在64位机器,可以标识的空间就非常大了。
这里我们明白了:

  • 在32位机器上,地址是32个0或者1组成的二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4字节。
  • 因此在64位的机器上就是一个指针变量大小对应8个字节。
    总结:
  • 指针是用来存放地址的,地址就是唯一标识一块地址的。
  • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

2.指针和指针类型

前面我学习了,整型,短整型,浮点型,字符型。这些都是变量的类型,那么指针有没有类型呢?
有的

int num = 10;
p = &num

要将&num(num的地址)保存到p中,我们知道p就是一个指针变量,那么它的类型是怎么样的呢?
我们给指针变量相应的类型。

char* pc = NULL;
int* pi = NULL;
short* ps = NULL;
long* pl = NULL;
long long* pll = NULL;

我们可以发现,指针的定义方式是type + *
但是我们又知道,指针变量的大小都是是固定的不是4个字节就是8个字节。那么为什么要搞出指针的类型呢?有什么意义吗?
意义就在于给*发出信息

指针类型可以决定指针解引用的时候访问多少字节
指针类型决定了指针解引用操作的权限
指针的类型决定了指针向前或者向后走一步有多大距离

2.1 指针±整数

#include <stdio.h>
int main()
{
	int a = 0;
	char* pc = (char*)&a;
	int* pi = &a;
	printf("%p\n",&a);
	printf("%p\n",pc);
	printf("%p\n",pc+1);
	printf("%p\n",pi);
	printf("%p\n",pi+1);
	return 0;
}
//打印结果
/*
006FFE20
006FFE20
006FFE21
006FFE20
006FFE24
*/

可以发现用char* 作为指针类型的+1只能向后移动一个字节,而用int*作为指针类型的+1却可以向后移动4个字节。
也就是说:
指针的类型决定了指针向前或者向后走一步有多大距离

2.2 指针的解引用

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	int* pi = &n;
	*pc = 0;
	*pi = 0;
	return 0;
}

下面我们观察在调试过程当中内存的变化。
编译编译
编译

从这三张图我们可以了解到:
指针的类型决定了,对这种解引用有多大的权限(能操作几个字节)
比如 char*的指针解引用就只能访问一个字节,而int*的指针就能访问4个字节。

3.野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3.1为什么会有野指针

1.指针未初始化

#include <stdio.h>
int main()
{
	int* p;//局部变量指针未初始化,默认为随机值
	*p = 100;
	return 0;
}

2.指针越界访问

#include <stdio.h>
int main()
{
	int arr[10] = {0};
	int* p = arr;
	for(int i = 0;i<=10;++i)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	return 0;
}

3.指针指向的局部变量释放

#include <stdio.h>
int* test()
{
	int a = 0;
	return &a;
}
int main()
{
	int* pa = test();
	printf("%p\n",pa);
	return 0;
}

3.2 如何规避野指针

1.指针初始化
2.小心指针越界
3.指针指向空间释放即置为NULL
4.避免返回局部变量的地址
5.指针使用前检查其有效性

#include <stdio.h>
int main()
{
	int* p = NULL;
	//明确知道指针应该初始化为谁的地址,就直接初始化
	//不知道指针初始化为什么值,就暂时初始化为NULL;
	//...
	int a = 10;
	p = &a;
	if(p!=NULL)
	{
		*p = 100;
	}
	return 0;
}

4.指针运算

  • 指针±整数
  • 指针-指针
  • 指针的关系运算

4.1 指针±整数

#include <stdio.h>
int main()
{
	int arr[5] = {0};
	for(int* p = arr;p<=&arr[4];)
	{
		*p++ = 0;
	}
	return 0;
}

4.2 指针减指针

指针-指针返回绝对值是它们间的元素个数,

#include <stdio.h>
int main()
{
	int arr[5] = {0};
	int* pa = &arr[0];
	int* pb = &arr[4]printf("%d\n",pb-pa);
	return 0;
}
//打印结果
//4

4.3 指针的关系运算

for(vp = &values[N_VALUES];vp>&values[0];)
{
	*--v = 0;
}

//代码简化
for(vp = &values[N_VALUES-1];vp>=&values[0];vp--)
{
	*v = 0;
}

实际上大部分的编译器上都是可以完成上面的代码通过的,然而我们还要要避免这样写,因为标准不保证它可行。

规定:

允许指向数组元素的指针与指向数组最后元素的后面的那个内存的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

5.指针与数组

指针变量就是指针变量,不是数组。指针变量的大小是4/8字节,专门是用来存放地址的
数组就是数组,不是指针,数组是一块连续的空间,可以存放一个或多个类型相同的数据
数组中,数组名就是数组首元素的地址,数组名 == 地址 == 指针
当我们知道数组首元素的地址的时候,因为数组又是连续存放的,所以通过指针就可以遍历访问数组,数组是可以通过指针来访问的。

#include <stdio.h>
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	printf("%p\n",arr);
	printf("%p\n",&arr[0]);
	return 0;
}
//打印结果:
/*
012FFEB0
012FFEB0
*/

可见数组名和首元素的地址是一样的。
数组名表示的就是数组首元素的地址。(两种情况)

1.sizeof(数组名),计算的是整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
2.&数组名,取出的整个数组的地址。&数组名,数组名表示整个数组,但是整个数组会以首元素的的地址显示。

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问以数组就成为可能。

#include <stdio.h>
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int* p  = arr;
	for(int i = 0;i<10;++i)
	{
		printf("&arr[%d] = %p == p+%d = %p\n",i,&arr[i],i,p+i);
	}
	return 0;
}
//打印结果:
/*
&arr[0] = 00B3F708 == p+0 = 00B3F708
&arr[1] = 00B3F70C == p+1 = 00B3F70C
&arr[2] = 00B3F710 == p+2 = 00B3F710
&arr[3] = 00B3F714 == p+3 = 00B3F714
&arr[4] = 00B3F718 == p+4 = 00B3F718
&arr[5] = 00B3F71C == p+5 = 00B3F71C
&arr[6] = 00B3F720 == p+6 = 00B3F720
&arr[7] = 00B3F724 == p+7 = 00B3F724
&arr[8] = 00B3F728 == p+8 = 00B3F728
&arr[9] = 00B3F72C == p+9 = 00B3F72C
*/

所以p+i就是计算的数组arr下标为i的地址。
那我们就可以直接通过指针来访问数组。

#include <stdio.h>
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int* p  = arr;
	for(int i = 0;i<10;++i)
	{
		printf("%d ",*(p+i));
	}
	return 0;
}
//打印结果:1 2 3 4 5 6 7 8 9 10

也就是说arr[i] = *(p+i),这样的话,对于计算机来说,肯定是按*(p+i)来处理的,就是把arr[i]转换成*(p+i)。然后我们知道*(p+i)和*(i+p)是没有区别的。所以我们是可以写i[arr]来打印数组的。

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ",i[arr]);
	}
	return 0;
}
//打印结果:1 2 3 4 5 6 7 8 9 10

注意:不建议这样写,会有点装了。

6.二级指针

指针变量也是指针,是变量就有地址,那指针变量的地址存放在哪里呢?
二级指针

a的地址存放在pa中,pa的地址存放在ppa中,pa是一级指针。而ppa是二级指针。
对于二级指针的运算有:

  • *ppa通过对ppa中的地址进行解引用,这样找到的是pa,*ppa其实访问的是pa
int a = 10;
*ppa = &a;//等价于pa = &b
  • ppa先通过*ppa找到pa进行解引用操作:*pa,那找到的就是a

7.指针数组

指针数组就是存放指针的数组
比如整型数组是存放整型的数组,字符数组是存放字符的数组。
整型数组

那么指针数组就是:

int* arr2[5];

arr2是一个数组,有5个元素,每一个元素是一个整型指针;
整型指针数组

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/789478.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Apache中使用CGI

Apache24 使用Visual Studio 2022 // CGI2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <stdio.h> #include <stdlib.h>#include <stdio.h>void main() {//设置HTML语言printf("Content-type:text/html\n\n&q…

Ansys Zemax|什么是点扩散函数( PSF )

附件下载 联系工作人员获取附件 概览 这篇文章讲述了&#xff1a; 什么是点扩散函数&#xff1f; 点列图 快速傅里叶变换计算的点扩散函数&#xff08;FFT PSF&#xff09; 惠更斯算法计算的点扩散函数&#xff08;Huygens PSF&#xff09; 如何使用非序列模式下的透镜和…

地下水环评(一级)实践技术及Modflow地下水数值模拟

主要围绕的环评导则&#xff0c;结合不同行业类别&#xff0c;实例讲解地下水环境影响评价的原则、内容、工作程序、方法。包括数据处理分析、数值模型构建以及环评报告编写等。涉及地下水流场绘制软件&#xff08;Surfer&#xff09;的操作流程及数据处理、地下水数值模拟软件…

JVM:类的生命周期

文章目录 一、介绍二、加载阶段三、连接阶段1、验证阶段2、准备阶段3、解析阶段 四、初始化阶段 一、介绍 类的生命周期描述了一个类加载、连接&#xff08;验证、准备和解析&#xff09;、初始化、使用、卸载的整个过程。 二、加载阶段 加载&#xff08;Loading&#xff09…

【论文速读】| JADE:用于大语言模型的基于语言学的安全评估平台

本次分享论文&#xff1a;JADE : A Linguistics-based Safety Evaluation Platform for Large Language Models 基本信息 原文作者&#xff1a;Mi Zhang, Xudong Pan, Min Yang 作者单位&#xff1a;Whitzard-AI, System Software and Security Lab Fudan University 关键…

JavaWeb__正则表达式

目录 1. 正则表达式简介2. 正则表达式体验2.1 验证2.2 匹配2.3 替换2.4 全文查找2.5 忽略大小写2.6 元字符使用2.7 字符集合的使用2.8 常用正则表达式 1. 正则表达式简介 正则表达式是描述字符模式的对象。正则表达式用于对字符串模式匹配及检索替换&#xff0c;是对字符串执行…

用SurfaceView实现落花动画效果

上篇文章 Android子线程真的不能刷新UI吗&#xff1f;(一&#xff09;复现异常 中可以看出子线程更新main线程创建的View&#xff0c;会抛出异常。SurfaceView不依赖main线程&#xff0c;可以直接使用自己的线程控制绘制逻辑。具体代码怎么实现了&#xff1f; 这篇文章用Surfa…

vue 中 使用腾讯地图 (动态引用腾讯地图及使用签名验证)

在设置定位的时候使用 腾讯地图 选择地址 在 mounted中引入腾讯地图&#xff1a; this.website.mapKey 为地图的 key // 异步加载腾讯地图APIconst script document.createElement(script);script.type text/javascript;script.src https://map.qq.com/api/js?v2.exp&…

C++11中重要的新特性之 lambda表达式 Part two

序言 在上一篇文章中&#xff0c;我们主要介绍了 C11 中的新增的关键词&#xff0c;以及 范围for循环 这类语法糖的使用和背后的逻辑。在这篇文章中我们会继续介绍一个特别重要的新特性分别是 lambda表达式 。 1. lambda表达式 1.1 lambda的定义 C11 中的 lambda表达式 是一种…

APB总线协议

一、APB总线介绍 关于总线的一些概念&#xff1a; 总线&#xff1a;计算机内部和计算机之间传输数据的共用通道。 总线位宽&#xff1a;总线能够一次性传送的二进制数据位数&#xff0c;例如8bit、16bit、32bit、64bit等。 总线工作频率&#xff1a;即时钟频率&#xff08;时…

PHP实现用户认证与权限管理的全面指南

目录 引言 1. 数据库设计 1.1 用户表&#xff08;users&#xff09; 1.2 角色表&#xff08;roles&#xff09; 1.3 权限表&#xff08;permissions&#xff09; 1.4 用户角色关联表&#xff08;user_roles&#xff09; 1.5 角色权限关联表&#xff08;role_permissions…

【内网渗透】内网渗透学习之域渗透常规方法

域渗透常规方法和思路 1、域内信息收集1.1、获取当前用户信息1.1.1、获取当前用户与域 SID1.1.2、查询指定用户的详细信息 1.2、判断是否存在域1.2、查询域内所有计算机1.3、查询域内所有用户组列表1.4、查询所有域成员计算机列表1.5、获取域密码信息1.6、获取域信任信息1.7、查…

最短路径算法:Dijkstra算法探险记

想象一下,你是一只小蚂蚁,名字叫小明。你住在一个大大的花园里,这个花园有很多小路,小路之间还有交叉点,就像是一个迷宫一样。现在,你接到了一个任务:找到从你家到花园里一个特定地方(比如一块超级大的糖果)的最短路径! 第一步:画出地图 首先,我们需要一张地图来…

YOLOv8改进 | 注意力机制 | 增强模型在图像分类和目标检测BAM注意力【小白必备 + 附完整代码】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv8改进有效…

【模块化与包管理】:解锁【Python】编程的高效之道

目录 1.什么是模块&#xff1f; 2. 模块的导入过程 3. 理解命名空间 4. import语句的多种形式 5. 模块的执行与重新导入 6. 包&#xff08;Package&#xff09; 7. sys模块和os模块 sys模块 常用属性 示例&#xff1a;使用sys模块 os模块 常用功能 示例&#xff1…

前端埋点数据收集和数据上报

原文地址 什么是埋点 学名叫时间追踪(Event Tracking), 主要针对用户行为或者业务过程进行捕获&#xff0c;处理和发送相关技术及实施过程. 埋点是数据领域的一个专业术语&#xff0c;也是互联网领域的俗称&#xff0c;是互联网领域的俗称 埋点是产品数据分析的基础&#xf…

【AIGC】一、本地docker启动私有大模型

本地docker启动私有大模型 一、最终效果中英文对话生成代码 二、资源配置三、搭建步骤启动docker容器登录页面首次登录请注册登录后的效果 配置模型尝试使用选择模型选项下载模型选择适合的模型开始下载 试用效果返回首页选择模型中英文对话生成代码 四、附录资源监控 五、参考…

动手学深度学习54 循环神经网络

动手学深度学习54 循环神经网络 1. 循环神经网络RNN2. QA 1. 循环神经网络RNN h t h_t ht​ 与 h t − 1 h_{t-1} ht−1​ x t − 1 x_{t-1} xt−1​有关 x t x_t xt​ 与 h t h_t ht​ x t − 1 x_{t-1} xt−1​ 有关 怎么把潜变量变成RNN–假设更简单 潜变量和隐变量的区…

Java面试八股之什么是布隆过滤器

什么是布隆过滤器 布隆过滤器&#xff08;Bloom Filter&#xff09;是一种空间效率极高的概率型数据结构&#xff0c;用于判断一个元素是否可能存在于一个集合中。布隆过滤器可以给出“可能存在”或“一定不存在”的答案&#xff0c;但不能保证“一定存在”。其主要特点是&…

WTM的项目中EFCore如何适配人大金仓数据库

一、WTM是什么 WalkingTec.Mvvm框架&#xff08;简称WTM&#xff09;最早开发与2013年&#xff0c;基于Asp.net MVC3 和 最早的Entity Framework, 当初主要是为了解决公司内部开发效率低&#xff0c;代码风格不统一的问题。2017年9月&#xff0c;将代码移植到了.Net Core上&…