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

文章目录

  • 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个元素,每一个元素是一个整型指针;
整型指针数组

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(HAL)stm32f407+freertos通过usb驱动移远4G模块-EC600U

概述 本篇文章主要介绍: 如何使用STM32CubeMX创建stm32F407+freertos+usb host的基础工程。USB-HOST-CDC驱动运行过程。如何根据4G模块的具体信息修改usb相关代码。MCU如何通过usb与4G模块通信,收发数据。调试过程中遇到的问题以及解决办法。 整个过程中在网上搜罗了很多参考…

suricata7 rule加载(三)加载options

suricata7.0.5 加载options (msg:“HTTP Request Example”; flow:established,to_server; http.method; content:“POST”; http.uri; content:“query.php”; bsize:>9; http.protocol; content:“HTTP/1.1”; bsize:8; http.host; content:“360”; bsize:>3; class…

LabVIEW自动测控与故障识别系统

使用LabVIEW 2019在Win10 64位系统上开发自动测控软件&#xff0c;通过与基恩士NR-X100数据采集仪通讯&#xff0c;实时采集和分析数据&#xff0c;自动识别判断产品是否合格&#xff0c;并增加数据记录和仿真功能。 具体解决方案&#xff1a; 1. 系统架构设计 硬件接口&#…

IP 地址:优化网络游戏

IP地址和网络游戏 在现代网络游戏中&#xff0c;IP地址不仅用于服务器分配&#xff0c;还能针对性进行玩家匹配与优化网络延迟。本文将探讨IP地址在网络游戏中的具体应用。 *服务器分配* 全球服务器分布&#xff1a; 网络游戏需要在全球范围内提供快速、稳定的连接&#xff…

网络安全合规建设

网络安全合规建设 一、法律安全需求基本合规&#xff08;1&#xff09;《网络安全法》重要节点等级保护政策核心变化 二、安全需求 业务刚需&#xff08;1&#xff09;内忧&#xff08;2&#xff09;外患 三、解决方法&#xff08;1&#xff09;总安全战略目标图&#xff08;2&…

springboot文献检索系统-计算机毕业设计源码48521

摘要 文献检索系统主要功能模块包括用户管理、公告信息、新闻资讯、文献信息等&#xff0c;采取面对对象的开发模式进行软件的开发和硬体的架设&#xff0c;能很好的满足实际使用的需求&#xff0c;完善了对应的软体架设以及程序编码的工作&#xff0c;采取MySQL作为后台数据的…

!vue3中defineEmits接收父组件向子组件传递方法,以及方法所需传的参数及类型定义,避免踩坑!

使用说明 1、在子组件中调用defineEmits并定义要发射给父组件的方法 const emits defineEmits([‘foldchange’]) 2、使用defineEmits会返回一个方法&#xff0c;使用一个变量emits(变量名随意)去接收 3、在子组件要触发的方法中&#xff0c;调用emits并传入发射给父组件的方法…

封装了一个仿照抖音效果的iOS评论弹窗

需求背景 开发一个类似抖音评论弹窗交互效果的弹窗&#xff0c;支持滑动消失&#xff0c; 滑动查看评论 效果如下图 思路 创建一个视图&#xff0c;该视图上面放置一个tableView, 该视图上添加一个滑动手势&#xff0c;同时设置代理&#xff0c;实现代理方法 (BOOL)gestur…

挑战杯 opencv 图像识别 指纹识别 - python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器视觉的指纹识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c;适…

基于swagger插件的方式推送接口文档至torna

目录 一、前言二、登录torna三、创建/选择空间四、创建/选择项目五、创建/选择应用六、获取应用的token七、服务推送7.1 引入maven依赖7.2 test下面按照如下方式新建文件 一、前言 Torna作为一款企业级文档管理系统&#xff0c;支持了很多种接口文档的推送方式。官方比较推荐的…

蓝牙人员定位精准吗?是否会对人体有伤害?

不知道大家现在使用的蓝牙人员定位系统都是什么样的呢&#xff1f;其实就出行而言&#xff0c;使用GPS定位也就是足够了的&#xff0c;而且目前的定位相对也比较精准了&#xff0c;效果还是很不错的。但是如果说是室内定位&#xff0c;很显然常规的定位系统是无法满足使用需求的…

[数据结构] 基于插入的排序 插入排序希尔排序

标题&#xff1a;[数据结构] 排序#插入排序&希尔排序 水墨不写bug 目录 &#xff08;一&#xff09;插入排序 实现思路&#xff1a; 插入排序实现&#xff1a; &#xff08;二&#xff09;希尔排序 希尔排序的基本思想&#xff1a; 希尔排序的实现&#xff1a; 正…

PHP同城多商户多行业系统小程序源码

同城新生态&#xff0c;解锁多商户多行业系统的无限魅力&#x1f306;&#x1f680; &#x1f308; 开篇&#xff1a;同城新纪元&#xff0c;多商户多行业系统引领潮流&#xff01; 想象一下&#xff0c;在同一个城市内&#xff0c;无论是美食探索、购物狂欢&#xff0c;还是…

Python在量化交易中的应用

量化交易近年来越来越受到投资者的青睐。Python因其简洁的语法和丰富的库&#xff0c;成为量化交易的首选编程语言。本文将从Python量化交易的基础知识、主要技术及其在实际交易中的应用三个方面进行介绍。 一、Python量化交易的基础知识 1. 量化交易的概念 量化交易是指利用…

东方通Tongweb发布vue前端

一、前端包中添加文件 1、解压vue打包文件 以dist.zip为例&#xff0c;解压之后得到dist文件夹&#xff0c;进入dist文件夹&#xff0c;新建WEB-INF文件夹&#xff0c;进入WEB-INF文件夹&#xff0c;新建web.xml文件&#xff0c; 打开web.xml文件&#xff0c;输入以下内容 …

sdwan是硬件还是网络协议?

SD-WAN&#xff08;Software-Defined Wide Area Network&#xff0c;软件定义广域网&#xff09;并不是一个硬件产品或单一的网络协议&#xff0c;而是结合了软件、硬件和网络技术的一种解决方案。SD-WAN的核心在于其软件定义的特性&#xff0c;它通过软件来控制和管理广域网的…

【BUG】RestTemplate发送Post请求后,响应中编码为gzip而导致的报错

BUG描述 20240613-09:59:59.062|INFO|null|810184|xxx|xxx||8|http-nio-xxx-exec-1|com.xxx.jim.xxx.XXXController.?.?|MSG接收到来自xxx的文件请求 headers:[host:"xxx", accept:"text/html,application/json,application/xhtmlxml,application/xml;q0.9,*…

Apache Seata分布式事务原理解析探秘

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 前言 fescar发布已有时日&#xff0c;分布式事务一直是业界备受关注的领域&#xff0c;fesca…

如何探索高效知识管理:FlowUs知识库体验很好

在当今信息爆炸的时代&#xff0c;有效的知识管理对于个人和团队的发展至关重要。FlowUs 知识库作为一款创新的知识管理工具&#xff0c;正逐渐成为众多用户的首选&#xff0c;为他们带来了高效、便捷和有条理的知识管理体验。 FlowUs 知识库的一大特色在于其简洁直观的界面设计…

雷池WAF动态防护功能初体验

一、 介绍 大名鼎鼎的雷池WAF最近新上了个名为 动态防护 的功能 所谓动态防护&#xff0c;是在用户浏览到的网页内容不变的情况下&#xff0c;将网页赋予动态特性&#xff0c;即使是静态页面&#xff0c;也会具有动态的随机性。 说白了就是给你网站的 html 和 js 代码加上加密…