【C/C++】数组指针:array 地址 array *parray 两次解引用 **parray 值相同的原因解析

news2024/11/18 3:41:10

一、提出问题

#include <stdio.h>

int main()
{ 
    char array[16] = {'A', 'B'}; 
    char (*parray)[16] = &array;  
    
    printf("========================\n");
    
    printf("   array:   \t%#lx\n", array);    
    printf("&  array:   \t%#lx\n", &array);    
    printf("&* array:   \t%#lx\n", &*array); 
    printf("*& array:   \t%#lx\n", *&array); 
 
    printf("   parray:  \t%#lx\n", parray);  
    printf("*  parray:  \t%#lx\n", *parray); 
    printf("*& parray:  \t%#lx\n", *&parray);
    printf("&* parray:  \t%#lx\n", &*parray);
   
    printf("========================\n");
    return 0;
}

  上述程序中 array &array &*array *&array parray *parray *&parray &*parray 输出都相同 。在测试计算机上均为 0x61ff10 (如下图),这是什么原因呢?下面逐步解析,结论见文末。

在这里插入图片描述

二、解决思路

第一组:array 与 &array 的值相同

#include <stdio.h>

int main()
{ 
    char array[16] = {'A', 'B'}; 

    char (*parray)[16] = &array;  
    
    printf("   array:   \t%#lx\n", array);     // 输出 0x61ff10 
    printf("&  array:   \t%#lx\n", &array);    // 输出 0x61ff10

    printf("数组array的第二元素的地址:  \t%#lx\n", array+1);  // 输出 0x61ff11
    printf("数组array的下一数组的地址:  \t%#lx\n", &array+1); // 输出 0x61ff20
    
    printf("数组array的第二元素的地址:  \t%c\n", *(array+1)); // 输出字符 B
    printf("数组array的下一数组的地址:  \t%c\n", *(&array+1));// 输出为空 
    
    
    printf("   array:   \t%d\n", sizeof(array));  // 输出 16,整个数组array的大小也为16,即array代表整个数组
    printf("   array:   \t%d\n", sizeof(&array)); // 输出 4,则 &array 应为指针变量
    printf("   array:   \t%d\n", sizeof(array+1)); // 输出 4,因为 array+1 时,array 隐式转换为指针变量了 
    return 0;
}
  1. array 为数组名,长度为 16,可理解为数组对象。当 array+1 时,其会隐式转换为数组的首元素的地址;
  2. &array 为指针变量,长度为 4,表示 整个 数组 array 的地址,但是值等于数组的首元素的地址。

虽然两者值相同,但是含有不同。在上述代码中 array + 1 表示数组的第二个元素的地址,而 &array + 1 表示 整个 数组 array 的 “下一个数组”,即指向数组 array 尾部的下一地址。

注:本机测试时编译器为32位,故指针变量长度位 4 1

第二组:&*array 与 *&array

#include <stdio.h>

int main()
{ 
    char array[16] = {'A', 'B'}; 

    char (*parray)[16] = &array;  
    
    printf("&* array:   \t%#lx\n", &*array);  // 输出 0x61ff10 
    printf("*& array:   \t%#lx\n", *&array);  // 输出 0x61ff10 

    printf("&* array:   \t%d\n", sizeof(&*array));  // 输出 4,数组首元素的地址
    printf("*& array:   \t%d\n", sizeof(*&array));  // 输出 16,*&array 等价为 array

    printf("*&*array:   \t%c\n", *&*array);       // 输出 A
    printf("**&array:   \t%c\n", **&array);       // 输出 A
    return 0;
}
  1. &*array为指针变量,长度为 4。array 为数组名,*array 为数组首元素,故 &*array 为数组首元素的地址,并且其长度为 4,类型为指针变量。
  2. *&array为数组,长度为 16。&array 为指针变量,指向整个数组。对指向整个数组的指针 &array 进行一次解引用,则得到整个数组的对象。

另一方面,* 与 & 是互逆运算符,在表面上可以理解为相互抵消。但是需要注意 sizeof(&*array) 的值为 4,即 &*array 为指针变量; sizeof(*&array) 为 16,即 *&array 等价于 array,表示数组对象。

第三组:parray 与 *parray

#include <stdio.h>

int main()
{ 
    char array[16] = {'A', 'B'}; 

    char (*parray)[16] = &array;  
 
    printf("   parray:  \t%#lx\n", parray);    // 输出 0x61ff10 
    printf("*  parray:  \t%#lx\n", *parray);   // 输出 0x61ff10 
    
    printf("   parray:  \t%#lx\n", parray+1);    // 输出 0x61ff20,则 parray 指向整个数组
    printf("*  parray:  \t%#lx\n", *parray+1);   // 输出 0x61ff11,则*parray 指向数组首个元素 
    
    printf("**parray:   \t%c\n", **parray);        // 输出 A
    
    printf("   parray:  \t%d\n", sizeof(parray));  // 输出 4
    printf("   parray:  \t%d\n", sizeof(*parray)); // 输出 16 
    
    return 0;
}
  1. parray 为 char int (*)[16] 类型的指针变量,长度为 4。从声明中可以看出,该指针等价于 &array,即指向的是数组对象 array。
  2. *parray 表示对指针变量 parray 进行了一次解引用,即得到了数组 array 的对象,且其长度为16,与数组名 array 作用等价,比如 (*parray)[0] 与 array[0] 的值均为 A。而 *array,数组名发生隐式转换后,表示为数组首元素,故若再解引用一次,即 **parray 则表示数组 array 的首元素:A。

第四组:*&parray 与 &*parray

#include <stdio.h>

int main()
{ 
    char array[16] = {'A', 'B'}; 

    char (*parray)[16] = &array; 
    
    printf("*&parray:  \t%#lx\n",   parray);  // 输出 0x61ff10
    printf("*&parray:  \t%#lx\n", *&parray);  // 输出 0x61ff10
    printf("&*parray:  \t%#lx\n", &*parray);  // 输出 0x61ff10

    printf("*&parray:  \t%d\n", sizeof(*&parray));  // 输出 4
    printf("&*parray:  \t%d\n", sizeof(&*parray));  // 输出 4

    printf("*&parray的下一单元:   %#lx\n", *&parray+1); // 输出 0x61ff20
    printf("&*parray的下一单元:   %#lx\n", &*parray+1); // 输出 0x61ff20

    return 0;
}

此组没什么好讲的,理解成 *& 抵消掉就好了。注意代码中 parray 是 0x61ff10,而 *&parray+1 和 &*parray+1 都是 0x61ff20,再次印证了 parray 是指向整个数组的,作用与 &array 等价。

三、本文结论

char array[16] = {'A', 'B'}; 
char (*parray)[16] = &array; 

  在上述代码中,array 为数组名,姑且理解为该数组的对象 ,当对 array 进行运算时,会发生“隐式转换”。通过 sizeof(array) 知其长度为16,即数组的长度。因此,array 本质不应是指针,但是在进行运算,比如 array + 1 时,array 会隐式地转换成指向数组首元素的指针变量,而 array + 1 则指向数组第二个元素。&array 则表示指向整个数组的指针变量,其类型为 int (*)[16] ,而 &array + 1 则会跨过整个数组 array,指向下一个 “数组”(如果存在的话)。
  从变量声明中可知 parray 与 &array 作用等价,则 *parray 与 *&array(即 array)等价,为数组名,表示数组对象。在进行运算时,*parray 会像 array 一样,发生隐式转换。

以下节选自书籍《C++ Primer Plus》第 213 页 2

在这里插入图片描述

补充扩展

#include <stdio.h>

int main()
{ 
    char array[16] = {'A', 'B'}; 

    char (*parray)[16] = &array;  
    
    printf("   array:   \t%c\n", (&array[0])[0]);   // 输出 A
    printf("   array:   \t%c\n", (&array[0])[1]);   // 输出 B
    printf("   array:   \t%c\n", (&array[1])[0]);   // 输出 B
    printf("   array:   \t%c\n", (&array[1])[1]);   // 输出 空

    return 0;
}
#include <stdio.h>

int main()
{ 
    char array[16] = {'A', 'B'}; 

    char (*parray)[16] = &array;  
 
    printf("   parray:  \t%#lx\n", parray);    // 输出 0x61ff10 
    printf("*  parray:  \t%#lx\n", *parray);   // 输出 0x61ff10 
  
    printf("   parray:  \t%c\n", parray[0][0]);    // 输出 A 
    printf("   parray:  \t%c\n", parray[0][1]);    // 输出 B 
    // printf("   parray:  \t%c\n", parray[1][0]); // 输出 空 
    // printf("   parray:  \t%c\n", parray[1][1]); // 输出 乱码 

    printf("   parray:  \t%c\n",  *parray [0]);    // 输出 A 
    printf("   parray:  \t%c\n", (*parray)[1]);    // 输出 B 

    printf("   parray:  \t%c\n", **parray  );      // 输出 A 
    printf("   parray:  \t%c\n", **parray+1);      // 输出 B 
 
    return 0;
}

以上问题及解决基于测试所得出,由于本人知识有限,欢迎指出文中错误。

参考链接

[1] C语言之指针变量占几个字节
[2] 何时发生隐式转换
[3] 受启:c语言中数组指针取值*(解引用)问题
[4] C++ Primer Plus 中文第六版 第213页


  1. 2 ↩︎

  2. 4 ↩︎

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

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

相关文章

SQL server 2012 配置数据库邮件实现邮件发送

日常开发中经常遇到邮件推送场景&#xff0c;我们可以利用SQL server也可以实现邮件发送功能。 一、配置邮件服务器 然后再弹出的页面中选择下一步 输入配置文件名&#xff0c;并添加新用户 在弹出的页面配置邮件服务器的地址、用户名、密码等相关信息 以上信息完成&#xff0…

树莓派 Raspberry Pi Zero 2W 安装默认系统时 ssh 登录并开启摄像头推流一段时间B

Raspberry Pi Zero 2W有点鸡肋&#xff0c;hdmi口用的microhdmi口&#xff0c;不是树莓派4b的minihdmi口&#xff0c;然后zero 2W也没有usb接口&#xff0c;有一个microusb安卓的otg接口&#xff0c;很烦&#xff0c;还好有wifi蓝牙模块&#xff0c;这样子还能ssh&#xff0c;不…

【冒泡排序】

前言 在计算机科学中&#xff0c;排序算法是一种常见且重要的算法。排序算法的目标是将一组无序的数据按照一定的规则进行重新排列&#xff0c;以便更方便地进行搜索、查找或其他操作。 冒泡排序&#xff08;Bubble Sort&#xff09;是最简单的排序算法之一&#xff0c;它的原…

基于Python+MySQL所写的商城管理系统

点击以下链接获取源码资源&#xff1a; https://download.csdn.net/download/qq_64505944/87971437?spm1001.2014.3001.5503 《51商城》程序使用说明 51商城项目分为网站前台和后台两个部分&#xff0c;下面将分别介绍这2个部分的使用。 1.网站前台 在虚拟环境中启动程序后&a…

Callback自定义测试-业务安全测试实操(23)

Callback自定义测试 测试原理和方法 在浏览器中存在着同源策略,所谓同源是指域名、协议、端口相同。当使用Aiax异步传输数据时,非同源域名之间会存在限制。其中有一种解决方法是JSONP (JSONwithPadding),基本原理是利用了HTML里<script></script>元素标签,远程…

Python 对象拷贝的那点事?

1.变量&#xff0c;引用和对象 变量无类型&#xff0c;它的作用仅仅在某个时候引用了特定的对象而已&#xff0c;具体在内存中就是一个指针&#xff0c;仅仅拥有指向对象的空间大小。 变量和对象的关系在于引用&#xff0c;变量引用对象后&#xff0c;也就对应了赋值的过程。…

VBA快速合并数据

实例需求&#xff1a;原始数据保存在工作表的A列至C列&#xff0c;现需要根据材料编号合并交付日期和交付数量&#xff0c;并且交付日期的日期格式采用两位数字年份简写格式&#xff0c;合并后的数据保存在E列和F列&#xff0c;如下图所示。 示例代码如下。 Sub demo()Dim o…

MSP430F249 Proteus仿真数码管秒表-0050

MSP430F249 Proteus仿真数码管秒表-0050 Proteus仿真小实验&#xff1a; MSP430F249 Proteus仿真数码管秒表-0050 功能&#xff1a; 硬件组成&#xff1a;MSP430F249单片机 2位数码管2个按键&#xff08;清零 开始/暂停&#xff09; 1.点击开始键后数码管开始秒表计时0~9…

Spring Cloud - HTTP 客户端 Feign 、自定义配置、优化、最佳实践

目录 一、Feign 是什么&#xff0c;有什么用呢&#xff1f; 二、Feign 客户端的使用 2.1、远程调用 1.引入依赖 2.在order-service&#xff08;发起远程调用的微服务&#xff09;的启动类添加注解开启Feign的功能 3.编写 Feign 客户端 4.通过 Feign 客户端发起远程调用 …

附件1.服务器操作系统安全加固要求及配置建议【上】

文章目录 加固文件说明【重启auditd服务后/etc/audit/audit.rules文件内容消失怎么处理】【用户的的本地登录和远程登录默认都会被审计&#xff1b; 2&#xff0c;可配置对chown、chmod、chcon等命令的执行进行审计&#xff1b;【ssh会话默认会被审计&#xff1b;】【可添加审计…

kafka入门用这一篇就够了!

目录 1&#xff0c;kafka简单介绍 2&#xff0c;kafka使用场景 3&#xff0c;kafka基本概念 kafka集群 数据冗余 分区的写入 读取分区数据 顺序消费 提交策略 零拷贝技术&#xff08;netty&#xff09; 1&#xff0c;kafka简单介绍 kafka是一款分布式、支持分区的、多…

3.springboot开发篇

SpringBoot开发实用篇 ​ KF-1.热部署 热部署是不用重启项目&#xff0c;项目自动更新 非springboot项目热部署实现原理 ​ 开发非springboot项目时&#xff0c;我们要制作一个web工程并通过tomcat启动&#xff0c;通常需要先安装tomcat服务器到磁盘中&#xff0c;开发的程序…

【openGauss数据库】--运维指南02-逻辑备份与恢复

【openGauss数据库】--运维指南02-逻辑备份与恢复 &#x1f53b; 一、 openGauss数据库备份与恢复概述&#x1f530; 1.1 备份与恢复类型&#x1f530; 1.2 类型对比 &#x1f53b; 二、 配置文件的备份与恢复&#x1f53b; 三、逻辑备份----gs_dump & gs_dumpall&#x1…

Uipath Excel 实战01-自动生成流水号最佳方法

Uipath RPA 自动生成流水号最佳方法&#xff0c;提高RPA 性能&#xff0c;速度毫秒级。通过本案例将学会Excel 写入单元格、Excel 自动填充公式、Excel 自动调整列范围、保存Excel 文件以及使用Excel 函数。 公式参考&#xff1a; Excel公式参考&#xff1a; Excel CONCATENAT…

动态规划——地下城游戏

题目链接 leetcode在线oj题——地下城游戏 题目描述 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健…

chatgpt赋能python:线上免费Python教程推荐

线上免费Python教程推荐 Python作为一门简单易学的编程语言&#xff0c;在各个行业和领域都越来越受欢迎。很多人想学Python&#xff0c;但是可能因为费用等问题而卡住了。今天&#xff0c;我们来介绍一些免费的Python教程&#xff0c;帮助那些想要学习Python但没有预算的人。…

上手vue2的学习笔记2之安装vue的踩坑经历

上一篇笔记 上手vue2的学习笔记1之了解前端三剑客&#xff0c;简单介绍了学习vue框架之前应该具备的基础知识和四个我认为非常有用的学习链接&#xff0c;建议大家动手实践一下&#xff0c;更多深刻的理解前端三剑客之间的关系。 这一篇笔记主要介绍我在安装vue过程中遇到的坑…

华为FIT痩AP旁挂式隧道组网实验(一)

拓扑图 实验设备型号ACAC6005S1S5700S2S3700APAP2050DNAP4AP2050DNAR1AR200 没有配置好之前,是没有这个AP范围圈的 配置流程 接入交换机创建VLAN,配置对应端口的链路类型,放行vlan,开启端口隔离 # 与AP连接的接口(0/0/2) [S2]vlan batch 100 101 [S2]int e0/0/2 [S2-Ethern…