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

文章目录

  • 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://xiahunao.cn/news/3226590.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

智能雷达AI小程序源码系统 销售名片+企业商城+公司动态 带完整的安装代码包以及搭建教程

系统概述 智能雷达AI小程序源码系统是基于先进的AI技术和小程序框架开发的全能型企业级应用。它不仅整合了个人销售名片的便捷分享&#xff0c;还融入了功能丰富的企业商城和实时更新的公司动态展示&#xff0c;实现了从品牌形象塑造到产品销售&#xff0c;再到客户关系维护的…

TransIT-VirusGEN® Transfection Reagent

Mirus转染试剂TransIT-VirusGEN Transfection Reagent&#xff0c;该产品旨在增强载体转染到 贴壁或悬浮的HEK 293细胞的转染效率&#xff0c;并增加重组腺相关病毒或慢病毒的产量。 使用TransIT-VirusGEN转染试剂转染悬浮或贴壁HEK293细胞可获得最高的转染效率。使用不同的转…

【Flask从入门到精通:第一课:flask的基本介绍、flask快速搭建项目并运行】

从0开始入手到上手一个新的框架&#xff0c;应该怎么展开&#xff1f;flask这种轻量级的框架与django这种的重量级框架的区别&#xff1f;针对web开发过程中&#xff0c;常见的数据库ORM的操作。跟着学习flask的过程中&#xff0c;自己去学习和了解一个新的框架&#xff08;San…

常见的过压保护芯片、过压保护的基本参数和选型

过压保护也叫过电压保护&#xff0c;是当电压超过预定的最大值时&#xff0c;使电源断开或使受控设备电压降低的一种保护方式。 过压保护芯片是为了防止输入电压的时候浪涌和波纹过大&#xff0c;导致烧坏后面的元器件芯片。因此过压保护芯片是很有必要的芯片。 常见的过压保护…

经验分享:征信查询多了会不会影响大数据综合评分?

很多人在申请贷款的时候&#xff0c;会有一个疑问&#xff0c;就是自己的征信没逾期&#xff0c;就是查询偏多一点&#xff0c;但能达到申贷要求&#xff0c;为什么还会被拒贷?其实就是大数据花了的原因&#xff0c;那征信查询多了会不会影响大数据综合评分呢?接下来本文就为…

AI自动生成PPT哪个软件好?揭秘5款自动生成PPT的工具

在职场的竞技场上&#xff0c;演示文稿如同战士的利剑&#xff0c;其锋芒直接影响着演讲者的说服力。 然而&#xff0c;制作一份高质量的PPT往往需要耗费大量时间与精力。随着科技的进步&#xff0c;AI自动生成PPT成为了提升效率的新选择。面对市场上琳琅满目的软件&#xff0…

如何给ubuntu虚拟机扩容

虚拟机设置 鼠标点击硬盘&#xff0c;弹出对话框后&#xff0c;点击扩展&#xff0c;输入扩展后的硬盘大小&#xff0c;我这里扩展到100G 安装工具 sudo apt-get install gparted 重新分区

今天,纷享AI正式发布,开启智能CRM新纪元

纷享销客作为国产CRM中连续四年保持近40%增长的领先品牌&#xff0c;一直在探索AICRM领域的数字化变革。 7月10日&#xff0c;纷享AI产品正式上线。与通用大模型不同&#xff0c;纷享AI是在合规之下&#xff0c;开放性的接入各种大模型平台&#xff0c;并结合纷享销客在营销服…

如何学习一门新技术,十年 MarkDown 程序员怎么做

案例源码仓库地址&#xff1a; https://github.com/Rodert/go-demo官方文档&#xff1a; https://etcd.io/视频教程&#xff1a; https://space.bilibili.com/404747369 文章目录 介绍使用场景 安装&搭建搭建 ETCD与 ETCD 交互集群 GoETCD 编码 介绍 谈使用场景之前&#…

【IEEE官方列表会议,EI, Scopus稳定检索】第三届半导体与电子技术国际研讨会(ISSET 2024,2024年8月23-25)

2024年第三届半导体与电子技术国际研讨会&#xff08;ISSET 2024&#xff09;将于2024年8月23-25日在中国西安举行。 ISSET 2024将围绕“半导体”与“电子技术”等相关最新研究领域&#xff0c;为来自国内外高等院校、科学研究所、企事业单位的专家、教授、学者、工程师等提供一…

AGE Cypher 查询格式

使用 ag_catalog 中的名为 cypher 的函数构建 Cypher 查询&#xff0c;该函数返回 Postgres 的记录集合。 Cypher() Cypher() 函数执行作为参数传递的 Cypher 查询。 语法&#xff1a;cypher(graph_name, query_string, parameters) 返回&#xff1a; A SETOF records 参…

deep learning 环境配置

1 NVIDIA驱动安装 ref link: https://blog.csdn.net/weixin_37926734/article/details/123033286 2 cuda安装 ref link: https://blog.csdn.net/qq_63379469/article/details/123319269 进去网站 https://developer.nvidia.com/cuda-toolkit-archive 选择想要安装的cuda版…

【常见开源库的二次开发】基于openssl的加密与解密——openssl认识与配置(一)

一、什么是openssl&#xff1f; OpenSSL 是一个开源的软件库&#xff0c;它提供了一系列加密工具和协议&#xff0c;主要用于实现安全通信&#xff0c;如在网络上的数据传输。它支持多种加密算法&#xff0c;包括对称加密、非对称加密、散列函数、伪随机数生成器、数字签名、密…

rk3588s 定制版 tc358775 调试 lvds 屏幕 (第一部分)

硬件: 3588s 没有 lvds 接口 , 所以使用的 东芝的 tc358774 (mipi ---> lvds芯片), 这个芯片是参考 3399 的 官方设计得来的,3399 的官方demo 板上应该是 使用到了 这颗芯片 参考资料: 1 网上的 GM8775C 转换芯片。 2 瑞芯微的 3588s 的资料 总体的逻辑: 1 3588s…

吹田电气绿色能源 未来可期

在2024年7月的上海慕尼黑电子展上&#xff0c;吹田电气功率分析仪成为了备受瞩目的明星产品。作为电子测试与测量领域的重要工具&#xff0c;功率分析仪在展会上展示了其在绿色能源和高效能量管理方面的最新应用&#xff0c;引发了广泛关注和热议。 领先技术&#xff0c;精准测…

linux磁盘分区管理

首先关机状态下&#xff0c;先配置硬盘 硬盘分区管理 识别硬盘 》分区规划 》 格式化 》 挂载使用 [rootlocalhost ~]# lsblk 查看硬盘 分区划分&#xff08;m帮助, p 查看分区, n 创建分区, d 删除分区, q 退出, w 保存&#xff0c; g gpt分区&#xff09; [roo…

MVC分页

public ActionResult Index(int ? page){IPagedList<EF.ACCOUNT> userPagedList;using (EF.eMISENT content new EF.eMISENT()){第几页int pageNumber page ?? 1;每页数据条数&#xff0c;这个可以放在配置文件中int pageSize 10;//var infoslist.C660List.OrderBy(…

nginx安装、视频频服务器-windows

编译安装nginx 1、安装perl 安装地址: https://strawberryperl.com&#xff0c;选择msi安装程序即可 2、安装sed for windows 下载地址&#xff1a;https://sourceforge.net/projects/gnuwin32/files/sed/&#xff0c;执行安装程序结束后&#xff0c;将安装包bin目录配置到…

hutool ScriptUtil Script for [js] not support !

导入一个旧项目&#xff0c;发现项目中ScriptUtil 报错 检查后发现jdk没有配置—_—!!!&#xff0c;用的是idea默认的jdk&#xff0c;换回jdk1.8就可以了 查阅资料后Java 8中引入的Nashorn JavaScript引擎在Java 9中被移除了。Nashorn是一个基于JVM的轻量级JavaScript引擎&…

KNIME 5.2.5 版本界面切换

1、安装完KNIME后&#xff0c;点击“Create workflow in your local space.” 2、发现是这个样子 4、进行切换。点击“menu”&#xff0c;最后点击“Switch to classic user interfaceto” 5、最终显示结果&#xff1a;