C语言的灵魂——指针(3)

server/2025/2/9 8:38:31/

前言:上期我们介绍了const修饰指针,saaert断言都是针对指针本身的,文章后面我们用指针与数组建立了联系,这种联系或者是关系就是这篇文章所要介绍的。上一篇文章的传送门:指针2

指针3

  • 一,数组名的含义及理解
  • 二,使用指针访问数组
  • 三,一维数组传参的本质
  • 四,二级指针
  • 五,指针数组
  • 六,指针数组模拟二维数组

一,数组名的含义及理解

谈到数组想必大家都知道一个点就是:数组名就是数组的首地址,也是首元素的地址。这就是数组名的含义我=我们不妨写一个代码来验证一下:

#include<stdio.h>
int main()
{int arr[10]={1,2,3,4,5,6,7,8,9,10};printf("%p\n",arr);//%p打印地址的格式占位符printf("%p\n",&arr[0]);//打印首元素的地址return 0;
}

在这里插入图片描述
通过运行结果我们能很明显的看到数组名就是数组首元素的地址。 既然已经知道它的含义了那我们为什么还要重点来讲它呢?如果没有例外那就不会在这里讲它了,下面我们来看代码:

#include<stdio.h>
int main()
{int arr[10]={1,2,3,4,5,6,7,8,9,10};printf("%zd\n",sizeof(arr));return 0;
}

这段代码的运行结果会是什么呢?如果我们认为arr是首元素的地址,而数组元素的类型是int类型那么打印数来的大小应该就是4个字节或8个字节才对(4个字节还是8个字节由平台大小决定)。下面我们来看结果:
在这里插入图片描述
结果明显与我们所想的不符,那为什么会出现这种情况呢?其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有两个例外:

1.sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小, 单位是字节。

2.&数组名,这⾥的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的。

除此之外,任何地方使用数组名,数组名都表示首元素的地址.


那我们再来看一段代码:

#include <stdio.h> 
int main() 
{ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("&arr[0] = %p\n", &arr[0]); printf("arr = %p\n", arr); printf("&arr = %p\n", &arr); return 0; 
}

在这里插入图片描述
看到这里有些人可能又会懵了,前面不是说 **&数组名** 取出的是整个数组的地址吗?那为什么这里 &arrarr &arr[0] 打印出来的地址是一样的呢?难道整个数组的地址与首地址是一样的吗?
这里就要引用我在指针(1)指针变量和地址中介绍&这个操作符所说的了:

但有一点需要特别注意取地址取出来的是较小的地址,因为我们知道数据类型占几个字节又知道较小的地址剩下的顺藤摸瓜直接往后推算即可。

在这里插入图片描述
要想探究 &arr 我们只需要加上偏移量就可以很明显的看出来了:

#include <stdio.h> 
int main() 
{ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("&arr[0]   = %p\n", &arr[0]); printf("&arr[0]+1 = %p\n", &arr[0]+1); printf("arr       = %p\n", arr); printf("arr+1     = %p\n", arr+1); printf("\n");printf("&arr      = %p\n", &arr); printf("&arr+1    = %p\n", &arr+1); return 0; 
}

在这里插入图片描述
这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是 ⾸元素的地址,+1就是跳过⼀个元素。

但我们看到&arr——>&arr+116进制从A8变成了D0,我们用D0减去A8看看是不是40个字节就能验证&arr是不是整个数组的地址了。
在这里插入图片描述
我们发现&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。
看到这里大家应该搞清楚数组名的意义了吧。

二,使用指针访问数组

弄个清楚了数组名,这就让我们有了使用指针来访问数组的基础。我们来看一个常规访问数组的代码:

#include<stdio.h>
int main()
{int arr[10]={0};int i=0;int sz=sizeof(arr)/sizeof(arr[0]);for(i=0;i<sz;i++){scanf("%d",&arr[i]);}int j=0;for(j=0;j<sz;j++){printf("%d\n",arr[j]);}return 0;
}

在这里插入图片描述
这是我们最原始的访问数组的方法,不知大家有没有注意到一个点,其实我们以前求数组元素长度用的就是 sizeof(arr)/sizeof(arr[0]) 只是当时我们不知道。这也说明了上面说所的点sizeof(数组名)求的是整个数组的大小。

言归正传,既然数组名就是地址,而地址就是指针那么能不能使用一个指针变量指向数组,然后使用指针来访问数组呢?当然可以:

#include<stdio.h>
int main()
{int arr[10] = {0};int* p = arr;int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0;i < sz;i++){scanf("%d", p+i);//替换scanf("%d",&arr[i]);}int j = 0;for (j = 0;j < sz;j++){printf("%d ", *(p + j));//替换printf("%d\n",arr[j]);}	return 0;
}

这个代码搞明⽩后,我们再试⼀下,如果我们再分析⼀下,数组名arr是数组⾸元素的地址,可以赋值 给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可 以访问数组呢

#include<stdio.h>
void test(int arr[], int sz1)//参数本质上就是int *arr 
{                    //要想求数组元素个数需要传sz1过来int sz2 = sizeof(arr) / sizeof(arr[0]);printf("%d\n", sz2);printf("%d\n", sz1);
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz1 = sizeof(arr) / sizeof(arr[0]);printf("%d\n", sz1);test(arr,sz1);return 0;
}

这样也是可行的,将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i] 是等价于 *(p+i),实际上我们写出数组下标的形式来访问数组元素的时候,编译器在处理的时候还是转换成指针来处理。

那我们想象一下 *(i+arr) 是否能写成 i[arr] 呢?答案是可以的只不过代码的可读性不高,其实 i[arr] 也不难理解[ ]这个操作符是下标引用操作符,i和arr只是它的两个两个操作数而已并不影响实际的结果。

同理arr[i] 应该等价于 *(arr+i),数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移 量求出元素的地址,然后解引用来访问的。

接着我们来看看一维数组传参

三,一维数组传参的本质

前面在讲函数传参的时候我们也讲过数组传参,学完了指针后我们便可以来探索一下它的本质,先来看一段代码:

#include<stdiio.h>
//一维数组传参的时候形参是可以写成数组的形式,但是即使写成数组的形式本质其实是指针变量。
void test(int arr[],int sz1)//参数本质上就是int *arr 
{                    //要想求数组元素个数需要传sz1过来int sz2=sizeof(arr)/sizeof(arr[0]);printf("%d",sz2);printf("%d",sz1);
}
int main()
{int arr[10]={1,2,3,4,5,6,7,8,9,10};int sz1=sizeof(arr)/sizeof(arr[0]);printf("%d",sz1);test(arr,sz1);return 0;
}

在这里插入图片描述
通过运行结果我们有个疑惑为什么sz2的结果是2呢?首先我们能够确定的一点是 sizeof(arr[0]) 的大小就是4个字节(一个整型类型)这点是毫无疑问的,那么谁/4等于2呢?小学生都会,答案是8。那为什么是8呢?
注意sz2的值也可能是1这而取决于平台大小!

还记得最开始举的第二个例子吗?
如果我们认为arr是首元素的地址,地址就是指针;而数组元素的类型是int类型那么打印数来的大小应该就是4个字节或8个字节才对(4个字节还是8个字节由平台大小决定)。

所以我们得出一个非常重要的结论,数组传参本质上传过去的就数组元素的首地址(是一个指针)!

所以函数形参的部分理论上应该使用指针变量来接收⾸元素的地址。那么在函数内部我们写 sizeof(arr) 计算的是⼀个地址的大小(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函 数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。

四,二级指针

前面我们介绍了一级指针变量,指针变量就是用来存放普通变量的地址;那以此类推指针变量的地址当然就是用二级指针来存储。
二级指针与一级指针一样无非就是对指针变量本身的使用以及解引用:

#include<stdio.h>
int main()
{
//使用指针对象int a = 10;int* pa = &a;int** ppa = &pa;
//解引用*pa = 20;printf("*pa=%d\n", *pa);int m = 100;*ppa = &m;//pa=&mprintf("%p\n", &m);printf("%p\n", pa);printf("%d\n", **ppa);//对Pa解//printf("*pa=%d\n,*pa");** ppa = 30;//直接对m进行操作printf("**ppa=%d\n",**ppa);
}

在这里插入图片描述

在这里插入图片描述

所以 *pa pa解引用就是改变a的值,改变 *ppa 对就是改变 *pa 的所存的地址,**ppa 对解引用就是改变a的值。就像有三个抽屉第一第二个抽屉都有锁,第三个抽屉放着第二个抽屉的钥匙,第二个抽屉放着第一个抽屉的钥匙。
在这里插入图片描述

五,指针数组

前面我们学过
字符数组——是数组,是用来存放字符的数组;char arr[5]
整型数组——是数组,是用来存放整型的数组;int arr[5]

以此类推:
指针数组——是数组,是用来存放指针的数组;int*arr[5] (存放整型指针的数组)

#include<stdio.h>
int main()
{int a=10;int b=20;int c=30;int d=40;int e=50;//与其一个个用指针变量来存放abcde的地址还不如直接用一个指针数组来存放int *arr[5]={&a,&b,&c,&d,&e};return 0;
}

指针数组的每个元素都是用来存放地址(指针)的.
在这里插入图片描述
而指针数组的每个元素是地址,⼜可以指向⼀块区域:
在这里插入图片描述

六,指针数组模拟二维数组

明白上面的基础知识后我们就可以用指针数组来模拟一下二维数组了:

#include <stdio.h> 
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]); //parr[i]其实就是代表arr1/arr2/arr3//parr[][j]其实就是输出每一个数组内的所有元素}printf("\n"); }return 0;
}

在这里插入图片描述
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数 组中的元素。 上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每一行并非是连续的。

好了以上就是本章的全部内容啦!
感谢能够看到这里的读者,如果我的文章能够帮到你那我甚是荣幸,文章有任何问题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!

在这里插入图片描述


http://www.ppmy.cn/server/166174.html

相关文章

苹果公司宣布正式开源 Xcode 引擎 Swift Build145

2025 年 2 月 1 日&#xff0c;苹果公司宣布正式开源 Xcode 引擎 Swift Build145。 Swift 是苹果公司于 2014 年推出的一种开源编程语言&#xff0c;用于开发 iOS、iPadOS、macOS、watchOS 和 tvOS 等平台的应用程序。 发展历程 诞生&#xff1a;2014 年&#xff0c;苹果在全球…

深度学习 Pytorch 逻辑回归建模实验

接下来进行逻辑回归的建模实验&#xff0c;首先需要导入相关库和自定义的模块。 # 随机模块 import random# 绘图模块 import matplotlib as mpl import matplotlib.pyplot as plt# numpy import numpy as np# pytorch import torch from torch import nn,optim import torch.…

Qwen2-VL多模态大模型

Qwen2-VL多模态大模型 Qwen2-VL 是一个多模态大模型&#xff0c;支持视觉和语言的理解与生成任务。它结合了视觉&#xff08;Vision&#xff09;和语言&#xff08;Language&#xff09;的能力&#xff0c;能够处理图像和文本的联合输入&#xff0c;并生成高质量的文本输出。以…

c#中Thread.Join()方法的经典示例

在 C# 中&#xff0c;Thread.Join 是一个非常有用的方法&#xff0c;它可以让主线程&#xff08;调用线程&#xff09;等待子线程&#xff08;被调用线程&#xff09;执行完毕后再继续执行。 1、经典示例1 using System; using System.Threading;public class Example {stati…

第四十三章:工作变迁与生活新篇:从上海到杭州湾

在经历了那次令人难忘的沪绍骑行之旅后&#xff0c;小冷本以为生活和工作会沿着既定的轨道平稳前行。然而&#xff0c;命运似乎总喜欢给人带来意想不到的转折。随着公司业务的不断拓展和战略布局的调整&#xff0c;小冷发现自己的工作节奏和生活重心正在悄然发生改变。 频繁出差…

mysql系统库介绍,数据字典(介绍,存储方式,常见表,访问权限),系统表(介绍,不同功能的表)

目录 mysql系统库 介绍 数据字典 介绍 不同版本下的存储方式 常见的数据字典表 访问权限 系统表 介绍 权限授予系统表 对象信息系统表 服务器端帮助系统表 时区系统表 mysql系统库 介绍 MySQL 默认创建 的特殊数据库&#xff0c;主要用于存储服务器运行时所需的信…

MySQL InnoDB引擎 高度为3的B+树,可以存储的数据量

一、普通B树 1、B 树结构概述 B 树是一种平衡的多路搜索树&#xff0c;常用于数据库和文件系统中。在 B 树中&#xff0c;所有的数据记录都存储在叶子节点&#xff0c;非叶子节点只存储索引信息。B 树的高度从根节点开始计算&#xff0c;根节点高度为 1。 2、计算所需参数 …

Vue 中的自定义指令是什么?如何使用?

在 Vue.js 中&#xff0c;自定义指令是一种允许开发者在 DOM 元素上添加特定行为的功能。自定义指令可以用来扩展 Vue 的功能&#xff0c;提供一些特定的 DOM 操作或行为&#xff0c;而不需要在组件中直接实现这些逻辑。 自定义指令的概念 自定义指令类似于 Vue 内置指令&…