C:指针学习-指针变量—学习笔记
今日伊雷娜:
目录
前言:
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也能直接使用,所以在函数指针调用的时候 * 是可以省略的 ,它是没有实际意义的。
这里写*只是为了方便理解。
4、函数指针数组
数组是用来存放相同类型数据的存储空间,在前面数组与指针这一篇中我们也介绍了指针数组的概念,如:
int * arr[10];//数组中的每个元素是int*
那要是把函数的地址存放到一个数组中,那么这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int main()
{//指针数组int* arr[5]; //整型指针数组char* ch[5]; //字符指针数组return 0;
}
如果我们将函数指针也放在数组中呢?
int add(int x, int y)
{return x + y;
}
int sub(int x, int y)
{return x - y;
}
int main()
{int(*pa)(int, int) = add;int(*ps)(int, int) = sub;return 0;
}
从上面代码中我们可以发现pa,ps的类型是一样的,既然如此,我们就可以将他们放到一个数组中,毕竟数组是用来存放相同类型数据的存储空间,那我们如何创建一个函数指针数组呢?
int ( * parr[4] )( int, int )
这样写parr就变为数组名了,有4个元素,然后指针为parr[4];
如果我们把parr[4]去掉再看剩下的部分int ( * )( int, int ) ,欸,这不是一个函数指针类型,也就是说我们创建的这个数组parr[4]的元素类型是函数指针,所以我们所创建的这个数组就是函数指针数组。
这个函数指针数组是怎么创建的呢?
我们只需要在函数指针变量的基础上,在后面加上一个 [元素个数],就可以表示函数指针数组了
这时候这个数组就可以存放一些函数指针了
int (*parr[4])(int, int) = { add,sub };
函数指针数组的作用:数组的创建时为了方便更好的数据管理,当我们想要存放多个函数的地址,并且函数返回类型统一,我们就可以造一个函数指针数组。
结语:本篇文章到这里就结束了,主要介绍了一些指针变量的用法,希望大家在看完这篇文章后能够有所收获!下篇再见啦!!!
相关文章:

C:指针学习-指针变量—学习笔记
今日伊雷娜: 目录 前言: 1、字符指针变量 1.1 使用字符指针存放字符 1.2 使用字符指针变量存放字符串 2、数组指针变量 2.1 什么是数组指针变量? 2.2 数组指针变量初始化 2.3 关于数组指针类型的解析 3、函数指针变量 3.1 函数地址 …...

【MySQL 07】表的增删查改 (带思维导图)
文章目录 🌈 一、insert 添加数据⭐ 1. 单行数据 全列插入⭐ 2. 多行数据 指定列插入⭐ 3. 插入否则更新⭐4. 插入否则替换 🌈 二、select 查询数据⭐ 1. select 列🌙 1.1 全列查询🌙 1.2 指定列查询🌙 1.3 查询字段…...

快速上手Git
Git相关概念 Git是一个开源的分布式版本控制系统,由Linus Torvalds在2005年创建,用于有效、高速地处理从小到大的项目版本管理。它是由 Linux 之父 Linus Torvalds 开发的,并已经成为了现代软件开发领域中最流行的版本控制系统之一。 git的工…...

RTC时钟测试
1. 基础知识 Linux 的系统时间有时跟硬件时间是不同步的。 Linux时钟分为系统时钟(System Clock)和硬件(Real Time Clock,简称RTC)时钟。系统时钟是指当前Linux Kernel中的时钟,而硬件时钟则是主板上由电池供电的时钟,这个硬件时钟可以在BIO…...

大数据技术——实战项目:广告数仓(第六部分)报表数据导出至clickhouse
目录 第11章 报表数据导出 11.1 Clickhouse安装 11.2 Clickhouse建表 11.2.1 创建database 11.2.2 创建table 11.3 Hive数据导出至Clickhouse 第11章 报表数据导出 由于本项目最终要出的报表,要求具备交互功能,以及进行自助分析的能力,…...

Android studio模拟制作-简易的订餐交易小案例
一、最终呈现效果 订餐支付小案例效果 二、布局设计activity_main.xml <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/android"xml…...

消防隐患在线小程序的设计
管理员账户功能包括:系统首页,个人中心,用户管理,消防隐患举报管理,消防隐患分类管理,统计分类管理,处理结果管理,系统管理 微信端账号功能包括:系统首页,我…...

【Vue3】路由Params传参
【Vue3】路由Params传参 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长,很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来,技术出身的人总是很难放下一些执念,遂将这些知识整理成文,以纪念曾经努力学习奋斗的日…...

授权cleanmymac访问全部磁盘 Mac授权访问权限 cleanmymac缺少权限
CleanMyMac是Mac系统下的一款专业的苹果电脑清理软件,同时也是一款优秀的电脑系统管理软件。它能有效清理系统垃圾,快速释放磁盘内存,缓解卡顿现象,保障系统顺畅地运行。 全磁盘访问权限,就好比机场内进行的安全检查。…...
Ubuntu/18.04 LTS下编译 BoringSSL 库
1、准备一个 Ubuntu/18.04 LTS 系统的设备 2、安装软件 GIT、GCC、CMAKE、G、Golang:1.16 及以上版本 3、克隆仓库源 git clone https://boringssl.googlesource.com/boringssl cd boringssl 4、使用特定版本 git checkout 9fc1c33e9c21439ce5f87855a6591a9324e569fd 5、编…...

【stm32项目】多功能智能家居室内灯光控制系统设计与实现(完整工程资料源码)
多功能智能家居室内灯光控制系统设计与实现 目录: 目录: 前言: 一、项目背景与目标 二、国内外研究现状: 2.1 国内研究现状: 2.2 国外研究现状: 2.3 发展趋势 三、硬件电路设计 3.1 总体概述 3.2 硬件连接总…...

xss靶场详解
目录 1.第一题 2.第二题 3.第三题 4.第四题 5.第五题 6.第六题 7.第七题 8.第八题 1.第一题 在源码script标签里边,innerhtml是用于访问或修改 HTML 元素内的 HTML 内容的,这里是访问spaghet这个元素的,并通过括号里面的东西搜索当前…...

华为的流程管理
华为建设流程体系始于2000年,那时华为公司面临着快速扩张和全球化发展的挑战,意识到传统的管理模式已经无法满足业务发展的需求。为了提高公司的管理效率和竞争优势,华为决定启动流程体系的建设。在建设过程中,华为借鉴了业界最佳…...
操作系统Linux
1.Linux命令 ls:查看当前目录下所有目录和文件ps:查看所有正在运行的进程top:显示当前系统中占用资源最多的一些进程,shiftm按照内存查看大小netstat:查看端口的命令vi:查看文件的命令rm:删除文…...

1、.Net UI框架:MAUI - .Net宣传系列文章
.NET MAUI(Multi-platform App UI)是一个跨平台的UI框架,它是.NET统一应用模型的一部分,允许开发者使用C#和.NET来创建适用于iOS、Android、macOS和Windows的应用程序。MAUI继承了Xamarin.Forms的一些概念,但提供了更多的原生平台集成和改进的…...
Spring boot 使用 jSerialComm 对串口使用发送信息并接收
什么是 jSerialComm? jSerialComm 是一个 Java 库,旨在提供一种独立于平台的方式来访问标准串行端口,而无需外部库、本机代码或任何其他工具。它旨在替代 RxTx 和(已弃用的)Java Communications API,具有更…...

江协科技STM32学习笔记(第10章 SPI通信)
第10章 SPI通信 10.1 SPI通信协议 10.1.1 SPI通信 SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线; 串行外设接口; I2C无论是软件还是软件电路,设计的都还是比较复杂的,硬件…...
力扣热题100_回溯_22_括号生成
文章目录 题目链接解题思路解题代码 题目链接 22. 括号生成 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 示例 1: 输入:n 3 输出:[“((()))”,“(()())”,“(())()…...
【k8s】ubuntu24.04 containerd 手动从1.7.15 换为1.7.20
24.04的这个应该是apt 安装的1.7.20-1 root@k8s-master-pfsrv:~# sudo apt update && sudo apt install containerd.io -y 命中:1 http://mirrors.aliyun.com/docker-ce/linux/ubuntu noble InRelease 命中:2 https://dl.google.com/linux/chrome/deb stable InRelease…...

Java二十三种设计模式-备忘录模式(19/23)
本文深入探讨了备忘录模式,从定义、组成、实现到使用场景、优缺点、与其他模式的比较,以及最佳实践和替代方案,全面解析了如何在软件开发中有效地保存和恢复对象状态,以支持复杂的撤销操作和历史状态管理。 备忘录模式:…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...