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

【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语言的学习路上一起进步!

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

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

相关文章

力扣|两数相加|链表

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

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

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

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

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解归并排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 归并排序代码 一. 基本思想 在讲归并排序之前&#xff0c;问问自己&#x…

Nexus3 Docker 私有仓库

Nexus3 Docker 私有仓库 安装并部署 Nexus3 $ docker search nexus3$ docker pull sonatype/nexus3$ mkdir /home/tester/data/docker/nexus3/sonatype-work $ sudo chown -R 200 /home/tester/data/docker/nexus3/sonatype-work$ docker run -d --namenexus3 \ --restartalw…

【prometheus-operator】k8s监控redis

1、准备exporter https://github.com/oliver006/redis_exporter oliver006-redis_exporter-amd64.tar # 安装镜像 docker load -i oliver006-redis_exporter-amd64.tar # 上传镜像 docker tag oliver006/redis_exporter ip/monitor/redis_exporter:latest docker push ip/mo…

算法---前缀和练习-2(和为k的子数组)

和为k的子数组 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 创建一个无序映射&#xff08;哈希表&#xff09; hash&#xff0c;用于统计前缀和的出现次数。初始时&#xff0c;将前缀和为 0 的次数设为 1&#xff0c;表示…

抖音IP属地怎么更改

抖音是一个非常受欢迎的短视频平台&#xff0c;吸引了无数用户在上面分享自己的生活和才艺。然而&#xff0c;随着快手的火爆&#xff0c;一些用户开始担心自己的IP地址会被他人获取&#xff0c;引起个人隐私风险。那么&#xff0c;抖音用户又该如何更改到别的地方呢&#xff1…

[Spring Cloud] 简单搭建与请求转发

文章目录 简介相关链接搭建选择Spring Boot版本选择组件版本创建nacos配置创建网关gateway配置文件image.png创建微服务Spring Boot配置文件 gateway项目配置gateway项目文件pom.xmlApplication.javabootstrap.ymllogback-spring.xml 启动gateway 微服务配置微服务项目文件pom.…

使用pandas进行数据清洗

采集到原始的数据中会存在一些噪点数据&#xff0c;噪点数据是对分析无意义或者对分析起到偏执作用的数据。如何清洗&#xff1a; 清洗空值/缺失值清洗重复值清洗异常值 import pandas as pd from pandas import DataFrame,Series import numpy as np pandas处理空值操作 i…

ppp实验

拓扑图 实验步骤 配置IP地址及创建mp逻辑口 [R1]int ser 3/0/0 [R1-Serial3/0/0]ip add 192.168.1.1 24 [R1-Serial3/0/0] [R2]int se3/0/0 [R2-Serial3/0/0]ip add 192.168.1.2 24 [R2-Serial3/0/0]int mp [R2-Serial3/0/0]int mp-g [R2-Serial3/0/0]int mp-group 0…

中国气象局发布大地磁暴预警:空间站轨道或受影响

什么是地磁暴&#xff1f; 地磁暴作为最典型的太阳爆发活动&#xff0c;一次地磁暴是一次日冕物质抛射过程&#xff0c;能将数以亿吨计的太阳物质以数百千米每秒的高速抛离太阳表面。 不光是巨大质量与速度汇聚成的动能&#xff0c;它们还携带着太阳强大的磁场能&#xff0c;一…

前端基础篇-前端工程化 Vue 项目开发流程(环境准备、Element 组件库、Vue 路由、项目打包部署)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 环境准备 1.1 安装 NodeJs 1.2 验证 NodeJs 环境变量 1.3 配置 npm 的全局安装路径 1.4 切换 npm 的淘宝镜像( npm 使用国内淘宝镜像的方法(最新) ) 1.5 查看镜像…

406. 根据身高重建队列(力扣LeetCode)

文章目录 406. 根据身高重建队列题目描述贪心算法代码 406. 根据身高重建队列 题目描述 假设有打乱顺序的一群人站成一个队列&#xff0c;数组 people 表示队列中一些人的属性&#xff08;不一定按顺序&#xff09;。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi &…

tabs自定义样式

使用el-tabs 去修改样式的话比较麻烦&#xff0c;索性直接用div来制作。 <div class"contain"><div class"tab_wrap"><div :class"[skew, first, active 1 ? isActive: ]" click"tabClick(1)"><span class&quo…

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 &&…

3 Spring之DI详解

5&#xff0c;DI相关内容 前面我们已经完成了bean相关操作的讲解&#xff0c;接下来就进入第二个大的模块DI依赖注入&#xff0c;首先来介绍下Spring中有哪些注入方式? 我们先来思考 向一个类中传递数据的方式有几种? 普通方法(set方法)构造方法 依赖注入描述了在容器中建…

19.严丝合缝的文明——模板方法模式详解

“项目评审的节点又快到了&#xff0c;PPT你写了没&#xff1f;” “Oops&#xff0c;忘了&#xff0c;有模板没&#xff1f;给我一份” 概述 模板&#xff0c;一个频繁出现在办公室各类角色口中的词&#xff0c;它通常意味着统一、高效、经验和优质。各项汇报因为PPT的模板变…

trinus 3d打印机安装调试到成功打印3-没有热床模型脱落底床不粘模型翘边错位

由于没有自带热肠&#xff0c;改装的话需要额外购买配套的热床。但是如果手头没有的话&#xff0c;那只能使用原厂赠送的两张美文纸。美纹纸很容易用破&#xff0c;尤其是喷头可能会划破。另外拆模型的时候会引起气泡。 于是翘边和模型脱落就成了家常便饭。 这些问题的根源都在…

C#学习笔记1:C#基本文件结构与语法

现在开始我的C#学习之路吧&#xff0c;这也许不适合0编程基础的人看&#xff0c;因为我会C语言了&#xff0c;笔记做的可能有思维上的跳跃&#xff0c;如果0基础可能会觉得有些地方转折得莫名奇妙&#xff0c;但我的学习笔记实操还是比较多的&#xff0c;基本都是真实运行程序结…

七.(1)堆排序--前传

目录 七.(1)堆排序--前传 20-堆排序前传-树的基础知识 根节点 叶子节点 树的深度(高度) 树的 度 孩子节点/父亲节点 子树 21.堆排序前传-二叉树的基础知识 二叉树的存储方式: 22-堆排序前传-堆和堆的向下调整 什么是堆? 堆的向下调整性质 23-堆排序的过程演示 七…