C语言指针详解(三)目录版

C语言指针详解(三)目录版

    • 1、字符指针变量
      • 1.1、字符指针变量的一般应用
      • 1.2、常量字符串
      • 1.3、常量字符串与普通字符串的区别
        • 1.3.1 常量字符串的不可修改性
        • 1.3.2 常量字符串的存储
    • 2、数组指针变量
      • 2.1、数组指针变量定义
      • 2.2、数组指针变量的初始化
    • 3、二维数组传参本质
    • 4、函数指针变量
      • 4.1、函数指针变量的创建
        • 4.1.1、验证函数地址的存在
        • 4.1.2、函数指针变量表达式
      • 4.2、函数指针变量的使用
      • 4.3、深入理解函数指针
        • 4.3.1、例题一
        • 4.3.2、例题二
        • 4.3.3、例题一解析
        • 4.3.4、例题二解析
        • 4.4、关键字 typedef 介绍
        • 4.4.1、typedef 表达式与实例演示
        • 4.4.2、define 与 typedef 的区别
    • 5、函数指针数组
    • 6、转移表

1、字符指针变量

我们知道若指针所指向的内容是字符,那么指针的类型就是字符指针类型 char* ,这个指针变量就是字符指针变量。

1.1、字符指针变量的一般应用

int main()
{char ch = 'a';char* pc = &ch;*pc = 'w';printf("%c", ch);return 0;
}

在这里插入图片描述

1.2、常量字符串

#include<stdio.h>
int main()
{const char* pt = "hello world.";//请思考这里是把一个字符串放到了 pt 指针当中了吗?printf("%s\n", pt);return 0;
}

如上面代码所示的 char* name = “字符串” 就是常量字符串的表达方式。那么我们思考一下注释中的问题:这里是把一个字符串放到了 pt 指针当中了吗?
答案是:否。毕竟前面是一个指针变量,其存储的应当为地址,故放入的是首字符的地址。因为字符是连续存放的,输出时系统会自动向后遍历,直至遇到 “/0”。
运行效果图:
在这里插入图片描述

1.3、常量字符串与普通字符串的区别

1.3.1 常量字符串的不可修改性

1.1 的代码和运行图中我们可见一般情况下的字符(字符串)是可以通过指针解引用来进行修改的。而常量字符串则不可修改,如下面代码示例演示

int main()
{char* pt = "hello world.";printf("%s\n", pt);*pt = "Hello World";return 0;
}

在这里插入图片描述
原因请见下文 1.3.2

1.3.2 常量字符串的存储

我们知道在存储位置中有栈区,堆区,静态区。其实在此之外还有一部分称为代码段。一般字符(字符串)存储在栈区中,而常量字符串则存储在代码段中。所以即使通过指针解引用也不能更改字符串内容。正因如此,当多个指针变量指向的常量字符串内容一致时,系统不会存储多份相同的常量字符串,而是会只存储一份,各个变量共用这一份常量字符串(即字符串地址相同)。为使各位对此更加清晰,我们通过以下代码和图解来进行进一步解析。

int main()
{char str1[] = "You are handsome !";char str2[] = "YOu are handsome !";char* str3 = "You are so beautiful";char* str4 = "You are so beautiful";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;
}

在这里插入图片描述
在这里插入图片描述

2、数组指针变量

2.1、数组指针变量定义

数组指针,顾名思义它是一种指针。我们已经熟知:
整型指针变量:int * p;存放的是整型变量的地址,指向整型数据的指针。
浮点型指针变量:float * p;存放的是浮点型变量的地址,指向浮点型数据的指针。
由上推知:数组指针变量应当存放的是数组的地址,指向数组的指针。
数组指针变量表达式:int (*p) [20];
在这个表达式中,p会先和*结合,表明 p 是一个指针变量,然后指针指向的是一个大小为20个整型变量的数组。
注意:因为 [ ] 的优先级要高于 * 号,所以必须加上 ()来保证 p 先和 * 结合

2.2、数组指针变量的初始化

既然数组指针变量存放的是数组的地址,那么我们就要将数组的地址给指针变量。数组地址:&数组名

int arr[10] = {0};//定义数组并初始化
int (*p) [10] = &arr;//将数组的地址赋值给指针变量 

在这里插入图片描述

3、二维数组传参本质

在数组章节中曾提及二维数组传参的如下写法:

#include <stdio.h>void print_arr(int arr[4][5], int a, int b)
{int i = 0;for (i = 0; i < a; i++){int j = 0;for (j = 0; j < b; j++){printf("%d", arr[i][j]);}printf("\n");}
}int main()
{int arr[4][5] = { {1,2,3,4,5},{2.3,4,5,6},{3,4,5,6,7},{4,5,6,7,8}};print_arr(arr, 4, 5);return 0;
}

在上述代码中,实参为二维数组,形参也为二维数组。那么下面来介绍另一种传参方式,使用数组指针进行传参。
二维数组在之前数组章节曾讲过,其可以看成每个元素是一维数组的数组,即二维数组的每个元素是一个一维数组,二维数组首元素就是第一行,是一个一维数组。
在这里插入图片描述
二维数组的数组名就是第一行的地址,是一个一维数组的地址,第一行一维数组类型为int [5],故其地址类型为int (*) [5];。如此也就证明二维数组传参实际上是传递了第一行一维数组的地址。由此可知,函数形参部分也可以写成指针形式。如下所示:

#include <stdio.h>void print_arr(int (*p) [5], int a, int b)//数组形参设置为数组指针
{int i = 0;for (i = 0; i < a; i++){int j = 0;for (j = 0; j < b; j++){printf("%d", *( * ( p + i ) + j ) );//深入理解系统对数组arr[ i ]的编译。// arr[ i ] = *(arr + i)}printf("\n");}
}int main()
{int arr[4][5] = { {1,2,3,4,5},{2.3,4,5,6},{3,4,5,6,7},{4,5,6,7,8} };print_arr(arr, 4, 5);return 0;
}

4、函数指针变量

4.1、函数指针变量的创建

根据上文内容,我们可以推断出函数指针变量是存储函数地址的变量,同时我们可以通过函数的地址来调用函数。

4.1.1、验证函数地址的存在

下面我们来验证函数地址的存在:

#include<stdio.h>void test()
{printf("hehe\n");
}int main()
{printf("test = %p\n", test);//函数名printf("&test = %p\n", &test);//取函数地址return 0;
}

在这里插入图片描述
由此说明函数存在地址,函数名与 &函数名 都可以得到函数的地址。

4.1.2、函数指针变量表达式

表达式如下:

int*pf)(int x , int y);
//或 int (*pf)(int , int)

在这里插入图片描述
去掉变量名即为其变量类型:

int*)(int x , int

4.2、函数指针变量的使用

在使用函数指针变量调用函数时,(*pf)()与 pf()两种方法都是可行的,因为二者表示的意思都一致,都是对应函数的地址。
代码演示:

#include<stdio.h>int div(int a , int b)
{return a / b;
}int main()
{int (*pf) (int, int) = div;printf("%d\n", (*pf)(4 , 2) );//调用方式一printf("%d\n", pf(4, 2));//调用方式二return 0;
}

在这里插入图片描述

4.3、深入理解函数指针

4.3.1、例题一
(*( void (*)() ) 0)();//请思考此行代码的意义

请思考上面代码的意义,解析见 4.3.3

4.3.2、例题二
void (*signal(int, void(*)(int)))(int);//请思考此行代码的意义

请思考上面代码得意义,解析见 4.3.4

4.3.3、例题一解析

在这里插入图片描述
入手关键点在 0 处,其前方应当为0的类型,( void (*) () ) 0 典型的强制类型转换,将0从整型 int 强制转换为函数指针类型 void ( * ) () ,从而 0 就成为一个函数指针变量,整体就是通过函数指针调用函数。

4.3.4、例题二解析

在这里插入图片描述
入手关键点在中间的函数名及其参数部分 signal(int, void(*)(int)) 我们可见在这一小部分中已经包含了signal函数的函数名和该函数内的两个参数的类型,但无参数名,根据C语言标准可知这应当是一个函数声明。故剩余部分为函数的返回类型,为 void (*) (int)函数指针类型。因为解引用操作符 * 后要跟名称,所以signal(int, void(*)(int))放在了 * 之后。综上,整体为一个函数声明

4.4、关键字 typedef 介绍
4.4.1、typedef 表达式与实例演示

typedef是用来类型的重命名的,可以简化复杂的类型,如 4.3.4 例题二中的函数指针类型。其重定义表达式如下:

typedef name1 name2;

name1 表示需要重定义类型名
name2 表示 name1 重定义后的类型名

具体演示如下:

#include<stdio.h>int div(int a , int b)
{return a / b;
}int main()
{typedef int(*son)(int, int);// * 后要跟名称,所以新的类型名要放在 * 后son pf = div;printf("%d\n", (*pf)(18 , 2) );printf("%d\n", pf(10, 2));return 0;
}

在这里插入图片描述

4.4.2、define 与 typedef 的区别

1、 本质区别:
#define 是预处理指令,用于文本替换。在编译之前,预处理器会直接将 #define 定义的宏替换成指定的文本。
typedef 是类型定义命令,用于为已存在的数据类型创建一个新的名字。
2.、作用范围:
#define 定义的宏没有作用域限制,一旦定义,就会一直有效,除非被 #undef 取消定义。
typedef 定义的类型有作用域限制,它遵循C语言的变量作用域规则。
3、 类型检查:
#define 不进行类型检查。它仅仅是在预处理阶段进行文本替换,所以不会检查替换后的类型是否正确。
typedef 会进行类型检查。当你使用 typedef 定义的新类型时,编译器会检查类型是否匹配。
4、 使用方式:
#define 可以用于定义常量、宏函数等,不仅仅限于类型。
typedef 仅用于定义类型的别名。
5、 内存分配:
#define 不会分配内存,因为它只是在预处理阶段进行文本替换。
typedef 本身也不分配内存,但它定义的类型在创建变量时会分配内存。

5、函数指针数组

我们知道数组是一个存储相同类型数据的存储空间。故函数指针数组就是一块连续的存储函数指针的空间。
表达式如下:

type (* name[number]();

name 会先和 [ ] 结合,表明 name 是数组,数组的内容是 type (*) () 类型的函数指针。(因为 * 后面要跟名称,所以 * 后面跟上数组名)

6、转移表

转移表是函数指针数组实例化的体现。
下面以简易计算器的改造为例:

//改造前的计算器
#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;
}

上述代码的条件分支中我们可以看到有许多重复的部分
在这里插入图片描述
这样就出现两个问题:

1、将这样重复的代码实现成函数。 2、这个函数又能完成不同的任务

如此我们有两种思路:

1、设计回调函数

2、引入函数指针数组来设计转移表。

在此我们以思路二进行改造(思路一请见《C语言指针详解(四)》)。首先我们要思考,设计转移表的话函数指针数组中的元素应当如何设置。我们一共自定义了 4 个函数,所以我们就把这四个函数的地址放入数组。具体演示如下:

#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 (*parr[5])(int , int) = { 0 , add , sub , mul , div };//转移表//之所以首元素为 0  是为了便于选择,使1-4都对应具体函数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 = (parr[input])(x, y);printf("ret = %d\n", ret);}else if (input == 0){printf("退出计算器\n");}else{printf("输入错误");}} while (input);return 0;
}

全文至此结束!!!
写作不易,不知各位老板能否给个一键三连或是一个免费的赞呢()(),这将是对我最大的肯定与支持!!!谢谢!!!()()

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图的DFS

LeetCode2368 受限条件下可到达节点的数目 class Solution { public:int dfs(vector<vector<int>>& g,int x,int fa){int sum1;for(int y:g[x]){if(y!fa) sumdfs(g,y,x);}return sum;}int reachableNodes(int n, vector<vector<int>>& edges, …

C语言指针(3)

目录 一、字符指针变量 二、数组指针变量 三、⼆维数组传参的本质 四、函数指针变量 五、typedef 关键字 六、函数指针数组 一、字符指针变量 字符指针char* &符号名 符号名&#xff0c;这都是获取的是首元素地址。 int main() {char a[] "abcdef";cha…

另一棵树的子树 - 力扣(LeetCode)C语言

572. 另一棵树的子树 - 力扣&#xff08;LeetCode&#xff09;&#xff08;点击前面链接即可查看题目&#xff09; 一、题目 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&…

机器学习中的关键距离度量及其应用

引言 在当今的数据驱动世界中&#xff0c;机器学习算法扮演着至关重要的角色&#xff0c;它们在图像分类、面部识别、在线内容审核、零售目录优化和推荐系统等多个领域发挥着重要作用。这些算法的核心在于它们能够识别和利用数据之间的相似性。而实现这一点的关键&#xff0c;…

ShardingSphere 内核工作原理

文章目录 内核工作原理配置管控SQL Parser: SQL解析引擎SQL Router- SQL 路由引擎SQL Rewriter : SQL 优化引擎SQL Executor &#xff1a; SQL执行引擎Result Merger&#xff1a; 结果归并 内核工作原理 ShardingSphere的整体架构图是这样的&#xff1a; 配置管控 在进入Shar…

MySQL事务,锁,MVCC总结

mysql中最重要的就是事务&#xff0c;其四大特性让我们维持了数据的平衡&#xff0c;一致。那么事务究竟是什么&#xff0c;与什么相关&#xff0c;他的使用步骤&#xff0c;以及使用过程中我们会遇到什么问题呢&#xff1f;下面我们一起学习交流! 1.MySQL的存储引擎&#xff…

SPIFFS与LittleFS的对gz文件格式的区别

SPIFFS 只能安装在Arduino上。LittleFS支持Arduino IDE和VScode的 PlatformIO。 SPIFFS serveStatic: server.serveStatic("/", SPIFFS, "/") 负责提供 SPIFFS 文件系统中的文件。您可以在 SPIFFS 上放置 .gz 文件&#xff0c;并该方法将自动处理它们。 …

git cz代码提交规范,适用于node14以上

1.效果 3. 在项目中如何添加 3.1 安装(只提供npm安装方式)全局安装 commitizen npm i -D commitlint commitlint/config-conventional commitizen cz-git 3.2 配置模版 在项目的根目录下配置添加文件 commitlint.config.js 并写入如下代码 /** type {import(cz-git).UserCo…

C# 类型转换

隐式&#xff08;implicit&#xff09;类型转换 1.不丢失精度的转换 2.显示&#xff08;explicit&#xff09;类型的转换 有可能丢失精度的转换 使用convert转换 ToString方法&#xff1a;将数值类型转换成字符串型

PDF密码移除技巧: 五大 PDF 密码移除器

如果您想解密或删除 PDF 密码&#xff0c;该怎么办&#xff1f;PDF 是一种经常用于商业的格式&#xff0c;您可以在培训、教育和商业场合使用它。添加这些 PDF 文件的密码可以保护您的安全和隐私。因此&#xff0c;有很多 PDF 都用密码加密&#xff0c;当您想要查看这些 PDF 时…

吃透张宇1000题和660题,能保底100分吗?

暑假已经过一半了&#xff0c;很多人都在埋头做题&#xff0c;如果你选择的是1000题660题 一定要好好看这篇笔记&#xff01; 因为很多人做题做到现在&#xff0c;有点迷茫 主要的迷茫点有三个&#xff1a; 1、为什么1000题和660题也都做不少了&#xff0c;遇到新题&#x…

RS485 芯片SN65HVD72DR导致的死机问题调试

最近再一次栽倒在这颗RS485 芯片上了&#xff0c;硬件说这和芯片功耗有点高&#xff0c;要控下电源, 结果10次有9次程序死机&#xff01; 先上图&#xff0c;请各位高手看看&#xff0c;这电路有问题没有&#xff1f; 为什么我会说是RS485 芯片导致的死机&#xff1f;因为 只要…

ai自动配音工具

AI拟音大师&#xff0c;给你的无声视频添加生动而且同步的音效 &#x1f61d;文件夹是一种基于文本的视频到音频生成框架,可以生成高质量的音频,在语义上相关,并与输入视频时间同步。 下载地址&#xff1a;https://pan.quark.cn/s/5a2be1cc5551

被工信部认可的开源软件治理解决方案

近日&#xff0c;工信部网络安全产业发展中心正式发布了“2023年信息技术应用创新解决方案”&#xff0c;开源网安凭借“基于SCA技术开源软件治理解决方案”顺利入选&#xff0c;成为经工信部认可的优秀解决方案&#xff0c;这是开源网安连续两届荣获此荣誉。 工业和信息化部网…

基于强化学习的Deep-Qlearning网络玩cartpole游戏

1、环境准备&#xff0c;gym的版本为0.26.2 2、编写网络代码 # 导入必要的库 import gym import torch import torch.nn as nn import torch.optim as optim import numpy as np from collections import deque import random# 定义DQN网络 class DQN(nn.Module):def __init__…

基于web的购物网站的设计与实现(系统源码+lw+部署文档+讲解等)

文字目录&#xff1a; 目录 详细视频演示 系统实现界面 1.1系统开发环境以及运行环境 1.1.1系统开发环境 1.1.2系统运行环境 1.2系统功能实现 1.3管理员模块实现 2 技术介绍 2.1 thinkphp5介绍 2.2 MySQL数据库 2.3 B/S结构 4.1系统结构设计 4.2系统功能结构设计…

如何挑选理想的报表工具?从入门到专业,测评十大热门工具的优缺点

报表能够用表格和图表等格式动态显示数据&#xff0c;因此衍生出相应的报表工具&#xff0c;已经有20年以上的发展历史了&#xff0c;期间报表工具不断随着需求的改变而更新迭代&#xff0c;今天博主就来推荐十款实用的报表工具&#xff0c;祝你轻松解决烦人的中国式复杂报表。…

【MySQL进阶】MySQL主从复制

目录 MySQL主从复制 概念 主从形式 一主多从 多主一从 双主复制 主从级联复制 主从复制原理 三个线程 两个日志文件 主从复制的主要工作模式 异步复制 半同步复制 全同步复制 MySQL主从复制 概念 MySQL主从复制是一种数据分布机制&#xff0c;允许从一个数据库服…

32.x86游戏实战-使用物品call

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

【TDH社区版大事件】图分析、全文检索、小文件治理、数据开发工具通通都有!

星环科技大数据基础平台TDH社区版&#xff0c;在保留了商业版核心技术优势的基础上最大程度地降低了用户使用大数据技术的门槛与成本&#xff0c;具有更轻量、更简单、更易用等特性。 此次TDH社区开发版、社区版、社区订阅版均发布了新版本&#xff0c;带来新的产品组件和新的…