《C和指针》读书笔记(第十三章 高级指针话题)

news2024/11/25 13:02:46

目录

  • 0 简介
  • 1 进一步探讨指向指针的指针
  • 2 高级声明
  • 3 函数指针
    • 3.1 回调函数
    • 3.2 转移表
  • 4 命令行参数
    • 4.1 传递命令行参数
    • 4.2 处理命令行参数
  • 5 字符串常量
  • 6 总结

0 简介

众所周知,指针是C语言的灵魂,所以本书(《C和指针》)才会将较多的笔墨放在指针的相关话题上,本章我们将看到更多关于指针的应用,更好地诠释了C语言的独特魅力。

本期内容概览:
在这里插入图片描述

1 进一步探讨指向指针的指针

如果学会了指针,也就不难理解指向指针的指针,也就是说,当前指针指向的地址中又存在一个指针,该指针指向的地址中才保存着我们需要访问的数值。

可以通过一个简单的例子验证一下:

#include <stdio.h>
int main()
{
	int i = 10;
	int *pi = &i;
	int **ppi = &pi;
	printf("i = %d\n",i);
	printf("*pi = %d\n", *pi);
	printf("**ppi = %d\n", **ppi);
}

运行,打印输出:
在这里插入图片描述

可以看出,与我们预想的结果是一致的。

2 高级声明

书中列举了一些高级指针的声明问题,除非想融会贯通,否则只需要看其余部分就可以,因为其余部分已经列举了具体的声明方法。这部分只是有更深层次的解释而已。

3 函数指针

函数指针,顾名思义,就是指向函数的指针,本质上与普通的指针并没有太大的区别,只是在形式上有一些区别而已,在用法上也更加有意思。

3.1 回调函数

通俗来说,回调函数就把一个函数指针作为参数传递给其他函数,后者将“回调”用户的函数。被当作参数传递的函数就称作回调函数

书上有这样一段描述:

任何时候,如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行只能由函数调用者定义的工作,你都可以使用这个技巧。许多窗口系统使用回调函数连接多个动作,如拖拽鼠标和点击按钮来指定用户程序中的某个特定函数。

听起来有点绕,具体说来就是,当我们设置好了某个接口(功能),但我们需要的某一部分无法提前预知,这个时候我们就需要回调我们后来好的函数去执行具体的任务,也就是说,需要根据具体情况去考虑具体的实现。

举个例子,在C语言中的stdlib库中,封装好了一个实现快速排序算法的函数qsort,在调用的时候,需要我们自己去写排序函数。

快速排序算是一种非常经典且常见的排序算法,对排序算法的具体实现原理感兴趣的请移步:
十大经典排序算法(C语言实现)
里面有非常详细的解释。

我们先实现一个简单的数组排序,

	int a[] = {1,3,5,11,2};
	qsort(a, 5, sizeof(int), compare);

	for (int i = 0; i < 5; i++)
	{
		printf("a[%d] is %d\n", i, a[i]);
	}

比较函数是这样实现的,

int compare(const void  *a, const void *b)
{
	int *p1 = (int *)a, *p2 = (int *)b;
	if (*p1 > *p2)
	{
		return 1;
	}
	else if (*p1 == *p2)
	{
		return 0;
	}
	else if (*p1 < *p2)
	{
		return -1;
	}
}

既然都封装好了排序函数,为什么不帮我们也写好比较函数呢?

这是因为,排序函数并无法提前预知我们需要比较的数据类型,是整形,浮点型抑或是结构体成员?所以需要我们自己来完成比较函数,好实现排序。书上举的例子也就是这样的思想。当然,回调函数的应用远不止如此。

3.2 转移表

转移表(或者叫转换表)就是一个函数指针数组,这样一来,调用相同类型的函数就变得非常简单,可以直接通过数组下标来找到相关的函数。

假设我们需要设计一个小型的计算器(可以执行加减乘除计算),如果我们采用传统的方法,会写出这样的程序:

enum OPER
{
	ADD = 1,
	SUB,
	MUL,
	DIV
};

double cal_add(double a, double b)
{
	return a + b;
}
double cal_sub(double a, double b)
{
	return a - b;
}
double cal_mul(double a, double b)
{
	return a * b;
}
double cal_div(double a, double b)
{
	return a / b;
}
//使用传统方法
//使用传统方法
double cal_all_1(int oper, double op1, double op2)
{
	double result = 0;
	switch (oper)
	{
	case ADD: result = cal_add(op1, op2); break;
	case SUB: result = cal_sub(op1, op2); break;
	case MUL: result = cal_mul(op1, op2); break;
	case DIV: result = cal_div(op1, op2); break;
	default:
		break;
	}
	return result;
}

现在我们采用转移表实现相同的功能:

先定义如下的转移表

double(*oper_func[])(double, double) = { cal_add, cal_sub, cal_mul, cal_div };

现在算法的执行函数就变成了这样:

//使用函数指针数组
double cal_all_2(int oper, double op1, double op2)
{
	double result = 0;
	if (oper <= 4)
		result = oper_func[oper - 1](op1, op2);
	else
		exit();
	return result;
}

显然,第二种方法简洁了很多,想要执行相关的运算,只需要通过数组下标进行索引,然后传入实参即可。下面我们来看主函数的编写:

#include <stdio.h>
#include <stdlib.h>
#include "transfor_table.h"

int main()
{
	double res1 = 0,res2 = 0;
	
	res1 = cal_all_1(ADD, 10, 10);
	printf("fun_1:10 + 10 = %f\n", res1);

	res2 = cal_all_2(ADD, 10, 10);
	printf("fun_2:10 + 10 = %f\n", res2);

	system("pause");
	return 0;
}

执行,打印输出如下的内容:
在这里插入图片描述

可以发现,二者执行后产生了相同的结果,只是这样的测试并不严谨,需要更多的测试案例去支撑,为了说明问题,这里只是做了简单比较而已。

注意:除法运算的函数不要写成书中那样(也就是div()),这样会与C语言库中的函数命名冲突,导致编译报错,提示重命名。

4 命令行参数

在有的C程序中,main函数会比较特殊,其自身也接受两个参数,就像下面这样

int main(int argc, char **argv)

这连个形参接受的就是命令行传过来的参数!

4.1 传递命令行参数

在上述的main函数中,第1个参数称为argc,表示命令行参数的数目,第2个参数称为argv,它指向一组参数值(估计是argument counterargument vector的缩写)。

这个程序的编译和运行稍微麻烦一些,具体的操作方法可以参考VS使用Developer Command Prompt 命令行编译和执行C++代码

举个例子,编写如下的程序:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
	printf("%d\n", argc);
	while(*++argv != NULL)
		printf("%s\n", *argv);
	return EXIT_SUCCESS;
}

4.2 处理命令行参数

编译并运行以上程序,输出如下:
在这里插入图片描述
第一个参数是自动统计我们输入字符串参数个数的,显示3,应该我们点击回车也算一个?然后正确打印出了我们输入的内容。

5 字符串常量

单纯的字符串也代表其第一个元素的指针,这点可以和数组对比,只不过数组是数组名。我们先看个例子:

void string_basic()
{
	printf("%c\n",*("xyz" + 1));
	printf("%c\n", "xyz"[2]);
}

打印输出
在这里插入图片描述
如果"xyz"是首元素的指针的话,那么+1表示往后移动一个元素的地址,然后再间接访问,得到第二个元素y

当然,与数组类似,字符串也可以通过下标来访问元素,这样一来,就自然而然得到了字符z


另外,书上给了个神秘函数,但并未揭晓谜底,这个神秘函数的功能到底是什么。一起来看看。
函数定义如下:

//参数是一个0~100的值
void mystery(int n)
{
	n += 5;
	n /= 10;
	printf("%s\n", "**********" + 10 - n);
}

前两个语句很容易理解,就是将一个整数四舍五入之后再求其十位上的数值。最后一个printf语句就需要理解字符串常量了,经过分析,打印输出的*号数量就是刚刚计算出来的十位上的数值大小。可以做个实验:

	for(int i = 0; i < 10; i++)
		mystery(i*10);

则打印输出:
在这里插入图片描述
若不是10的整数倍,也可以得到类似的输出,处理方法都是一样的。


书中最后一部分又增加了一个例子,将二进制值转换为字符串,一起来看看:

void binary_to_ascii(unsigned int value)
{
	unsigned int quotient;
	quotient = value / 10;
	if (quotient != 0)
		binary_to_ascii(quotient);
	putchar(value % 10 + '0');
}

注意这里用了递归,并不是很难理解,在主函数中这样调用:

	binary_to_ascii(1001100);

打印输出:
在这里插入图片描述
可见我们的算法没有问题。

书中随后又给了将16进制的某位转化成字符串的方法:

	putchar("0123456789ABCDEF"[value % 16]);

同样地,我们可以仿照上述例子,写一个将16进制数转化为字符串的函数:

void hexadecimal_to_ascii(unsigned int value)
{
	unsigned int quotient;
	quotient = value / 16;
	if (quotient != 0)
		hexadecimal_to_ascii(quotient);
	putchar("0123456789ABCDEF"[value % 16]);
}

然后在主函数中调用:

	hexadecimal_to_ascii(0xF5);

打印输出:
在这里插入图片描述
可以看出,我们得到了正确的答案!

6 总结

回调函数转移表,并不仅仅是理解其工作过程和主要思想,更重要的是知道如何在实际开发中运用它。

如果某个执行环境实现了命令行参数,这些参数是通过两个形参传递给main函数的。这两个形参通常是称为argcargv

拓展阅读:指针函数和函数指针

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

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

相关文章

为安全带来光明:光耦继电器的 10 种救生应用

在安全性和可靠性至关重要的世界中&#xff0c;光耦继电器已成为推动各行业进步的关键技术。这些卓越的设备经常在主流新闻中被忽视&#xff0c;但它们一直在默默地为保障生命和提高整体运营效率的关键系统提供动力。今天&#xff0c;我们重点介绍光耦继电器的十种救生应用&…

设计模式-01简单工厂模式详解 详细代码对比

目录 ChatGpt问答原生代码简单工厂模式代码 简单工厂模式&#xff08;Simple Factory Pattern&#xff09;新增boat 对比两种方法原生代码为什么使用强制转换&#xff1f;简单工厂模式 简单工厂方法总结与原生代码的区别&#xff1a;优点:缺点&#xff1a; 参考 本文将介绍什么…

golang指针的学习笔记

package main // 声音文件所在的包&#xff0c;每个go文件必须有归属的包 import ("fmt" )// 引入程序中需要用的包&#xff0c;为了使用包下的函数&#xff0c;比如&#xff1a;Printin// 字符类型使用 func main(){ // 基本数据类型&#xff0c;变量存的就是值&am…

做tiktok怎么运营?

一、揭开tiktok的神秘面纱 说到tiktok&#xff0c;你可能想到的是那些精彩的短视频&#xff0c;以及那些在几秒钟内就能吸引无数粉丝的创作者。然而&#xff0c;tiktok的魅力远不止于此。这个全球最受欢迎的短视频社交平台&#xff0c;正以惊人的速度改变着社交媒体的面貌。 二…

【IR】Vision-Language Tracking

调研&#xff1a;视觉-语言跟踪 0x01 Transformer vision-language tracking via proxy token guided cross-modal fusion, PRL2023AbstractIntroductionContribution效果Conclusion 0x02 Divert More Attention to Vision-Language Object Tracking, NeurIPS2022AbstractIntro…

起飞!Python 3.11的10个高效新特性

性能有巨大的提升是Python 3.11的一个重要的改进&#xff0c;除此以外Python 3.11还有增加了许多新的特性。在本文中我们将介绍Python 3.11新特性&#xff0c;通过代码示例演示这些技巧如何提高生产力并优化代码。 1、模式匹配 Python 3.11引入了模式匹配&#xff0c;可以简化…

OpenAI 函数调用教程

推荐&#xff1a;使用 NSDT场景编辑器 快速搭建3D应用场景 什么是OpenAI函数调用&#xff1f; OpenAI API 非常擅长以系统的方式生成响应。只需几行代码即可管理提示、优化模型输出以及执行、生成和语言应用程序。 即使有这么多好东西&#xff0c;OpenAI API对开发人员和工程…

【双指针】移动零

双指针-移动零 283. 移动零 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1…

03-JVM内存模型剖析与优化

1. JDK体系结构 2. Java语言的跨平台特性 3. JVM整体结构及内存模型 补充一个问题&#xff1a; 在minor gc过程中对象挪动后&#xff0c;引用如何修改&#xff1f; 对象在堆内部挪动的过程其实是复制&#xff0c;原有区域对象还在&#xff0c;一般不直接清理&#xff0c;JVM内部…

【C++基础】类与对象(上):访问限定符、类作用域、类实例化、类对象模型、this指针

​&#x1f47b;内容专栏&#xff1a; C/C编程 &#x1f428;本文概括&#xff1a; C基础语法。访问限定符、类作用域、类实例化、类对象模型、this指针等。 &#x1f43c;本文作者&#xff1a; 阿四啊 &#x1f438;发布时间&#xff1a;2023.9.6 面向过程和面向对象初识 C语…

【网络爬虫笔记】爬虫Robots协议语法详解

Robots协议是指一个被称为Robots Exclusion Protocol的协议。该协议的主要功能是向网络蜘蛛、机器人等搜索引擎爬虫提供一个标准的访问控制机制&#xff0c;告诉它们哪些页面可以被抓取&#xff0c;哪些页面不可以被抓取。本文将进行爬虫Robots协议语法详解&#xff0c;同时提供…

MQ 消费者和队列对应关系

参考 Consumer and Consumer Group Load Balancing https://rocketmq.apache.org/docs/4.x/consumer/01concept2 旧版本MQ结论 消费者应用和topic队列一对多的关系。 &#xff08;一个消费组consumer group里&#xff0c;一个消费者应用可以消费多个队列的消息。一个队列的消…

Podman安装与使用

1.Podman简介 Podman是一个无守护进程的容器引擎&#xff0c;用于在Linux系统上开发、管理和运行OCI容器。 Podman的主要功能包括&#xff1a; 创建和管理容器&#xff1a;Podman可以创建、启动、停止和删除容器&#xff0c;以及管理容器的生命周期。容器镜像管理&#xff1…

华为云云服务器评测|云耀云服务器L实例快速部署MySQL使用指南

文章目录 前言云耀云服务器L实例介绍什么是云耀云服务器L实例&#xff1f;产品优势智能不卡顿价优随心用上手更简单管理更省心 快速购买查看优惠卷购买 安装MySQL重置密码安装更新apt的软件源列表安装MySQL 设置用户名、密码、权限配置安全组 总结 前言 哈喽大家好&#xff0c…

lumion电脑速度太慢怎么办?还不是试试云电脑高效上云设计

在设计与渲染领域&#xff0c;Lumion是一款广受欢迎的3D软件&#xff0c;然而&#xff0c;使用本地电脑进行Lumion设计和渲染存在电脑卡顿崩溃&#xff0c;导致效率慢问题。本文将介绍Lumion设计师使用云电脑的优势&#xff0c;以及如何利用云电脑提高创作效率、释放无限创意。…

李宏毅-21-hw3:对11种食物进行分类-CNN

一、代码慢慢阅读理解总结内化&#xff1a; 1.关于torch.nn.covd2d()的参数含义、具体用法、功能&#xff1a; &#xff08;1&#xff09;参数含义&#xff1a; 注意&#xff0c;里面的“padding”参数&#xff1a;《both》side所以是上下左右《四》边都会加一个padding数量…

Support for password authentication was removed on August 13, 2021 解决方案

打开你的github&#xff0c;Setting 点击Developer settings。 点击generate new token 按照需要选择scope 生成token&#xff0c;以后复制下来。 给git设置token样式的remote url git remote set-url origin https://你的tokengithub.com/你的git用户名/仓库名称.git然后就可…

MySQL 连接查询和存储过程

一、连接查询 mysql的连接查询&#xff0c;通常都是将来自两个或多个表的记录行结合起来&#xff0c;基于这些表之间的共同字段&#xff0c;进行数据的拼接 首先&#xff0c;要确定一个主表作为结果集&#xff0c;然后将其它表的行有选择性的连接到选定的主表结果上&#xff…

算法训练day41|动态规划 part03(LeetCode343. 整数拆分、96.不同的二叉搜索树)

文章目录 343. 整数拆分思路分析代码实现 96.不同的二叉搜索树思路分析代码实现 343. 整数拆分 题目链接&#x1f525;&#x1f525; 给定一个正整数 n&#xff0c;将其拆分为至少两个正整数的和&#xff0c;并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 示例 1: …

【2023研电赛】安谋科技企业命题二等奖:基于R329的AI交互早教机器人

本文为2023年第十八届中国研究生电子设计竞赛安谋科技企业命题二等奖分享&#xff0c;参加极术社区的【有奖活动】分享2023研电赛作品扩大影响力&#xff0c;更有丰富电子礼品等你来领&#xff01;&#xff0c;分享2023研电赛作品扩大影响力&#xff0c;更有丰富电子礼品等你来…