当前位置: 首页 > news >正文

那些年与指针的情仇(二)---二级指针指针与数组的那点事函数指针

在这里插入图片描述关注小庄 顿顿解馋(。・∀・)ノ゙

欢迎回到我们的大型纪录片《那些年与指针的爱恨情仇》,在本篇博客中我们将继续了解指针的小秘密:二级指针,指针与数组的关系以及函数指针。请放心食用!


文章目录

  • 一. 二级指针
  • 二. 数组与指针的那点事儿
    • 1.🏠 数组名的理解
      • 1.1 数组名本质理解
      • 1.2 sizeof数组名和取地址数组名
    • 2. 🏠 指针数组
    • 3.🏠 字符串常量
    • 4.🏠 数组指针
    • 5.🏠 数组传参
  • 三. 函数指针

一. 二级指针

前面我们讲到了指针变量是个存储指针(地址)的变量,我们知道变量在创建的时候操作系统会给他分配内存空间同时给他编号(地址),那么指针变量的指针(地址)能否被存储呢?这里就引入我们二级指针的概念了

二级指针:存储指针变量地址的指针

地址变量
0x0012ffaaa
0x00134455*p = &a
0x0012aaff**pp=&p

*在上面表格中pp变量其实就是对应的二级指针,我们可以通过双重解引用pp来找到a,*pp = p, *(*pp)=a

注:二级指针类型定义时的*理解逻辑可以类比我们之前理解指针变量类型的定义,二级指针变量前的*说明这是个指针(存储地址的小子),而前面的int*说明它指向的对象是int *类型的,他的变量类型是int **

二. 数组与指针的那点事儿

1.🏠 数组名的理解

1.1 数组名本质理解

int arr[10] = {1,2,3,4,6,7,8,9,10};
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);

输出结果
&arr[0] = 004F9CC
arr = 004F9CC

这里我们可以得出数组名本质是首元素的地址,明白这个后我们以后访问数组就可以用指针的方式le

int arr[10]={1,23,4,5,6,7,8,9,10};
int* p = arr;
for(int i = 0;i < 10;i++)
{printf("%d ",*(p+i));
}//p+i指针加减法顺藤摸瓜到下个元素
//

延伸:*此时p与arr等价->arr[i] = *(p+i)= *(arr+i) *

那是否所有情况下都是这个样子呢?嘿嘿,我们看下面的代码

1.2 sizeof数组名和取地址数组名

Sizeof数组名

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));

输出结果 : 40

看到这里有同学会疑惑,数组名本质不是首元素地址吗?

小庄如是说:没错,但是这是一种特殊情况也就是特例,sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节

&数组名

//代码1
int arr[10] = {1,2,3,4,5,6,78,9,10};
printf("%p\n",&arr[0]);
printf("%p\n",&arr);
printf("%p\n",arr);
//代码2
printf("&arr[0]+1 = %p\n", &arr[0]+1);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1 = %p\n", &arr+1);

代码1输出结果:
0x004FC550 //&arr[0]
0x004FC550 //&arr
0x004FC550 //arr
代码2输出结果:
0077F824 //&arr[0] +1
0077F824 //arr+1
0077F848 //&arr+1

我们可以发现代码1输出结果相同,代码2就出现不同了其中&arr[0]和arr的结果相同这就再次验证了数组名的本质是首元素的地址,但对于&数组名+1,&arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组的!

因此我们可以得出&数组名,这里的数组名表示整个数组,取出的是整个数组的地址,只不过数组都是从首个元素开始的。相当于你拿一箱水果只能一个一个搬,而比你壮的可以整箱搬起来

总结:对于数组名的运用,sizeof+数组名和&+数组名是特例表示整个数组地址,其他地方都是首元素地址

2. 🏠 指针数组

抛砖引玉:前面我们学习了数组的相关知识,整形数组是存储整型数据的数组,字符数组是存储字符型数据的数组… 那指针数组呢?

指针数组:存储指针的数组,数组每个元素都是来存储指针(地址)的,主体是数组.

  • 语法形式

Datatype * arr[size]
这里的Datatype*指的是数组存储元素的类型,也就是arr是一个存储整型指针的数组,大小为size。

  • 指针数组模拟二维数组
int arr1[] = {1,2,3,4,5};
int arr2[] = {6,7,8,9,10};
int arr3[] = {11,12,13,14,15};
int* arr[3] = {arr1,arr2,arr3};
int i,j;
for(i=0;i<3;i++)
{for(j=0;j<5;j++){printf("%d ",arr[i][j]);}printf("\n");
}
//我们取每个整形数组首元素地址存储到指针数组中,就可以依靠这个首元素地址顺藤摸瓜到整个数组
//从而打印出每行实现二维数组的效果

延伸:arr[i][j] = * (*(arr+i)+j) ,arr[i]相当于是数组名,以每行为一个元素。

3.🏠 字符串常量

字符串我们学过下面两种表达式

1  char arr1[5] = "good";
2  char arr2[5] = {'g','o','o','d','\0'};

还有另外一种

const char* p = "good";

这里const修饰在*的前面说明p指向内容不可变,我们这时称这个字符串为常量字符串,这里是否表示把整个字符串放进p指针呢?不是的,这里意思是把字符串首元素地址放进p里,我们来段代码测试下。

char * p = "hello"
printf("%s",p);
printf("%c",*p);

输出结果为hello h,也就是说字符串打印是通过首地址来找到位置打印的跟字符数组类似

char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n"

输出结果:
str1 and str2 are not same
str1 and str2 are same

结论:1.c语言会把常量字符串存储到单独的一个内存区域,几个指针指向同一个字符串时指向的是同一块内存空间2.用他们初始化不同数组会开辟不同相独立的内存空间。

4.🏠 数组指针

抛砖引玉:整形指针是指向整形数据的指针,字符型指针是指向字符型的指针…那数组指针呢?

数组指针:指向对象为数组的指针,也就是存储的数组的地址

  • 数组指针变量
1 int (*p)[10];

解释:这里的先与p结合表示p是个指针变量(存地址的小子),p指向的是一个大小为10存储元素为整形数据的数组,指针类型为int()[10]
注:【】的优先级高于*,所以要加上()来让*与p先结合表示他是个指针变量

  • 数组指针变量初始化
int(*p)[10] = &arr; //取地址数组名取的是整个数组地址

5.🏠 数组传参

  • 一维数组传参
void test(int arr[])
{
int sz2 = sizeof(arr)/sizeof(arr[0]);
printf("sz2 = %d\n", sz2);
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz1 = sizeof(arr)/sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test(arr);
return 0;
}

输出结果 sz1 = 10 sz2 = 1;

这里我们可以看到在函数内部和外部求数组长度得出的结果是不同的,原因就是数组传参时传的是数组名,也就是说数组传参本质上传的是首元素地址,此时函数内部sizeof(arr) == sizeof(arr[0]),因此我们不能在函数内部求数组长度

明白一维数组传参本质我们可以这样传数组

void test(int* p);

总结:一维数组传参可以写成数组的形式也可以写成指针的形式

  • 二维数组传参

类比一维数组传参的本质 二维数组传参传的也是首元素地址,那二维数组的首元素是什么?

我们知道二维数组是一维数组的数组,也就是说二维数组的第一行元素就是它的首元素!那第一行元素地址怎么表示呢?这就与我们之前的数组指针串联起来了,也就是int(*p)[size];

void test(int arr[3][5]);
void test(int(*p)[5]);

总结:二维数组传参传的是第一行一维数组的地址,传参既可以写成二维数组的形式也可以写成数组指针的形式。

三. 函数指针

  • 函数指针变量

经过前面的类比我们不难得出:
函数指针变量:存储函数地址的指针变量

函数是否有地址呢?我们写代码测试下

void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}

输出结果
test: 005913CA
&test: 005913CA

所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名的方式获得函数的地址
延伸:函数指针调用(*p)(x,y),由于函数名等价于&函数名,故函数指针调用也可以p(x,y)

  • 函数指针变量的创建
int (*pf3) (int x, int y)//函数指针类型为 int(*)(int x ,int y)

解释:这里的*表示pf3是个指针变量用来存储指针,前面的int表示指向函数的返回值是int,后面的(int x,int y)表示的是指向函数参数的类型和个数(参数变量名可写可不写),可以类型数组指针变量的定义

  • 函数指针数组

类比指针数组,把几个函数的地址存到一个数组,那这个数组就叫函数指针数组

那函数指针数组如何定义呢?

int (*parr1[3])();

数组特征是【】在函数指针变量基础上加上【】让parr1先与【】结合就是数组了,数组存储元素的类型为int(*)()

  • typedef重新定义
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
typedef void(*pfun_t)(int);//新的类型名必须在*的右边
  • 转移表

知道函数指针数组后我们可以实现转移表

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf( "请选择:" );
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输⼊操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
printf( "ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出计算器\n");
}
else
{
printf( "输⼊有误\n" );
}
}while (input);
return 0;
}

这里将函数数组作为一个跳板将函数们放进一个表中方便使用,这里的函数指针数组就叫转移表


本次分享到这就结束了,喜欢的话给小庄三连吧!

相关文章:

那些年与指针的情仇(二)---二级指针指针与数组的那点事函数指针

关注小庄 顿顿解馋(&#xff61;&#xff65;∀&#xff65;)&#xff89;&#xff9e; 欢迎回到我们的大型纪录片《那些年与指针的爱恨情仇》&#xff0c;在本篇博客中我们将继续了解指针的小秘密&#xff1a;二级指针&#xff0c;指针与数组的关系以及函数指针。请放心食用&a…...

APPCRASH 文件管理器不停重启问题记录 (最后reinstall解决)以及重装后到底会怎样

问题诱因&#xff1a; 时间 2024年1月 &#xff08;严重怀疑&#xff0c;但无法实锤&#xff09; 是我不知道什么时候&#xff08;应该是近期&#xff09;安装了powertoy的预览版&#xff0c;系统不知道什么时候&#xff08;也应该是这两天&#xff09;安装了微软出品的PC man…...

导出excel功能,前端的解决方案

import { utils, writeFileXLSX } from xlsx // 导出excel async exportToExcel() {// 获取要导出的业务数据&#xff08;这里的接口自己改成实际使用的接口&#xff09;const res await getRuleListAPI(this.params)// 表头英文字段key&#xff08;这里的数据改成接口返回的实…...

【lesson24】MySQL索引的理解

文章目录 建立测试表插入多条记录查看插入结果中断一下---为何IO交互要是 Page重谈page理解单个page理解多个page 页目录单页情况多页情况复盘一下InnoDB 在建立索引结构来管理数据的时候&#xff0c;其他数据结构为何不行&#xff1f;B vs B聚簇索引 VS 非聚簇索引 建立测试表…...

Oracle篇—分区索引的重建和管理(第三篇,总共五篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…...

前端大厂面试题探索编辑部——第一期

目录 简介 题目 单选题 题解 A选项的Content-Security-Policy http-equiv属性 content属性 B选项的CSRF 使用CSRF Token 验证Referer和Origin头 C选项的HTTP Only D选项的防止SQL注入 输入验证和转义 简介 这个是一个长系列&#xff0c;我会以题目为导向&#xff…...

图扑 HT UI 5.0 全新升级,开箱即用!

为顺应数字时代的不断发展&#xff0c;图扑 HT UI 5.0 在原有功能强大的界面组件库的基础上进行了全面升级&#xff0c;融入了更先进的技术、创新的设计理念以及更加智能的功能。HT UI 5.0 使用户体验更为直观、个性化&#xff0c;并在性能、稳定性和安全性等方面达到新的高度。…...

数据结构----链表介绍、模拟实现链表、链表的使用

文章目录 1. ArrayList存在的问题2. 链表定义2.1 链表的概念及结构2.2 链表的组合类型 3. 链表的实现3.1 单向、不带头、非循环链表的实现3.2 双向、不带头节点、非循环链表的实现 4.LinkedList的使用4.1 什么是LinkedList4.2 LinkedList的使用4.2.1. LinkedList的构造4.2.2. L…...

微信小程序如何控制元素的显示和隐藏

目录 方式一:display 方式二:wx:if 有时在微信小程序元素的显示需要通过特定的条件,比如组件的显示,通常需要在主界面有指定操作。可以通过以下两种方式控制元素的显示和隐藏。 方式一:display 在wxml或者wxss中设置display样式可以控制元素显示情况,元素默认显示,可…...

解决ssh: connect to host github.com port 22: Connection timed out

当连接 GitHub 时无法连接到 22 端口时&#xff0c;可以尝试将端口更换为 443 首先&#xff0c;尝试使用以下命令从 GitHub 克隆仓库&#xff1a; $ git clone gitgithub.com:xxxxx/xxxx.git my-awesome-proj如果出现以下错误信息&#xff1a; Cloning into my-awesome-proj…...

idea 创建 spring boot

1.创建步骤 2. 编码添加 2.1 这是自动生成的启动函数 package com.example.comxjctest4;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class Application {publi…...

【智能家居入门之微信小程序控制下位机】(STM32、ONENET云平台、微信小程序、HTTP协议)

实现微信小程序控制单片机外设动作 一、使用ONENET可视化组件控制单片机外设动作二、使用微信小程序控制单片机外设动作三、总结 本篇博客话接上文&#xff1a; https://blog.csdn.net/m0_71523511/article/details/135892908 上一篇博客实现了微信小程序接收单片机上传的数据…...

07.领域驱动设计:了解3种常见微服务架构模型的对比和分析

目录 1、概述 2、整洁架构 3、六边形架构 4、三种微服务架构模型的对比和分析 5、从三种架构模型看中台和微服务设计 5.1 中台建设要聚焦领域模型 5.2 微服务要有合理的架构分层 5.2.1 项目级微服务 5.2.2 企业级中台微服务 5.3 应用和资源的解耦与适配 6、总结 1、概…...

设计模式——模板方法模式(Template Method Pattern)

概述 模板方法模式&#xff1a;定义一个操作中算法的框架&#xff0c;而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。模板方法模式是一种基于继承的代码复用技术&#xff0c;它是一种类行为型模式。模板方法模式是结…...

07. STP的基本配置

文章目录 一. 初识STP1.1. STP概述1.2. STP的出现1.3. STP的作用1.4. STP的专业术语1.5. BPDU的报文格式1.6. STP的选择原则&#xff08;1&#xff09;选择根桥网桥原则&#xff08;2&#xff09;选择根端口原则 1.7. 端口状态1.8. STP报文类型1.9. STP的收敛时间 二. 实验专题…...

oracle分区范围修改与数据迁移处理

背景 由于对应用上线后流量越来越大&#xff0c;原来的按年自动分区性能跟不上&#xff0c;因此决定改成按月自动分区&#xff0c;同时将原有分区数据重新迁移到新的分区 步骤 修改表分区为一个月一个分区 alter table my_table set INTERVAL (NUMTOYMINTERVAL(1, month));…...

回归预测 | Matlab实现CPO-LSSVM【24年新算法】冠豪猪优化最小二乘支持向量机多变量回归预测

回归预测 | Matlab实现CPO-LSSVM【24年新算法】冠豪猪优化最小二乘支持向量机多变量回归预测 目录 回归预测 | Matlab实现CPO-LSSVM【24年新算法】冠豪猪优化最小二乘支持向量机多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CPO-LSSVM【24年…...

SeaTunnel Web安装 一把成

安装相关jar包&#xff0c;以及SeaTunnel 和Web 打成的包&#xff0c;可以直接使用&#xff0c;但是需要安装MySQL客户端的分享&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1qrt1RAX38SgIpNklbQJ7pA 提取码&#xff1a;0kmf 1. 环境准备 环境名称版本系统环境C…...

对话泛能网程路:能源产业互联网,行至中程

泛能网的能源产业互联网的标杆价值还不仅于此。其在产业互联之外&#xff0c;也更大的特殊性在于其也更在成为整个碳市场的“辅助运营商”&#xff0c;包括电力、碳等一系列被泛能网帮助企业改造和沉淀的要素资产&#xff0c;都在构成着碳交易市场的未来底层。 这恰是产业互联…...

Doris简介及单机部署(超详细)

文章目录 一、Doris简介1、Doris介绍2、Doris架构 二、Doris单机部署&#xff08;Centos7.9&#xff09;1、下载Doris2、准备环境3、安装部署3.1 创建存储目录3.2 配置 FE3.3 启动 FE3.4 查看 FE 运行状态3.5 配置 BE3.6 启动 BE3.7 添加 BE 节点到集群3.8 查看 BE 运行状态3.9…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

手机平板能效生态设计指令EU 2023/1670标准解读

手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读&#xff0c;综合法规核心要求、最新修正及企业合规要点&#xff1a; 一、法规背景与目标 生效与强制时间 发布于2023年8月31日&#xff08;OJ公报&…...

安卓基础(Java 和 Gradle 版本)

1. 设置项目的 JDK 版本 方法1&#xff1a;通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分&#xff0c;设置 Gradle JDK 方法2&#xff1a;通过 Settings File → Settings... (或 CtrlAltS)…...

离线语音识别方案分析

随着人工智能技术的不断发展&#xff0c;语音识别技术也得到了广泛的应用&#xff0c;从智能家居到车载系统&#xff0c;语音识别正在改变我们与设备的交互方式。尤其是离线语音识别&#xff0c;由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力&#xff0c;广…...