C语言小白急救 指针进阶讲解1


文章目录

  • 指针
  • 一、 字符指针
  • 二、 指针数组
  • 三、数组指针
    • 1.数组的地址
    • 2.数组指针
    • 3.数组指针的应用
  • 四、数组参数、指针参数
    • 1. 一维数组传参
    • 2.二维数组传参
    • 3.一级指针传参
    • 4.二级指针传参
  • 五、函数指针
    • 1.函数的地址
    • 2.函数指针
    • 3.练习


指针

指针的概念:
1.指针就是个变量。用来存放地址,地址唯一标识一块内存空间
2.指针的大小固定是4/8个字节,32位平台,64位平台
3.指针是有类型的,指针的类型决定指针 ± 整数所访问空间的步长,以及指针解引用操作时的权限

一、 字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* 。
用法示例:

int main()
{char a = 'a';char* m = &a;const char* p = "abcd";//加const会更稳定,避免对此常量字符串进行更改//此字符串表示首元素a的地址//等价于char arr[] = "abcd";//char* p = arr;printf("%c\n","abcd"[1]);return 0;
}

例题:

int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");if (*str3 == *str4)printf("*str3 and *str4 are same\n");elseprintf("*str3 and *str4 are not same\n");if (&str3 == &str4)printf("&str3 and &str4 are same\n");elseprintf("&str3 and &str4 are not same\n");return 0;
}

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

解析

int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)//两个数组会分配两个独立的空间,所以首元素地址不一样printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)//两个指针变量指向的是都是同一常量字符串"hello bit",存放的都是其的地址printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");if (*str3 == *str4)//对两指针变量解引用后,发现其中的数据都是首元素hprintf("*str3 and *str4 are same\n");elseprintf("*str3 and *str4 are not same\n");if (&str3 == &str4)//两指针变量在创建时会为它们分配随机空间,所以地址不相等printf("&str3 and &str4 are same\n");elseprintf("&str3 and &str4 are not same\n");return 0;
}

在这里插入图片描述

二、 指针数组

指针数组是数组,是存放指针的数组(即存放在数组中的元素都是指针类型的)

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

常用的就是使用一个指针数组来描述一个二维数组
例如:

int main()
{int arr1[] = { 1,2,3,4,4 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int* arr[] = { arr1,arr2,arr3 };//存放的都是数组名:首元素的地址(int* 型)int i = 0;for (i=0;i<3;i++){int j = 0;for (j=0;j<5;j++){printf("%d ",arr[i][j]);}printf("\n");}return 0;
}```
![在这里插入图片描述](https://img-blog.csdnimg.cn/584d2e6b315a48d1b63ea4106b4c3643.png)2:```c
int main()
{char* arr[3] = { "abb","bcc","cdd" };//内部存放的是三个常量字符串的首元素地址,int* 型,可根据首元素地址找到整个字符串int i = 0;for (i=0;i<3;i++){printf("%s ",arr[i]);}return 0;
}

在这里插入图片描述

三、数组指针

数组指针就是指向数组的指针。
数组指针需要指向数组,那么我们就要取出数组的地址

1.数组的地址

数组名
数组名是数组首元素的地址,但两种情况除外
1.sizeof(数组名) 这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
2.&数组名 这里的数组名表示整个数组的地址,取出的是数组的地址
例如:

int main()
{int arr[10];printf("%p\n",arr);printf("%p\n",&arr[0]);printf("%p\n",&arr);printf("%p\n",arr+1);printf("%p\n",&arr[0]+1);printf("%p\n",&arr+1);return 0;
}

在这里插入图片描述

2.数组指针

数组指针就是指向数组的指针。
int (p)[10];
//解释:p先和
结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合。

每次±1 都会跳过指向数组中所有元素的字节数

int main()
{int arr[10] = { 0 };//为了防止变成int* p[10];一个指针数组,所以要在*p处打括号// 前面的int表示指向的数组是int型的// p是用来存放数组的地址的,p就是数组指针// 后面的[10]要明确的表示出来,每+1会跳出10*4个字节int* brr[10] = { 0 };//指针数组int* (*q)[10] = &brr;int arr1[] = { 1,2,3 };int(*p1)[3] = &arr1;return 0;
}

3.数组指针的应用

一维数组中不适用

int main()
{//在一维数组中,int arr[] = {1,2,3};int* p = arr;int i = 0;for (i=0;i<3;i++){printf("%d ",p[i]);}return 0;
}

二维数组中使用

void Print(int (*p)[5],int r,int c)
//传输过来的是数组首行元素的地址,用一个数组指针接受,首行共有5个元素
{int i = 0;for (i=0;i<3;i++){int j = 0;for (j=0;j<5;j++){printf("%d ",p[i][j]);}printf("\n");}
}int main()
{int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };Print(arr,3,5);//二维数组传参,数组名是数组首行元素的的地址//在这里数组名就是{1,2,3,4,5}的地址return 0;
}

四、数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

1. 一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}int main()
{int arr[10] = {0};int *arr2[20] = {0};test(arr);test2(arr2);
}

解析:

void test(int arr[])
//数组传参,形参是可以写成数组的
{}
void test(int arr[10])
//我们其实传输的是数组首元素的地址,有无大小不影响
{}
void test(int* arr)
//传输的是数组首元素的地址,可以用指针来接受
{}
void test2(int* arr[20])
//指针数组传参,形参也用指针数组,可以
{}
void test2(int** arr)
//传输的是指针数组首元素的地址,也就是int* 的地址
//用二级指针来存储一个一级指针的地址 ,可以        
{}int main()
{int arr[10] = { 0 };int* arr2[20] = { 0 };test(arr);test2(arr2);
}

所以:一维数组传参,可以
用数组接收,1级指针接收,2级指针接收

2.二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{int arr[3][5] = {0};test(arr);
}

解析:

void test(int arr[3][5])
//二维数组传参,用二维数组接收,可以
{}
void test(int arr[][])
//二维数组,行可以省略,列不可以省
{}
void test(int arr[][5])//可以
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)
//二维数组首元素地址是首行元素,不能用int* 指针接收
{}
void test(int* arr[5])
//传输的实际是首元素地址,指针数组不能接收
{}
void test(int(*arr)[5])
//与之前的例子一样,十个指向一行的指针来接受
{}
void test(int** arr)
//二级指针是用于接受一级指针的地址,与上面的int* 的错误类似
{}
int main()
{int arr[3][5] = { 0 };test(arr);
}

所以:二维数组传参,可以
用二维数组接收,函数形参的设计只能省略第一个[]的数字(行可以省略,列不可以省),因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。

3.一级指针传参

oid print(int* p, int sz)
//形式参数写成一级指针就行了
{int i = 0;for (i = 0; i < sz; i++){printf("%d\n", *(p + i));}
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);//一级指针p,传给函数print(p, sz);return 0;
}

思考:已知形参是个一级指针,能接收什么参数

int main()
{int a = 0;int* p = &a;int arr[5];test(arr);//传输一维数组的数组名test(p);//传一级指针test(&a);//传整形变量的地址return 0;
}

所以一级指针传参时,可以传输:一维数组的数组名,一级指针,整形变量的地址

4.二级指针传参

void test(int** ptr)
{printf("num = %d\n", **ptr);
}
int main()
{int n = 10;int* p = &n;int** pp = &p;test(pp);test(&p);return 0;
}

思考:当函数的参数为二级指针的时候,可以接收什么参数?

void test(char **p)
{}
int main()
{char c = 'b';char*pc = &c;char**ppc = &pc;char* arr[10];test(&pc);test(ppc);test(arr);//Ok?return 0;
}

解析:

void test(char** p)
{}
int main()
{char c = 'b';char* pc = &c;char** ppc = &pc;char* arr[10];test(&pc);//传输1级指针变量的指针test(ppc);//传输2级指针test(arr);//传输1级指针数组首元素return 0;
}

所以,二级指针传参可以传输:1级指针变量的指针,2级指针,1级指针数组首元素

五、函数指针

函数指针->指向函数的指针->存放的是函数的地址->怎么得到函数的地址?

1.函数的地址

首先,我们看一段代码

void test()
{printf("hehe\n");
}
int main()
{printf("%p\n", test);printf("%p\n", &test);return 0;
}

运行结果:
在这里插入图片描述
从图中可以看出,函数名与&函数所表示的地址一样,都是函数的地址

2.函数指针

那我们的函数的地址要想保存起来,怎么保存?

void test()
{printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

注意:要注意在* 与 P之间打括号,如果不打的话,就会变成函数声明

int* pfun1();//返回值为int* 类型,函数名为pfun1,() 无参数//就变成了一个函数声明

所以:函数指针的完全表达方式就是
返回类型 (指针名)(参数类型)=&函数;

此外,在函数调用时,我们的

void test()
{printf("hehe\n");
}
int main()
{void (*p)() = &test;test();(*p)();p();return 0;
}

在这里插入图片描述
由结果可以看出,我们三种对函数的调用的结果都是一样的,那么就说明,在函数调用中,是否在函数指针中使用 * 没有影响,都可以进行函数调用;但是,不能随便写,加 * 号的时候必须要括起来。

3.练习

//代码1
(*(void (*)())0)();

解析:

int main()
{( *( void (* )( ))0)();//其内部的 void (* )( )是一个函数指针// ( void (* )( ))0) 是对0进行强制类型转化,转化为函数指针类型//比如:int a=(int)3.14;//( *( void (* )( ))0)();这个整体就是对一个函数的调用//这个函数没有参数,返回类型是void//类似于:void(*p)()=&函数名;//(*p)();return 0;
}
//代码2
void (*signal(int , void(*)(int)))(int);

解析:

int main()
{void ( *signal (int, void(*)(int) ) )(int);//这个代码是一个函数声明//函数名是signal//signal (int, void(*)(int) )//第一个参数是int 地二个参数是函数指针类型:void(*)(int)//最外围是返回类型,返回类型是一个函数指针:void(* )(int)return 0;
}

改进:

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

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

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

相关文章

跨越边界:从前端切图仔走进iOS开发(Swift版--上集)

本文简介 点赞 关注 收藏 学会了 本文将以前端开发者的视角&#xff0c;和各位工友进入iOS开发的世界。 本文以实战为导向&#xff0c;快速掌握iOS开发这个技能。 无论你是想要扩展技能领域&#xff0c;还是对iOS开发充满好奇&#xff0c;花一个下午学习本文都能打开iOS开…

微服务中间件--http客户端Feign

http客户端Feign http客户端Feigna.Feign替代RestTemplateb.自定义Feign的配置c.Feign的性能优化d.Feign的最佳实践分析e.Feign实现最佳实践(方式二) http客户端Feign a.Feign替代RestTemplate 以前利用RestTemplate发起远程调用的代码&#xff1a; String url "http:…

【ARM】Day9 cortex-A7核I2C实验(采集温湿度)

1. 2、编写IIC协议&#xff0c;采集温湿度值 iic.h #ifndef __IIC_H__ #define __IIC_H__ #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h" #include "led.h" /* 通过程序模拟实现I2C总线的时序和协议* GPIOF ---> AHB4* I2C1_S…

GMS基本模块TIN、Solids、Modflow2000/2005、MT3DMS、MODPATH。及其在地下水流动、溶质运移、粒子追踪方面的应用

解决地下水数值模拟技术实施过程中遇到的困难&#xff0c;从而提出切实可行的环境保护措施&#xff0c;达到有效保护环境、防治地下水污染&#xff0c;推动经济社会可持续发展的目的。 &#xff08;1&#xff09;水文地质学&#xff0c;地下水数值模拟基础理论&#xff1b;&am…

构建智慧停车场:4G DTU实现无线数据高速传输

物联网技术的快速发展使得各种设备能够实现互联互通&#xff0c;无线网络技术给我们的日常生活带来了极大的便利。其中的网络技术如无线WiFi及4G网络已经成为了物联网应用中不可或缺的组成部分。而在工业领域中对4G无线路由器的应用是非常广泛的&#xff0c;人们通过4G工业路由…

意外发现Cortex-M内核带的64bit时间戳,比32bit的DWT时钟周期计数器更方便,再也不用担心溢出问题了

视频&#xff1a; https://www.bilibili.com/video/BV1Bw411D7F5 意外发现Cortex-M内核带的64bit时间戳&#xff0c;比32bit的DWT时钟周期计数器更方便&#xff0c;再也不用担心溢出问题了 介绍&#xff1a; 看参数手册的Debug章节&#xff0c;System ROM Table里面带Timestam…

【Java】基础练习(十)

1.判断邮箱 输入一个电子邮箱&#xff0c;判断是否是正确电子邮箱地址。 正确的邮箱地址&#xff1a; 必须包含 字符&#xff0c;不能是开头或结尾必须以 .com结尾和.com之间必须有其他字符 (1) Email类&#xff1a; package swp.kaifamiao.codes.Java.d0823; /** 输入一个…

Android——基本控件下(十七)

1. 文本切换&#xff1a;TextSwitcher 1.1 知识点 &#xff08;1&#xff09;理解TextSwitcher和ViewFactory的使用。 1.2 具体内容 范例&#xff1a;切换显示当前时间 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools&…

docker安装redis

docker安装redis 一、基本介绍二、前期准备三、docker安装redis3.1 redis镜像拉取3.2 Docker挂载redis配置文件3.3 启动redis容器3.4 验证Redis容器是否正常运行 四、Docker删除Redis容器五、Docker删除Redis镜像 一、基本介绍 Docker 是一个开源的应用容器引擎,参考链接&…

探索最短路径问题:寻找优化路线的算法解决方案

1. 前言&#xff1a;最短路径问题的背景与重要性 在现实生活中&#xff0c;我们常常面临需要找到最短路径的情况&#xff0c;如地图导航、网络路由等。最短路径问题是一个关键的优化问题&#xff0c;涉及在图中寻找两个顶点之间的最短路径&#xff0c;以便在有限时间或资源内找…

【管理运筹学】第 5 章 | 整数规划 (2,割平面法及 0-1 变量的特性)

文章目录 引言三、割平面法四、0-1 型整数规划4.1 0-1 变量的特性4.1.1 投资问题4.1.2 约束条件满足个数问题 写在最后 引言 前文我们介绍了整数规划的一种求解方法——分支定界法&#xff0c;可以求解纯整数和混合整数规划问题。现在我们来学习另一种整数规划求解方法——割平…

Java 中使用 ES 高级客户端库 RestHighLevelClient 清理百万级规模历史数据

&#x1f389;工作中遇到这样一个需求场景&#xff1a;由于ES数据库中历史数据过多&#xff0c;占用太多的磁盘空间&#xff0c;需要定期地进行清理&#xff0c;在一定程度上可以释放磁盘空间&#xff0c;减轻磁盘空间压力。 &#x1f388;在经过调研之后发现&#xff0c;某服务…

MyBatid动态语句且模糊查询

目录 什么是MyBtais动态语句&#xff1f;&#xff1f;&#xff1f; MyBatis常用的动态标签和表达式 if标签 Choose标签 where标签 MyBatis模糊查询 #与$的区别 ​编辑 MyBatis映射 resultType resultMap 什么是MyBtais动态语句&#xff1f;&#xff1f;&#xff1f;…

视频转音频mp3怎么弄?

视频转音频mp3怎么弄&#xff1f;在很多人看来&#xff0c;音频就是视频中的一部分&#xff0c;其实这时是一定道理的&#xff0c;视频是一种包含图像和有声音的多媒体文件&#xff0c;没有声音的视频是不完美的。时代发展到现在&#xff0c;短视频已经融入了我们生活的方方面面…

【日常积累】Linux中vi/vim的使用

概述 vim是由vi发展演变过来的文本编辑器&#xff0c;因其具有语法高亮显示、多视窗编辑、代码折叠、支持插件等功能&#xff0c;由于其功能相比vi来说更加强大&#xff0c;所以在实际工作中的使用更加广泛。 vim工作模式 Vim具有多种工作模式&#xff0c;常用的工作模式有&…

微服务架构2.0--云原生时代

云原生 云原生&#xff08;Cloud Native&#xff09;是一种关注于在云环境中构建、部署和管理应用程序的方法和理念。云原生应用能够最大程度地利用云计算基础设施的优势&#xff0c;如弹性、自动化、可伸缩性和高可用性。这个概念涵盖了许多方面&#xff0c;包括架构、开发、…

Prometheus+Grafana+AlertManager监控Linux主机状态

文章目录 PrometheusGrafanaAlertManager监控平台搭建开始监控Grafana连接Prometheus数据源导入Grafana模板监控Linux主机状态 同系列文章 PrometheusGrafanaAlertManager监控平台搭建 Docker搭建并配置Prometheus Docker拉取并配置Grafana Docker安装并配置Node-Exporter …

Python系统学习1-9-类三之特征

一、封装 数据角度&#xff1a;将一些基本数据类型复合成一个自定义类型。 优势&#xff1a;将数据与对数据的操作相关联。 代码可读性更高&#xff08;类是对象的模板&#xff09;。 行为角度&#xff1a;向类外提供必要的功能&#xff0c;隐藏实现的细节。 优势&#xff…

【VRTK4.0运动专题】手柄控制物体移动和旋转

文章目录 原理预设体将两轴转化为位置向量或角度后&#xff0c;调用运动脚本的方法&#xff0c;对指定的物体进行移动或旋转 步骤1、将轴转化为位置向量或角度&#xff1a; 建轴转化预设体&#xff0c;关联两轴&#xff0c;2、准备带有要用方法的运动脚本&#xff1a; 建功能物…

在线图片怎么转换成PDF?在线图片转换成PDF步骤介绍

文件格式要转化不知道怎么办?想要网上下载文件格式转换软件&#xff0c;但是却不知道下载哪个好?今天小编小编就给大家分享一下靠谱的小圆象PDF转换器工具&#xff0c;想知道这款软件好不好用?在线图片怎么转换成PDF?那就进来看看吧。 在线图片怎么转换成PDF 小圆象PDF转换…