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

文章目录

  • 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.mzph.cn/web/43048.shtml

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

相关文章

MVC 返回集合方法,以及分页

返回一个数据集方法 返回多个数据集方法 》》定义一个Model public class IndexMoel {public List<UserGroup> UserGroup{get;set;}public List<User> User{get;set;}}》》》控制器 //db 是 EF 中的上下文 var listnew IndexModel(); list.UserGroupdb.UserGro…

微信小程序中wx.navigateBack()页面栈返回上一页时执行上一页的方法或修改上一页的data属性值

let pages getCurrentPages();let prevPage pages[pages.length - 2]; // 获取上一个页面实例对象console.log(prevPage) //打印信息// 在 wx.navigateBack 的 success 回调中执行需要的方法wx.navigateBack({delta: 1, // 返回上一页success: function() {//修改上一页的属性…

秒懂设计模式--学习笔记(8)【结构型-组合模式】

目录 7、组合模式7.1 组合模式&#xff08;Composite&#xff09;7.2 叉树结构7.3 文件系统7.4 目录树展示7.5 自相似性的涌现7.6 组合模式的各角色定义7.7 组合 7、组合模式 7.1 组合模式&#xff08;Composite&#xff09; 是针对由多个节点对象&#xff08;部分&#xff0…

关于string的‘\0‘与string,vector构造特点,反迭代器与迭代器类等的讨论

目录 问题一&#xff1a;关于string的\0问题讨论 问题二&#xff1a;C标准库中的string内存是分配在堆上面吗&#xff1f; 问题三&#xff1a;string与vector的capacity大小设计的特点 问题四&#xff1a;string的流提取问题 问题五&#xff1a;迭代器失效 问题六&#xf…

个人开发实现AI套壳网站快速搭建(Vue+elementUI+SpringBoot)

目录 一、效果展示 二、项目概述 三、手把手快速搭建实现本项目 3.1 前端实现 3.2 后端方向 五、后续开发计划 一、效果展示 默认展示 一般对话展示&#xff1a; 代码对话展示&#xff1a; 二、项目概述 本项目是一个基于Web的智能对话服务平台&#xff0c;通过后端与第…

【C语言】指针(4):深入理解指针

目录 ​编辑 一、回调函数 二、qsort使用举例 2.1 使用qsort排序整型数据 2.2 使用qsort排序结构体数据 三、qsort的模拟实现 四、NULL、\0、0、0、null、NUL的区别 五、C99中的变长数组 一、回调函数 函数指针是将函数的地址取出来&#xff0c;再通过函数地址去调用&a…

untiy 在菜单栏添加自定义按钮 点击按钮弹出一个Unity窗口,并在窗口里添加属性

using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Rendering.PostProcessing;public class AutoGenerateWindow : EditorWindow //这是定义一个窗口 {public string subjecttName "科目名字";//科目的名字public GameOb…

url链接地址,#前的参数 和 #后的参数有什么区别

例如 http://localhost:8080/?beforeParams1#/workSchemelist/index?afterParams1 beforeParams 和 afterParams 区别 打印出来可以发现&#xff1a; beforeParams 是 url 的search参数&#xff0c;通过window.location.search获取 afterParams 是 route 的query参数&#…

外贸网站设计的要点

外贸网站设计是一种专门针对国际贸易领域的网站设计&#xff0c;需要考虑到不同国家和文化背景的用户&#xff0c;因此设计过程要更加细致和精准。以下是外贸网站设计的关键要点&#xff1a; 首先&#xff0c;多语言支持是不可或缺的&#xff0c;因为外贸网站的用户可能来自不同…

[Python自动化办公]--从网页登录网易邮箱进行邮件搜索并下载邮件附件

[Python自动化办公]–从网页登录网易邮箱进行邮件搜索并下载邮件附件 使用说明 ​ 本文使用Python的selenium库进行操作邮箱登录、固定名称搜索邮件并下载附件&#xff0c;Python版本&#xff1a;3.9.16, selenium版本&#xff1a;4.19.0&#xff0c;EdgeBrowser版本:126.0.2…

LVS集群及其它的NAT模式

1.lvs集群作用&#xff1a;是linux的内核层面实现负载均衡的软件&#xff1b;将多个后端服务器组成一个高可用、高性能的服务器的集群&#xff0c;通过负载均衡的算法将客户端的请求分发到后端的服务器上&#xff0c;通过这种方式实现高可用和负载均衡。 2.集群和分布式&#…

用户增长 - 私域 - 社群运营自检清单SOP(社群运营30问)

Check List: 1.你的目标用户是谁&#xff1f; 2.你的目标用户有哪些需要立马解决的需求&#xff1f;有哪些长期需求&#xff1f;这些需求的优先级是什么&#xff1f; 3.做社群的目的是什么&#xff1f; 4.你的用户和业务是否适合做社群&#xff1f; 5.你做哪类社群才能更好的帮…

确定适合您需求的负载组

大多数关键任务行业都使用 UPS 和发电机等备用电源在停电期间为其设施提供持续电力。负载组允许您在需要时测试电源&#xff0c;以确保在您最需要的时候提供可靠的电力。 选择正确的负载组对于准确的电源测试至关重要。为了帮助您找到最适合您设施需求的负载组&#xff0c;EAK…

【机器学习】主成分分析(PCA):数据降维的艺术

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 主成分分析&#xff08;PCA&#xff09;&#xff1a;数据降维的艺术引言PCA的基…

技术成神之路:设计模式(四)工厂方法模式

1.定义 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的接口&#xff0c;而不是通过具体类来实例化对象。工厂方法模式的主要作用是让子类决定实例化哪一个类&#xff0c;从而实现对象创建的延迟到具体子类…

2024年6月国产数据库大事记-墨天轮

本文为墨天轮社区整理的2024年6月国产数据库大事件和重要产品发布消息。 目录 2024年6月国产数据库大事记 TOP102024年6月国产数据库大事记&#xff08;时间线&#xff09;产品/版本发布兼容认证代表厂商大事记厂商活动相关资料 2024年6月国产数据库大事记 TOP10 2024年6月国…

【Python】已解决:SyntaxError invalid syntax

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;SyntaxError invalid syntax 一、分析问题背景 在Python编程中&#xff0c;SyntaxError: invalid syntax是一个常见的错误&#xff0c;它通常表示代码中存在语法…

案例|水上水下一体化测量,为九寨沟精准把脉

​ 九寨沟&#xff0c;被誉为“人间仙境”&#xff0c;其湖群以独特的地理位置和优美的自然景观吸引着世界各地的游客&#xff0c;更是九寨沟生态系统中不可或缺的重要组成部分。因此&#xff0c;精准地掌握湖群的地形数据、水体分布及变化情况&#xff0c;能够揭示水下生态系…

【数据结构与算法基础】算法复杂度

欢迎光顾我的homepage 前言 算法就是定义良好的计算过程&#xff0c;它取一个活一组的值输入&#xff0c;并产生出一个或一组值作为输出。简单来说&#xff0c;算法就是一系列的计算步骤&#xff0c;用来将输入数据转化成输出结果。 一、算法效率 如何去衡量一个算法的好坏&am…

[C++]——同步异步日志系统(3)

同步异步日志系统 一、日志系统框架设计1.1模块划分1.1.1 日志等级模块1.1.2 日志消息模块1.1.3 日志消息格式化模块1.1.4 日志落地模块&#xff08;日志落地的方向是工厂模式&#xff09;1.1.5 日志器模块&#xff08;日志器的生成是建造者模式&#xff09;1.1.6 异步线程模块…