C语言-走进指针世界

引入

在C语言的学习过程中,指针是躲不掉的一大困难,开始的时候,可能你会觉得初始化整形指针和解引用不过如此,但是当类型逐渐复杂起来以后,没有对指针和类型的深入理解,想要看懂和很好的运用指针就比较困难了,希望通过我的这篇文章,让大家对指针能有更好更深入的认识。

指针

指针是什么?

在计算机中,必不可少的不只是CPU的运算,还需要有内存对数据的存储,然而在存储数据的过程中,必然会面对这样的问题:那么多的内存单元,数据到底存在哪?CPU运算时又要从哪读取数据呢?这时候就要想了:如果你有一个朋友住在了宾馆,让你去找他,你会怎么找呢?肯定不是一个一个房间敲开找,要问道朋友的房间号,才能一次性找到他的位置。那么我们是否可以给每个内存都给一个编号呢?答案是,可以。这便有了指针。

1.内存被划分成一个个的单元,一个内存单元大小是一个字节

2.每个内存单元给一个编号,这个编号就是地址,C语言中简称:指针

大概就是以下的换算

内存单元的编号==地址==指针

指针变量地址

#include <stdio.h>
int main()
{int a = 10;int *pa = &a;//*说明pa是一个指针变量(指向int类型),其中存放的是指针,也就是a的地址//&a表示的是取出a变量的地址,然后通过语句赋给pa*pa = 20;//此处的*为解引用,相当于通过pa存的地址找到a,可以理解为*pa==aprintf("%d %d\n",*pa,a);//此时打印的两值都为20return 0;
}

通过以上的讲解,大概认识了指针是个什么东西,并且了解了其简单使用了,下面专们来讲讲指针变量的大小。

指针变量大小:

指针变量专门用来存地址,指针变量的大小取决于地址存放所要多大的空间

1.32位机器上:地址线32根,二进制32bit位,要将此地址存起来,需要四个字节的空间

2.64位机器:同理,一个地址需要八个字节 

注:地址和类型所占的空间不同,64位机器就像一个相比32位机器更大的宾馆,所需要的编号位数需要更大,但是每个房间的大小却不变一样。故:int类还是占4个字节的空间,只是找到这个空间的编号地址变复杂了而已。

指针变量类型

在刚刚的代码块中,我们谈到了int *pa,意思是pa是一个指针,指向的变量是int类型的数据,那我们合理联想一下,是不是有int*,就有char*,long long int*呢?答案是:是的,char *p意思是开辟指向char类型的指针空间,long long int*就是指向long long int类型的。前面提到的指针解引用*p中,int *p中的*p可以访问4个字节的空间,而char *p的*p可以访问1个字节的空间,以此类推。

指针加减整数

根据上述指针变量类型,我们就可以对指针进行加减运算,见下方代码

#include<stdio.h>
int main()
{int a = 10;int *pa = &a;char *pc = (char*)&a;//pa + 1---->地址 + 4//pc + 1---->地址 + 1//以上加减跳过的字节数是以指针类型为依据的,注意观察return 0;
}

而在C语言中,有数组这样的一种数据类型,他的下标随地址的增加而增加,且在内存中连续存放,那我们是否可以通过指针而非[i]来访问数组元素呢?,见代码

#include<stdio.h>
int main()
{int arr[10]={0,1,2,3,4,5,6,7,8,9};int *p = arr;//数组名是首元素地址for(int i = 0;i < 10;i++){printf("%d ",arr[i]);}    for(int i = 0;i < 10;i++){printf("%d ",*(p + i));//上下两块代码打印的结果是一样的,其实运行的效果也是一样的} return 0;
}//代码可以CV自测一下

指针减指针

指针减指针得到的是int类型的数据,但是相减时一定要满足这样一个条件,就是:两个相减的指针指向的是同一块空间。eg:指向同一个数组里的不同元素,这样大指针减小指针便可以得到指针间的元素个数了。

#include<stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};printf("%d\n",&arr[9]-&arr[0]);//将打印指针间的元素个数return 0;
}

void类型指针

在指针中,有这样一种特殊的指针,那就是void*(无具体类型指针),在用指针变量存放不同类型的地址时,不是相同类型的地址,是不能相互赋值的,要想放入指针变量类型不同的地址,只有两个办法,一个是强制类型转换,另一个便是void*,见代码

char a = 'm';
int *p = &a;//这样写会报错
int *p = (int*)&a;//将a的地址类型强制转换成int*,放入时不报错
void *p = &a;//将a的地址放入void*类型的p中,不报错

void*类型的指针可以放其他任意类型的指针,比如char*,int*等等,虽然放的指针类型很多,但还是有一定的局限性

注:

1.void*类型不能直接解引用

2.void*类型指针不可加减整数 

虽然void*可以包含许多类型的指针,但这恰恰让其只知道指向的位置,却不知道访问多大的空间,故产生以上问题。 

加餐const 

const使变量有了常属性,见下代码

#include<stdio.h>
int main()
{const int a = 10;//给a加上consta = 20;//这时修改a的值时编译器就会报错int *pa = &a;*pa = 20;//这时候a的值将被成功改掉,pa执政相当于一个后门,间接改aconst int *pb = &a;//给*pb加上const属性*pb = 0;//这时候通过*pb来改a时就会报错return 0;
}

其中,const虽然有了常属性,但本质上还是属于变量范畴(通常叫:常变量),const对于变量只是在语法上做了限制。

当const放在不同位置时,起到的作用也是不同的

const int a = 10;//不能通过a改变a变量的值
const int *pa = &a;//不能通过*pa改变a的值,但pa指针可以被直接改变
int const *pb = &a;//pb和pa的性质相同
int *const pc = &a;//可以通过*pc改变a的值,但pc的值不能直接改变
const int * const pd = &a;//不可通过*pd改变a,同时也不能直接改变pd

注意观察,其实不难发现,根据const所在的位置,就可以判断其在哪个变量上做了限制了。

指针关系运算(比大小)

比的是地址的高低(感觉没啥讲的)

野指针

我们在C语言学习过程中,肯定不免听到别人说“野指针”这三个字,现在我们来聊聊野指针吧。

现在我来给大家生成一个野指针

#include<stdio.h>
int main()
{int *ptr;*ptr = 20;//非法访问return 0;
}

其中ptr就是一个野指针,也就是所指向不可知地址的指针 ,由于生成的指针ptr指向方向不定,所以在访问或改变其指向的值的时候是极其不安全的,因为没有人会知道他指向的是哪一块空间。

避免生成野指针:

1.给指针初始化

2.不知道初始化什么,给指针赋NULL

3.指针用完记得置NULL

4.避免返回局部变量的地址

(在局部变量销毁,空间释放后局部变量指针就成为野指针而引发错误)

 assert断言

如果你在为害怕误用指针而烦恼,建议来看看assert吧,它能一定程度上帮助你判断指针是否可以使用,在assert.h中定义了宏assert();

#include<stdio.h>
#include<assert.h>
int main()
{int *pa = NULL;assert(pa != NULL);//当pa为空指针时报错return 0;
}

传值和传址

在函数调用过程中,有时候会遇到传递参数的问题,但有时候单纯传递数却无法解决一些问题

比如:写一个函数用来交换两个变量的值

#include<stdio.h>
void Swap1(int x,int y)
{int tmp = x;x = y;y = tmp;
}
void Swap2(int* pa,int* pb)
{int tmp = *pa;*pa = *pb;*pb = tmp;
}
int main()
{int a = 10;int b = 20;Swap1(a,b);printf("%d %d\n",a,b);//这里将会打印 10 20,可以看到变量并没有交换Swap2(&a,&b);printf("%d %d\n",a,b);//这里打印 20 10,变量成功交换return 0;
}

可以看出,当要用来改变传递变量的值的时候,可以传递它的地址,而直接传递其数值就达不到这样的效果。 

传址:让函数与主调函数建立联系

二级指针

指针的内存用来存放地址数据,那么能否创建一个指向指针变量的指针呢?

当然可以,见代码

#include<stdio.h>
int mian()
{int a = 10;int * p = &a;//取a的地址int ** pp = &p;//取指针p的地址,pp为二级指针int *** ppp = &pp;//取二级指针pp的地址,ppp为三级指针(用的很少)//其中**pp==aprintf("%d\n",**p);return 0;
}

指针数组

什么是指针数组,字面意思:存放指针的数组

#include<stdio.h>
int main()
{int a = 10;int b = 20;int c = 30;int* arr[3] = {&a,&b,&c};//其中arr数组的每个元素都为int类型的指针变量return 0;
}

 数组指针

也是字面意思:指向数组的指针

注:在看这些名字的时候,可以注意一下它的命名特点和方式,比如数组在后其本质就是数组,指针在后其就是指针。

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*parr)[10] = &arr;//其中parr是数组指针,&arr取得是整个数组的指针
//[]的优先级 > *
//*代表parr是一个指针,而[10]代表parr指向的数组有十个元素,每个元素是int类型
//parr + 1会跳过整个数组
//用parr访问元素(*parr)[i]

二维数组传参

#include<stdio.h>
void print1(int arr[3][5],int r,int c){....}//这两种传参方式都对
void print2(int (*arr)[5],int r,int c){....}
int main()
{int arr[3][5];print(arr,3,5);//二维数组传参return 0;
}

函数指针 

 字面意思:指向函数的指针

#include<stdio.h>
int Add(int x,int y)
{return x + y;
}
int main()
{int (*pf)(int,int)=&Add;int ret = (*pf)(4,9);int (*pf2)(int,int) = Add;//Add本身就可以作为地址赋给pf2int ret2 = (*pf2)(4,9);int ret3 = pf(4,9);//调用是pf可不用解引用printf("%d %d %d\n",ret,ret2,ret3);//打印的三个值是相同的return 0;
}

函数指针数组 

总的来说其实还是要看名字,就是存放函数指针的数组

使用举例

#include<stdio.h>
int add(int x, int y)
{return x + y;
}
int sub(int x, int y)
{return x - y;
}
int mul(int x,int y)
{return x * y;
}
int div(int x, int y)
{return x / y;
}
void menu()
{printf("1.add\n");printf("2.sub\n");printf("3.mul\n");printf("4.div\n");printf("0.exit\n");
}
int main()
{int a, b;menu();int input;int (*parr[5])(int, int) = { NULL,add,sub,mul,div };//上面的函数指针数组里分别放了四个函数指针scanf("%d", &input);while (input < 5 && input>0) {scanf("%d%d", &a, &b);int ret = parr[input](a, b);//通过调用函数指针数组来间接访问各个函数printf("%d", ret);}return 0;
}

以上的代码,完成了一个简单的计算器,同时规避了一堆if else的冗杂,这便是函数指针数组的一个妙用。

总结

以上讲了指针的基本类型,但是关于指针的细节还有很多很多,再往后深入就是要注意指针的不同类型和运算,才是深入了解和应用指针的关键。那么今天的指针内容就先分享到这里,如果感觉对你还有帮助的话,记得留一个小小的赞再走哦!

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

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

相关文章

【华为OD题库-048】拔河比赛-java

题目 公司最近准备进行拔河比赛&#xff0c;需要在全部员工中进行挑选。选拔的规则如下: 1.按照身高优先、体重次优先的方式准备比赛阵容 2.规定参赛的队伍派出10名选手 请实现一个选拔队员的小程序。 输入为一个数组&#xff0c;记录了部门人员的身高、体重信息&#xff0c;如…

Linux下unzip解压乱码问题的解决

1、通过unzip行命令解压&#xff0c;指定字符集 unzip -O CP936 xxx.zip (用GBK, GB18030也可以)CP936其实就是GBK&#xff0c;IBM在发明Code Page的时候将GBK放在第936页&#xff0c;所以叫CP936。GBK全称《汉字内码扩展规范》&#xff08;GBK即“国标”、“扩展”汉语拼音的…

振南技术干货集:各大平台串口调试软件大赏(4)

注解目录 &#xff08;串口的重要性不言而喻。为什么很多平台把串口称为 tty&#xff0c;比如 Linux、MacOS 等等&#xff0c;振南告诉你。&#xff09; 1、各平台上的串口调试软件 1.1Windows 1.1.1 STCISP &#xff08;感谢 STC 姚老板设计出 STCISP 这个软件。&#xf…

相机标定张正友、opencv和halcon对比(1)

本文将从基本标定开始&#xff0c;结合实际工作经验&#xff0c;分析张正友、opencv和halcon三者相机标定的深层原理与不同之处&#xff0c;内容比较多&#xff0c;如果出现错误请指正。 相机光学模型 我们使用的镜头都是由多组镜片组成&#xff0c;它实际上是一种厚透镜模型…

羊大师提问,为什么吃得越咸越容易出现健康问题?

羊大师提问&#xff0c;为什么吃得越咸越容易出现健康问题&#xff1f; 在现代社会中&#xff0c;有一种追求咸味食物的趋势&#xff0c;许多人都钟爱于吃咸味食物。吃咸味食物往往容易导致健康问题&#xff0c;引发各种疾病。那么为什么吃的越咸越容易生病呢&#xff1f; 今…

C++二分查找算法:132 模式枚举3

说明 本篇是视频课程的讲义&#xff0c;可以看直接查看视频。也可以下载源码&#xff0c;包括空源码。 本文涉及的基础知识点 二分查找算法合集 本题不同解法 包括题目及代码C二分查找算法&#xff1a;132 模式解法一枚举3C二分查找算法&#xff1a;132 模式解法二枚举2代码…

黑洞:宇宙中最神秘的天体

黑洞&#xff1a;宇宙中最神秘的天体 一、引言 在浩瀚的宇宙中&#xff0c;有一种神秘的天体&#xff0c;它强大到连光也无法逃逸&#xff0c;这就是黑洞。自从黑洞理论被提出以来&#xff0c;它一直是物理学家和天文学家研究的焦点。尽管我们还无法直接看到黑洞&#xff0c;…

使用 Redis Zset 有序集合实现排行榜功能(SpringBoot环境)

目录 一、前言二、Redis Zset 的基本操作三、通过Redis 命令模拟排行榜功能3.1、排行榜生成3.2、排行榜查询 四、SpringBoot 使用 Redis Zset 有序集合实现排行榜功能 一、前言 排行榜功能是非常常见的需求&#xff0c;例如商品售卖排行榜单、游戏中的积分排行榜、配送员完单排…

VirtualBox上安装CentOS7

基础环境&#xff1a;宿主机是64位Windows10操作系统&#xff0c;通过无线网访问网络。 macOS可以以类似方式进行安装&#xff0c;不同之处见最后补充。 Step1 安装VirtualBox VirtualBox是一款免费、开源、高性能的虚拟机软件&#xff0c;可以跨平台运行&#xff0c;支持Wi…

【神印王座】永恒之塔秘密透露,林鑫告白李馨,皓晨采儿甜蜜接吻

Hello,小伙伴们&#xff0c;我是拾荒君。 《神印王座》第83集如期而至&#xff0c;带来了令人期待已久的更新。与众多热情的观众一样&#xff0c;拾荒君一得到更新消息&#xff0c;便急不可耐地观赏起来。这一集中&#xff0c;龙皓晨随着月魔宫的月夜商队成功抵达联盟&#xf…

判断Xposed框架

判断Xposed框架 1: 根据包名判断 列举一些常用的应用判断是否安装,来判断. private static List<String> asList Arrays.asList("de.robv.android.xposed", "com.topjohnwu.magisk", "io.va.exposed", "org.meowcat.edxposed.man…

C++: string的模拟实现

C: string的模拟实现 一.前置说明1.模拟实现string容器的目的2.我们要实现的大致框架 二.默认成员函数1.构造函数2.拷贝构造函数1.传统写法2.现代写法 3.析构函数4.赋值运算符重载1.传统写法2.现代写法 三.遍历和访问1.operator[]运算符重载2.iterator迭代器 四.容量相关函数1.…

Linux下将Java项目(Jar包)打包成服务

目录 ​​​​​​​​​​​​​​ 创建一个新的Systemd服务单元文件。打开终端并使用文本编辑器创建一个新的服务单元文件&#xff0c;例如myapp.service。 在该文件中输入以下内容&#xff0c;替换其中的占位符为您自己的配置&#xff1a; 保存并关闭文件。 使用以下命…

ssm+vue的公司安全生产考试系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的公司安全生产考试系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结…

探索前端设计的新境界——介绍IVueUI工具助力Vue页面设计

在快速发展的前端领域&#xff0c;Vue.js作为一款渐进式JavaScript框架&#xff0c;一直备受开发者喜爱。然而&#xff0c;在Vue前端开发的旅程中&#xff0c;页面设计常常是一个不可避免的挑战。今天&#xff0c;我要向大家介绍一款令Vue前端开发者受益匪浅的工具——www.ivue…

Python文件操作

目录 一.文件的编码二.文件的读取三.文件的写入四.文件的追加五.文件操作综合案例 一.文件的编码 编码就是一种规则集合&#xff0c;记录了内容和二进制进行相互转换的逻辑。最常见的是UTF-8编码计算机只认识0和1&#xff0c;所以需要将内容翻译成0和1才能保存在计算机中。同时…

“大+小模型”赋能油气行业高质量发展

近日&#xff0c;中国石油石化科技创新大会暨新技术成果展在北京盛大举行&#xff0c;九章云极DataCanvas公司携油气行业一站式AI综合解决方案重磅亮相&#xff0c;充分展示了公司助推油气行业实现AI规模化应用深厚的AI技术实力和领先的AI应用水准&#xff0c;赢得了行业专家和…

spring boot整合Jasypt实现配置加密

文章目录 目录 文章目录 前言 一、Jasypt是什么&#xff1f; 二、使用步骤 1.引入 2.测试使用 3.结果 总结 前言 一、Jasypt是什么&#xff1f; Jasypt&#xff08;Java Simplified Encryption&#xff09;是一个Java库&#xff0c;提供了一种简单的加密解密方式&#xff0c…

JavaWeb | JSP基本语法

目录: 1.JSP2.JSP注释3.JSP 和 HTML有什么区别?4.JavaScript (JS) 和 JSP有什么区别&#xff1f;5.JSP表达式6.JSP程序段7.JSP声明8.URL传值9.JSP指令9.1Page指令的作用导入包设定字符集设定错误页面设定MIME类型和字符编码 9.2include指令9.3taglib指令 10.JSP动作11.include…

热门话题解析:pytest测试用例顺序问题解决方案!

前言 上一篇文章我们讲了在pytest中测试用例的命名规则&#xff0c;那么在pytest中又是以怎样的顺序执行测试用例的呢&#xff1f; 在unittest框架中&#xff0c;默认按照ACSII码的顺序加载测试用例并执行&#xff0c;顺序为&#xff1a;09、AZ、a~z&#xff0c;测试目录、测…