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.hqwc.cn/news/539377.html

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

相关文章

CSDN 编辑器设置图片缩放和居中

CSDN 编辑器设置图片缩放和居中 文章目录 CSDN 编辑器设置图片缩放和居中对齐方式比例缩放 对齐方式 Markdown 编辑器插入图片的代码格式为 ![图片描述](图片路径)CSDN 的 Markdown 编辑器中插入图片&#xff0c;默认都是左对齐&#xff0c;需要设置居中对齐的话&#xff0c;…

微服务分布式基于Springcloud的拍卖管理系统597wx

越来越多的用户利用互联网获得信息&#xff0c;但各种信息鱼龙混杂&#xff0c;信息真假难以辨别。为了方便用户更好的获得信息&#xff0c;因此&#xff0c;设计一种安全高效的拍卖管理系统极为重要。 为设计一个安全便捷&#xff0c;并且使用户更好获取拍卖管理系统&#xff…

SSL VPN基础原理

目录 SSL ---安全传输协议&#xff08;安全套接层&#xff09;---TLS ----传输层安全协议 SSL的工作原理 SSL会话建立的过程 ​编辑 数据传输过程中的封装示意图 无客户端认证的过程 有客户端认证的过程 SSL VPN的核心技术---虚拟网关技术 服务器验证的点&#xff1a; 资源…

如何安装ES

Elasticsearch入门安装 ES的官方地址&#xff1a;Elasticsearch 平台 — 大规模查找实时答案 | Elastic 我们进到网页可以看到platform&#xff08;平台&#xff09; 我们可以看到Elasticsearch logstash kibanba beats 这几个产品 Elasticsearch&#xff1a;分布式&…

Linux本地部署开源AI的PDF工具—Stirling PDF并实现公网随时访问

文章目录 1. 安装Docker2. 本地安装部署StirlingPDF3. Stirling-PDF功能介绍4. 安装cpolar内网穿透5. 固定Stirling-PDF公网地址 本篇文章我们将在Linux上使用Docker在本地部署一个开源的PDF工具——Stirling PDF&#xff0c;并且结合cpolar的内网穿透实现公网随时随地访问。 S…

BBS模型层搭建

BBS模型层搭建 目录 BBS模型层搭建建表思想配置文件模型层User应用&#xff1a;Blog应用&#xff1a;Article应用&#xff1a; 建表思想 配置文件 settings.py&#xff1a; # 默认用户模型指定 AUTH_USER_MODEL User.Userinfo底部添加即可&#xff0c;用于替换默认的Abstrac…

PyCharm创建一个简单的Django项目

1.Django简介 Django 是一个开放源代码的 Web 应用程序框架&#xff0c;由 Python 编写而成。它遵循 MVC&#xff08;模型-视图-控制器&#xff09;的软件设计模式&#xff0c;采用了 MTV&#xff08;模型-模板-视图&#xff09;的架构。Django 的设计目标是使开发复杂的、数据…

Ultra Fast Structure-aware Deep Lane Detection的训练实战

Ultra Fast Structure-aware Deep Lane Detection的训练实战 1、模型介绍 论文 知乎 代码 CULane数据集简介 2、基于CULane数据集格式的训练 2.1、video to img import glob import os import cv2# --------视频转图像----------------------------------------- def vide…

Self-supervised Contextual Keyword and Keyphrase Retrieval with Self-Labelling

文章目录 题目摘要方法数据集实验 题目 通过自我标记进行自我监督的上下文关键字和关键词短语检索 论文地址&#xff1a;https://www.preprints.org/manuscript/201908.0073/v1 项目地址&#xff1a;https://github.com/naister/Keyword-OpenSource-Data 摘要 在本文中&#x…

最新的yolov9,目标检测使用教程

1.克隆仓库&#xff0c;仓库地址&#xff1a; https://github.com/WongKinYiu/yolov9/tree/main 百度盘地址&#xff1a;https://pan.baidu.com/s/15v7XJIMDFG3XHJXfFs6ZDA 密码&#xff1a;1234 除了这两个模型&#xff0c;另外三个模型有问题...&#xff0c;下载ce模型即可 …

openGauss学习笔记-242 openGauss性能调优-SQL调优-典型SQL调优点-SQL自诊断

文章目录 openGauss学习笔记-242 openGauss性能调优-SQL调优-典型SQL调优点-SQL自诊断242.1 SQL自诊断242.1.1 告警场景242.1.2 规格约束 openGauss学习笔记-242 openGauss性能调优-SQL调优-典型SQL调优点-SQL自诊断 SQL调优是一个不断分析与尝试的过程&#xff1a;试跑Query&…

设计模式在业务中的实践

一、前言 随着美团外卖业务的不断迭代与发展&#xff0c;外卖用户数量也在高速地增长。在这个过程中&#xff0c;外卖营销发挥了“中流砥柱”的作用&#xff0c;因为用户的快速增长离不开高效的营销策略。而由于市场环境和业务环境的多变&#xff0c;营销策略往往是复杂多变的…