深入理解指针(指针篇2)

在指针篇1我们已经了解了整型指针,当然还有很多其他类型的指针,像字符指针、数组指针、函数指针等,他们都有他们的特别之处,让我们接着学习。

1. 指针类型介绍和应用

1.1 字符指针变量

字符指针变量类型为char*,一般这样使用:

int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}

还有一种使用方式:

int main()
{const char* pstr = "hello bit.";//这⾥是把⼀个字符的地址传给pstr变量printf("%s\n", pstr);return 0;
}

1.2 数组指针变量(与指针数组区分)

提到数组指针,我们会想数组指针到底是数组还是指针,指针数组又是什么,他们怎么区分?

我们可以这样理解:

数组指针,就是一个存放内容是数组的指针,那自然是指针了

指针数组,就是一个数组元素是指针的数组,那自然是数组了

我们也可以通过后缀来理解,后缀是本质,前者都是形容词。

知道了两者的区别后我们看一段代码:

int *p1[10];  //指针数组
int (*p2)[10];//数组指针

int *p1[10] 解释:

[ ]的优先级高于*,p1先与[10]结合,表明这是一个数组,前面的int*表明数组中的元素是int*类型的,所以这是一个指针数组。

int (*p2)[10]解释:因为有()的存在,p2先与*结合,说明p2是一个指针变量,然后指针指向的是一个大小为10个整型的数组,所以这是一个数组指针。

1.3 函数指针变量

通过前面的学习我们不难猜到函数指针是用来存放函数地址的,未来可以通过函数地址调用函数

函数的地址与数组有相似之处,函数名就是函数的地址,也可以通过&函数名的方式获得函数的地址。我们要将函数的地址存放起来,就需要创建函数指针变量了,函数指针变量的写法也和数组指针非常相似,如下代码:

void test()
{printf("hehe\n");
}void (*pf1)() = &test;
void (*pf2)()= test;int Add(int x, int y)
{return x+y;
}int(*pf3)(int, int) = Add;
int(*pf4)(int x, int y) = &Add;//x和y写上或者省略都是可以的

上面说到可以通过函数地址调用函数,函数地址存放在函数指针变量中,我们是不是可以通过函数指针调用指针指向的函数呢?我们通过代码实现一下。

 #include <stdio.h>int Add(int x, int y){return x+y;}int main(){int(*pf3)(int, int) = Add;printf("%d\n", (*pf3)(2, 3));//输出5printf("%d\n", pf3(3, 5));   //输出8return 0;}

2. 二维数组的本质

有了数组指针的理解,我们就可以讲一讲二维数组传参的本质了

过去二维数组要传参给一个函数时,代码如下:

#include <stdio.h>void test(int a[3][5], int r, int c)
{int i = 0;int j = 0;for(i=0; i<r; i++){for(j=0; j<c; j++){printf("%d ", a[i][j]);}printf("\n");}
}int main()
{int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};test(arr, 3, 5);return 0;
}

这里,形参除了写成二维数组的形式,还可以怎么写?

我们想想,二维数组名就是数组首行元素的地址,第一行的一维数组类型就是int [5],第一行的地址类型就行int (*) [5],那么意味着二维数组传参的本质也是传递了地址,传的是第一行这个一位数组的地址,形参也可以写成指针形式,如下:

#include<stdio.h>void test(int (*p)[5], int r, int c)
{int i = 0;int j = 0;for(i = 0;i < r; i++){for(j = 0; j < c; j++){printf("%d ",*(*(p + i) + j));}printf("\n");}
}int main()
{int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};test(arr, 3, 5);return 0;
}

3. 函数指针数组(应用:转移表)

3.1 函数指针数组定义

数组是一个存放相同数据类型的存储空间,把几个函数的地址存放到一个数组中,这个数组就叫函数指针数组。嗯,函数指针数组,听起来好复杂,它又应该怎么定义呢?

int (*parr[num]) ();

解释一下,变量名是parr,先与[num]结合表明这是一个数组,去掉数组部分,剩余部分是数组元素类型,剩余部分是int(*) (),很明显类型是函数指针,所以这是一个元素类型为函数指针的数组。这样是不是有所理解了呢。

3.2 转移表

举例:计算器加减乘除的实现

#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;do{printf("***********************\n");printf("*1.add           2.sub*\n");printf("*        0.exit       *\n");printf("*3.mul           4.div*\n");printf("***********************\n");printf("请选择:>\n");scanf("%d", &input);switch (input){case 1:printf("输⼊操作数:>\n");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

上面代码没有处理除数为0的情况,而且代码非常冗余,下面这一段代码重复了多次,只有选择函数部分不一致,如果我们加减乘除函数存放在数组中,通过数组选择加减乘除函数,是不是大大减少了代码量呢?下面我们实现一下。

 printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = func(x, y);printf("ret = %d\n", ret);break;

 使用函数指针数组实现:

//转移表实现计算器加减乘除(使用函数指针数组)
#include<stdio.h>void menu()
{printf("***********************\n");printf("*1.add           2.sub*\n");printf("*        0.exit       *\n");printf("*3.mul           4.div*\n");printf("***********************\n");
}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 = 0;int y = 0;int input = 1;int (*p[5])(int x, int y) = { NULL, add, sub, mul, div };do{menu();printf("请选择:>\n");scanf("%d", &input);//判断有效性if (input >= 1 && input <= 4){printf("请输入操作数:>\n");scanf("%d %d", &x, &y);//判断除数为0的情况if (input == 4 && y == 0){printf("除数不能为0\n");continue;}int ret = (*p[input])(x, y);printf("%d\n", ret);}else if (input == 0){printf("退出计算器\n");break;}else{printf("输入有误,请重新输入\n");}} while (input);return 0;
}

4. 回调函数

4.1回调函数定义

回调函数就是通过函数指针调用的函数。

当我们将一个函数的指针(地址)作为参数传递给另一个函数时,如果该指针被用于调用其指向的函数,这种被间接调用的函数就称为回调函数。回调函数的独特之处在于:它不是由函数定义方直接调用,而是在特定事件或条件发生时由第三方触发,用于响应相应的事件或条件。

4.2回调函数的应用(加减乘除计算器优化)

在上面第三节中我们实现加减乘除计算器时,可不可以用回调函数实现呢,封装一个函数,把加减乘除函数的地址作为参数传给函数,下面编程实现一下。

#include <stdio.h>void menu()
{printf("***********************\n");printf("*1.add           2.sub*\n");printf("*        0.exit       *\n");printf("*3.mul           4.div*\n");printf("***********************\n");
}
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 func(int (*p)(int,int))
{int x = 0;int y = 0;int z = 0;printf("输⼊操作数:>\n");scanf("%d %d", &x, &y);z = (*p)(x,y);
printf("%d\n", z);
}
int main()
{int input = 1;int ret = 0;do{menu();printf("请选择:>\n");scanf("%d", &input);switch (input){case 1:func(add);break;case 2:func(sub);break;case 3:func(mul);break;case 4:func(div);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

5. qsort使用举例及模拟实现(冒泡排序)

qsort是C语言中用于排序各种同类型数据的库函数,使用者需要实现一个比较函数,传入待比较的两个参数,并返回一个整型数据。(返回值>0,前者>后者;返回值<0,前者<后者;返回值=0,前者=后者)

5.1 qsort使用举例

5.1.1 使用qsort排序整型数据

//使用qsort排序整形
#include<stdio.h>
;
int int_cmp(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = { 0,5,4,2,3,1,6,7,8,9 };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}return 0;
}

5.1.2 使用qsort排序结构体

//使用qsort排序结构体
#include<stdio.h>
#include<string.h>struct Person
{char name[20];int age;
};void print(struct Person p[])
{for (int i = 0; i < 3; i++){printf("%s %d \n", p[i].name, p[i].age);}
}//按照年龄排序
int stu_cmp_by_age(const void* p1, const void* p2)
{return ((struct Person*)p1)->age - ((struct Person*)p2)->age;
}
//按照年龄排序
void test1()
{struct Person s[] = { {"zhangsan", 21}, {"lisi", 25}, {"wangwu", 29} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), stu_cmp_by_age);print(s);
}//按照名字排序
int stu_cmp_by_name(const void* p1, const void* p2)
{return strcmp(((struct Person*)p1)->name, ((struct Person*)p2)->name);
}//按照名字排序
void test2()
{struct Person s[] = { {"zhangsan", 21}, {"lisi", 25}, {"wangwu", 29} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), stu_cmp_by_name);print(s);
}int main()
{test1();test2();return 0;
}

5.2 qsort的模拟实现(冒泡排序)

在指针篇1中我们学习了冒泡排序,这次我们使用冒泡排序的方法模拟实现一下qsort库函数。因为qsort不知道使用者要比较的是什么类型数据,所以传入的参数地址要使用void*类型(void*类型在指针篇2.3.3中提到)

//qsort模拟实现(使用冒泡排序)
#include<stdio.h>int int_cmp(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}void Swap(char* one, char* two, size_t width)
{for (int i = 0; i < width; i++){char tmp = *one;*one = *two;*two = tmp;one++;two++;}
}void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void*,const void*))
{for (int i = 0; i < sz - 1; i++){for (int j = 0; j < sz - 1 - i; j++){//数组内部两两比较if (cmp((char*)base + j * width, (char*)base+(j + 1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;bubble_sort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

6. sizeof和strlen的对比

6.1 sizeof

sizeof是C语言中的一个单目操作符,sizeof是计算变量所占内存空间大小的,单位是字节,如果操作数是数据类型的话,计算的是使用此类型创建的变量所占内存空间的大小。代码示例:

#inculde <stdio.h>int main()
{int a = 10;printf("%d\n", sizeof(a));printf("%d\n", sizeof a);printf("%d\n", sizeof(int));//都可以正常输出4return 0;
}

6.2 strlen

strlen是C语言库函数,使用需要包含头文件string.h,功能是求字符串长度。函数原型:

size_t strlen ( const char * str );

统计的是传入参数的地址到遇到 \0 之前字符串中字符的个数,strlen会一直向后找直到找到 \0 为止,所以可能存在越界查找。

#include <stdio.h>int main()
{char arr1[3] = {'a', 'b', 'c'};char arr2[] = "abc";printf("%d\n", strlen(arr1));//随机值printf("%d\n", strlen(arr2));//3printf("%d\n", sizeof(arr1));//3printf("%d\n", sizeof(arr2));//4return 0;
}

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

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

相关文章

Python+Selenium自动化爬取携程动态加载游记

1. 引言 在旅游行业数据分析、舆情监测或竞品研究中&#xff0c;获取携程等平台的游记数据具有重要价值。然而&#xff0c;携程的游记页面通常采用动态加载&#xff08;Ajax、JavaScript渲染&#xff09;&#xff0c;传统的**<font style"color:rgb(64, 64, 64);backg…

ESP8266服务器建立TCP连接失败AT+CIPSTART=“TCP“,“192.168.124.1“,8080 ERROR CLOSED

1.检查服务器端口8081是否开启监听2.检查路由项是否被防火墙拦截方法 1&#xff1a;使用 netsh查看防火墙规则​netsh advfirewall firewall show rule nameall dirout | findstr "8081"如果无输出&#xff0c;说明防火墙未针对该端口设置规则&#xff08;可能默认拦…

Linux 内存管理(2):了解内存回收机制

目录一、透明大页1.1 原理1.2 透明大页的三大优势1.3 透明大页控制接口详解1.4 使用场景与最佳实践1.5 问题排查与监控1.6 与传统大页的对比二、Linux伙伴系统水位机制详解2.1 三种核心水位详解2.2 水位在伙伴系统中的实现2.3 水位触发机制的实际行为2.4 水位关键操作接口2.5 水…

前端学习7:CSS过渡与动画--补间动画 (Transition) vs 关键帧动画 (Animation)

一、补间动画&#xff08;Tween Animation&#xff09;vs 关键帧动画&#xff08;Keyframe Animation&#xff09;概念对比表&#xff1a;补间动画 (Transition)关键帧动画 (Animation)定义元素从初始状态到结束状态的过渡效果通过定义多个关键帧控制动画的中间状态触发方式需要…

PyTorch 损失函数详解:从理论到实践

目录 一、损失函数的基本概念 二、常用损失函数及实现 1. 均方误差损失&#xff08;MSELoss&#xff09; 2. 平均绝对误差损失&#xff08;L1Loss/MAELoss&#xff09; 3. 交叉熵损失&#xff08;CrossEntropyLoss&#xff09; 4. 二元交叉熵损失&#xff08;BCELoss&…

MinIO深度解析:从核心特性到Spring Boot实战集成

在当今数据爆炸的时代&#xff0c;海量非结构化数据的存储与管理成为企业级应用的关键挑战。传统文件系统在TB级数据面前捉襟见肘&#xff0c;而昂贵的云存储服务又让中小企业望而却步。MinIO作为一款开源高性能对象存储解决方案&#xff0c;正以其独特的技术优势成为开发者的首…

腾讯云服务上下载docker以及使用Rabbitmq的流程

执行以下命令&#xff0c;添加 Docker 软件源并配置为腾讯云源。sudo yum-config-manager --add-repohttps://mirrors.cloud.tencent.com/docker-ce/linux/centos/docker-ce.repo sudo sed -i "s/download.docker.com/mirrors.tencentyun.com\/docker-ce/g" /etc/yu…

UE5 一些关于过场动画sequencer,轨道track的一些Python操作

删除多余的轨道 import unreal def execute():movie_scene_actors []sequence_assets []data 0.0# 获取编辑器实用工具库lib unreal.EditorUtilityLibrary()selected_assets lib.get_selected_assets()for asset in selected_assets:if asset.get_class() unreal.LevelS…

前端性能优化“核武器”:新一代图片格式(AVIF/WebP)与自动化优化流程实战

前端性能优化“核武器”&#xff1a;新一代图片格式(AVIF/WebP)与自动化优化流程实战 当你的页面加载时间超过3秒时&#xff0c;用户的跳出率会飙升到40%以上。而在所有的前端性能优化手段中&#xff0c;图片优化无疑是投入产出比最高的一环。一张未经优化的巨大图片&#xff0…

单元测试学习+AI辅助单测

标题单元测试衡量指标具体测试1、Resource2、MockBean3、Test4、Test模板5、单测示例H2数据库JSON1、使用方式AI辅助单测使用方法单元测试 单元测试一般指程序员在写好代码后&#xff0c;提交测试前&#xff0c;需要验证自己的代码是否可以正常工作&#xff0c;同时将自己的代…

Spring Cloud Gateway与Envoy Sidecar在微服务请求路由中的架构设计分享

Spring Cloud Gateway与Envoy Sidecar在微服务请求路由中的架构设计分享 在现代微服务架构中&#xff0c;请求路由层承担着流量分发、安全鉴权、流量控制等多重职责。传统的单一网关方案往往面临可扩展性和可维护性挑战。本文将从真实生产环境出发&#xff0c;分享如何结合Spri…

GitHub Pages+Jekyll 静态网站搭建(二)

GitHub PagesJekyll 静态网站搭建&#xff08;二&#xff09;GitHub PagesJekyll 静态网站搭建&#xff08;二内容简介搭建模板网站部署工作流程GitHub PagesJekyll 静态网站搭建&#xff08;二 内容简介 &#x1f6a9; Tech Contents 该文主要涉及Jekyll主题的下载与使用。Gi…

Django 实战:I18N 国际化与本地化配置、翻译与切换一步到位

文章目录一、国际化与本地化介绍定义相关概念二、安装配置安装 gettext配置 settings.py三、使用国际化视图中使用序列化器和模型中使用四、本地化操作创建或更新消息文件消息文件说明编译消息文件五、项目实战一、国际化与本地化介绍 定义 国际化和本地化的目标&#xff0c;…

通过国内扣子(Coze)搭建智能体并接入discord机器人

国内的扣子是无法直接授权给discord的&#xff0c;但是用国外的coze的话&#xff0c;大模型调用太贵&#xff0c;如果想要接入国外的平台&#xff0c;那就需要通过调用API来实现。 1.搭建智能体&#xff08;以工作流模式为例&#xff09; 首先&#xff0c;我们需要在扣子平台…

【办公类-107-02】20250719视频MP4转gif(削减MB)

背景需求 最近在写第五届智慧项目结题(一共3篇)写的昏天黑地,日以继夜。 我自己《基于“AI技术”的幼儿园教学资源开发和运用》提到了AI绘画、AI视频和AI编程。 为了更好的展示AI编程的状态,我在WORD里面插入了MP4转gif的动图。 【教学类-75-04】20241023世界名画-《蒙…

一文讲清楚React的render优化,包括shouldComponentUpdate、PureComponent和memo

文章目录一文讲清楚React的render优化&#xff0c;包括shouldComponentUpdate、PureComponent和memo1. React的渲染render机制2. shouldComponentUpdate2.1 先上单组件渲染&#xff0c;验证state变化2.2 上父子组件&#xff0c;验证props2. PureComponent2.1 单组件验证state2.…

物联网iot、mqtt协议与华为云平台的综合实践(万字0基础保姆级教程)

本学期的物联网技术与应用课程&#xff0c;其结课设计内容包含&#xff1a;mqtt、华为云、PyQT5和MySQL等结合使用&#xff0c;完成了从华为云配置产品信息以及转发规则&#xff0c;到mqtt命令转发&#xff0c;再到python编写逻辑代码实现相关功能&#xff0c;最后用PyQT5实现面…

使用IntelliJ IDEA和Maven搭建SpringBoot集成Fastjson项目

使用IntelliJ IDEA和Maven搭建SpringBoot集成Fastjson项目 下面我将详细介绍如何在IntelliJ IDEA中使用Maven搭建一个集成Fastjson的SpringBoot项目&#xff0c;包含完整的环境配置和代码实现。 一、环境准备 软件要求 IntelliJ IDEA 2021.x或更高版本JDK 1.8或更高版本&#x…

Java从入门到精通!第九天, 重点!(集合(一))

十一、集合1. 为什么要使用集合(1) 数组存在的弊端1) 数组在初始化之后&#xff0c;长度就不能改变&#xff0c;不方便扩展。2) 数组中提供的属性和方法比较少&#xff0c;不便于进行添加、删除、修改等操作&#xff0c;并且效率不高&#xff0c;同时无法直接存储元素的个数。3…

为什么使用时序数据库

为什么使用时序数据库&#xff1f; 时序数据库&#xff08;Time-Series Database, TSDB&#xff09;是专为时间序列数据优化的数据库&#xff0c;相比传统关系型数据库&#xff08;如MySQL&#xff09;或NoSQL数据库&#xff08;如MongoDB&#xff09;&#xff0c;它在以下方面…