当前位置:网站首页>【C语言】指针和数组的深入理解(第三期)
【C语言】指针和数组的深入理解(第三期)
2022-08-04 20:29:00 【程序猿教你打篮球】
眼里藏星河,笑里带月亮.
目录
1、指针变量在口语中为什么会跟指针混用?
这里我们先阐述一个问题,为什么很多人会混用指针变量和指针呢?其实说法不是完全错误,为什么呢?请看如下代码:
int main()
{
int a = 10; //当 a 做左值,a 是变量
int b = a; //当 a 做右值,a本质就是10
int* p1 = &a; //当 p1 做左值,p1是一个变量
int* p2 = p1; //当 p1 做右值 p1 等价于 &a,也就是a的地址,即指针
return 0;
}
看以上代码相信大家就能明白了,当指针做右值的时候,他本质上就是一个地址,地址就是指针,所以在这种情况下,指针变量可以理解成指针,但不建议这么理解,之所以在前两期我没有强调这个问题,是怕初学者会弄混淆了,有了前两期的学习,在来看这个问题就很轻松了。
但是我们心里面还是要把指针和指针变量区分开来,知道什么时候是混用什么时候不是混用的,为了书面表达以及名词阐述,后面的内容在口语上可能会混用,但是你们自己要有一个谱,还是建议要区分清楚这两个概念。
2、指针数组和数组指针
2.1 什么是指针数组?
很多小伙伴在接触指针数组和数组指针的时候是非常头疼的,总会混淆两个的概念, 那么本期会以一个很清晰讲解带大家深入理解:
我们再来回顾一遍数组的定义:具有相同类型元素的集合,我们称为数组
指针是类型吗?不是,指针只是地址,那 int* 是类型吗?double* 是类型吗?是的!分别是整型指针类型,双精度浮点型指针类型,假设我有十个整型指针,也就对应其中十个变量的地址,我们可以把它们放到一个数组里吗?可以的!这就是指针数组:存放指针的数组,每个指针的类型都是一样。
它的语法格式是这样的:int* p[10] 意思是这个数组有十个元素,每个元素类型是 int*,那么指针数组是数组还是指针?数组!存放指针的数组!
2.2 什么是数组指针?
上期我们也学习过 &arr,说它要放在一个数组指针里,我们今天就来探讨下什么是数组指针:
前面我们也了解过,int* p 它是一个可以指向整型数据的指针变量,float* p 它是一个可以指向单精度浮点数数据的指针变量。那么可以理解 *p 表示他是个指针变量,int 就是它对应指向的数据类型,那我如果要指向一个数组呢?
数组的类型是什么?int arr[10] 其实在C语言中这样定义数组在阅读上是有点变扭的,像Java中int[10] arr, 是定义数组,上期也讲过,数组拿 int arr[10] 来说,他的类型就是 int[10],一个数组十个元素,每个元素是 int 类型。所以我们要拿指针去指向int[10] 这种类型该如何写呢?
int (*p)[10] 这样就是我们的数组指针,因为 [ ] 的优先级比 * 高,所以我们先 p 跟 * 结合,表示它是一个指针变量,指向的类型是 int[10],刚好跟我们 int* p 的说法吻合。
数组指针是指针!是一个存放数组地址的指针变量。(有时候为了说法方便,会混用指针和指针变量,大家要自己注意区分)
2.3 指针数组和数组指针的布局
为了方便大家的理解,请看以下图片,但是这并不是真实的内存布局。就好似指针变量并没有一根线去指向一个地址,他只是一个变量存放着地址。
所以看到这里,你应该更能理解上一期取地址数组名与数组名的区别了吧!
2.4 数组指针的使用
简单的使用:
void printArr1(int arr[3][5], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void printAarr2(int(*arr)[5], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
printArr1(arr, 3, 5);
printAarr2(arr, 3, 5);
return 0;
}
在我们打印数组中,可以用数组指针来接收,数组名是 arr,表示首元素地址,但是二维数组的首元素的第一行,所以我们这里传递的首元素地址本质是第一行一维数组的地址,所以我们可以用数组指针来接受,因为数组指针是一个指向数组的指针。
3、多维数组和多维指针
3.1 二维数组
二维数组,其实也是个麻烦的地方,很多书中都把二维数组画成一个几行几列的图形,这样图确实可以帮助我们理解,但是我们也要清楚一点,这些是示意图,并非真正的内存布局。下面我们来通过代码来看看二维数组在内存中如何布局的:
通过打印每个元素的地址,我们也能发现,二维数组在内存中的布局是连续的。如果我们要画比较真实的内存布局的话,应该这么画:
数组的定义是:具有相同类型元素的集合,数组当中可以保存任何类型,这里的二维数组图是不是相当于数组保存了数组,一个数组有三个元素,每个元素保存了三个数组类型 int[3],其实我们完全可以理解成,这些二维数组可以当成一维数组来看,只不过它内部的元素也是数组而已,这里就能说明,既然一维数组是线性递增的,而一维数组每个元素又保存的数组,所以整体二维数组也是线性递增的,所以:二维数组可以被看作内部元素是一维数组的数组。
那既然是连续的,我们第一次打印数组每个元素的内存时,是不是可以换种方法打印?
int main()
{
int arr[3][3] = { 0 };
int* p = (int*)arr;
for (int i = 0; i < 3 * 3; i++)
{
printf("%p\n", p + i);
}
return 0;
}
但是我们通常也不会这么去做,下面有一道关于二维数组的题,如果你能看懂,并且能自己分析,那就证明对数组的理解没有问题了,一定要注意数组名是什么的问题!
int main()
{
int arr[3][3] = { 0 };
printf("%d\n", sizeof(arr)); //36->计算的是整个数组的大小
printf("%d\n", sizeof(arr[0][0])); //4->算的是第一个元素的大小
printf("%d\n", sizeof(arr[0])); //12
//arr[0]相当于第一行一维数组的数组名,单独放在sizeof内部
//所以算出的是第一行一位数组的大小
printf("%d\n", sizeof(arr[0] + 1)); //4
//arr[0]相当于第一行一维数组的数组名,+1为第二行数组数组名
//并没有单独放在sizeof内部,也没有取地址,所以求的是第二行第一个元素的大小
printf("%d\n", sizeof(*(arr[0] + 1))); //4
//arr[0] + 1相当于第二行数组名,对第二行数组名解引用,没有&,也没有单独放在sizeof内部
//所以求的是第二行第一个元素的大小->数组名代表首元素地址
printf("%d\n", sizeof(arr + 1)); //4或8
//arr表示数组首元素地址,+1表示跳过一个元素,所以求得是第二个元素地址的大小
//地址的大小在32位平台下为4字节,64位平台为8字节
printf("%d\n", sizeof(*(arr + 1))); //12
//数组名代表首元素地址,arr表示数组首元素,也就是第一行的一维数组
//arr+1就是第二行的一维数组的地址,对一维数组的地址解引用,访问的是整个数组,所以是12
printf("%d\n", sizeof(&arr[0] + 1)); //4或8
//arr[0]表示数组首元素,也就是第一行的一维数组,取地址取出的是一维数组的地址
//对数组+1本质跳过一个数组,也就表示第二行的地址,但它并没有单独出现在sizeof内部
//表示的是第二行首元素的地址,地址大小是4或8字节
printf("%d\n", sizeof(*(&arr[0] + 1))); //12
//&arr[0] + 1取出的是第二行一维数组的地址,对整个一维数组的地址解引用,访问的是整个数组
printf("%d\n", sizeof(*arr)); //12
//数组名代表首元素地址,解引用访问第一个元素,二维数组的第一个元素是第一行的整个一维数组
//所以*arr也就是第一行的数组名即arr[0],单独放在了sizeof内部,计算的是整个一维数组的大小
printf("%d\n", sizeof(arr[2])); //12
//arr[2]表示二维数组的第二个元素也就是第二行的数组名,同上,计算的是整个一维数组的大小
return 0;
}
3.2 二级指针
在学习二级指针之前,我们要了解,指针变量是一个变量,既然是变量就有地址,如果我们要保存一个一级指针变量的地址该如何做呢?用二级指针变量来存放一级指针变量的地址!
简单使用:
二级指针, 当 pp 第一次解引用,访问的是 p 指针变量,如果此时进行赋值,则是修改了 p 里面存放的地址,当 pp 第二次解引用,本质是先访问 p 指针变量,然后对 p 指针变量解引用,最后也就是访问 p 保存地址对应的内容。
至于超过二维的数组,和超过二级的指针,一般用的很少,感兴趣的可以按照上面分析的方法下来自己研究研究。
3.3 一道面试题
int main()
{
int arr[5][5];
int(*p)[4];
p = arr;
printf("%p,%d\n", &p[4][2] - &arr[4][2], &p[4][2] - &arr[4][2]);
return 0;
}
我们结合图解,再来分析这道题的解法:
这道题,首先要找出 &arr[4][2] 所对应的地址,这个很简单,可是 &p[4][2] 对应的地址如何找呢?首先我们知道 指针+1 跳过的是对应类型的大小,而 p 的类型是 int[4] 的类型,所以他 +1 可以跳过四个整型,而 p[4][2] 又可以写成 *(*(p+4)),这样一来 p + 4 先是跳过了 16 个整型,因为是二维数组,解引用也就是相当于找到了一维数组,站在 p 的角度 +4 之后解引用可以访问后续的一维数组,在进行 +2 解引用,则跳过了两个整型,就来到了图中的位置。
首先我们来看图,&arr[4][2] 的地址肯定是要高于 &p[4][2] 的,我们也知道,指针相减,得到的是之间的元素个数,所以 &arr[4][2] - &p[4][2] 得到的肯定是 -4,那如果以 %p 打印的话是打印无符号进制,我们将 -4 的补码当成无符号数打印出来就是:0xFFFFFFFC
下期预告:【C语言】指针和数组的深入理解(第四期)
边栏推荐
- 使用百度EasyDL实现森林火灾预警识别
- C#移动OA办公系统源码(基于微信企业号)
- String中的hashcode缓存以及HashMap中String作key的好处
- AWS SES 的监控和告警
- 如何用好建造者模式
- JSD-2204-酷莎商城(管理员模块)-密码加密-Day10
- Aura clock chip generation configuration file script
- About the state transfer problem of SAP e-commerce cloud Spartacus UI SSR
- 数字IC设计中基本运算的粗略的延时估计
- [Academic related] Tsinghua professor persuaded to quit his Ph.D.:I have seen too many doctoral students have mental breakdowns, mental imbalances, physical collapses, and nothing!...
猜你喜欢
37.轮播图
C#移动OA办公系统源码(基于微信企业号)
[Academic related] Tsinghua professor persuaded to quit his Ph.D.:I have seen too many doctoral students have mental breakdowns, mental imbalances, physical collapses, and nothing!...
深度解析:为什么跨链桥又双叒出事了?
ASP.NET商贸进销存管理系统源码(带数据库文档)源码免费分享
零知识证明笔记——私密交易,pederson,区间证明,所有权证明
搭建MyCat2双主双从的MySQL读写分离
二叉树的遍历
【AGC】构建服务1-云函数示例
Web3时代的战争
随机推荐
大资本已开始逃离加密领域?
【有奖征文】秋招特训,打造你的专属产品体验
【CAS:2306109-91-9 |胺-PEG4-脱硫生物素】价格
帝国CMS仿核弹头H5小游戏模板/92game帝国CMS内核仿游戏网整站源码
c语言小项目(三子棋游戏实现)
Apache服务器配置多个站点
How to promote the implementation of rural revitalization
Chrome 开发者工具 performance 标签页的用法
JS new一个构造器发生了什么?从零手写一个new方法
从卖产品到卖“链路”:20条策略 解读直播带货迭代玩法
面试官:Redis中过期的key是怎么被删除的?
Using Baidu EasyDL to realize forest fire early warning and identification
【AGC】构建服务1-云函数示例
C语言基础[通俗易懂]
Oreo域名授权验证系统v1.0.6公益开源版本网站源码
Chrome安装zotero connector 插件
How to use the Chrome DevTools performance tab
web 应用开发最佳实践之一:避免大型、复杂的布局和布局抖动
常用正则表达式[通俗易懂]
面试官:JVM运行时数据区包含哪几部分?作用是啥?