灵魂指针,教给(三)

欢迎来到白刘的领域   Miracle_86.-CSDN博客

系列专栏  C语言知识

先赞后看,已成习惯

   创作不易,多多支持!

目录

          一、 字符指针变量

二、数组指针变量

2.1 数组指针变量是什么

2.2 数组指针变量如何初始化

 三、二维数组传参本质

 四、函数指针变量

4.1 函数指针变量的创建

4.2 函数指针的使用

4.3 两段有意思的代码

4.3.1 typedef关键字

 五、函数指针数组

 六、转移表


一、 字符指针变量

指针的类型中有一种为字符指针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;
}

其实上述方法是将字符串的首字符的地址放在了指针pstr里了,也就是字符h的地址。

《剑指offer》中收录了这样一道题:

#include <stdio.h>
int main()
{
	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");

	return 0;
}

来看运行结果:

你猜对结果了嘛,下面我们来简单讲解一下这道题。

首先四个字符串,str1.2.3.4,在3和4的前面用const修饰了,所以这里的str3和str4是常量字符串。

而在C/C++中对于一个常量字符串,是不会额外开辟空间的,所以str3和str4的首地址是一个地址,所以两个字符串也是相同的。而str1和str2不是常量字符串,是可修改操作的,所以分别开辟了两个空间来存放这两个字符串,因此它俩的首字符地址也不相同,所以两个字符串也不相同。

二、数组指针变量

2.1 数组指针变量是什么

前面我们学习了一个类似,非常容易混淆的,叫指针数组,当时也挖坑了,说以后会讲这个。

灵魂指针,教给(二)-CSDN博客

 我们之前说,指针数组,它是数组,那数组指针呢?是指针

我们已经熟悉:

整型指针变量:int * pint; 用来存放整型变量的地址,能指向整型数据的指针。

 

浮点型指针变量:float * pint; 用来存放浮点型变量的地址,能指向浮点型数据的指针。

所以我们能推断出数组指针,它是用来存放数组地址,能指向数组的指针

来思考下面两行代码,分别是什么:

int *p1[10];
int (*p2)[10];

第一行是在前面我们学的指针数组,第二行为数组指针。

如何去记忆呢?我们要记住一个点就好,就是[ ]的优先级是比 * 高的,我们去看变量名和谁先结合就好,第一行先和中括号结合,所以是数组,变量名为数组名;第二行加了小括号,所以先和*结合,成了数组指针。

那数组指针的类型是什么呢?我们在学习数组的时候说去掉数组名,剩下的就是类型,那来看看数组指针的类型怎么写呢?

int (*)[10];

2.2 数组指针变量如何初始化

 数组指针用来存放数组的地址,那怎么获取数组的地址呢?没错,就是我们之前说的&arr。

int arr[10] = {0};
&arr;//得到的就是数组的地址

如果要存放整个数组的地址,就存放在数组指针中:

int(*p)[10] = &arr;

我们调试也能看到&arr和p的地址是一样的。

数组指针类型解析:

int (*p) [10] = &arr;
 |    |    |
 |    |    |
 |    |    p指向数组的元素个数
 |    p是数组指针变量名
 p指向的数组的元素类型

 三、二维数组传参本质

有了数组指针,我们就能详细了解二维数组传参本质了。

之前,我们将一个二维数组传给一个函数的时候,我们往往会这么写:

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;
}

总结:二维数组传参,形参部分可以写成数组,也可以写成指针。

四、函数指针变量

4.1 函数指针变量的创建

通过类比关系,不难理解函数指针。它应该是存放指针地址的,可以通过函数地址调用函数的。

那函数指针到底是不是这样的呢?我们来做个测试:

#include <stdio.h>
void test()
{
	printf("hehe\n");
}
int main()
{
	printf("test: %p\n", test);
	printf("&test: %p\n", &test);
	return 0;
}

运行结果如下:

我们确实打印出来了地址,所以函数确实是有地址的,并且函数名就是地址,所以可以用&函数名的方式获得函数的地址。

我们如果想将函数的地址存放起来,也非常简单,这就用到了函数指针变量,写法其实和数组指针的写法非常类似:

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(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

就是后面的改成参数了而已,非常简单。

函数指针类型详解:

int (*pf3) (int x, int y)
 |     |    ------------ 
 |     |         |
 |     |         pf3指向函数的参数类型和个数的交代
 |     函数指针变量名
 pf3指向函数的返回类型

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

4.2 函数指针的使用

通过函数指针调用指针指向的函数。

#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));
	printf("%d\n", pf3(3, 5));
	return 0;
}

运行结果:

 

我们发现,写(*pf3)(2,3),或者pf3(3,5)都可以。

4.3 两段有意思的代码

代码1

 (*(void (*)())0)();
//首先void(*)()    --这是指针变量
//( void(*)() )0    --强制类型转换
//这就意味着我们假设0地址这个位置放着无参,返回类型为void的函数

代码2

void (*signal(int , void(*)(int)))(int);
//void(* signal(int,void(*)(int)))(int);
//声明了一个函数,signal(int,void(*)(int))
//它有两个参数,一个是int,一个是函数地址
//把signal的地址放进了void类型的函数指针,参数为int

这两段代码均出自于《C陷阱与缺陷这本书》。

4.3.1 typedef关键字

我们好久没讲关键字了,今天就新认识一个,叫typedef。它是用来干什么的呢?用来给类型重命名的,可以将复杂的类型简单化。

举个栗子:比如说你觉得unsigned int 写起来不方便,你想给它改成uint,代码如下:

typedef unsigned int uint;
//将unsigned int 重命名为uint

如果是指针类型,能否重命名呢?当然可以,比如将int*改成ptr_r:

typedef int* ptr_t;

但是对于数组指针和函数指针,稍微有点不同。

比如我们要将int(*arr)[5]改写成parr_t,我们就需要这么改:

typedef int(*parr_t)[5]; //新的类型名必须在*的右边

同理,函数指针也是这样,比如我们将void(*)(int)改成pf_t,就要这么写:

 typedef void(*pfun_t)(int);//新的类型名必须在*的右边

练练手,如果我们将刚刚的代码2进行简化,我们可以这么写:

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

五、函数指针数组

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

比如:

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

如果我们想把函数的地址都存储到一个数组中,那这个数组就叫函数指针数组,它是怎么定义的呢?

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

答案是parr1,

parr1先和[3]结合,说明其是数组,然后数组的内容是什么呢?是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;
	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);
		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;
}

很好想,但是这么写稍微有点冗余了(就是switch里有许多重复或者相似的代码片段)。

而我们学完了函数指针数组,我们就可以这么写,我们把加减乘除的函数都装到数组里,代码如下:

#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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在Linux系统安装SVN并配置固定公网地址远程访问【内网穿透】

文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…

qsort函数

目录 1.qsort函数是什么 1.1qsort函数的原型 2.qsort函数的使用 2.1使用qsort函数排序整型数据 2.2使用qsort排序结构数据 3.qsort函数的模拟实现 1.qsort函数是什么 很多小伙伴们都没有听说过qsort这个函数&#xff0c;qsort函数是C语言标准库中的一个排序函数&#xf…

前端精准测试调用链路分析

精准测试在评估需求的测试范围时&#xff0c;需要评估一下代码的影响范围&#xff0c;这个范围有两部分&#xff1a;一是需求直接修改的代码&#xff1b;二是修改代码影响到的功能模块。代码影响到的功能一般是通过调用链路分析来实现的&#xff0c;java和kotlin代码可以由java…

【Java从入门到精通】Java异常处理

异常是程序中的一些错误&#xff0c;但并不是所有的错误都是异常&#xff0c;并且错误有时候是可以避免的。 比如说&#xff0c;你的代码少了一个分号&#xff0c;那么运行出来结果是提示是错误 java.lang.Error&#xff1b;如果你用System.out.println(11/0)&#xff0c;那么…

每日OJ题_路径dp②_力扣63. 不同路径 II

目录 力扣63. 不同路径 II 解析代码 力扣63. 不同路径 II 63. 不同路径 II 难度 中等 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;…

week06 day04 (数据库高级函数 procedure 、sql写函数)

一. ER模型 矩形&#xff1a; 代表实体椭圆&#xff1a;代表实体的属性菱形&#xff1a;relation 代表实体之间的关系 二. 存储过程&#xff08;procedure&#xff09; 1. 语法 语法: create procedure 存储过程名(参数,…) begin//代码 end// 注意&#xff1a; 因为在存储…

C语言 —— 图形打印

题目1&#xff1a; 思路&#xff1a; 如果我们要打印一个实心正方形&#xff0c;其实就是一个二维数组&#xff0c;i控制行&#xff0c;j控制列&#xff0c;行列不需要控制&#xff0c;arr[i][j]直接打印星号即可。 对于空心正方形&#xff0c;我们只需要控制行和列的条件&…

MyBatis学习笔记|2024最新版Mybatis

Mybatis简介 MyBatis历史 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到GithubiBatis一词来源于“internet”和“aba…

应对电子邮件危害:垃圾邮件的特征、影响和预防策略

垃圾邮件是一种侵入性的未经请求的电子通信形式&#xff0c;主要通过电子邮件传播。它将大量无关、欺骗性或恶意内容发送给邮箱用户&#xff0c;内容涵盖令人讨厌的广告和欺诈行为、以及网络钓鱼链接。 截至 2022 年 12 月&#xff0c;垃圾邮件占电子邮件流量的 45% 以上。除了…

数据结构---C语言栈队列

知识点&#xff1a; 栈&#xff1a; 只允许在一端进行插入或删除操作的线性表&#xff0c;先进后出LIFO 类似一摞书&#xff0c;按顺序拿&#xff0c;先放的书只能最后拿&#xff1b; 顺序栈&#xff1a;栈的顺序存储 typedef struct{Elemtype data[50];int top; }SqStack; SqS…

Window部署Oracle并实现公网环境远程访问本地数据库

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle&#xff0c;是甲骨文公司的一款关系…

掌握Redis,看完这篇文章就够了!

目录 1.Redis介绍 2.Redis服务器与客户端 3.Redis配置文件 4.Redis数据类型操作 4.1使用python连接数据库 4.2 字符串 4.3 哈希 4.4 键对应操作 4.5 列表 4.6 集合 4.7 有序集合 1.Redis介绍 Redis 是一个开源的内存数据库&#xff0c;它提供了一个高性能的 key-val…

基于PyTorch深度学习实战入门系列-Numpy基础全

Numpy的使用 导入Numpy模块 import numpy as np创建数组&#xff08;一维数组、小数数组、二维数组&#xff09; # 创建一个一维数组 n1 np.array([1, 2, 3]) # 创建一个含有小数的一维数组 n2 np.array([0.1, 0.2, 0.3]) # 创建一个简单的二维数组 n3 np.array([[1, 2], [3…

基于Java+SpringBoot+vue+element实现前后端分离牙科诊所管理系统详细设计

基于JavaSpringBootvueelement实现前后端分离牙科诊所管理系统详细设计 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 ** 作者主页 央顺技术团队** 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式…

【阿里云系列】-基于云效构建部署NodeJS项目到ACK

准备工作 01、编写Dockerfile文件可以根据不同的环境&#xff0c;新建不同的Dockerfile文件&#xff0c;比如Dockerfile-PROD # Deliver the dist folder with NginxFROM nginx:stable-alpine ENV LANGC.UTF-8 ENV TZAsia/ShanghaiCOPY dist/ /usr/share/nginx/html COPY ngi…

字节跳动也启动春季校园招聘了(含二面算法原题)

字节跳动 - 春招启动 随着各个大厂陆续打响春招的响头炮&#xff0c;字节跳动也官宣了春季校园招聘的正式开始。 还是那句话&#xff1a;连互联网大厂启动校招计划尚且争先恐后&#xff0c;你还有什么理由不马上行动&#xff1f;&#xff01; 先来扫一眼「春招流程」和「面向群…

怎么恢复删除的文件?三个实用数据恢复方法分享

在日常工作和生活中&#xff0c;我们经常会遇到误删文件的情况&#xff0c;这可能会带来不小的麻烦。幸运的是&#xff0c;随着科技的发展&#xff0c;现在有很多方法可以帮助我们恢复被删除的文件。下面让我来分享三种常用的文件恢复方法&#xff0c;一起来看看吧&#xff01;…

【Springboot】--如何将springboot+vue项目部署到云服务器?

目录 一、准备工作 1、购买云服务器 2、获取面板地址 二、jdk和数据库 1、安装环境 2、配置jdk环境变量 3、java版本的问题 4、添加数据库 三、前端部署 1、vue 2、创建站点 ​编辑 四、后端部署 1、application.yml 2、idea打包 3、运行jar包 4、开放端口 五、…

大模型时代下的 BI——智能问数

「智能问数」是 Sugar BI 基于文心大语言模型推出的对话式数据问答产品&#xff0c;让用户能够通过自然语言的方式进行对答形式的数据查询&#xff0c;系统自动使用可视化图表的方式呈现数据结果&#xff0c;并支持对数据做summary总结。 智能问数功能邀测中&#xff0c;欢迎CS…

Dgraph 入门教程四《开发环境的搭建》

在深入 了解Dgraph 之前&#xff0c;我们先了解下开发环境&#xff0c;毕竟让开发人员真正动起手来才是第一步。 Dgraph 支持很多种语言的开发&#xff0c;包括GO&#xff0c;Python&#xff0c;JS&#xff0c;C#和Java等等。出于项目需要&#xff0c;只了解下JS和Java。 1、…