【C语言】指针的进阶2

news2024/12/23 14:00:16

指针进阶

  • 函数指针数组
  • 指向函数指针数组的指针
  • 回调函数
  • 指针和数组经典题目的解析

函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

int* arr[10];//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组。那下面哪一个是函数指针数组呢?

int (*parr1[10])();
int* parr2[10]();
int (*)() parr3[10];

答案是:parr1。 parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。
函数指针数组的用途:转移表。
请看下面的一个简单计算器的实例:

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(" 3:mul     4:div \n");
		printf("========================\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入操作数:");
			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;
}

那么用函数指针数组怎么实现呢?请看下面的代码:

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) = { NULL, add, sub, mul, div }; //转移表
	while (input)
	{
		printf("======================\n");
		printf(" 1:add    2:sub \n");
		printf(" 3:mul    4:div \n");
		printf("======================\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("请输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
		}
		else
			printf("输入有误\n");
		printf("ret = %d\n", ret);
	}
	return 0;
}

指向函数指针数组的指针

指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针 。我们来看下面的代码:

	int (*pf)(int, int);//函数指针
	int (*pfArr[5])(int, int);//函数指针数组
	//&pfArr函数指针数组的地址,p就是指向函数指针数组的指针
	int (*(*p)[5])(int, int) = &pfArr;

解析:p先和 * 结合说明是一个指针,之后与[]结合,说明是一个数组指针,再与*结合说明用一个指针指向了数组指针,之后又指向了一个函数的地址,该函数有两个int类型参数,返回值是int。

总而言之,指向函数指针数组的指针就是在函数指针数组的基础上,再加一个 * 表示一个指针去指向它。

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
在这里插入图片描述
首先演示一下qsort函数的使用:
在这里插入图片描述
在这里插入图片描述

#include <stdlib.h>//qsort需要引入头文件
int int_cmp(const void * p1, const void * p2)
{
	return (*( int *)p1 - *(int *) p2);
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	qsort(arr, sz, sizeof (int), int_cmp);
	for (i = 0; i< sz; i++)
	{
		printf( "%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

使用回调函数,模拟实现qsort(采用冒泡的方式)

int int_cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
void swap(void* p1, void* p2, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)p1 + i);
		*((char*)p1 + i) = *((char*)p2 + i);
		*((char*)p2 + i) = tmp;
	}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < count - 1; i++)
	{
		for (j = 0; j < count - i - 1; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble(arr, sz, sizeof(int), int_cmp);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

void* 的指针是无具体类型的指针,它可以接收任意类型的地址,这种类型的指针是不能直接进行解引用操作,也不能直接进行指针运算。

测试qsort排序结构体数据

struct Stu
{
	char name[10];
	int age;
};
//按年龄排序
int cmp_stu_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
//按姓名排序
int cmp_stu_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int main()
{
	struct Stu arr[] = { {"zhangsan", 17}, {"lisi", 18},{"wangwu", 15} };
	int sz = sizeof(arr) / sizeof(arr[0]); 
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);
	int i = 0;
	for (i = 0; i < sz; i++) 
	{
		printf("%d\n", arr[i].age);
	}
	return 0;
}

指针和数组经典题目的解析

数组名是数组首元素的地址但有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名,这里的数组名表示的是整个数组,取出的是整个数组的地址。
请看下面的题目:

//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//4*4=16字节
printf("%d\n",sizeof(a+0));//数组名a是数组首元素地址,a+0还是首地址,地址大小为4/8字节
printf("%d\n",sizeof(*a));//数组名a是数组首元素地址,*a就是首元素,大小为4字节
printf("%d\n",sizeof(a+1));//数组名a是数组首元素地址,a+1就是第二个元素的地址,大小为4/8字节
printf("%d\n",sizeof(a[1]));//数组第二个元素,大小为4字节
printf("%d\n",sizeof(&a));//&a是数组的地址,数组的地址也是地址大小为4/8字节
printf("%d\n",sizeof(*&a));//*和&相互抵消,所以*&a相当于a,所以大小为16个字节
printf("%d\n",sizeof(&a+1));//&a是整个数组的地址,&a+1就是跳过整个数组,但结果任然是一个地址,大小为4/8字节
printf("%d\n",sizeof(&a[0]));//表示首元素地址,大小为4/8个字节
printf("%d\n",sizeof(&a[0]+1));//表示第二个元素的地址,大小为4/8个字节
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//arr表示整个数组,计算的是整个数组的大小,总共6个字节
printf("%d\n", sizeof(arr+0));//arr表示数组首元素的地址,arr+0还是数组首元素的地址,是地址就是4/8个字节
printf("%d\n", sizeof(*arr));//arr表示数组首元素的地址,*arr就是首元素,大小1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]就是数组第二个元素,大小是1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,但是数组的地址也是地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr+1));//&arr + 1是跳过整个数组后的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr[0]+1));//表示第二个元素的地址,是4/8个字节

printf("%d\n", strlen(arr));//因为字符数组arr中没有\0,所以在求字符串长度的时候,会一直往后找直到找到\0,所以结果就是随机值
printf("%d\n", strlen(arr+0));//arr + 0是首元素的地址,和第一个一样,也是随机值
printf("%d\n", strlen(*arr));//error
//strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,'a'的ASCII码值是97,那就是将97作为地址传参,就会从97这个地址开始统计字符串长度,这就非法访问内存了
printf("%d\n", strlen(arr[1]));//error 原因同上
printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,那么传递给strlen函数后,依然是从数组的第一个元素的位置开始往后统计
printf("%d\n", strlen(&arr+1));//原因同上也是随机值
printf("%d\n", strlen(&arr[0]+1));//&arr[0] + 1是第二个元素的地址。结果也是随机值
char arr[] = "abcdef";//等价于[a b c d e f \0]
printf("%d\n", sizeof(arr));//7个字节
printf("%d\n", sizeof(arr+0));//arr + 0是首元素的地址,大小1个字节
printf("%d\n", sizeof(*arr));//*arr其实就是首元素,大小1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,大小1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr+1));//&arr + 1是跳过一个数组的地址,结果仍然是地址大小为4/8个字节
printf("%d\n", sizeof(&arr[0]+1));//&arr[0] + 1是第二个元素的地址 大小为4/8个字节

printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr+0));//6
printf("%d\n", strlen(*arr));//error
printf("%d\n", strlen(arr[1]));//error
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr+1));//跳过整个数组向后数,后面是未知的所以结果是随机值
printf("%d\n", strlen(&arr[0]+1));//从第二个元素往后数,长度为5
char* p = "abcdef";
printf("%d\n", sizeof(p));//p是一个指针变量大小就是4/8个字节
printf("%d\n", sizeof(p+1));//p+1是'b'的地址,是地址大小就是4/8个字节
printf("%d\n", sizeof(*p));//*p 就是'a',大小就是1个字节
printf("%d\n", sizeof(p[0]));//p[0]--> *(p+0) --> *p 大小1个字节
printf("%d\n", sizeof(&p));//&p --> char** 大小4/8个字节
printf("%d\n", sizeof(&p+1));//直接跳到字符串后面的,实际还是地址,大小4/8个字节
printf("%d\n", sizeof(&p[0]+1));//&p[0] + 1得到是'b'的地址,大小4/8个字节

printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p+1));//5
printf("%d\n", strlen(*p));//error
printf("%d\n", strlen(p[0]));//error
printf("%d\n", strlen(&p));//随机值
printf("%d\n", strlen(&p+1));//随机值
printf("%d\n", strlen(&p[0]+1));//5
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));//3*4*4 = 48
printf("%d\n",sizeof(a[0][0]));//4
printf("%d\n",sizeof(a[0]));//a[0]是第一行这个一维数组的数组名,数组名算是单独放在sizeof内部了,计算的是整个数组的大小,大小是16个字节
printf("%d\n",sizeof(a[0]+1));//a[0]作为第一行的数组名,没有单独放在sizeo内部,没有&,a[0]表示数组首元素的地址,也就是a[0][0]的地址,所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节
printf("%d\n",sizeof(*(a[0]+1)));//计算的是第一行第2个元素的大小,为4个字节
printf("%d\n",sizeof(a+1));//a是数组首元素的地址,是第一行的地址,a+1就是第二行的地址,是地址大小就是4/8个字节  (它的类型是int(*)[4])
printf("%d\n",sizeof(*(a+1)));//*(a+1) --> a[1] -> sizeof(*(a+1))->sizeof(a[1]) 计算的是第二行的大小,就是16个字节
printf("%d\n",sizeof(&a[0]+1));//&a[0]是第一行的地址,&a[0]+1 是第二行的地址,是地址大小就是4/8个字节
printf("%d\n",sizeof(*(&a[0]+1)));//计算的是第二行的大小,16个字节
printf("%d\n",sizeof(*a));//a是数组首元素的地址,就是第一行的地址,*a 就是第一行,*a --> *(a+0) --> a[0],大小为16个字节
printf("%d\n",sizeof(a[3]));//第三行的大小,16个字节

总结:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

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

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

相关文章

React实现点击切换组件

实现如上组件 组件代码&#xff1a; import { SwapOutlined } from "ant-design/icons" import React, { useState } from "react" import ./index.lessinterface ISwitchTypeProps {onChange?: (val) > booleanactiveKey?: stringleft: { key: str…

C语言 指针的运算

一、介绍 在C语言中&#xff0c;指针的运算分为三类 1、指针 整数 、指针 - 整数2、指针 - 指针3、指针的关系运算 二、指针 整数 、指针 - 整数 因为数组在内存中是连续存放的&#xff0c;只要知道第一个元素的地址&#xff0c;顺藤摸瓜就能找到后面的所…

Chrome有些网站打不开,但是火狐可以打开

Chrome有些网站打不开&#xff0c;但是火狐可以打开 问题描述火狐成功界面谷歌报错界面局域网设置使用代理服务器访问成功 解决方案参考 问题描述 开了一个tizi&#xff0c;Chrome不能使用&#xff0c;火狐可以。之前装过插件Ghelper白嫖科学上网&#xff0c;那次之后好像浏览…

机器人CPP编程基础-02变量Variables

机器人CPP编程基础-01第一个程序Hello World 基础代码都可以借助人工智能工具进行学习。 C #include<iostream>using namespace std;main() {//Declaring an integer type variable A, allocates 4 bytes of memory.int A4;cout<<A <<endl;//Prints the a…

Rust语法:变量,函数,控制流,struct

文章目录 变量可变与不可变变量变量与常量变量的Shadowing标量类型整数 复合类型 函数控制流if elseloop & whilefor in structstruct的定义Tuple Structstruct的方法与函数 变量 可变与不可变变量 Rust中使用let来声明变量&#xff0c;但是let声明的是不可变变量&#x…

【校招VIP】java语言考点之static和并发

考点介绍&#xff1a; static考点是面试的高频考点&#xff0c;很多同学不理解使用场景&#xff0c;只是从加载出发。 一般从容易到难提问&#xff0c;比如从static的含义和理解、到JVM的存储或者到线程安全性&#xff0c;再到单例模式等。 java语言考点之static和并发 相关题…

Python web实战之Django 的缓存机制详解

关键词&#xff1a;Python、Web 开发、Django、缓存 1. 缓存是什么&#xff1f;为什么需要缓存&#xff1f; 在 Web 开发中&#xff0c;缓存是一种用于存储数据的临时存储区域。它可以提高应用程序的性能和响应速度&#xff0c;减轻服务器的负载。 当用户访问网页时&#xff…

竞赛项目 深度学习的智能中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…

[Kubernetes]Kubeflow Pipelines - 基本介绍与安装方法

1. 背景 近些年来&#xff0c;人工智能技术在自然语言处理、视觉图像和自动驾驶方面都取得不小的成就&#xff0c;无论是工业界还是学术界大家都在惊叹一个又一个的模型设计。但是对于真正做过算法工程落地的同学&#xff0c;在惊叹这些模型的同时&#xff0c;更多的是在忧虑如…

React使用antd的图片预览组件,点击哪个图片就预览哪个的设置

使用了官方推荐的相册模式的预览&#xff0c;但是点击预览之后&#xff0c;每次都是从图片列表的第一张开始预览&#xff0c;而不是点击哪张就从哪张开始预览&#xff1a; 所以这里我就封装了一下&#xff0c;对初始化预览的列表进行了逻辑处理&#xff1a; 当点击开始预览的…

分析 Linux 启动流程基本实现

下载 Linux 内核网址&#xff1a; https://www.kernel.org/ 最新 Linux 内核是 5.15 版本。现在常用 Linux 内核源码为4.14、4.19、4.9 等版本&#xff0c;其中 4.14 版本源码压缩包大概 90M&#xff0c;解压后 700M&#xff0c;合计 61350 个文件。如此众多的文件&#xff0…

【前端 | CSS】盒模型clientWidth、clientHeight、offsetWidht、offsetHeight

图 先看一个例子 html <div class"container"><div class"item">内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容</div> </…

C++ 混合Python编程 及 Visual Studio配置

文章目录 需求配置环节明确安装的是64位Python安装目录 创建Console C ProjectCpp 调用 Python Demo 参考 需求 接手了一个C应用程序&#xff0c;解析csv和生成csv文件&#xff0c;但是如果要把多个csv文件合并成一个Excel&#xff0c;分布在不同的Sheet中&#xff0c;又想在一…

3D Web轻量化引擎HOOPS Communicator如何实现对BIM桌面端的支持?

HOOPS Communicator是一款简单而强大的工业级高性能3D Web轻量化渲染开发包&#xff0c;其主要应用于Web领域&#xff0c;主要加载其专有的SCS、SC、SCZ格式文件&#xff1b;HOOPS还拥有另一个桌面端开发包HOOPS Visualize&#xff0c;主要加载HSF、HMF轻量化格式文件。两者虽然…

Ant Design Vue 下拉框输入框 可以输入 可以查询

Ant Design Vue 下拉框 可以输入 可以查询 直接上代码 效果图 &#xff08;输入内容查询后端 返回下拉的值 &#xff0c;如何查询后端是空的直接 把输入的内容 赋值给 输入框&#xff09; 在这里插入图片描述 <template><div><a-selectv-model.lazy"i…

网络服务之DHCP

DHCP 一.了解DHCP1.1 DHCP是什么1.2DHCP好处1.3DHCP 的分配方式1.4DHCP一次完整过程1.5 DHCP报文 二.Linux系统中的DHCP2.1安装DHCP服务2.2配置文件 三.模拟实现DHCP服务四.虚拟内网环境中实现时间同步 一.了解DHCP 1.1 DHCP是什么 DHCP&#xff1a;动态主机配置协议&#xf…

Vue中data变量使用的注意事项

因为在Vue中&#xff0c;data中的属性往往都是用于双向绑定&#xff0c;所以Vue会对其有劫持&#xff0c;所以我们在对data属性进行操作时&#xff0c;尽量不要对其直接操作&#xff0c;比如下面代码&#xff1a; export default {data() {return {list: []}},methods: {init(…

Oracle 开发篇+Java调用OJDBC访问Oracle数据库

标签&#xff1a;JAVA语言、Oracle数据库、Java访问Oracle数据库释义&#xff1a;OJDBC是Oracle公司提供的Java数据库连接驱动程序 ★ 实验环境 ※ Oracle 19c ※ OJDBC8 ※ JDK 8 ★ Java代码案例 package PAC_001; import java.sql.Connection; import java.sql.ResultSet…

易服客工作室:WordPress 6.3 Lionel发布

WordPress 6.3 Lionel已经发布&#xff0c;它以美国著名爵士乐艺术家莱昂内尔汉普顿 (Lionel Hampton)的名字命名。汉普顿是一位多产的爵士颤音琴演奏家、钢琴家和打击乐演奏家&#xff0c;因与查尔斯明格斯、昆西琼斯等伟大人物合作以及作为同名莱昂内尔汉普顿管弦乐团的乐队领…

SpringBoot 3.x整合Fluent Mybatis极简流程

此为基础配置&#xff0c;不包括其他高级配置&#xff0c;需要其他高级配置请查阅官方文档&#xff1a;[fluent mybatis特性总览 - Wiki - Gitee.com](https://gitee.com/fluent-mybatis/fluent-mybatis/wikis/fluent mybatis特性总览) 版本信息 Spring Boot 版本&#xff1a…