C:指针学习-指针变量—学习笔记

news2024/11/16 21:35:07

今日伊雷娜:

目录

前言:

1、字符指针变量

1.1 使用字符指针存放字符

1.2 使用字符指针变量存放字符串

 2、数组指针变量

2.1 什么是数组指针变量?

2.2 数组指针变量初始化

2.3 关于数组指针类型的解析

3、函数指针变量

3.1 函数地址

3.2 函数指针变量的创建

3.3 关于指针的连续定义

3.4 函数指针变量的使用

3.5 补充函数调用


前言:

本篇文章涉及字符指针变量,数组指针变量,函数指针变量,以及函数指针数组。


1、字符指针变量

在指针类型中我们知道有一种指针类型叫char*

1.1 使用字符指针存放字符

#include <stdio.h>
int main()
{
	char ch = 'l';
	char* pc = &ch;//取出ch的地址放到ch里
	*pc = 'l';
	return 0;
}

pc为字符指针变量,指针变量是变量,存放的是字符,类型是char*

关于字符指针变量还有另外一种用法。也就是存放字符串。


1.2 使用字符指针变量存放字符串

    char* p = "JonlyMay";//字符指针p指向了一个字符串

这句代码的意思是什么呢?是把  "JonlyMay"字符串存放到指针变量p 中吗?

重点:p 中存放的是该字符串首字符的地址

我们来与数组类比理解 char* p = "JonlyMay"中是将首元素地址赋给字符指针变量p:

char arr[ ] = "JonlyMay";
char* p = arr

我们创建了一个字符数组arr,并把数组首元素地址arr赋给了p,p变量里存放了数组首元素地址

我们可以用代码再来理解一下

#include <stdio.h>
int main()
{
	char arr[] = "JonlyMay";
	printf("%c\n", arr[2]);
	printf("%c\n", "JonlyMay"[2]);
	return 0;
}

可以把 "JonlyMay"想象成一个数组,[2]就访问下标为2的元素

 const char* p  = "JonlyMay"   //这里加上const的原因是"JonlyMay" 是常量字符串,不能被修改的

其实仔细想想也不可能是将整个字符串都存放到指针变量p中,空间也不够啊!

p如果在x86的环境下,只能向内存申请4个字节的空间,而JonlyMay+\0九个字节也放不下!

总结:

const char* p  = "JonlyMay" 这串代码的意思是:把一个常量字符串首字符 J 的地址存放到指针变量 p 中。

 2、数组指针变量

2.1 什么是数组指针变量?

在介绍数组指针变量之前,我们需要先提一下字符指针变量整型指针变量,那这哥两是什么意思呢?

(pc)字符指针变量是一种变量里面存放的是(&ch)字符指针 (地址)

char ch;

char* pc = &ch;

(pn)整型指针变量是一种变量,里面存放的是(&n)整型指针(地址)

int n ;

int* pn = &n;

通过与它们的类比,我们应该能得出数组指针变量也是一种变量,里面存放的是数组指针(地址)

现在我们了解了指针数组指针变量的基本作用,那数组指针(地址) 怎么得到呢?

比如说 int arr[10] ,我们怎么得到数组的地址呢?

arr,&arr[0]:表示首元素地址;

&arr表示的是数组的地址

数组的地址是 通过取地址数组名得到的(&arr)

也就是说数组指针变量就是用来存放&arr的

我们知道

字符指针变量的类型是字符指针(char*);

整型指针变量的类型是整型指针(int*),

数组指针变量(parr)的类型是什么呢?是数组指针吗?好像是的,但是应该怎么表达呢?

我们举一个整型数组的例子

int main()
{
	int arr[10] = { 0 };
	int* parr[10] = &arr;
	return 0;
}

乍一看似乎没毛病,但是在int* parr[10] = &arr int* 就变成了整型指针 parr和[10]结合变为数组了,parr是数组名,那int* parr[10] = &arr的意思就变成了一个名为parr的数组有10个元素,每个元素类型为int*。

但是我们想要的parr是一个指向数组的指针,应该怎么做呢?

其实这里面涉及到了操作符优先级的问题 ,如果对操作符优先级不太了解的,可以看一看博主关于操作符的博文:C:操作符介绍-学习笔记-CSDN博客

int* parr[10] = &arr,在这串代码中,parr先和[10]结合变成了数组,我们希望得到的是一个指针变量parr,而当parr和[10]结合后就变为了数组名,得不到想要的结果。主要原因是(*)操作符的优先级低于([ ])操作符

说了这么多,怎么解决呢?其实很简单,我们只需要使用圆括号()和 parr括起来就可以了

从上面的图中我们可以看到圆括号的优先级是最高的,所以当我们想得到一个指针,就需要* 和 parr先结合,而不是parr和[10]先结合

表达形式:int (*parr) [10] = &arr;

parr与*结合说明parr是指针,指针指向的是数组 [10]说明数组有10个元素每个元素类型是int

现在这里的parr就是数组指针变量。

现在关于数组指针变量的表达形式我们已经很清楚了

那数组指针变量的类型是什么?int(*)[10]

int* pn = &n;中pn的类型就是int *

char* pc = &ch;中pc的类型就是char*;

所以变量的类型就是去掉变量名字,剩下的就是变量类型

int (*parr) [10] = &arr中parr的类型就是 int(*)[10],&arr的类型也是 int(*)[10]。

来举一个稍微有点难度的例子来帮助我们更好的掌握数组指针变量类型

胡言乱语:写到这突然想到一个很有意思的两句话,你已经学会加减乘除了,来,写道高数题练练手。

哈哈哈哈哈哈哈哈哈。(作者日常发癫,不必理会)言归正传,来看一下题目

 int* arr[9] = { 0 };
 p = &arr;   

问:这个p的类型是什么?

答:int*(*)[9]

首先,p先于*结合(*p),然后指向数组arr,有9个元素(*p)[9] ,数组中每个元素类型是int*,

所以变为 int*(*p)[9] 。所以数组指针变量p的类型就是int*(*)[9]

不知道大家是否还记得在这篇C:指针和数组之间的关系-学习笔记-CSDN博客文章中关于&arr+1后跳过了40个字节

相信到这里应该能够理解为什么跳过40个字节了吧!

&arr的类型是数组指针,int(*)[10],数组指针认为指向的数组有10个元素,每一个元素类型都是int。所以+1后跳过了4*10个字节。

总结:数组指针变量是用来存放数组的地址,数组的地址通过&数组名获得,将数组地址存起来放到数组指针变量中,数组指针变量的类型就是( 数组元素类型(*)[元素个数]

2.2 数组指针变量初始化

关于数组指针变量的初始化有两种方式:

1、直接使用数组的地址进行初始化
例如对一个整型数组int arr[10],可以这样初始化;

int (*p)[10] = &arr;

2、先声明数组指针变量,然后在后续的代码中通过赋值来初始化。

int (*p)[5];
    p = &arr;

2.3 关于数组指针类型的解析

数组指针变量是一种特殊类型的指针,它指向的是整个数组,而不是单个元素。

数组指针变量的声明形式通常为 数据类型 (*指针变量名)[数组大小]

例如 int (*p)[5] 声明了一个指向包含 5 个整数的数组的指针 p 。

通过数组指针访问数组元素时,通常需要结合下标来进行。

对于上述的 p,(*p)[0] 表示数组的第一个元素,(*p)[1]表示第二个元素,以此类推。

图文总结:

int (*p) [10] = &arr;
 |    |    |
 |    |    |
 |    |    p指向数组的元素个数
 |    p是数组指针变量名
 p指向的数组的元素类型

3、函数指针变量

3.1 函数地址

通过前面的学习我们知道变量可以取地址,数组也可以取地址。那么想问大家一个问题,函数有地址吗?函数可以取地址吗? 答案是函数有地址

既然函数有地址,我们该怎么得到呢?

上图中,我们可以发现想要得到函数的地址可以通过&函数名函数名两种方式得到函数的地址

注意:

 对于函数来说,只有一个地址,这里不要和数组搞混了,数组中&数组名拿到的是数组首元素地址,但是函数不存在什么首元素地址。

3.2 函数指针变量的创建

既然我们知道函数的地址是怎么得到的时候,我们该怎么将函数地址存起来呢?

比如上面那个add函数,我们想将它存到pf中,该怎么表示呢?pf的类型该怎么写呢?

pf = &add;

这里可以类比数组指针变量,int (*p) [10] = &arr;

照猫画虎,这里小编先把 pf 的类型写出来

int (*pf)(int x, int y) = &add;

类比int (*p) [10] = &arr;理解int (*pf)(int x, int y) = &add;

首先, int (*p) [10] = &arr; 中,p是一个数组指针,它指向的是一个包含 10 个整数的数组。

而int (*pf)(int x, int y) = &add; 中,pf 是一个函数指针,它指向的是一个接收两个int 类型参数并返回 int 类型值的函数。

就如同数组指针 p 通过 &arr 获得了指向数组的地址,函数指针 pf 通过 &add 获得了指向函数 add的地址。

对于数组指针,通过 (*p) [i] 的形式可以访问数组中的第 i 个元素。

对于函数指针,通过 (*pf)(int x, int y) 的形式可以调用所指向的函数,并传递参数类型int ,int

注意:

(int x, int y)中形参的x y是可以省略的,只需要传递参数类型就可以了  

pf就是函数指针变量

函数指针类型图解:

int    (*pf)    (int x, int y) 
 |       |            |
 |       |            |
 |       |            pf指向函数的参数类型和个数的交代
 |       函数指针变量名
 pf指向函数的返回类型

3.3 关于指针的连续定义

如果是连续定义两个整数,我们可以这么写

int a , b;

那如果是来连续定义两个指针呢?我们还可以这么写吗?

int* a , b;

答案是不对的;我们可以来看一下

当我们将鼠标放到a上时,可以看到a的类型是 int*

但是当我们将鼠标放到b上时,可以看到b的类型是 int

所以,关于来纳许定义两个指针我们需要每一个变量都有*

正确表达形式:int *a , *b;

3.4 函数指针变量的使用

我们将函数地址存放起来,是为了后面的使用,那么我们应该怎么使用呢?

int (*pf)(int x, int y) = &add;

我们怎么调用这个函数呢?

#include <stdio.h>
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int x, int y) = &add;
	int ret = (*pf)(2, 3);//调用函数
    printf("%d",ret);
	return 0;
}

 结果:

 int ret = (*pf)(2, 3)解读:

 pf 是一个函数指针,*pf 表示对函数指针进行解引用,得到它所指向的函数。

然后 (*pf)(2, 3) 就是调用这个函数指针所指向的函数,并向其传递参数 2 和 3。

最后,将函数的返回值赋给变量 ret  。

3.5 补充函数调用

还记得我们正常函数调用是怎么操作的吗?

#include <stdio.h>
int add(int x, int y)
{
	return x + y;
}
int main()
{	
	int ret = add(2, 3);
	printf("%d", ret);
	return 0;
}

int (*pf)(int x, int y) = &add;

int ret = (*pf)(2, 3);

这个是指我们先将函数地址存放到指针变量pf中去,然后我们通过函数指针变量来调用函数

我们来对比一下这两种调用方式

//第一种:
int (*pf)(int x, int y) = &add;
int ret = (*pf)(2, 3);
printf("%d", ret);
//第二种:
ret = add(2, 3);
printf("%d", ret);

前面我们说过add就是函数的地址,pf中也是函数的地址,那*pf为什么要解引用呢?是否可以直接使用呢?来测试一下

可以发现没有解引用pf也能直接使用,所以在函数指针调用的时候 是可以省略的 ,它是没有实际意义的。

这里写*只是为了方便理解。


结语:本篇文章到这里就结束了,主要介绍了一些指针变量的用法,希望大家在看完这篇文章后能够有所收获!下篇再见啦!!!

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

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

相关文章

如何将CSDN文章导出为pdf文件

第一步&#xff1a; 打开想要导出的页面&#xff0c;空白处点击鼠标右键⇒点击“检查”或“check”&#xff0c;或直接在页面按F12键。 第二步&#xff1a; 复制以下代码粘贴到控制台&#xff0c;并按回车。 若提示让输入“允许粘贴”或“allow pasting”&#xff0c;按提示…

InnoDB锁机制和事务管理介绍_案例解答

前言 本文对《Innodb 锁机制和事务管理介绍》这篇文章中的思考题进行解答。查看完解答后如果对原理有疑问的&#xff0c;可以重新看下原文帮助理解。 初始化信息 验证环境&#xff1a;8.0.28 MySQL Community Server&#xff0c;InnoDB引擎&#xff0c;RR隔离级别。 初始化信…

苍穹外卖项目DAY03

苍穹外卖项目Day03 1、菜品管理 1.1、公共字段自动填充 1.1.1、问题分析 业务表中的公共字段&#xff1a; 问题&#xff1a;代码冗余、不便于后期维护 1.1.2、实现思路 自定义注解AutoFill&#xff0c;用于标识需要进行公共字段自动填充的方法自定义切面类AutoFillAspect&…

Mybatis和Mybatis-plus区别和联系

MyBatis 和 MyBatis-Plus 是两个用于 Java 数据持久层的框架&#xff0c;它们在功能和使用场景上有所不同。如果项目需要高度自定义和复杂的 SQL 逻辑处理&#xff0c;且开发团队熟悉 SQL&#xff0c;那么 MyBatis 是一个更好的选择。相反&#xff0c;如果希望提高开发效率&…

EPLAN P8 2024-操作记录

目录 设备标识符包含页面 效果如下​编辑 步骤如下 连接点代号的分隔符创建 效果如下 步骤如下 中断点连接排序 效果如下 步骤如下 触点关联参考位置调整、 效果如下 步骤如下 端子排定义 效果如下 步骤如下 端子排连接 效果如下 离散端子操作如下 连接…

操作系统基础知识:调度器、闲逛进程,闲逛进程的特性,什么事件会触发“调度程序”?

被调度程序选中和时间用完由调度程序引起&#xff0c;调度程序决定&#xff1a; 让谁运行由调度算法决定&#xff1b;运行多长时间由时间片大小决定。 什么事件会触发“调度程序”? 1.创建新进程 2.进程退出 3.运行进程阻塞 4.I/0中断发生(可能唤醒某些阻塞进程) 非抢占式调…

「每周只上一天班」谷歌散漫制度遭前CEO怒斥:输给OpenAI,再下去要输创业公司了

「谷歌决定拥抱生活与工作平衡&#xff1a;更早下班、远程工作&#xff0c;胜过在竞争中取胜。」施密特说道。「而说到初创公司&#xff0c;他们之所以能成功&#xff0c;是因为人们在拼命地工作。」 在本周三公布的一份斯坦福大学公开课视频中&#xff0c;谷歌前 CEO 埃里克・…

机器学习 之 sklearn的使用介绍和如何找到API

scikit-learn&#xff08;简称 sklearn&#xff09;是基于python语言的一个第三方机器学习库&#xff0c;它提供了简单而有效的工具来进行数据分析和建模。建立在numpy pandas SciPy和Malpotlib库上&#xff0c;下面是对如何使用 sklearn 以及如何找到其 API 的一个基本介绍&am…

算法:DFS解决FloodFill算法

目录 题目一&#xff1a;图像渲染 题目二&#xff1a;岛屿数量 题目三&#xff1a;岛屿的最大面积 题目四&#xff1a;被围绕的区域 题目五&#xff1a;太平洋大西洋水流问题 题目六&#xff1a;扫雷游戏 题目七&#xff1a;衣橱整理 题目一&#xff1a;图像渲染 有一幅…

Unity MessagePack代替Json让你的数据更小还更快

Unity MessagePack代替Json让你的数据更小还更快 前言项目下载并安装MessagePack编写测试代码添加并设置脚本生成AOT代码编写加载AOT代码文件运行效果 参考 前言 前端给后端发的Json数据有点大&#xff0c;使用MessagePack优化一下&#xff08;MessagePack原理官网解释的很清晰…

前端案例:极速问诊项目(移动端自适应)(HTML+CSS+JS)

一个简单的移动端案例&#xff0c;模拟不同设备下逻辑分辨率不同&#xff0c;宽高自适应 正常打开整体布局 打开 f12 &#xff08;ctrl shift M&#xff09;或者点击左上角图标,将其模拟为移动端设备 在移动设备iPhone6/7/8&#xff0c;逻辑分辨率375的整体布局 其banner图尺…

1.9万亿字节跳动,没钱了?

字节再融资 近期投行圈热度最高的事情&#xff0c;是字节跳动正在积极与相关金融机构展开洽谈&#xff0c;期望对其现有的 50 亿美元贷款进行再融资&#xff0c;期限三年。 若是再融资能成&#xff0c;大概率会成为中国借款人年内进行的最大规模贷款再融资交易事件。 这下可好了…

Cesium for Unreal 打包像素流去掉 CLICK TO START

文章目录 1. 像素流去掉 CLICK TO START问题分析1. 像素流去掉 CLICK TO START 问题 分析 在后缀添加参数:MatchViewportRes=true&HoveringMouse=true 例如: http://192.168.0.106:5027/?MatchViewportRes=true&HoveringMouse

【课程总结】day24(上):大模型三阶段训练方法(LLaMa Factory)

前言 本章我们将通过 LLaMA-Factory 具体实践大模型训练的三个阶段&#xff0c;包括&#xff1a;预训练、监督微调和偏好纠正。 大模型训练回顾 训练目标 训练一个医疗大模型 训练过程实施 准备训练框架 LLaMA Factory是一款开源低代码大模型微调框架&#xff0c;集成了业…

Spring Security 01.两个功能

在 Spring Security 的架构设计中&#xff0c;认证&#xff08;Authentication&#xff09;和授权&#xff08;Authorization&#xff09;是分开 的&#xff0c;在本书后面的章节中读者可以看到&#xff0c;无论使用什么样的认证方式&#xff0c;都不会影响授权&#xff0c;这是…

国际网络组网如何布署?

国际网络组网的布署涉及多个关键的步骤和注意事项&#xff0c;以确保网络的可靠性、可扩展性和安全性。通过深入分析网络需求、基础设施选择、网络设备配置、数据安全及灾难恢复计划等方面&#xff0c;可以实现有效的国际网络组网布署。 在网络需求分析的阶段&#xff0c;首先需…

Flow Simulator 案例分享:换热器的一维仿真

换热器的 CFD 仿真 换热器广泛用于动力设备、空调系统和电子设备等冷、热介质的热量交换。由于结构上存在大量的管束和翅片结构&#xff0c;在 CFD 仿真中通常不会创建全细节模型&#xff0c;而是采用多孔介质和换热效率模型来简化。 各种类型的换热器 Flow Simulator中的换热器…

探索Python的隐秘武器:itsdangerous的威力与妙用

文章目录 **探索Python的隐秘武器&#xff1a;itsdangerous的威力与妙用**第一部分&#xff1a;背景介绍第二部分&#xff1a;itsdangerous是什么&#xff1f;第三部分&#xff1a;如何安装itsdangerous&#xff1f;第四部分&#xff1a;itsdangerous的五个简单函数第五部分&am…

DVWA中文件上传漏洞之High渗透测试

1、代码分析 我们可以看到DVWA中文件上传漏洞的代码&#xff0c;现在我们对这个进行渗透测试。 方法1&#xff1a;直接用脚本改成图片格式看是否可行。改成jpg或者jpeg或者png 通过这种方式&#xff0c;我们发现上传的时候会被拦截&#xff0c;所以这个方式不可取 方法2&…

【MATLAB源码-第248期】基于matlab的EMD算法+ICA算法轴承故障分析。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 经验模态分解&#xff08;EMD&#xff09;与轴承故障识别 EMD的基本原理 EMD 是一种自适应的信号分解技术&#xff0c;最初由 Huang 等人在 1998 年提出&#xff0c;旨在分析非线性和非平稳信号。传统的信号处理方法通常假…