指针与数组:深入C语言的内存操作艺术

数组名的理解


在上⼀个章节我们在使⽤指针访问数组的内容时,有这样的代码:                                                                

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

这⾥我们使⽤ &arr[0] 的⽅式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址,⽽且
是数组⾸元素的地址,我们来做个测试。

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

运行后你会发现,数组名和数组首元素的地址打印出来的结果是一模一样的呀,这就再次印证了数组名就是数组首元素(也就是第一个元素)的地址这一说法。

不过,这时候可能有的同学就会产生疑问啦。要是数组名是数组首元素的地址,那下面这段代码又该怎么理解呢?

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

这段代码输出的结果是 40 呢。按之前说的,如果 arr 仅仅是数组首元素的地址,那输出的应该是 4 或者 8 才对啊。
其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:

  •    sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节。
  • 当用 &数组名 时,这里的数组名同样表示的是整个数组,取出的是整个数组的地址(要知道整个数组的地址和数组首元素的地址可是有区别的哟)。

除了这两个例外情况之外,在其他任何地方使用数组名时,数组名所表示的就是首元素的地址啦。

这时候呀,可能又有好奇的同学会进一步尝试下面这段代码呢。

#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;
}


你看,这三个打印结果又是一模一样的,这下可能又让人纳闷了,那 arr 和 &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("&arr = %p\n", &arr);
    printf("&arr+1 = %p\n", &arr+1);
    return 0;
}

 输出结果如下

&arr[0] = 0077F820                                                                                                                    &arr[0]+1 = 0077F824

arr = 0077F820

arr+1 = 0077F824

&arr = 0077F820

&arr+1 = 0077F848

从这里我们可以发现,&arr[0] 和 &arr[0]+1 相差 4 个字节,arr 和 arr+1 也相差 4 个字节,这是因为 &arr[0] 和 arr 都是首元素的地址呀,当进行 +1 操作时,就是跳过一个元素哦。而 &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]);
    //输入
    int* p = arr;
    for(i=0; i<sz; i++)
    {
        scanf("%d", p+i);
        //scanf("%d", arr+i); 也可以这样写哦
    }
    //输出
    for(i=0; i<sz; i++)
    {
        printf("%d ", *(p+i));
    }
    return 0;
}

 把这段代码弄明白后,咱们再思考一个问题哈。既然数组名 arr 是数组首元素的地址,还可以赋值给 p,在这里它们其实是等价的。我们知道可以用 arr[i] 来访问数组的元素,那 p[i] 是不是也可以访问数组呢?咱们再来看看下面这段代码哦。  

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    //输入
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);
    //输入
    int* p = arr;
    for(i=0; i<sz; i++)
    {
        scanf("%d", p+i);
        //scanf("%d", arr+i);  也可以这样写
    }
    //输出
    for(i=0; i<sz; i++)
    {
        printf("%d ", p[i]);
    }
    return 0;
}

 你瞧,在代码的第 18 行那里,把 *(p+i) 换成 p[i] 也是能够正常打印输出的哦。所以呀,本质上 p[i] 是等价于 *(p+i) 的呢。同理,arr[i] 也应该等价于 *(arr+i) 哦。在编译器处理数组元素访问的时候,实际上就是将其转换成首元素的地址加上偏移量求出元素的地址,然后再通过解引用的方式来访问元素的哟。

一维数组传参的本质

咱们已经学习过数组了,也知道数组是可以传递给函数的呀。那在这个小节呢,咱们就来探讨一下数组传参的本质到底是什么。

咱们先从一个问题入手哈,之前我们都是在函数外部去计算数组的元素个数,那能不能把数组传给一个函数后,在函数内部去求数组的元素个数呢?咱们看看下面这段代码以及它的运行情况哦。

#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;
}

我们会发现呀,在函数内部并没有正确地获得数组的元素个数呢。这就需要深入了解一下数组传参的本质啦。在上个小节咱们学习过,数组名其实就是数组首元素的地址哦。那么在数组传参的时候,传递的就是数组名呀,也就是说从本质上讲,数组传参传递的就是数组首元素的地址呢。

所以呀,函数形参的部分理论上应该使用指针变量来接收这个首元素的地址哦。这样一来,在函数内部写 sizeof(arr) 时,计算的其实是一个地址的大小(单位是字节),而不是数组的大小(单位字节)啦。正是因为函数的参数部分本质上是指针,所以在函数内部是没办法求得数组元素个数的哟。咱们再看看下面这两种函数定义形式哦。

void test(int arr[])  //参数写成数组形式,本质上还是指针
{
    printf("%d\n", sizeof(arr));
}
void test(int* arr)  //参数写成指针形式
{
    printf("%d\n", sizeof(arr));  //计算一个指针变量的大小
}
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    test(arr);
    return 0;
}

总结一下哈,一维数组传参的时候,形参的部分既可以写成数组的形式,也可以写成指针的形式哟。

冒泡排序

冒泡排序的核心思想呢,就是让两两相邻的元素进行比较哦。下面给大家介绍两种实现冒泡排序的方法呢。

方法 1

void bubble_sort(int arr[], int sz)  //参数接收数组元素个数
{
    int i = 0;
    for(i=0; i<sz-1; i++)
    {
        int j = 0;
        for(j=0; j<sz-i-1; j++)
        {
            if(arr[j] > arr[j+1])
            {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}
int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr, sz);   
    for(i=0; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

 方法 2  优化

void bubble_sort(int arr[], int sz)  //参数接收数组元素个数
{
    int i = 0;
    for(i=0; i<sz-1; i++)
    {
        int flag = 1;  //假设这一趟已经有序了
        int j = 0;
        for(j=0; j<sz-i-1; j++)
        {
            if(arr[j] > arr[j+1])
            {
                flag = 0;  //发生交换就说明,无序
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
        if(flag == 1)  //这一趟没交换就说明已经有序,后续无序排序了
            break;
    }
}
int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr, sz);
    for(i=0; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

二级指针

要知道呀,指针变量它本身也是变量呢,既然是变量那就有地址呀。那指针变量的地址存放在哪儿呢?

对于二级指针的运算呀,有下面这些情况哦:

  • *ppa 呢,是通过对 ppa 中的地址进行解引用操作,这样就能找到 pa 啦,所以 *ppa 其实访问的就是 pa 哦。比如下面这样的代码:
    int b = 20;
    *ppa = &b;  //等价于 pa = &b;
  • **ppa 呢,先是通过 *ppa 找到 pa,然后再对 pa 进行解引用操作,也就是 *pa,这样找到的就是 a 啦。像下面这样:
    **ppa = 30;
    //等价于*pa = 30;

    指针数组

那指针数组到底是指针还是数组呀?咱们可以类比一下哦,整型数组呢,是用来存放整型数据的数组,字符数组是存放字符的数组。那指针数组呀,就是存放指针的数组哦。

指针数组模拟二维数组

咱们来看看下面这段代码哈。

#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]);
        }
        printf("\n");
    }
    return 0;
}

 在这里呀,parr[i] 是用来访问 parr 数组的元素哦,而 parr[i] 找到的数组元素指向了整型一维数组,那么 parr[i][j] 就是整型一维数组中的元素啦。不过要注意哦,上述代码虽然模拟出了二维数组的效果,但实际上它并非完全等同于二维数组呢,因为每一行的数据在内存中并非是连续存放的哟。

通过以上对数组名、指针访问数组、数组传参、冒泡排序以及二级指针、指针数组等多方面知识的详细讲解与探讨,相信大家对这些 C 语言中重要的知识点有了更深入且清晰的理解。希望大家在后续的编程学习与实践中,能够灵活运用这些知识,不断提升自己的编程能力哦。                                                                                                                                                                           

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python的数字类型

python的数字类型包括&#xff1a;整数&#xff0c;浮点数&#xff0c;复数。 整数 python的整数没有长度限制&#xff0c;无限大&#xff0c;有无限的精度 python的整数除法&#xff0c;即便能整除&#xff0c;结果也是小数&#xff0c;小数 在python中用float类型表示&…

【连续学习之SS-IL算法】2021年CPVR会议论文Ss-il:Separated softmax for incremental learning

1 介绍 年份&#xff1a;2021 期刊&#xff1a; 2021CPVR Ahn H, Kwak J, Lim S, et al. Ss-il: Separated softmax for incremental learning[C]//Proceedings of the IEEE/CVF International conference on computer vision. 2021: 844-853. 本文提出的SS-IL&#xff08…

3.BMS系统原理图解读

一、BMS电池板 (1)电池的连接关系&#xff1a;串联 (2)采样控制点&#xff1a;CELL0 - CELL5 (3)端子P1和P3&#xff1a;BAT和BAT- (4)开关S1&#xff1a;控制充放电回路的机械开关 二、BMS控制板 (1)主控MCU 电源 复位 晶振 (2)LED指示灯&#xff1a;4电量指示 1调试指…

洛谷P5250 【深基17.例5】木材仓库(c嘎嘎)

题目链接&#xff1a;P5250 【深基17.例5】木材仓库 - 洛谷 | 计算机科学教育新生态 题目难度&#xff1a;普及/提高 解题心得:本题借鉴了大佬的做法&#xff08;因为没想多好的处理方法~~&#xff09;&#xff0c;本题可以用map&#xff0c;对于操作1&#xff0c;存的话直接另…

pyqt和pycharm环境搭建

安装 python安装&#xff1a; https://www.python.org/downloads/release/python-3913/ python3.9.13 64位(记得勾选Path环境变量) pycharm安装&#xff1a; https://www.jetbrains.com/pycharm/download/?sectionwindows community免费版 换源&#xff1a; pip config se…

ArcGIS Pro地形图四至角图经纬度标注与格网标注

今天来看看ArcGIS Pro 如何在地形图上设置四至角点的经纬度。方里网标注。如下图的地形图左下角经纬度标注。 如下图方里网的标注 如下为本期要介绍的例图&#xff0c;如下&#xff1a; 图片可点击放大 接下来我们来介绍一下 推荐学习&#xff1a;GIS入门模型构建器Arcpy批量…

深度学习与图像处理(国产深度学习框架——飞桨官方指定教材)

计算机视觉从小白到大师之路 《深度学习与图像处理&#xff08;PaddlePaddle版&#xff09;》这一本就够了 1.引言 随着人工智能技术的飞速发展&#xff0c;各行各业对深度学习、图像处理相关领域的人才需求日益迫切。本书旨在通过系统的理论讲解与丰富的实战案例&#xff0…

Bluetooth Spec【0】蓝牙核心架构

蓝牙核心系统由一个主机、一个主控制器和零个或多个辅助控制器组成蓝牙BR/ EDR核心系统的最小实现包括了由蓝牙规范定义的四个最低层和相关协议&#xff0c;以及一个公共服务层协议&#xff1b;服务发现协议&#xff08;SDP&#xff09;和总体配置文件要求在通用访问配置文件&a…

代码随想录Day51 99. 岛屿数量,99. 岛屿数量,100. 岛屿的最大面积。

1.岛屿数量深搜 卡码网题目链接&#xff08;ACM模式&#xff09;(opens new window) 题目描述&#xff1a; 给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接…

【机器学习与数据挖掘实战】案例06:基于Apriori算法的餐饮企业菜品关联分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联…

突破传统,探索单页网站的强大潜力!

单页网站简单、直接&#xff0c;而且设计通常令人惊叹&#xff0c;非常适合展示关键信息而不会让访问者不知所措。 然而&#xff0c;构建单页网站有其自身的挑战&#xff0c;尤其是在 SEO 方面。由于内容数量有限且针对特定关键字的页面较少&#xff0c;可能很难在 SERP 中进行…

攻防世界web新手第四题easyphp

<?php highlight_file(__FILE__); $key1 0; $key2 0;$a $_GET[a]; $b $_GET[b];if(isset($a) && intval($a) > 6000000 && strlen($a) < 3){if(isset($b) && 8b184b substr(md5($b),-6,6)){$key1 1;}else{die("Emmm...再想想&quo…

Python大数据可视化:基于Python的王者荣耀战队的数据分析系统设计与实现_flask+hadoop+spider

开发语言&#xff1a;Python框架&#xff1a;flaskPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 比赛信息管理 看板展示 系统管理 摘要 本文使用Python与…

【已解决】pyinstaller打包ico图片报错:OSError: [WinError 225] 无法成功完成操作,因为文件包含病毒或潜在的垃圾软件。

起因&#xff1a; pyinstaller加上 --icon 参数打包时报错。 命令如下&#xff1a; 解决&#xff1a; 关闭 Windows 的病毒防护即可&#xff0c;步骤如下。 点屏幕右下角通知栏&#xff0c;进入“病毒和威胁防护”&#xff1a; 打开&#xff1a; 关闭实时保护&#xff08…

Cloudflare 边缘网络架构:无处不在的 BPF-2019

大家觉得有意义和帮助记得及时关注和点赞!!! 译者序边缘网络DDos Mitigation负载均衡TCP/UDP Socket DispatchSOCKMAPPrometheus - ebpf_exporter无处不在的 eBPF 边缘网络 Cloudflare 的服务器运行 Linux 系统。 我们的数据中心分为两类&#xff1a; 大的“核心”数据中心&a…

智慧园区小程序开发制作功能介绍

智慧园区小程序开发制作功能介绍 智慧园区小程序系统作为一款面向园区企业的一站式线上服务平台&#xff0c;可为企业提供数智化的园区办公服务。智慧园区小程序功能介绍 1、园区公告、政策信息查看足不出户掌握最新动态&#xff0c;“园区公告、政策信息”等信息。首页点击对应…

基于 Python Django 的农产品销售系统的研究与实现

大家好&#xff0c;我是stormjun&#xff0c;今天为大家带来的是基于 Python Django 的农产品销售系统的研究与实现。该系统采用 Python 语言 开发&#xff0c;MySql 作为数据库&#xff0c;系统功能完善 &#xff0c;实用性强 &#xff0c;可供大学生实战项目参考使用。 博主介…

WEB开发 - Flask 入门:Jinja2 模板语法进阶 Python

在上一阶段&#xff0c;我们一起学习了基于Python地 web框架Flask&#xff0c;并且初步了解了这个框架有一种渲染方式叫做 模板语法&#xff0c;今天&#xff0c;我们一起再来深入地了解和学习这个叫做Jinja2地模板语法。 WEB开发 - Flask 入门&#xff1a;由浅入深地带你学习…

基于SpringBoot在线音乐系统平台功能实现十八

一、前言介绍&#xff1a; 1.1 项目摘要 随着互联网技术的迅猛发展和普及&#xff0c;人们对音乐的获取和欣赏方式发生了巨大改变。传统的音乐播放方式&#xff0c;如CD、磁带或本地下载的音乐文件&#xff0c;已经不能满足用户日益增长的需求。用户更希望通过网络直接获取各…

【YOLOv3】源码(train.py)

概述 主要模块分析 参数解析与初始化 功能&#xff1a;解析命令行参数&#xff0c;设置训练配置项目经理制定详细的施工计划和资源分配日志记录与监控 功能&#xff1a;初始化日志记录器&#xff0c;配置监控系统项目经理使用监控和记录工具&#xff0c;实时跟踪施工进度和质量…