【C语言】——指针四:字符指针与函数指针变量

news2024/9/29 7:26:58

【C语言】——指针四:字符指针与函数指针变量

    • 一、字符指针
    • 二、函数指针变量
      • 2.1、 函数指针变量的创建
      • 2.2、两段有趣的代码
    • 三、typedef关键字
      • 3.1、typedef的使用
      • 3.2、typedef与define比较
    • 四、函数指针数组

一、字符指针

  在前面的学习中,我们知道有一种指针类型为字符指针: c h a r ∗ char* char。下面我们来介绍它的使用方法。
  
使用方法:

#include<stdio.h>

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

  
  如果我们想存储字符串,可以用什么方法呢?之前我们一般都是用字符数组,那还有什么办法呢?其实,字符指针也是可以的。
  
使用方法:

#include<stdio.h>

int main()
{
	const char* pstr = "hello world";
	printf("%s\n", pstr);
	return 0;
}

  在const char* pstr = "hello world";代码中,可能很多小伙伴以为是把整个字符串"hello world"放进字符指针 p s t r pstr pstr 中,但其实,这里本质是把 " h e l l o "hello "hello w o r l d " world" world"首字符 ‘ h ’ ‘h’ h 的地址放在指针变量 p s t r pstr pstr 中。
  
  至于代码printf("%s\n", pstr);指针 p s t r pstr pstr不需要解引用,因为 p r i n t f printf printf 函数打印字符串本质是接收该字符串首元素地址,从该地址开始往后打印,直到遇到 ‘ \0 ’ 停止,解引用反而是错的。
  
  这里,也要随便提一下,代码printf("hello world"),同样不是把整个字符串 " h e l l o "hello "hello w o r l d " world" world" 传给 p r i n t f printf printf 函数,其本质也是将首字符 ‘ h ’ ‘h’ h 的地址传给 p r i n t f printf printf 。  p r i n t f printf printf 再从给来的地址开始打印,直到遇到 ‘ \0 ’ 停下。
  

在这里插入图片描述

  
  那字符指针与数组指针有什么区别呢?他们最大的区别就是

  • 字符数组里的内容可以修改
  • 字符指针中放的是常量字符串,内容不可修改
      
    因此,我们可以在字符指针 c h a r char char*前加上 c o n s t const const 修饰,以确保他不能被修改。
        
      
    下面,我们来看一道题,进一步感受字符指针与字符数组的区别
int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";

	const char* str3 = "hello world";
	const char* str4 = "hello world";

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

输出结果:
在这里插入图片描述
  

为什么会这样呢?
  
  这里,其实 s t r 3 str3 str3 s t r 4 str4 str4 指向同一个常量字符串,C/C++会把常量字符串存储到单独的一个内存空间(代码段)。
  
  因为常量字符串无法被修改,没必要存储两份,当多个字符指针指向同一个常量字符串时,他们实际会指向同一块内存
  
  但是用相同的常量字符串去初始化数组就会开辟出不同的内存块
  
  所以 s t r 1 str1 str1 s t r 2 str2 str2 不同, s t r 3 str3 str3 s t r 4 str4 str4 相同。
  
  

二、函数指针变量

2.1、 函数指针变量的创建

  
  什么是函数指针变量呢?
  
  在前面的学习中(【C语言】—— 指针三 : 参透数组传参的本质)我们了解到数组指针变量,他是用来存放数组指针地址的。同理函数指针变量应该是存放函数地址的,未来能通过他来调用函数。
  
  那么问题来了,函数是否有地址呢?
  
我们来做个测试:

#include<stdio.h>

void test()
{
	printf("hello world\n");
}

int main()
{
	printf("test:   %p\n", test);
	printf("&test:  %p\n", &test);

	return 0;
}

  
在这里插入图片描述
  
  可以看到,我们确实打印出了函数的地址,可见,函数是有地址的
  
  这里与数组有点相似,不论是直接打印数组名还是 &数组名,都能打印出地址,不同的是数组的数组名表示的是数组首元素的地址,而 &数组名 表示的是整个数组的地址,他们仅仅只是在数值上相等,类型是不一样的。而对于函数来说函数名&函数名 的效果是一模一样的。
  
  现在我们把函数的地址取出来了,那该存放在哪呢?老办法,将地址放在指针变量。对于函数的地址,当然是放在函数指针变量中啦,而函数指针变量的写法其实和数组指针变量非常相似(详情请看【C语言】—— 指针三 : 参透数组传参的本质)。
  
如下:

void test()
{
	printf("hello world\n");
}

void (*pf1)() = &test;
void (pf2)() = test;


int Add(int x, int y)
{
	return x + y;
}
int (*pf3)(int x, int y) = Add;
int (*pf4)(int, int) = &Add;//x 和 y 写上或者省略都是可以的

  
  注:函数指针变量中,参数类型的名字可省略,对于函数指针变量来说,重要的是参数类型返回类型,参数名叫什么并不重要。
  
  
函数指针类型解析:

图

  
  学习函数指针后,我们就可以通过函数指针来调用指针指向的函数啦

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*pf)(int, int) = Add;

	printf("%d\n", Add(1, 2));
	printf("%d\n", (*pf)(2, 3));
	printf("%d\n", pf(3, 4));
	
	return 0;
}

运行结果:
在这里插入图片描述

  
  在这里 Add(1, 2);是通过函数名调用;而(*pf)(2, 3);pf(3, 4);都是通过函数指针调用。
  
  两种函数指针的调用效果是一样的,因此对函数指针来说,解引用可以看作是摆设。
  
  

2.2、两段有趣的代码

  
  接下来,我们来看两段有趣的代码:
  

( *( void( * )( ) ) 0)( );

我们来慢慢分析
  

  • 先来看 void( * )( )部分,是不是觉得很熟呢?,如果你还看不出来,那这样呢: void( *p )( );现在认出来了吧。没错它是一个指针变量,一个变量去掉变量名是什么?是类型。没错 void( * )( )是一个函数指针类型
      
  • 那一个类型加上小括号是什么?是强制类型转换!所以(void(*)())0即是把 0 强制类型转换成函数指针类型(原来是 i n t int int 类型),如果还不理解,我们可以这样来看(void(*)())0x0012ff40。这样是不是清晰了许多呢?
      
  • 既然将 0 强转成函数指针类型,那即意味着 0 地址处放着一个函数,该函数返回类型是 v o i d void void没有参数
      
  • 接着,对位于该地址的函数进行解引用调用该函数:( * ( void( * )( ) ) 0)( ),
      
  • 因为这个函数没有参数(由类型void(*)()可知),所以后面的小括号(参数列表)不填。
      
  • 整句代码的意思是:调用在地址 0 出的函数,该函数的返回类型是 v o i d void void ,没有参数

  

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

这句代码也让我们一起来分析分析
  

  • 先来看里面的部分signal(int,void(*)(int))很明显,signal函数名,而int和void(*)(int)是函数的参数类型
      
  • 那前面的*是什么意思?解引用吗?其实我们不妨顺着函数的思路往下想,一个函数有了函数名参数类型,还差什么?是返回类型。那这个函数的返回类型是什么?剩下的部分就是返回类型
      
  • 其实,该函数的返回类型被劈开了,我们将它的函数名和参数类型拿走,剩下的就是它的返回类型void(*)(int),如果还不清晰,我们可以写成这样来理解(接下来的写法是错误的,仅仅是为了方便理解,题目写法是正确的)void(*)()signal(int,void(*)(int))
      
  • 所以,这一句代码是一个函数声明,函数名是signal;返回类型是void(*)();参数类型是intvoid(*)()

注:以上两段代码均出自 《C陷阱和缺陷》
  
  

三、typedef关键字

3.1、typedef的使用

  
  typedef 是用来类型重命名的,可以将复杂的类型简单化
  
  比如,如果觉得unsigned int写起来不方便,我们可以写成uint就方便多了,那我们可以这样写

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

  
  我们之前简单提到的结构体类型(详情请看【C语言】——详解操作符(下)),觉得每次都要加 s t r u c t struct struct 太麻烦了,那我们可以通过 t y p e d e f typedef typedef 将其重命名。

typedef struct student
{
	char name[20];
	int age;
}student;
//将结构体类型struct student重命名为student

  
  那如果是指针类型,可不可以通过 t y p e d e f typedef typedef 来重命名呢?答案是肯定的。比如,将int*重命名成ptr_t,我们可以这样写:

typedef int* ptr_t;

  
  但对于函数指针数组指针稍微有点区别。区别在哪呢?新的类型名的位置不同

  比如我们将数组指针类型int(*)[10]重命名为parr_t,我们可以这么写:

typedef int(*parr_t)[5];

  
  同样,函数指针变量的重命名也是一样的,比如将void(*)(int)重命名为pf_t,可以这样写:

typedef char(*pf_t)(int, int);

  
那么现在,我们就可以用 t y p e d e f typedef typedef 将代码void (*signal(int, void(*)(int)))(int);简化

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

  

3.2、typedef与define比较

  
  想了解 t y p e d e f typedef typedef d e f i n e define define 的区别,我们先来一组比较:

typedef int* ptr_t;
#define PTR_T int*

ptr_t p1, p2;
PTR_T p3, p4;

  
他们有什么区别呢?

  • p1p2都是指针变量
  • p3指针变量p4整形变量

为什么会这样呢?
  
  对于ptr_t,他是通过 t y p e d e f typedef typedef 来修饰的, t y p e d e f typedef typedef 的作用就是重命名,因此pyr_t就是int*,他们是画等号的。
  
  而对于PTR_T,他是通过 d e f i n e define define 修饰的,PTR_T仅仅是替换int*int* p3、p4;中, *给了 p3p3指针变量,而p4只剩int了,是整形变量
  
  

四、函数指针数组

  
  数组是一个存放相同类型数据的存储空间,之前,我们已经学过了指针数组(详情请看【C语言】—— 指针二 : 初识指针(下))

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

  
  那要把一个函数的地址放在数组中,这个数组就叫函数指针数组,那函数指针数组该怎么定义呢?

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

答案是: p a r r 1 parr1 parr1
  
  parr1先和[]结合,表示一个parr1是一个数组,那数组中的元素类型是什么呢?是int(*)()类型的函数指针

  
  那么函数指针数组有什么用呢?别急,敬请收看下一章:【C语言】——指针五:转移表与回调函数。
  
  
  
  


  好啦,本期关于字符指针和函数指针就介绍到这里啦,希望本期博客能对你有所帮助,同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!

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

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

相关文章

FaceBook广告账号验证教程

1.登录facebook账号,点击左边的ads manager。 2.点击Create ad创建广告。 3.选择广告投放意向。 4.填写广告信息。 5.创建广告后选择付款方式&#xff0c;这里我是使用信用卡付款。这里我是使用Fomepay的虚拟卡进行绑定的。 6.填写信用卡的持卡人姓名 卡号 有效期 安全码 7.填写…

【Jenkins】群晖 配置 ssh over push 插件

群晖 配置 ssh over push 插件 前提 部署好 Jenkins 且 安装 好 ssh over push 插件 开启 群晖 ssh 服务 及 SFTP 服务 配置 Jenkins Jenkins ——系统管理——publish over ssh 测试下&#xff1a; 遇到的问题&#xff1a; jenkins.plugins.publish_over.BapPublishe…

数据容器-序列-集合-Python

师从黑马程序员 序列 序列的常用操作-切片 切片&#xff1a;从一个序列中&#xff0c;取出一个子序列 语法&#xff1a;序列[起始下标:结束下标&#xff0c;步长] 注&#xff1a;此操作不会影响序列本身&#xff0c;而是会得到一个新的序列 my_list[0.1,2,3,4,5,6] result1…

LeetCode-热题100:79. 单词搜索

题目描述 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相…

解决长尾问题,BEV-CLIP:自动驾驶中复杂场景的多模态BEV检索方法

解决长尾问题&#xff0c;BEV-CLIP&#xff1a;自动驾驶中复杂场景的多模态BEV检索方法 理想汽车的工作&#xff0c;原文&#xff0c;BEV-CLIP: Multi-modal BEV Retrieval Methodology for Complex Scene in Autonomous Driving 链接&#xff1a;https://arxiv.org/pdf/2401.…

C++函数返回机制,返回类型

return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。 return语句有两种形式 return;return expression; 无返回值函数 没有返回值的return语句只能用在返回类型是void的函数中。 返回void的函数不要求必须有return语句&#xff0c;因为这类函数的最后一句…

手撕算法-接雨水

描述 分析 i位置能积累的雨水量&#xff0c;等于其左右两边最大高度的最小值。为了能获取i位置左右两边的最大高度。使用动态规划。两个dp数组&#xff1a; leftMaxrightMax 其中 leftMax[i] 代表i位置左边的最大高度rightMax[i] 代表i位置右边的最大高度 初始状态&#x…

新手装修:卫生间渗水原因及解决方法。福州中宅装饰,福州装修

引言 瓷砖渗水问题常常发生在卫生间区域&#xff0c;需要及时处理以免造成地面滑倒和墙面霉菌等问题&#xff0c;为了解决这一问题&#xff0c;我们应该怎么做呢&#xff1f; 首先要检查水管是否漏水&#xff0c;可以进行打压测试来确认是否存在漏水情况。如果发现水管破损造成…

php 快速入门(一)

一、配置系统环境 1.1 安装软件 1、安装php的开发软件&#xff1a;phpstorm 在这个软件中写代码 2、安装php的运行软件&#xff1a;phpstduy 写好的php程序需要放到phpstduy中&#xff0c;用户才能访问和测试 安装过程注意事项&#xff1a;安装的路径中不能有空格和中文字符&…

什么是 PDAF?它是如何工作的?相位检测自动对焦解释

常见问题解答 什么是相位对焦 PDAF 代表相位检测自动对焦。这是一种自动对焦方法,可以检测光线进入相机时的行进和交汇位置。在智能手机中,这是在传感器级别完成的。为了使物体聚焦,光线应该在同一点相遇。如果不这样做,系统将确定如何调整镜头以达到焦点。 PDAF 好用吗…

HTTP --- 下

目录 1. HTTP请求方式 1.1. HTML 表单 1.2. GET && POST方法 1.2.1. 用 GET 方法提交表单数据 1.2.2. 用 POST 方法提交表单数据 1.2.3. 总结 1.3. 其他方法 2. HTTP的状态码 2.1. 重定向 2.1.1. 临时重定向 && 永久重定向 2.1.2. 302 &&…

UE5 C++ 3D血条 响应人物受伤 案例

一.3Dwidget 1.创建C Userwidget的 MyHealthWidget&#xff0c;声明当前血量和最大血量 UCLASS() class PRACTICEC_API UMyHealthWidget : public UUserWidget {GENERATED_BODY() public:UPROPERTY(EditAnywhere,BlueprintReadWrite,Category "MyWidget")float C…

基于Springboot+Vue的在线考试系统!免费领取源码

今天给大家分享一套基于SpringbootVue的在线考试系统源码&#xff0c;在实际项目中可以直接复用。(免费提供&#xff0c;文末自取) 一、系统运行图 1、登陆页面 2、后台管理 3、全套环境资源 二、源码免费领取方式 关注本号&#xff0c;回复 考试 关注本号&#xff0c;回复…

【数据结构】快速排序(用递归)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解快速排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 快速排序2.1 hoare版本2.2 挖坑法2.3 前后指针法2.4 快速排序优化三数取中法…

数据结构:堆和二叉树遍历

堆的特征 1.堆是一个完全二叉树 2.堆分为大堆和小堆。大堆&#xff1a;左右节点都小于根节点 小堆&#xff1a;左右节点都大于根节点 堆的应用&#xff1a;堆排序&#xff0c;topk问题 堆排序 堆排序的思路&#xff1a; 1.升序排序&#xff0c;建小堆。堆顶就是这个堆最小…

设计模式-访问者(Visitor)模式详解和应用

文章目录 前言访问者模式介绍结构包含的角色应用场景代码示例访问者模式的扩展访问者模式优缺点总结 前言 最近在做一个根据数学表达式生成java执行代码的功能&#xff0c;其中用到了访问者模式。使我对访问者模式有了更深入的理解。故写下此篇文章分享出来&#xff0c;不足之…

ios逆向某易新闻 md5+aes

本期的案例比较简单&#xff0c;也许是ios逆向算法本来就比较简单的原因&#xff0c;所以前面我就多扯一些爬虫和逆向的东西。之前写的文章都是js逆向和android逆向的案例&#xff0c;这也是首篇ios的案例&#xff0c;所以会从入门开始讲起。 3大逆向对比 首先爬虫工程师大部…

Objective-C—Class底层结构探索,真心分享给你!!!

isa 走位图 在讲 OC->Class 底层类结构之前&#xff0c;先看下下面这张图&#xff1a; 通过isa走位图 得出的结论是&#xff1a; 1&#xff0c;类&#xff0c;父类&#xff0c;元类都包含了 isa, superclass 2&#xff0c;对象isa指向类对象&#xff0c;类对象的isa指向了元…

C语言操作符和数据类型的存储详解

CSDN成就一亿技术人 目录​​​​​​​ 一.操作符 一.算数操作符&#xff1a; 二.位移操作符&#xff1a; 三.位操作符&#xff1a; 四.赋值操作符&#xff1a; 五.单目操作符&#xff1a; 六.关系操作符&#xff1a; 七.逻辑操作符&#xff1a; 八.条件操作符&…

Java学习笔记 | JavaSE基础语法05 | 方法

文章目录 0.前言1. 方法概述2. 方法的定义和调用2.1 无参数方法定义和调用2.2 带参数方法定义和调用1 带参数方法定义和调用2 形参和实参3 带参数方法练习 2.3 带返回值方法的定义和调用1 带返回值方法定义和调用2 带返回值方法练习13 带返回值方法练习24 带返回值方法练习3 3.…