C语言笔记15

指针2

1.数组名的理解

int arr[ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };
int *p = &arr[ 0 ];17391692786

arr是数组名,数组名是首元素地址,&arr[0]就是取出首元素的地址放在指针变量p中。

#include <stdio.h>
int main()
{int arr[2][10] = { 1,2,3,4,5,6,7,8,9,10 };//一行为10个元素,一共两行;//printf("&arr[0] = %p\n", &arr[0]);//printf("arr = %p\n", arr);printf("%p\n", &arr[0]);//arr[0]是第一行一维数组的数组名,&数组名,打印第一行数组的地址(十六进制)  printf("%p\n", arr[0]);//arr[0]是第一行的数组名,数组名是首元素的地址,打印第一行数组第一个元素的地址printf("%p\n", *arr);//arr是二维数组的数组名,*arr代表取出二维数组的首元素 这个二维数组由两个一维数组组成,即*arr代表arr[0],*arr==*(arr+0)==arr[0]printf("%d\n", *arr);//地址打印为十进制printf("==============\n");printf("%p\n", &arr[1]); //arr[1]是第二行一维数组的数组名,&数组名,打印第二行数组的地址printf("%p\n", arr[1]);//arr[1]是第二行的数组名,数组名是首元素的地址,打印第二行数组第一个元素的地址printf("%p\n", *(arr + 1));//arr是二维数组的数组名,arr + 1代表访问第二行元素,*(arr + 1)获取第二行元素数组的地址  *(arr + 1)==arr[1]printf("%d\n", *(arr + 1));//地址打印为十进制printf("==============\n");printf("%p\n", *(arr + 1)+1);//*(arr + 1)获取第二行元素数组的地址   *(arr + 1)+1获取第二行第二个元素的地址printf("%p\n", &arr[1][1]);//获取第二行第二个元素的地址printf("==============\n");printf("%d\n", sizeof(arr));//特例:sizeof(数组名)表示计算arr数组的大小(数组空间内存)printf("%d\n", sizeof(arr[0]));//特例:sizeof(数组名)表示计算arr数组的大小(数组空间内存) arr[0]是第一行一维数组的数组名printf("%d\n", sizeof(arr[1]));//特例:sizeof(数组名)表示计算arr数组的大小(数组空间内存) arr[1]是第二行一维数组的数组名printf("%p\n", &arr);//特例:&数字名  表示整个二位数组的地址printf("==============\n");printf("%p\n", &arr[0] + 1);//&arr[0]获取第一行数组的地址  &arr[0] + 1获取第二行数组的地址printf("%p\n", &arr + 1);//&arr获取二维数组的地址  &arr+ 1 跳过此二维数组到下一个地方(跳过20*4=80Byte)return 0;
}

其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:

sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩, 单位是字节
&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)
除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地址。
2.一维数组传参的本质
#include < stdio.h>
void test(int arr[])
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz1 = sizeof(arr) / sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
}
//运行结果可以发现 sz1=10,sz2=1;

为什么这个函数的结果不对呢?

这就要搞清楚函数中传的参数是啥,也就是函数中传的参数是啥,从上方代码可test(arr);arr是一个一维数组,也是个数组名,刚从1.数组名的理解知道除了两个例外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地址。所以说传给函数test是数组的地址,那么sizeof(地址)=4或8(要看在32位系统还是64位系统),int sz2 = sizeof(arr) / sizeof(arr[0]);这个arr和arr[0]都是地址,所以sz2=1;

再看一个:

void test1(int arr[])//参数写成数组形式,本质上还是指针
{printf("%d\n", sizeof(arr));
}
void test2(int* arr)//参数写成指针形式
{printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };test1(arr);test2(arr);return 0;
}
//运行结果可以发现 sizeof(arr)=4;说明一维数组传参传的就是地址
总结:
数组名是数组⾸元素的地址;那么在数组传参 的时候,传递的是数组名,也就是说本质上: 一维数组传参传递的是数组⾸元素的地址 ⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
3.二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?
这就是 ⼆级指针
二级指针通俗来讲就是指针的指针,存放指针的指针。
其实三级指针就是存放 ⼆级指针地址的指针;依次往后推。
int main()
{int a = 100;int* pa = &a;int** pb = &pa;printf("a的地址是:%p\n", pa);printf("pa的地址是:%p", pb);return 0;
}

画个图描述一下

4.指针数组
先说下整形数组:存放整形的数组 int arr1[5] ={1,2,3,4,5}      5个int类型

字符数组:存放字符的数组char arr1[5]={'a','b','c','d','e'}      5个char类型                                          那么指针数组就是存放指针的数组呗, 那就有整形指针数组,字符指针数组等等;                     int* arr1[5]={指针1,指针2,指针3,指针4,指针5}                     5个 int* 类型   

        指针数组的每个元素都是⽤来存放地址(指针)的,⼜可以指向⼀块区域。用以下代码说一下指针数组。

int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中int* parr[3] = { arr1, arr2, arr3 };int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 5; j++){printf("%d ", parr[i][j]);}printf("\n");}return 0;}

        parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数 组中的元素。 上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。
5.数组指针
前面介绍过整形指针类型 int*,字符指针 类型 char*,那么数组指针 类型该如何表示呢
数组指针意思就是 数组的指针,又叫做数组的地址。
整形数组指针:int (*arr)[5];
字符数组指针:char(*arr)[5];
解释:
p先和*结合,说明p是⼀个指针变量,然后指针指向的是⼀个⼤⼩为5个整型的数组。所以p是
⼀个指针,指向⼀个数组,叫 数组指针。
这⾥要注意:[ ]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合
见以下代码所示:
#include <stdio.h>//整形数组指针
//int arr[10] = {0};
//&arr;//得到的就是数组的地址
//找个变量来接收找个数组地址 → int(*p)[10]=&arr
// int (*p) [10] = &arr;
// |     |    |
// |     |    |
// |     |    p指向数组的元素个数
// |     p是数组指针变量名
// p指向的数组的元素类型int main()
{int arr[5] = { 1, 2, 3, 4, 5 };  // 定义一个整形数组int(*p)[5];  // 定义一个指向包含5个整形元素的数组的指针p = &arr;  // 将指针指向数组//int(*p)[5]=&arr;// 通过指针访问数组中的元素for (int i = 0; i < 5; i++){//printf("arr[%d] = %d\n", i, (*p)[i]);printf("arr[%d] = %d\n", i, p[0][i]);}return 0;
}//字符数组指针
int main1() 
{char str[20] = "Hello, world!";  // 定义一个字符数组char(*p)[20];  // 定义一个指向包含20个字符的数组的指针p = &str;  // 将指针指向数组// 通过指针访问数组中的元素printf("String: %s\n", *p);//printf("String: %p\n", p);//printf("String: %p\n", *p);//printf("String: %p\n", p[0]);//printf("String: %c\n", **p);printf("String: %c\n", *p[0]);//**p== *p[0]return 0;
}
6.⼆维数组传参的本质
对于一维 数组传参的本质 传递的是数组⾸元素的地址,上面已经介绍过了,接下来说一下 ⼆维数组传参的本质。
       ⾸先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
就是可以把第一行看作第一个元素记作 arr[0]
                  第二行看作第二个元素记作 arr[1]
                  第三行看作第三个元素记作 arr[2]
根据数组名是首元素的地址规则,那么对于二维数组的数组名也是首元素的地址,首元素就是第一行,那么就是第一行的地址,arr[0]就是第一行的数组名。 第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类 型就是数组指针类型 int(*)[5],一维 数组传参的本质 传递的是数组⾸元素的地址,那么二维数组传递的也是首元素地址只不过这个首元素是个一维数组,也就是传递时第一行一维数组的地址(arr[0]的地址), 那么形参也是可以写成指针形式的
总体来说:
一维数组传参的本质传递的是数组⾸元素的地址;
二维数组传参的本质传递的是第一行一维数组的地址。
void test(int a[3][5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", a[i][j]);}printf("\n");}
}
//这是之前的传参写法,我们知道了二维数组传参的本质,可以修改如下
void test1(int(*p)[5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){//printf("%d ", p[i][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} };//test(arr, 3, 5);test1(arr, 3, 5);return 0;
}

7.函数指针

前面说过数组指针,是代表数组的指针,那么 函数指针就是函数的指针(地址),则 函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。 
       整形数组的指针类型表示为int(*)[5],那么函数指针类型该如何表示呢,类比来看:
int(*)(int )(int )。
如何创建函数指针变量,如下:
void test()
{printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;int Add(int x, int y)
{return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//函数名就是函数的地址&Add==Add 都可以获取函数的地址,x和y写上或者省略都是可以的 int (*pf3) (int x, int y)
|     |     ------------
|     |       |
|     |       pf3指向函数的参数类型和个数的交代
|     函数指针变量名
pf3指向函数的返回类型int (*) (int x, int y) //pf3函数指针变量的类型

ps:函数名就是函数的地址,可以用函数名来代表函数的地址,也可以用 &函数名 来表示

函数指针变量的使⽤, 见一下代码
#include <stdio.h>// 一个简单的函数,接收两个整数并返回它们的和
int add(int x, int y)
{return x + y;
}int main()
{// 声明一个函数指针,指向接收两个int参数并返回int的函数//int (*pf)(int, int);// 将函数指针指向具体的函数add//pf = add;int (*pf)(int, int) = add;  // int (*pf)(int, int)=&add;// 使用函数指针调用函数int result = pf(10, 20);int result1 = (*pf)(10, 30);  //注意一定是加上()不能是 *pf(10, 30)进行调用printf("Result: %d\n", result);  // 输出:Result: 30printf("Result1: %d\n", result1);  // 输出:Result: 40return 0;
}

7.函数指针数组

       数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组, 函数指针数组,是把函数的指针存放在数组里,一般情况是定义多个函数,把多个函数的地址存放在数组中,方便调用。
函数指针数组的定义:int (*parr1[ 3 ])(int ,int );
parr1 先和 [ ] 结合,说明 parr1是数组,数组的内容是 int (*)( int ,int ) 类型的函数指针
函数指针数组应用:设计一个计算器
//转移表  (设计一个简易计算器)
#include <stdio.h>
#include <stdlib.h>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("********************************\n");printf("******   1. add    2. sub  *****\n");printf("******   3. mul    4. diV  *****\n");printf("******   0. exit           *****\n");printf("********************************\n");
}
int main()
{int input = 0;int (*pf[5])(int,int) = {0, add, sub, mul, diV};do{  menu();int x, y = 0;scanf("%d", &input);if (input > 0 && input < 5){printf("请输入操作数:");scanf("%d %d", &x, &y);int ret = pf[input](x, y);printf("%d\n", ret);}else if (input == 0){printf("退出计算器");}else{printf("输入错误,重新输入\n");}} while(input);return 0;
}

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

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

相关文章

基于GWO灰狼优化的CNN-GRU-Attention的时间序列回归预测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1卷积神经网络&#xff08;CNN&#xff09;在时间序列中的应用 4.2 GRU网络 4.3 注意力机制&#xff08;Attention&#xff09; 4.4 GWO优化 5.算法完整程序工程 1.算法运行效果图预览…

C++初探_友元类

1.友元类 代码&#xff1a; #include <iostream> class Tv { public:friend class Remote;... };class Remote {... }; 2.友元成员函数 即&#xff1a;不需要整个类成为友元&#xff0c;只需要选择让特定的类成员成为另一个类的友元&#xff1a; &#xff08;1&…

logback 日志脱敏

工具类 CustomLogbackPatternLayoutEncoder.java import ch.qos.logback.classic.encoder.PatternLayoutEncoder;public class CustomLogbackPatternLayoutEncoder extends PatternLayoutEncoder {/*** 正则替换规则*/private LogbackReplaces replaces;/*** 使用自定义 MyLog…

图的拓扑序列(DFS1)

way&#xff1a;在图里面能延伸的越远&#xff0c;deep越大&#xff0c;说明它能从自己延伸很长到别的节点&#xff08;别的节点一定有入度&#xff09;&#xff0c;它越可能没有入度。 #include<iostream> #include<vector> #include<map> #include<set…

ChatGlm的部署和训练

一、chatGlm的环境部署 1.安装anocoda 下载安装anaconda。具体教程详见官网教程。 2.安装CUDA 1&#xff09;首先在终端查看你的Nividian版本&#xff0c;命令如下&#xff1a; 2)如果你没有下载你要去下载cuda下载网站&#xff0c;这里是12.3是因为我cuda version版本12…

【微记录】linux如何使用ebpf观测你的某个命令调用了内核的ioctl?(bpftrace sys_enter_ioctl)

场景 对于工程实践中需要查看某些ioctl是否被调用&#xff0c;尤其是一个新的模块初次调试&#xff0c;在不添加调试命令debug的情况下如何观测&#xff1f; 另外就是对于外部提供的二进制程序&#xff0c;如何观测该程序是否调用ioctl以及何时调用&#xff1f; 这些都通过ebp…

【保姆级】生成式网络模型基础知识(图像合成/语音合成/GPT)

生成式模型基础知识 初步接触生成任务 生成任务&#xff0c;顾名思义就是要去生成一个东西&#xff0c;比如生成图片/音频/文字等等。 大家接触最多比如chatGPT、stable diffusion、还有一些语音合成相关的东西。 那么问题来了&#xff0c;具体生成步骤是什么样的&#xff…

JVM 常见知识点总结

文章目录 一、类加载篇1.1 类加载过程1.2 类加载器分类1.3 双亲委派机制 二、JVM内存结构篇2.1 JVM内存结构2.2 对象的创建过程2.3 强软弱虚引用 三、垃圾回收篇3.1 如何判断对象是否死亡3.2 哪些对象可以作为gcroots3.3 垃圾回收算法3.4 Minor GC和Full GC的区别3.5 HotSpot为…

【计算机毕业设计】基于SSM++jsp的高校专业信息管理系统【源码+lw+部署文档+讲解】

目录 第1章 绪论 1.1 课题背景 1.2 课题意义 1.3 研究内容 第2章 开发环境与技术 2.1 MYSQL数据库 2.2 JSP技术 2.3 SSM框架 第3章 系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.2 系统流程 3.2.1 操作流程 3.2.2 登录流程 3.2.3 删除信息流…

ChatGPT-4o发布了,所有人都可以免费用

前言 美国时间 5 月 13 日&#xff0c;在 Sam Altman 缺席的情况下&#xff08;为什么缺席&#xff0c;猜测是可能后面还有更重磅的消息&#xff0c;这次的产品只是一个中间过渡产品&#xff09;&#xff0c;OpenAI CTO Mira Murati 介绍了新的 ChatGPT 产品 ChatGPT-4o。这个…

node安装以及node的包管理工具

node安装以及node的包管理工具 node安装nvm管理工具 node安装 1、下载node链接: 点击这里 2、选择需要下载的版本以及对应的系统版本 nvm管理工具 1、下载nvm链接: 点击这里 2、双击运行.exe文件 全部默认安装即可 3、nvm常用命令 nvm list avaliable 显示所有可以下载的n…

企业管理咨询公司不会选?一文带你避开“坑人”陷阱

近年来&#xff0c;企业管理咨询公司如雨后春笋般涌现&#xff0c;数量之多令人眼花缭乱。所以&#xff0c;面对这么多的企业管理咨询公司&#xff0c;企业该选谁&#xff1f;又该如何选择&#xff1f;本文将从以下几个方面为大家解析。 首先&#xff0c;我们要明确自己的需求和…

每日一练 | 华为认证真题练习 - OSPF NSSA区域配置注意事项础

Day310 - 每日一练 OSPF NSSA区域配置注意事项 01 真题题目 关于配置 NSSA 区域需要注意的事项中,描述正确的是&#xff1a; A. NSSA 区域可以传播来自 OSPF 网络其它区域的外部路由 B. 骨干区域可以配置成为 NSSA 区域 C. 虚连接可以穿越 NSSA 区域 D. 如果将一个区域配置…

调用unity的Application.Quit();方法退出会导致重启问题

最近测试提了一个bug&#xff0c;游戏退出会导致游戏重启 我发现我们是直接调用的unity自带的方法&#xff1a; Application.Quit(); 但是我使用demo工程&#xff0c;调用这个是正常的&#xff0c;摸不着头脑 尝试使用安卓方法&#xff1a; using (AndroidJavaClass system…

正则表达式和Lambda表达式的使用和区别(C++)

在C中&#xff0c;正则表达式和Lambda表达式都是强大的工具&#xff0c;分别用于不同的任务。下面是一些示例代码&#xff0c;展示了如何在C中使用正则表达式和Lambda表达式。 使用正则表达式&#xff08;C11及以上&#xff09; 在C中&#xff0c;正则表达式功能由标准库中的…

R语言:ROC分析

> install.packages("pROC") > library(pROC) > inputFile"结果.txt" > rtread.table(inputFile, headerT, sep"\t", check.namesF, row.names1) > head(rt) con treat TCGA-E2-A1L7-11A-con…

SynchronousQueue 的 常用场景及使用示例

SynchronousQueue 的 常用场景及使用示例 SynchronousQueue 是 Java 并发框架中的一个特殊类型的阻塞队列&#xff0c;它的特点是队列内部容量为零&#xff0c;即不存储元素。每个 put 操作必须等待一个对应的 take 操作&#xff0c;反之亦然&#xff0c;因此它主要用作线程间…

【论文速读】|大语言模型是零样本模糊测试器:通过大语言模型对深度学习库进行模糊测试

本次分享论文&#xff1a;Large Language Models are Zero-Shot Fuzzers: Fuzzing Deep-Learning Libraries via Large Language Models 基本信息 原文作者&#xff1a;Yinlin Deng, Chunqiu Steven Xia, Haoran Peng, Chenyuan Yang, Lingming Zhang 作者单位&#xff1a;U…

vue+vant项目0-1快速发布到--钉钉应用

uniapp开发笔记----vue开发项目配置钉钉应用 一、 vuevant开发项目1. 自定义vuevant项目或者已经有的旧项目1. 自定义vuevant项目1. 创建vue项目2. 安装依赖3. 引入所有组件4. 使用一个组件/效果和代码如下&#xff1a; 2. git官网仓库&#xff0c;直接拉默认dome代码3. 打包项…

Gone是什么—— 一个轻量的,基于Golang的,依赖注入框架

我从头到尾实现了一个Golang的依赖注入框架&#xff0c;并且集成了gin、xorm、redis、cron、消息中间件等功能&#xff1b;自己觉得还挺好用的&#xff0c;推荐给你&#xff01;也欢迎一起维护&#xff01; github地址&#xff1a;https://github.com/gone-io/gone 文档地址&am…