拿捏指针(二)

个人主页:秋邱'博客

所属栏目:C语言

(感谢您的光临,您的光临蓬荜生辉)

目录

前言 

数组与指针

数组名的理解

指针数组与数组指针

指针数组

 数组指针

数组传参

一维数组传参的本质

二维数组传参的本质

二维数组模拟 

二级指针

字符指针变量

函数指针变量


前言 

前面我们已经讲了,C语言的第一篇《拿捏指针(一)》,接下里我们继续深入的来了解指针。

1.0 数组与指针

1.1 数组名的理解

我们之前学习了,数组知道了数组arr就是首元素的地址,但却不理解&arr和&arr[0]的区别,脑子还是有点乱,今天我们一次给它讲明白。

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %p\n", &arr[0]);//&arr[0]表示首元素地址printf("&arr    = %p\n", arr);//&arrb表示整个数组的地址printf("arr     = %p\n", arr);//arr表示一维数组数组名return 0;
}

 输出结果:

&arr[0] = 00AFF754
&arr     = 00AFF754
arr        = 00AFF754

从输出的结果看,&arr[0],&arr和arr这三的地址都是一样的,那么就能得出结论,数组名就是首元素的地址。

那么我们再来看看一下的代码

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %zd字节\n", sizeof(&arr[0]));printf("&arr    = %zd字节\n", sizeof(&arr));printf("arr     = %zd字节\n", sizeof(arr));return 0;
}

输出结果:

&arr[0] = 4字节
&arr     = 4字节
arr       = 40字节

 我们能看出来,&arr[0],&arr和arr尽管都是打印首元素的地址,但还是有所区别的

如果arr是数组⾸元素的地址,那输出应该的应该是4/8才对,为什么打印的确实40?

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

  • sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩, 单位是字节
  •  &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)

除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地。 

1.2 指针数组与数组指针

相信看到指针数组和数字指针每个人都很头疼,不知道哪个是数组指针,哪个是指针数字。我带大家来看看吧。

1.2.1 指针数组

指针数组:存放指针的数组。

指针数组是指针还是数组,我们来类比一下,就知道。

 整形数组和字符串数组

整形数组是数组,字符串数组是数组,那么指针数组自然就是指针了。指针数组的每个元素都是⽤来存放地址(指针)的。

 1.2.2 数组指针

数组指针是数组还是指针呢?

int(*arr)[10];

 p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫数组指针

值得注意的是,[]的优先级大于*,所以必须得加括号。

int arr[10] = {0};
int(*p)[10] = &arr;

所以我们知道了,int(*)[10] = &arr这两个的类型其实是相同的。

数组指针类型解析:

int (*p) [10] = &arr;

intp     p指向的数组的元素类型

(*p)p是数组指向变量名

[10]     p指向数组的元素个数


2.0 数组传参

2.1 一维数组传参的本质

一维数组arr表示首元素的地址,那么一维数组传参传的是地址还是一个数值呢?

void  Print(int * arr)
{int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };Print(arr);return 0;
}

arr是首元素地址,既然是地址我们就能用指针接收,也可以用 int arr[10]

打印结果:

这里我们预期的打印是1 2 3 4  5 6 7 8 9 10,可是输出的确实1,这就印证了我们的猜想,数组传参传的也是首元素的地址。 

所以arr的元素个数我们还是要在main函数里初始化,在Print函数里是一个指针4个字节(32位下,如果是64位下是8个字节),当然sz=1。

2.2 二维数组传参的本质

我们知道了一维数组传参传的是首元素地址,那么二维数组也是同理的。

void  Print(int(*arr)[5])
{for(int j = 0; j < 3;j++){for (int i = 0; i < 5; i++){printf("%d ", arr[j][i]);}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);return 0;
}

 arr[3][5]我们也可以用数组的形式接收,int arr[3][]列数可以省略,而行数却不可以

 输出结果

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7

 这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗

⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。

 三行五列,第一行相当于arr[0],第二行相当于arr[2],第三行相当于arr[3];,第⼀⾏的地址是⼀维数组的类型就是数组指针类型 int [5] ,所以第⼀⾏的地址的类 int(*)[5] 那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀ ⾏这个⼀维数组的地址。

总结:

  • 数组传参的本质是传递了数组首元素的地址,所以形参访问的数组和实参的数组是同一个数组的。
  • 形参的数组是不会单独再创建数组空间的,所以形参的数组是可以省略掉数组大小的。

实际上还可以用指针的方式打印出来 

void test(int(*p)[5])
{int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 5; j++){printf("%d ", *(*(p + i) + j));}printf("\n");}
}

 因为二维数组在内存中是连续存放的,且arr是一个数组arr[0]里面有五个元素{1,2,3,4,5}; 我们可以把它想像成一维数组。

(arr+1)是首元素的地址,(arr+1)+1是首元素第一个的地址。

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

输出结果:

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7 

p[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数 组中的元素。 上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。


3.0 二级指针

二级指针跟一级指针的原理很相似

	int a = 10;int* pa = &a;

一级指针是将变量的地址放入一级指针变量里面,二级就是将一级的指针变量的地址放入二级的指针变量。

	int a = 10;int* pa = &a;//一级指针int** ppa = &pa;//二级指针

这就是一个二级指针,我们画一个图了解。

当我们解引用的时候,*ppa访问达到的指针变量pa的内容(a的地址),*pa继续解引用访问到的是a的内容

这就是二级指针,依次类推三级指针,四级指针,五级指针都是这样的。

4.0 字符指针变量

//代码1
int main()
{char ch = 'w';char* pc = &ch;*pc = 'w';return 0;
}//代码2
int main()
{char* pc = "hello world";return 0;
}

代码2,常常被人误解,以为 是将“hello world”一整串的地址放入了pc变量中,其实只是将"h"的地址放入了pc变量,这样的字符串我们称为常量字符串。

我们来看你一段代码


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");return 0;
}

  打印结果:

str1 and str2 are not same
str3 and str4 are same

 为什么会出现这样的结果呢?

这是因为str3和str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域, 当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

5.0 函数指针变量

上面我们已经学习了数组指针变量,字符串指针变量,同样函数指针变量也是类似的。

函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的

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

输出结果:

test:  00DE13CA
&test: 00DE13CA

 这里函数的取地址与数组是相似的,都是首元素的地址,&函数名的方法获得函数的地址。

如果我们需要将函数的地址存放起来,,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针 ⾮常类似。

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;//x和y写上或者省略都是可以的

调佣指针指向的函数

int add(int x,int y)
{return x + y;
}
int main()
{int a = 0;int b = 0;int(*pf)(int, int) = add;//存add的地址printf("%d\n", (*pf)(2, 3));printf("%d\n", pf(5, 5));return 0;
}

输出结果:

5

10 

这就是函数变量的用途。

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

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

相关文章

【应急响应靶场web2】

文章目录 前言 一、应急响应 1、背景 2、webshell查杀 3、日志排查 1&#xff09;apache日志 2&#xff09;nginx日志 3&#xff09;ftp日志 4、隐藏账户 5、文件筛选 二、漏洞复现 总结 前言 靶场来源&#xff1a;知攻善防实验室 一、应急响应 1、背景 小李在某…

实现HBase表和RDB表的转化(附Java源码资源)

实现HBase表和RDB表的转化 一、引入 转化为HBase表的三大来源&#xff1a;RDB Table、Client API、Files 如何构造通用性的代码模板实现向HBase表的转换&#xff0c;是一个值得考虑的问题。这篇文章着重讲解RDB表向HBase表的转换。 首先&#xff0c;我们需要分别构造rdb和hba…

关于Transfomer的思考

为何诞生 在说transformer是什么&#xff0c;有什么优势之类的之前&#xff0c;先谈一谈它因何而诞生。transformer诞生最重要的原因是早先的语言模型&#xff0c;比如RNN&#xff0c;由于其本身的训练机制导致其并行度不高&#xff0c;特别是遇到一些长句子的情况下。其次&…

论文阅读——MoCo

Momentum Contrast for Unsupervised Visual Representation Learning 动量在数学上理解为加权移动平均&#xff1a; yt-1是上一时刻输出&#xff0c;xt是当前时刻输入&#xff0c;m是动量&#xff0c;不想让当前时刻输出只依赖于当前时刻的输入&#xff0c;m很大时&#xff0…

DP-不同的二叉搜索树

给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;1提…

kubernetes部署集群

kubernetes部署集群 集群部署获取镜像安装docker[集群]阿里仓库下载[集群]集群部署[集群]集群环境配置[集群]关闭系统Swap[集群]安装Kubeadm包[集群]配置启动kubelet[集群]配置master节点[master]配置使用网络插件[master]node加入集群[node]后续检查[master]测试集群 集群部署…

算法思想总结:双指针算法

一、移动零 . - 力扣&#xff08;LeetCode&#xff09; 移动零 该题重要信息&#xff1a;1、保持非0元素的相对位置。2、原地对数组进行操作 思路&#xff1a;双指针算法 class Solution { public:void moveZeroes(vector<int>& nums){int nnums.size();for(int cur…

专升本 C语言笔记-06 常用的3种输入输出函数

1.scanf() 与 printf() 的使用 scanf() 格式化输入数据 格式:scanf("格式控制字符串",参数地址列表) scanf("%d,%d,%d",&a,&b,&c); printf("a %d\n",a); printf("b %d\n",b); printf("c %d\n",c); 注意 注…

ArcGIS模型构建器Pro版_更多花活演示

相比较ArcMap的模型构建器&#xff0c;Pro里最主要的变化就是增加了一组逻辑工具&#xff1a; 逻辑工具用于控制模型中的流程流&#xff0c;它们返回的结果是true或false。 这个结果一般用于 if-else 分支逻辑&#xff0c;例如&#xff1a;如果某字段存在的时候&#xff0c;执…

【vue baidu-map】实现百度地图展示基地,鼠标悬浮标注点展示详细信息

实现效果如下&#xff1a; 自用代码记录 <template><div class"map" style"position: relative;"><baidu-mapid"bjmap":scroll-wheel-zoom"true":auto-resize"true"ready"handler"><bm-mar…

力扣78. 子集

Problem: 78. 子集 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.定义一维数组track用于记录决策路径&#xff0c;二维数组res用于存储所有的子集&#xff1b; 2.决策阶段&#xff1a;从0阶段起来&#xff08;0阶段决策路径中为空集&#xff09;&#xff0c;每…

力扣每日一题 卖木头块 线性DP

Problem: 2312. 卖木头块 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 灵神题解 复杂度 时间复杂度: O ( n m ( m n ) ) O(nm(mn)) O(nm(mn)) 空间复杂度: O ( n m ) O(nm) O(nm) Code class Solution {public long sellingWood(int n, int m, int…