C:指针和数组之间的关系-学习笔记

news2024/11/10 15:35:35

 

目录

闲话:

引言:

1、数组名的理解

2、指针访问数组

3、一维数组传参的本质

4、二级指针

5、指针数组

6、指针数组模拟二维数组

结语:


闲话:

指针这个模块更新的比较慢,主要是小编还得学习指针的知识点,虽然说是学习笔记,但是也是需要小编自己学会后才能够更好的将知识点介绍给大家,所以还请见谅一下哈!!!

引言:

本篇文章将带来数组与指针之间的关系介绍,希望能对大家有所帮助!


1、数组名的理解

int arr[10] = {0};

从概念上讲:

数组名代表数组在内存中的起始地址。可以将数组名视为一个指向数组首个元素的指针。

例如,定义一个整型数组 int arr[10]  , arr  就代表了这个数组的起始地址。

来看一组对比:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf(" arr    = %p\n", arr);
	return 0;

 结果:(x86环境下展示的地址)

我们发现使用数组名打印地址和取首元素地址打印的结果相同。

 因此可以更加确定数组名就是数组首元素的地址。

但是!在 C 语言中,数组名具有特殊的含义和性质。因此,数组名肯定不能只有这么单一的用法喽!

来看一组代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf(" %zd\n",sizeof(arr));
	return 0;
}

这个结果会是多少呢?4还是8,或者都不是?验证一下结果

嗯,都不是,结果是40。其实对于这个结果也不是很出乎意料,毕竟我们前面还使用表达式sizeof (arr) / sizeof(arr[0])来计算数组元素个数

不过到这里我们就应该明白了,在这种情况下的数组名就不是首元素地址了。

并不是说数组名是首元素地址是错误的,只是有凡是都有例外嘛!数组名也是如此,数组名有两个例外:

  1. sizeof(数组名),这里的数组名表示的是整个数组,计算的是整个数组的大小,单位是字节;
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的,待会介绍)

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

我们来比较一下&arr,arr,&arr[0]这三个打印地址的结果

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
    printf("&arr[0]  =%p\n",&arr[0]);
    printf("arr      =%p\n",arr);
	printf("&arr     =%p\n",&arr);	
	return 0;
}

结果:

从上面的结果来看,&arr,arr,&arr[0]似乎没有什么区别,打印的结果也都是一样的,我们现在分别给它们+1,,再来看一下结果:

&arr[0]和arr在+1后的结果是相同的,都是跳过4个字节,而&arr则不同

&arr+1后跳过了40个字节,怎么算的呢?

十六进制计算
A8-80=28
A是10,10-8 = 2,8-0 = 8,
所以是28,十六进制的28对应的十进制数字是2 * 16 + 8 * 1= 40 

 

理解:关于&arr[0]和arr在+1后跳了4个字节,我们将首元素地址看作是一个整体,那么当我们+1的时候就会跳过首元素地址,来到第二个元素的地址;而&arr 取的是整个数组的地址,因此我们将整个数组看作一个整体,那么&arr+1后就应该跳过整个数组,由于该数组有十个元素,所以就是跳过40个字节。

问: 数组+1 后跳过几个字节是取决与什么?

答:指针类型决定了指针+-整数时候跳过多少字节。

&arr[0] 的指针类型是 int*

arr 的指针类型是 int*

总结:数组名就是首元素的地址,除了sizeof(数组名)和&数组名以外。

2、指针访问数组

前面说了数组名就是首元素地址,也可以看作是一个指向首元素的指针。既然如此,我们就可以很方便的使用指针来访问数组了。

怎么理解指针访问数组呢?

当我们使用指针来访问数组时,实际上是通过指针指向数组的起始地址,然后根据指针的移动和运算来访问数组中的各个元素。

先来看一下不使用指针访问一个一维数组的代码展示,也就是直接使用下标来访问数组

#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]);
	}
    //输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

我们都知道数组内的元素地址是连续的,如果我们能得到首元素地址,就可以访问整个数组,关于整型数组,我们希望一个整数一个整数的访问,因此我们使用整型指针最为合适

&arr[i]这个就是取数组中下标为 i 的元素的地址

现在我们来使用指针访问数组

与上代码变化:添加int* p = arr ,将输入for循环里的&arr[i]改写为p+i,将输出for循环里的&arr[i]改写为*(p+i)

代码展示:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
	int* p = arr;
    //输入
	for (i = 0; i < sz; i++)
	{
		scanf("%d",p+i);//p+i就是下标为i元素的地址
	}
    //输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ",*(p+i));
	}
	return 0;
}

int* p = arr这个表达式中我们将arr赋值给p。也就是说p其实是等价于arr的。p = arr ,arr是首元素地址,我们将arr赋给p的时候,p里面存放的也就是首元素地址。

既如此,我们是否可以将*(p+i)中的p换做arr ? 也就是*(arr+i),我们来检验一下

#include <stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数

   //输入

   for (i = 0; i < sz; i++)
   {
      scanf("%d",p+i); 
   }
    int* p = arr;

   //输出
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *(arr + i));
    }
    return 0;
}

 结果展示:

程序还是可以运行的,现在是不是更能够理解p等价于arr了?

 还有一些就不一 一举例说明了,直接总结一下吧!(不一定全,希望能够有大佬帮忙补充一下)

关于 p 等价于 arr

在输入中:

p + i   =  arr + i

在输出中:

arr[i] --- *(arr+i)   完全等价

arr[i]这种写法编译器在执行的时候也会转换成*(arr+i)来执行,也就是说数组底层运算逻辑也是通过指针的方式来执行的。

*(p+i) ---  p + i  完全等价

在整个流程中:

arr[i] = p[i] 

 关于arr[i]还有一个更加抽象的写法,arr[i] = i[arr] ,这里就是说明一下[]就是一个操作符而已,不推荐这种写法。

3、一维数组传参的本质

数组传参是指在函数调用时将数组作为参数传递给函数。

int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	test(arr);
	return 0;}

数组传参只需要写数组名就可以了。注意:数组名是arr,而不是arr[10]

数组传参形参该怎么写呢?

void test(int arr[])//元素个数写不写无所谓

等下会说为什么写不写都不影响

现在我们来分别在(test)函数外部与函数内部计算数组元素的个数、

来,展示!

可以看到在函数内部sz2结果为1,而函数外部sz1结果为10;这是为什么呢?

关于sz1 = 10;的结果我们都清楚,sizeof(arr)求得数组的总大小,sizeof(arr[0])求得数组首元素的大小,然后得出元素个数,但是为什么在test函数内部求得的元素个数结果变为1了呢?

我们来逆推一下,首先sizeof(arr[0])表示的是数组首元素的大小是不变的,因此sizeof(arr[0])等于4

sz2 = sizeof(arr) / 4 = 1;因此sizeof(arr)也等于4,那么在什么情况下能得到aizeof(arr) = 4 呢?

在数组传参的时候 test(arr);我们传递的是整个数组吗?还记得前面关于数组名的理解吗?这里arr既不是在sizeof中,前面也没有&符号,所以,test(arr)中的arr指的就是数组首元素的大小,因此我们传参过去的是首元素的地址,这便是一维数组传参的本质,既如此,我们便可以明白aizeof(arr) = 4是怎么得到的了,地址在32位机器上占4个字节,在64位机器上占8个字节 ,小编是在32位上操作的,所以最终得到izeof(arr) = 4 的结果。

void test(int arr[])//元素个数写不写无所谓
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}
#include <stdio.h>
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;}

 综上:数组传参传递的就是首元素地址

1.我们传递的不是整个数组,函数形参的部分是不会真实创建数组的,所以就不需要数组大小,也就是形参部分元素大小写不写都无所谓,没有什么影响

2.数组传过去的是数组首元素地址,地址应该拿指针来接收,所以函数形参部分应该使用指针变量来接收,而我们写成int arr[])是为了更加方便我们的理解。

void test(int arr[])可以写为void test(int* arr)

注意

一维数组传参的时候,形参可以写成数组的方式,主要是为了方便理解,形参也可以写成指针变量的方式 

如果我们想要在函数内部获取数组元素的个数,该怎么写呢?

void test(int arr[10],int sz)
{
    //遍历数组
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf(" %d ", arr[i]);
	}
}
#include <stdio.h>
int main() 
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	test(arr,sz);
	return 0;
}

4、二级指针

我们以前所接触的指针都叫一级指针,在介绍二级指针前,我们先谈一谈一级指针,一级指针的出现是为了什么呢?也就是一级指针的作用是什么?

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;//pa是指针变量,一级指针变量
	return 0;
}

一级指针变量也是变量,既然如此,在32位平台下我们要申请4个字节空间,在64位平台下我们要申请8个字节空间,因为该指针变量所存放是地址。所以一级指针变量的作用是存储了一个变量的地址

a是一个变量,a的类型是int ,通过&a(0x0012ff40)存放到指针变量pa中;

pa也是一个变量,pa的类型是int*,我们也可以通过&pa(取出pa的地址:0x0012ff48)存放起来,而存放pa的地址的变量该怎么写呢? int** ppa = &pa; 这里的ppa就是二级指针变量。

二级指针变量就是用来存放一级指针变量的地址的

再来理解一下

int * 中*说明pa是个指针变量,int是说明pa指向的变量是int类型的

int* * 中第二个*是说明ppa是指针变量,而前面的int* 是说明ppa指向的变量是int*类型的

5、指针数组

只谈及指针数组是不是感觉比较陌生?但是我们可以类比一下整型数组,字符数组。

int main()
{
	int arr1[] = { 1,2,3 };//整型数组
	char arr2[] = { 'a' };//字符数组
}

整型数组就是用来存放整型的数组,字符数组就是用来存放字符的数组,所以指针数组就是用来存放指针的数组

整数类型int,字符类型char,但是指针类型有很多,int*,char*,因此指针数组该怎么写呢?

int main()
{
	int* arr1[5] = { 0 };//整型指针数组
	char* arr2[10] = { 0 };//字符指针数组
     ……
}
#include <stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int* arr[3] = { &a,&b,&c };	
	int i = 0;//下标
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
}

指针数组的每个元素都是用来存放地址(指针)的。

6、指针数组模拟二维数组

#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*  int* int*
	int* arr[] = { arr1,arr2,arr3 };//arr是指针数组
	return 0;
}

理解指针数组arr,通过找到arr1,arr2,arr3首元素地址进而找到arr1,arr2,arr3中的全部元素 

我们怎么通过首元素地址打印全部元素呢?

	int i = 0;
	for (i = 0; i < 3; i++)
	{
			int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
	}

上面 i 是arr中的下标,j 则是arr1,arr2,arr3中数组的下标

我们先通过下标 i 找到arr1,arr2,arr3的首元素,然后再通过下标 j 分别找到arr1 ,arr2 ,arr3 中的元素。

总代码:

#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*  int* int*
	int* arr[] = { arr1,arr2,arr3 };//arr是指针数组
	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;
}

结果展示:


结语:

本篇文章到这里就结束了,希望本篇文章能够带你了解数组和指针之间的关系。下篇文章再见!

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

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

相关文章

ubuntu18.04 设置静态地址

修改配置文件 sudo vim /etc/netplan/01-network-manager-all.yaml 代码如下&#xff1a; network: version: 2 renderer: NetworkManager ethernets: ens33: # 配置的网卡名称&#xff0c;可以使用ifconfig -a查看本机的网卡 dhcp4: no # 关闭动态IP设置 …

黑神画Ⅹ--自主人工智能代理:从概念到实际应用

自主人工智能代理通过独立执行任务并做出最终决策来改变技术。与传统人工智能不同&#xff0c;它们能够分析、规划、适应并从经验中学习。机器学习和自然语言处理的进步扩大了它们在个人助理、聊天机器人、管理系统和自动驾驶汽车中的应用&#xff0c;展示了它们在各个领域的潜…

springmail发送邮件如何实现邮件动态内容?

springmail发送邮件怎么样&#xff1f;springmail发信优化方法&#xff1f; SpringMail作为一个强大的邮件发送框架&#xff0c;提供了多种方式来实现邮件内容的动态生成。AokSend将探讨如何通过SpringMail发送邮件&#xff0c;并动态生成邮件内容以满足不同的需求。 springm…

【图像去雾系列】使用SSR/MSR/MSRCR/MSRCP/automatedMSRCR算法对单图像进行图像增强,达到去雾效果

目录 一 图像去雾算法概述 二 SSR/MSR/MSRCR算法 三 实践 一 图像去雾算法概述 近些年来,出现了众多的单幅图像去雾算法,其主要可以分为 3 类:基于图像增强的去雾算法、基于图像复原的去雾算法和基于 CNN 的去雾算法。 ▲基于图像增强的去雾算法 通过图像增强技术突出图…

“泰山众筹:革新消费模式“

亲爱的伙伴们&#xff0c;是否渴望探索一种集日常消费与财富增长于一体的创新方式&#xff1f;今天&#xff0c;让我带您领略泰山众筹的魅力&#xff0c;这一革命性的消费增值理念&#xff0c;将彻底颠覆您的消费体验&#xff0c;让每一笔支出都化作通往财富之路的基石。 ✨ 泰…

了解LVS,配置LVS

项目一、LVS 1.集群Cluster Cluster: 集群是为了解决某个特定问题将堕胎计算机组合起来形成的单个系统 LB&#xff1a;负载均衡 HA&#xff1a;高可用 HPC&#xff1a;高性能计算 2.分布式 分布式是将一个请求分成三个部分&#xff0c;按照功能拆分&#xff0c;使用微服…

MySQL的InnoDB的页里面存了些什么 --InnoDB存储梳理(三)

文章目录 创建新表页的信息新增一条数据根据页号找数据信息脚本代码py_innodb_page_info根据地址计算页号根据页号计算起始地址 主要介绍表空间索引页里面有哪些内容&#xff0c;数据在表空间文件里面是怎么组织的 创建新表页的信息 CREATE TABLE test8 (id bigint(20) NOT N…

Nginx Web UI 部署

目录 1. 安装Docker 2. 拉取镜像 3. 启动程序 4. 访问测试 1. 安装Docker 准备一台虚拟机&#xff0c;关闭防火墙和selinu&#xff0c;进行时间同步 下载docker并配置加速器 # step 1: 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persisten…

8B 端侧小模型 | 能力全面对标GPT-4V!单图、多图、视频理解端侧三冠王,这个国产AI开源项目火爆全网

这两天&#xff0c; Github上一个 国产开源AI 项目杀疯了&#xff01;一开源就登上了 Github Trending 榜前列&#xff0c;一天就获得将近600 star。 这个项目就是国内大模型四小龙之一面壁智能最新大打造的面壁「小钢炮」 MiniCPM-V 2.6 。它再次刷新端侧多模态天花板&#xf…

Cobalt Strike 4.8 用户指南-第一节-Cobalt Strike介绍及安装

一、欢迎使用Cobalt Strike Cobalt Strike 是一个用于对手模拟和红队行动的平台。用于执行有针对性的攻击并模拟高级威胁行为者的后渗透行动。本节介绍 Cobalt Strike 功能集支持的攻击过程。本手册的其余部分将详细讨论了这些功能。 # 概述 图中的 Intrumentation & Tel…

数据重塑之数据去重

下面内容摘录自&#xff1a; 4章7节&#xff1a;用R做数据重塑&#xff0c;数据去重和数据的匹配-CSDN博客文章浏览阅读23次。数据重塑是数据分析和数据清洗中的重要步骤&#xff0c;其中包括数据去重和数据匹配。理解这两个概念以及它们的实现方法对于有效处理和分析数据至关重…

告别转换难题,四款PDF转CAD工具分享

CAD很难搞&#xff0c;将PDF转换为CAD更难搞&#xff0c;想要快速且完整的将PDF文件转换为CAD&#xff0c;自然不是靠一点点的复制重做&#xff0c;直接用PDF转CAD工具就能搞定。那用什么工具呢&#xff1f;我这就给你们捋捋几个神器是怎么帮我们搞定这个难题的。 1.PDF365在线…

milvus helm k8s开启权限管理,attu管理

version:2.4.5 apiVersion: v1 kind: ConfigMap metadata: name: my-release-milvus 该configMap 添加 &#xff0c;然后重启milvus 集群可生效 user.yaml: |-common:security:authorizationEnabled: true或者直接在value.yaml 中添加该配置 extraConfigFiles:user.yaml: |com…

程序员 10 个摸鱼神器分享给大家

问&#xff1a;程序员该不该上班摸鱼&#xff1f; 答&#xff1a;认真上班是劳动换取报酬&#xff0c;上班摸鱼才是从老板那赚钱。 曝光&#xff0c;程序员的 10 个摸鱼神器 摸鱼一时爽&#xff0c;一直摸一直爽 方案一&#xff1a;实物摸鱼方案二&#xff1a;命令行斗地主方案…

抖音用户主页视频数据爬虫详解(点赞,收藏,分享等)

一. 首先进行抓包分析&#xff0c;&#xff0c;&#xff0c;随便找个主页&#xff0c;f12&#xff0c;关键词搜索&#xff0c;发现这个包是以post开头 二.查看请求参数&#xff1a; 我们复制curl在spiderbox里面快速形成请求 对headers&#xff0c;params进行尝试删减&#x…

Linux下用gdb找到cpu占用率最高的线程

我们调试程序的时候&#xff0c;有时候会发现当程序运行时&#xff0c;会出现cpu占用率很高的情况。 一般情况下&#xff0c;程序执行时&#xff0c;cpu占用率比较高的话&#xff0c;就会影响其它程序的执行&#xff0c;所以就需要对程序进行优化&#xff0c;查找程序运行时&a…

亚马逊云科技产 Amazon Neptune 图数据库服务体验

目录 图数据库为什么使用图数据库Amazon Neptune实践登陆创建 S3 存储桶notebook图神经网络快速构建加载数据配置端点Gremlin 查询删除环境删除 S3 存储桶 总结 图数据库 图数据库是一种专门用于存储和处理图形数据结构的数据库管理系统。图形数据结构由节点&#xff08;Node&…

轻松打造:基于本地知识库的私有GPT助手定制教程”

背景知识 众所周知&#xff0c;目前大模型 LLM 的能力已经非常强大&#xff0c;chatgpt 已经可以很好的解决通用型问题&#xff0c;但是对于垂直专业领域的问题处理的还不够好。如果要利用 LLM 大模型根据已有的特定领域的知识&#xff0c;推理出该领域特定问题的答案&#xf…

node.js part1

Node.js Node.js 是一个跨平台JavaScript 运行环境&#xff0c;使开发者可以搭建服务器端的 JavaScript 应用程序。作用&#xff1a;使用Node.js编写服务器端程序 编写数据接口&#xff0c;提供网页资源浏览功能等等 前端工程化&#xff1a;为后续学习Vue和React等框架做铺垫. …

51单片机学习记录-数码管操作

这里实现了静态数码管的显示。51单片机一共有可以显示4个数字&#xff0c;可以通过控制P2(4-2)的端口选择8个数字显示器中的一个显示数字&#xff0c;控制P0端口写入显示的数值信息。将操作的逻辑使用了函数Nixie进行了封装。 #include <8051.h>unsigned char NixieTabl…