c语言指针基础下(下)

指针

字符指针变量

字符串变量的一般使用

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

上面就是通过指针的解引用改变了ch的值


int main()
{char* p = "ni hao a";//这个是常量字符串printf("%s\n", p);//打印的是一个字符串printf("%c\n",*p);//打印的是?return 0;
}

这个代码p打印的是什么呢?
在这里插入图片描述
可以发现
p打印的是n,也就是字符串的第一个元素,也就是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量p。
常量字符串的值是不可以改变的。也就是不可以通过解引用的操作改变。常量字符串的值。

在这里插入图片描述
打印字符串的三种方法

#include<stdio.h>
#include<string.h>
int main()
{//方法一:char* arr = { "hello world" };printf("%s\n", arr);//利用指针打印//方法二printf("%s\n", "hello world");//方法三int len = strlen(arr);//计算字符串 长度for (int i = 0;i < len;i++){printf("%c", *(arr + i));}return 0;
}

数组指针变量

数组指针变量的初步理解

数组指针是一种指针,是存放数组的地址的一种指针变量。
怎幺样创建一个数组指针变量,并且初始化?

int main()
{int arr[10] = { 0 };int (*p) [10] = &arr;//&arr取出的是整个数组的地址return 0;
}

为什么p要加括号?
因为[]操作符的计算优先级是大于
操作符的,如果不加括号p会与[]先结合,那就不是数组指针了,变成了指针数组
p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫 数组指针。
[]里面的数字不能省略
在这里插入图片描述

用图片来理解数组指针变量

在这里插入图片描述

数组指针的类型

int a;//当创建一个整形类型时,去掉变量名a,剩下的int就是变量类型
char ch;//去掉变量名ch剩下的char就是变量类型
char (*p)[10];//p是数组指针的变量名,去掉p剩下的char *[10]就是数组指针的类型。

所以[]里面的数字一定不能省略。它代表数组指针指向了几个元素。

二维数组传参

使用数组的形式进行二维数组的传参

void array(int arr[4][4], int x, int y)//用数组来接收
{for (int i = 0;i < x;i++){for (int j = 0;j < y;j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[4][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7} };array(arr, 4, 4);//把数组,行数,列数传过去return 0;
}

二维数组传地址

既然数组名是数组首元素的地址,那么二维数组的数组名地址是谁呢
在这里插入图片描述
因为在理解二维数组时,就是把二维数组看成了多个一维数组,二维数组的一行就是一个一维数组,所以二维数组的数组名对应的就是二维数组的第一行,每一行都可以看成一个元素。
所以二维数组的数组名就是第一行的地址
这就可以得到二维数组数组指针的初始化方式

int (*p) [4] = arr;//p是数组指针存放的是数组首元素的地址[4]代表首元素(第一行)有4个元素
```c
void array(int(*arr)[4], int x, int y)//用数组指针来接收数组首元素的地址
{for (int i = 0;i < x;i++){for (int j = 0;j < y;j++){printf("%d ", (*(arr + i))[j]);//arr+i会找到数组的每一行的地址,通过解引用会得到这个值,在通过下标引用操作打印每行的每个元素}printf("\n");}
}int main()
{int arr[4][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7} };array(arr, 4, 4);//把数组,行数,列数传过去return 0;
}

函数指针变量

如何创建函数指针变量

函数指针变量是存放函数地址的变量,以后可以通过函数指针来调用这个函数

&函数名和函数名的关系
int Add(int a, int b)
{return a + b;
}
int main()
{printf("%p\n",Add);printf("%p\n",&Add);return 0;
}

在这里插入图片描述
可以发现&函数名和函数名的地址是一样的,所以&函数名和函数名都可以表示一个函数的地址

存放函数地址

上面已经知道了函数也是有地址的,并且函数名和&函数名都可以表示函数的地址。
那怎样吧这个地址存起来呢?

int Add(int a, int b)
{return a + b;
}
int main()
{int(*pf)(int, int) = &Add;return 0;
}

pf是这个函数指针的名字,用括号让pf和*结合证明他是一个指针变量,后面的括号里面的类型是函数对应的形参的类型,前面的类型对应的是函数的返回值类型
这时有以下一段代码,要将他存到一个名为pa的函数指针中要怎样表示?

void* text(char* p, int* a)
{return NULL;
}
int main()
{= &text;return 0;
}

text函数的形参类型是char *和int *,所以后面的括号就是char *和int ,函数的返回值是void所以前面的括号就是void *。

void *(*pa)(char *,int *)=&text;

这就将text函数的地址存放到了pa函数变量中了。

函数指针类型

函数指针类型和数组指针类型一样去掉名字就是类型,void *(*pa)(char *,int *)的类型就是void ()(char *,int *)

函数指针变量的使用

int Add(int a, int b)
{return a + b;
}
int main()
{int(*p1)(int, int) = &Add;int(*p2)(int, int) = Add;int i = (*p1)(2, 7);//通过解引用p1,找到Add函数的地址printf("%d ", i);return 0;
}

在我们平时调用函数的时候是函数名(实参),函数名的本质也是一个地址,也是通过地址的方式调用函数,这时我们将函数的地址存到了p1,p2中那能不能直接通过p1,p2来调用函数?

int add(int a, int b)
{return a + b;
}
int main()
{int(*p1)(int, int) = &add;int(*p2)(int, int) = add;int i = p1(2, 7);int j = p1(2, 7);printf("%d ", i);printf("%d ", j);return 0;

在这里插入图片描述
发现是可以的所以以后调用函数时可以通过*函数名的方式也可以直接通过函数名的方式.

尝试解析两段代码

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

viod ()()是一个函数指针类型,(void ()()) 0类型+括号+0就是对0进行强制类型转换,将0转换为地址,前面的*是对0所对应的地址进行解引用操作,后面的括号是值函数在调用时不传任何参数。

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

这是一次函数声明,void()(int)是一个函数指针,signal是函数名,signal的参数是int,void()(int),剩下的void(*)(int)是函数的返回值。

typedef

typedef 是⽤来类型重命名的,可以将复杂的类型简单化。

typedef重命名无符号整形

在我们写代码时有时候会用到无符号整形变量unsigned int发现这样写太麻烦了,这时就可以通过typedef关键字来对类型进行简单化

//格式:
typedef unsigned int uint;

通过typedef关键字将unsigned int简写成了uint,uint的功能与unsigned int时完全相同的。

typedef unsigned int uint;
int main()
{uint  a= 20;printf("%u ", a);return 0;
}

在这里插入图片描述
用uint程序也可以正常运行输出

用typedef 重命名指针类型

typedef int* in;

将整形指针类型重命名成了in

用typedef 重命名数组指针类型

将 int(*)[5] 类型重命名为 pf

typedef  int(*pf)[5];

要让想重命名成的名字放在*号右边

用typedef关键字重命名函数指针

将 void(*)(int) 类型重命名为 pf_t

 void(* pf_t  )(int)

和数组指针重命名的规则一样将名字放在*右边

//那这个时候就可以对void (*signal(int , void(*)(int)))(int);进行简化
//通过上面的分析可以知道该代码的函数参数和返回值类型都是void(*)(int)
//这时可以将void(*)(int)重命名成pr_t
typedef void(* pf)(int);
pf signal(int,pf)//化解后的代码

define和typedef的区别

typedef int* pf;
#define PD int*
int main()
{pf p1, p2;PD p3, p4;return 0;
}

define是将int 的内容变成了PD ,typedef是将int重写成了pf虽然看起来是一样的,但通过调试发现两者有区别。
在这里插入图片描述

发现p1,p2,p3的类型都是int *,p4的类型是int
所以用define重命名只是作用于第一个变量,typedef是作用与全部变量

函数指针数组

函数指针数组的定义

前面学过指针数组

char * arr[5];//是字符指针数组,存放的是字符地址
int *arr[3];//是整形指针数组,存放的是整形地址

那么函数指针数组就是存放函数地址的数组喽。

函数指针数组的初始化

int (*pf[3])();

pf与[]结合证明pf是数组,数组有3个元素,每个元素类型是int(*)()的函数指针。
这时有几个函数

int Add(int x, int y)
{return x + y;
}
int sub(int x, int y)
{return x - y;
}
int mul(int x, int y)
{return x * y;
}
int div(int x, int y)
{return x / y;
}
int main()
{return 0;
}

可以看出以上几个函数的函数指针类型是一样的都是int (*)(int , int ),这时就可以将他们的地址存放到一个函数指针数组中

int (*arr[4])(int  int)={Add,Sub,Mul,Div};

arr先和[]结合证明他是一个数组,数组有4个元素,前面的证明这个数组指向的是一个指针,int ()(int , int )是这个数组的类型,里面存放的是Add,Sub,Mul,Div的地址

函数指针数组的使用

当把函数的地址存放到一个函数指针数组中时可以对其中的元素进行调用

int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
int main()
{int (*arr[4])(int, int) = { Add,Sub,Mul,Div };int i = 0;for (i = 0;i < 4;i++){int ret = arr[i](2, 4);//调用数组中的每个元素,将至=值返回到retprintf("%d ", ret);}return 0;
}

转移表

函数指针数组的⽤途:转移表
这时我们要实现一个计算机的程序,用来实现+ - * / 的操作。
方法一:用函数一个一个调用

int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
void menu()
{printf("**1.Add**********2.Sub********\n");printf("**2.Mul**********3.Div********\n");printf("**0.exit**********************\n");printf("*****************************\n");
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择");scanf("%d", &input);switch (input){case 1:printf("请输入两个操作数");scanf("%d%d", &x, &y);ret = Add(x, y);printf("结果为:%d\n",ret);break;case 2:printf("请输入两个操作数");scanf("%d%d", &x, &y);ret = Sub(x, y);printf("结果为:%d\n",ret);break;case 3:printf("请输入两个操作数");scanf("%d%d", &x, &y);ret = Mul(x, y);printf("结果为:%d\n",ret);break;case 4:printf("请输入两个操作数");scanf("%d%d", &x, &y);ret = Div(x, y);printf("结果为:%d\n",ret);break;case 0:printf("退出游戏");break;default :printf("选择错误,请重新选择");}} while (input);}

这段代码确实可以完成要求计算两个数的加减乘除的是有问题
在这里插入图片描述
图中括起来的部分操作过程都是一样的,相当于重复写了四段代码造成了代码的冗余,
解决方法1:将4个函数存放在一个函数指针数组中,然后对数组的元素进行调用、

int (*arr[4])(int, int) = { Add,Sub,Mul,Div };//创建一个函数指针数组,里面存放四个函数的地址

创建完一个函数指针数组后还有一个问题,当输入1的时候访问的是Sub函数,但我们希望输入1时访问Add函数,这时可以时首元素的值设为NULL

int (*arr[5])(int, int) = { NULL,Add,Sub,Mul,Div };

这样就可以解决那个问题了

int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
void menu()
{printf("**1.Add**********2.Sub********\n");printf("**2.Mul**********3.Div********\n");printf("**0.exit**********************\n");printf("*****************************\n");
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择");scanf("%d", &input);printf("输入两个整数");scanf("%d%d", &x, &y);int (*arr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//创建一个函数指针数组,里面存放四个函数的地址ret=arr[input](x, y);//当input=1是访问Add函数完成加法操作,当input=2时,访问Sub函数完成减法操作printf("结果为:%d\n",ret);} while (input);
}

这样也可以完成要求,但照样有问题
在这里插入图片描述
当输入的数不符合条件时系统不会有任何的提示,希望的效果是当输入0时程序会停止运行,输入<0或者>4的整数时系统提醒重新输入,

int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择\n");scanf("%d", &input);if (input >= 1 && input <= 4){printf("输入两个整数\n");scanf("%d%d", &x, &y);int (*arr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//创建一个函数指针数组,里面存放四个函数的地址ret = arr[input](x, y);printf("结果为:%d\n", ret);}else if(input == 0){printf("退出程序\n");}else{printf("输入错误重新选择\n");}} while (input);
}

在这里插入图片描述
这时这个代码就完整了

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

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

相关文章

超融合如何助力水务公司实现虚拟化与容器环境统一管理?

近些年&#xff0c;企业 IT 基础架构现代化转型的步伐逐渐加快&#xff0c;不少金融、医疗、政府等行业的用户&#xff0c;已在生产环境部署 Kubernetes 等云原生基础设施&#xff0c;为业务应用提供敏捷支持。不过&#xff0c;一些企业的容器化转型仍处于起步阶段&#xff0c;…

学习Android的第二十八天

目录 Android Service (服务) 线程 Service (服务) Service 相关方法 Android 非绑定 Service startService() 启动 Service 验证 startService() 启动 Service 的调用顺序 Android 绑定 Service bindService() 启动 Service 验证 BindService 启动 Service 的顺序 …

Instant --java学习笔记

Instant 时间线上的某个时刻 / 时间戳过获取lnstant的对象可以拿到此刻的时间&#xff0c;该时间由两部分组成:从1970-01-01 00:00:00 开始走到此刻的总秒数不够1秒的纳秒数 Instant的常见方法&#xff1a; Instant可以用来记录代码的执行时间&#xff0c;或用于记录用户操作某…

面试经验分享 | 通关某公司面试靶场

0x00:探测IP 首先打开时候长这个样&#xff0c;一开始感觉是迷惑行为&#xff0c;试了试/admin&#xff0c;/login这些发现都没有 随后F12查看网络&#xff0c;看到几个js文件带有传参&#xff0c;就丢sqlmap跑了一下无果 随后也反查了域名一下&#xff0c;发现没有域名&#…

[java入门到精通] 20 反射精讲

复习 1.索引&#xff1a;主要是提高查询性能。 2.索引分类&#xff1a;主键索引 唯一索引 普通索引 组合索引 全文索引 hash索引 3.索引底层数据结构是btree.非叶子结点是由&#xff1a;索引指针域 叶子结点&#xff1a;索引数据(数据地址)&#xff0c;是双链表 4.jdbc:java…

《高效便捷,探索快递柜系统架构的智慧之路》

随着电商业务的蓬勃发展&#xff0c;快递柜系统作为一种高效、便捷的最后一公里配送解决方案&#xff0c;正在受到越来越多企业和消费者的青睐。本篇博客将深入探讨快递柜系统的架构设计理念、优势和实践&#xff0c;帮助读者了解如何构建智能化的快递柜系统&#xff0c;提升物…

简单的网页才好学习嘛!!!

做一个个人博客第一步该怎么做&#xff1f; 好多零基础的同学们不知道怎么迈出第一步。 那么&#xff0c;就找一个现成的模板学一学呗&#xff0c;毕竟我们是高贵的Ctrl c v 工程师。 但是这样也有个问题&#xff0c;那就是&#xff0c;那些模板都&#xff0c;太&#xff01;…

idea中操作Git将本地仓库代码完整提交到Gittee中

文章目录 1.在idea中打开你的项目根目录&#xff0c; idea中vcs -> create Git Repository2.选中你的项目所在的目录3. git commit -m"你的备注"4. 直接强制本地仓库覆盖远程仓库的东西5.或者直接使用git 进行这个操作 1.在idea中打开你的项目根目录&#xff0c; …

【Python/crawl】如何使用Python爬虫将一系列网页上的同类图片下载到本地

【需求】 从网页https://www.zhainq.com/%e7%be%8e%e5%a5%b3%e5%86%99%e7%9c%9f%e6%9c%ba%e6%9e%84/%e6%97%a5%e6%9c%ac%e7%be%8e%e5%a5%b3%e5%86%99%e7%9c%9f/109012.html 开始&#xff0c;有十七页&#xff0c;每页都有大漂亮“小濑田麻由”的若干图片&#xff0c;想要将其…

Gitee的注册和代码提交(附有下载链接)

目录 一、Git的下载和安装二、安装图形化界面工具三、在Gitee上创建仓库四、如何把仓库开源五、Clone远程仓库到本地六、拷贝代码到本地的仓库七、Add-Commit-Push到远程仓库八、可能出现的问题8.1 建议在本地仓库直接创建项目8.2 第一次Push可能出现的问题8.3 怎么删除Gitee上…

Element-Plus: Select组件实现滚动分页加载

Element-Plus的select组件并没有自带滚动分页加载的功能&#xff0c;其虽然提供了自定义下拉菜单的底部的方式可以自定义上一页及下一页操作按钮的方式进行分页加载切换&#xff1a; 但如果不想通过点击分页按钮的方式&#xff0c;利用滚动触底进行下一页加载的话&#xff0c;…

2024年太原等保测评中心名单看这里!

开年来&#xff0c;不少太原小伙伴打算做等保了。大家都在问&#xff0c;目前太原等保测评中心有哪些&#xff1f;在哪里丫&#xff1f;这不我们小编就给大家整理一下&#xff0c;仅供参考哈&#xff01; 2024年太原等保测评中心名单看这里&#xff01; 序号&#xff1a;1 …

[C++]20.实现红黑树。

实现红黑树 一.基本概念&#xff1a;1.红黑树的概念&#xff1a;2.红黑树的性质&#xff1a; 二.实现红黑树&#xff1a;1.基本结构&#xff1a;2.插入节点的多种情况&#xff1a;1.叔叔存在且为红&#xff1a;2.叔叔不存在/存在且为黑(单旋变色)3.叔叔不存在/存在且为黑(多旋&…

C/C++中{}的用法总结(全)

C基础专栏&#xff1a;http://t.csdnimg.cn/UjhPR 目录 1.定义初始化列表&#xff08;Initializer List&#xff09; 2.类成员初始化列表 3.无默认构造函数的类的默认初始化&#xff08;C11 及以后版本&#xff09; 4.初始化器列表构造函数&#xff08;C11 及以后版本&…

Docker学习之镜像管理(超详解析)

Docker镜像生命周期&#xff08;可以把docker镜像理解为虚拟机镜像&#xff09; 实验内容&#xff1a; 搜索官方仓库镜像 [rootlocalhost ~]# docker search busybox //以查找busybox为例 搜索说明&#xff1a;name镜像名称 description镜像说明 stars点赞数量 official…

四川宏博蓬达法律咨询有限公司:法律服务的行业翘楚

在当今社会&#xff0c;法律服务已经成为人们生活中不可或缺的一部分。随着法律意识的提高&#xff0c;选择一家专业、可靠的法律咨询公司显得尤为重要。四川宏博蓬达法律咨询有限公司&#xff0c;作为业内的佼佼者&#xff0c;以其卓越的服务质量和广泛的业务范围&#xff0c;…

Nacos 集群搭建

1 . 集群结构图 : 其中包括3个nacos结点&#xff0c;然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx ; 我们计划的集群结构 : 三个nacos结点的地址 : 节点ipportnacos1192.168.150.18845nacos2192.168.150.18846nacos3192.168.150.18847 2 . 搭建集群 搭…

2024最新轻量应用服务器简介_轻量应用服务器购买指南

腾讯云轻量应用服务器开箱即用、运维简单的轻量级云服务器&#xff0c;CPU内存带宽配置高并且价格特别便宜&#xff0c;大带宽&#xff0c;但是限制月流量&#xff0c;轻量2核2G3M带宽61元一年、2核2G4M优惠价99元一年&#xff0c;540元三年、2核4G5M带宽165元一年&#xff0c;…

JAVA初阶数据结构(链表)练习(这些可以作为java包中的方法)

这里的每一个题大家都要仔细完成&#xff0c;这些题目每个我都至少思考了两个小时左右&#xff08;沉重心&#xff0c;慢慢来&#xff09; 1.反向链表的实现&#xff08;对链表进行翻转&#xff09;&#xff08;力扣有&#xff09; &#xff08;1&#xff09;图示 &#xff0…

2024考研国家线公布,各科分数线有哪些变化?考研国家线哪些涨了,哪些跌了?可视化分析告诉你

结论在文章结尾 2024考研国家线 一、近五年国家线趋势图-学术硕士 文学 管理学 工学照顾专业 体育学 交叉学科 军事学 历史学 理学 享受少数名族照顾政策的考生 中医类照顾专业 教育类 艺术类 医学 工学 哲学 法学 农学 经济学 二、近五年国家线趋势图-专业硕士 中医 应用心理 …