再谈二维数组,二级指针

2023-11-15

首先注意一个事实:
[]是一个运算符,  称 为 下 标 运 算 符 亦 称 变 址 运 算 符   ,我觉得应该是变址取内容 
*(p+n) 恒 等于
 p[n]
我们不能简单把指针看成地址,不要忽略掉数据类型。也就是说一个指针包含两方面,一个是地址,一个是数据类型。指针是变量,这个变量的值是个地址。变量的类型是指向一个数据类型。可能有两个指针的值一样但是他们其实是不一样的,因为他们指向的数据类型不一样,此时使用取内容符号*取得的东西就会不一样了。比如下面的a[2][3],a是个指针,a[0]也是个指针,而且这两个指针的值是一样的。但是这两个指针指向的数据类型不一样,所以使用取内容符号*取得的东西也不一样。对a[0]取内容,得到的是a[0][0]是个整数,因为a[0]就是指向的类型就是整数,占4个字节。使用*a[0]以后,系统会从a[0]开始往后取4个字节。然后把这4个字节的内容转化为一个整数也就是我们的a[0][0]了。而对a取内容呢,由于a指向的类型是一个大小为3的整数数组,简单说就是a指向的也是一个数据类型(指针的定义并并不要求只能指向是C语言里的基本数据类型,甚至指向结构体都可以),这个数据类型的大小是3个整数的大小,也就是12个字节。所以取内容*a候,系统会从a的值(是个内存地址)往后取出12个字节。然后把这12个字节的内容转化为一个3个整数的一维数组。也就是说*a其实表示的是一个一维数组。而且由恒等式
*(p+n) 恒 等于
 
 p[n]我们知道*a这个一维数组我们还可以表示为a[0]。到这里我们应该会明白为什么a和a[0]的值是一样的,而我们对他们都使用取内容*运算符并且打印出来以后
printf("%x %x %x ",*a[0],*a)显示的却不一样,这是由于他们指向的数据类型不一样(也就是说他们是两个不同类型的变量,虽然他们的值一样,但是由于这两个变量的类型不一样所以对他们进行同一个运算符操作后得到的结果不一样);[这和其他的变量也是类似,比如int a=128,unsigned char b=128,他们的值是一样的,但是a<<1和b<<1以后结果却不一样了,a变为256,b变为0了]。现在还有个问题,现在我们已经知道*a表示的是一个一维数组(其实计算机中数组都是一维的),我们用printf("%x",*a)会显示什么呢,很多人都知道显示的是*a这个一维数组的第一个元素(不是我们通常说的数,是个广义的数,比如一个结构体的实例,在这我么也称作一个数,严格说是一个数据类型的实例)的地址。
系统中其实没有二维数组这个概念,都是一维的,二维数组本质是一维数组(该数组的元素数据类型是一维数组)。
下面我们解释对于a[2][3]为啥,a和a[0]的值一样,首先a[0]是一个一维数组(数组的数据类型是整数),也是一个指针常量,1:该指针的值是数组第一个元素的地址,2:指向的数据类型是整数。所以a[0]=&a[0][0]。a呢,也是个数组(该数组数据类型是一维数组),a同时也是个指针常量,1:该指针的值等于该数组第一个元素的地址,该数组第一个元素是a[0](a[0]本身也是个一维数组),a[0]的地址是什么呢,a[0]是个数,该数的数据类型是一个大小为3的整数一维数组。所以a[0]这个数,内存大小是12个字节,数的首地址是xxx。该首地址也即是a[0]这个数组的第一个元素a[0][0]的首地址,(注意:a[0]和a[0][0]也都是数,而且它们的(首)地址相等,区别是他们的数据类型不一样,a[0]这个数的数据类型是一维数组,a[0][0]这个数的数据类型是整数,数据类型不相等往往大小也不想等,也有时候大小相等),所以a这个指针常量的值a=&a[0][0]。2:a指向的数据类型是一维数组,所以a+1内存移动12个字节(移动一个a所指向的数据类型这么大)。
问题:对于一个数组,我们只知道数组名就可以表示这个数组??数组名从另一个意义上只是一个指针常量。??其实对于数组int a[5],a和&a[0]的概念是不一样的,a是个指针,&a[0]是个内存地址。这两个不一样,比如虽然a=&a[0]但是a+1和&a[0]+1不一样。所以我认为,一个一维数组就是一个指针常量,他们完全等价,对于sizeof的问题还在研究。
这里还要说下一维数组 int a[2]。a我们可以看成是个变量,这个变量的数据类型是大小为2的一维数组。同时对一维数组这种数据类型的变量,变量名同时也是该数组的第一个元素的地址,但请记住并不只是第一个数据元素的地址这么简单。它的值没错的确是第一个元素的地址,但是它的数据类型我们别忘了,指向的是整数。
这里说下我们文中所指的数的概念,他不是我们数学里的数,它指的是数据类型的一个实例。数据类型他可以是C里的基本数据类型也可以是有这些基本数据类型构成的其他数据类型,比如结构体,数组等。
接下来的一个问题是对于 int a[5],和int * const p=a。那么a和p是一样的吗,有人说a是个指针常量,那么a完全就等于一个指针常量吗?如果a是且只是一个指针常量,那么p也是个指针常量。那么a和p效果应该完全一样,但是使用sizeof()运算符以后呢,得到的值却不一样,这是为什么呢。
总结:考虑指针a的时候一定要考虑两个方面,1:他的值,是个内存地址;2:他指向的数据类型。第二条会直接影响取内容*运算的结果,取内容时是以该指针的值为始址,往后取它指向的数据类型这么大小的内存。并把这段内存的数据内容(即二进制比特)读出转为它所指向的数据类型。第二条还会影响a++运算符和a+1的结果,a+1还是个指针,指向的数据类型也不变,不过他的值变成a+sizeof(a所指向的数据类型),也就是到了a这个指针的值往后跳一个它所指数据类型这么大小的那个内存地址。
数组就是个指针常量,指针常量就是代表一个数组。这是从*(p+n)恒等与p[n]得出这条的。在大多数情况是对的。只在sizeof()和&运算符下,此时不等价。在sizeof()和&(取地址)里,系统把int a[5] 的a不看成指针常量而是看成是一个变量(数),数据类型为一维数组的变量(数)。显然该变量的大小是20个字节,且该变量的地址,即&a是a的变量类型(数组)的第一个元素的地址即&a=&a[0]。所以&a也是合法的。现:int a[5],int *const p=a;那么a和p在大多数情况是等价的,只有在sizeof()和取地址&运算符下不一样。sizeof(a)=20,sizeof(p)=4。&a=&a[0],&p!=&p[0]。此时a不是一个指针常量而是一个数据类型为一维数组的变量。。。。。这也是由于程序中不保存数组的大小,真相在楼下的链接。
int a[2];那么&a这个指针的类型是int (*) [2],也就是说我们声明 int (*p) [2];此时我们可以把p=&a。int(*p)[2]那么p是一个指向两个整数的数组的指针
取地址运算符&返回的是,所取地址对象的指针,该指针的值是对象的内存地址,该指针所指数据类型就是 该对象的数据类型
以防删帖粘贴过来
----------------------------------------------------------------------------------------------
资料如下  
数组名的本质是代表数组对象的变量名,是一个左值,是一个不能被改变的左值。但是由于在程序中不保存数组的大小,所以通过数组名只能访问数组的左值,不能访问数组的右值。由于这个原因,数组名在作为右值使用的时候被赋予另外一个新的意义——指向数组第一个元素的指针,这就是   array-to-pointer   转换规则。 
数组名在程序中作为左值使用的情况屈指可数——在大部分情况下数组名都是作为右值使用的,这时它是一个指针,一个指向数组第一个元素的指针,一个指向不能再被改变的指针——因此是一个指针常量。
那么数组名在什么情况下作为左值使用的呢?根据标准的规定,只有当数组名作为sizeof、&运算符的操作数的时候,它是一个左值,类型为数组类型;除此之外的所有情况,数组名都是一个右值,被编译器自动转换为指针类型,这种情况下我们就说数组名是一个指针,并且是一个指针常量。 
单纯地说数组名是一个指针是片面的。我们通常说数组名是一个指针是建立在一个前提的基础之上的,那就是:数组名作为右值使用的时候。 
对于数组名总结如下: 
在作为左值使用的时候,数组名表示数组对象,是数组类型。在作为右值使用的时候,数组名是一个指针,是指针类型,不再是数组类型。数组名到底是数组还是指针取决于其上下文环境。 
----------------------------------------------------------------------------------------------
printf("%x %x %x %x",&a[0][0],a,*a,**a)
打印出来是1cfda4  1cfda4  1cfda4    1。
printf("%x %x %x",&a[1][0],a+1,*(a+1));
打印出来是1ffdb0 1ffdb0 1ffdb0。
数据类型转换时:原则是值不变。但有的能表示的值的范围就不一样,此时没办法保持值不变,那就保内存不变,就如char a=-128,1000 0000把a转成unsigned char 后,值变为128但是内存中的二进制比特还是1000 0000。
可以解释为啥不能用int **p=a了,因为这两个指针的指向的数据类型不一样,int**p指向一个int型指针,而int a[2][3]的a指向一个一维数组。其实赋值操作(即等号)起作用就是赋值,把右值赋给左值,此时右值可能是个变量(数)该变量除了1:值,还有2:数据类型。但作为右值我们只需要他的值(即右值属性)。。。所以要用int **p=a还是可以的,我们目的不就是要把一个地址值赋给int **p这个指针变量的值吗。至于他们指向的数据类型不用管(虽然可能会带来麻烦,结果和你想要的不一样,但是是可以的)。好吧,我们只要强制转换就行,即int **p=(int **) a。转换后a的值不变,但是a指向的数据类型是一个指针(大小是4个字节),转换完后*p就相当于(a其实没变,a是指针常量变不了,此时只是产生了一个a的临时变量)从a的值(等于&a[0][0])往后取4个字节(指针变量的大小是4个字节)把这段4个字节的内存的数据内容读出来值转成指针的值的格式(即16进制的整数,内存地址可以看成整数)是1,再从系统内存1去取4个字节(整数)这可能是操作系统的保护区不让取了,所以程序中道这里会出错,其实即使不出错肯定也是不对的。。。讨论完这,我们会发现把二维数组看成指针的指针(二级指针)会多么可笑。二维数组所指向的数据类型是一维数组,二级指针所指向的数据类型是指针

#include <iostream>

 

void fun(char ** p)

{

char (*p1)[10] = (char(*)[10])p;

std::cout<<p1[0][0]<<std::endl;

}

 

int main(int argc, char* argv[])

{

char data[][10] = {"abc","def"};

fun((char **)data);

return 0;

}

3、通过建立一个指针数组来引用二维数组元素若有以下定义:int *p[3], a[3][2], i,j ;在这里,说明符*p[3]中,也遵照运算符的优先级,一对[]的优先级高于*号,因此p首先与[]结合,构成p[3],说明了p是一个数组名,系统将为它开辟3个连续的存储单元;在它前面的*号则说明了数组p是指针类型,它的每个元素都是基类型为int的指针。若满足条件:0≤i<3,则p[i]和a[i]的基类型相同,p[i]= a[i]是合法的赋值表达式。

 

若有以下循环:for(i=0; i<3; i++) p[i]= a[i];在这里,赋值号右边的a[i]是常量,表示a数组每行的首地址,赋值号左边的p[i]是指针变量,循环执行的结果使p[0]、p[1]、p[2]分别指向a数组每行的开头。这时,数组p和数组a之间的关系如图9.6所示。

 

当p数组的每个元素已如图9.6所示指向a数组每行的开头时,则a数组元素a[i][j]的引用形式*(a[i]+ j)和*(p[i]+j)是完全等价的。由此可见,这时可以通过指针数组p来引用a数组元素,它们的等价形式如下:(1)*(p[i]+j) (2)*(*(p+i)+j) (3)(*(p+i))[j] (4)p[i][j] 不同的是:p[i]中的值是可变的,而a[i]中的值是不可变的。

 

 图9.64、通过建立一个行指针来引用二维数组元素若有以下定义:int a[3][2], (*prt)[2];在这里,说明符(*prt)[2]中,由于一对圆括号的存在,所以*号首先与prt结合,说明prt是一个指针变量,然后再与说明符[2]结合,说明指针变量prt的基类型是一个包含有两个int元素的数组。在这里,prt的基类型与a的相同,因此prt=a;是合法的赋值语句。prt+1等价于a+1、等价于a[1]。当prt指向a数组的开头时,可以通过以下形式来引用a[i][j]:(1) *(prt[i]+j) (2) *(*(prt+i)+j) (3)(*(prt+i))[j] (4) prt[i][j] 在这里,prt是个指针变量,它的值可变,而a是一个常量。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

再谈二维数组,二级指针 的相关文章

  • 45类商标分类表明细表

    商标注册分类采用的是尼斯分类 尼斯联盟成员国采用 商标注册用商品和服务国际分类 尼斯分类将商品和服务分成45个大类 其中商品为1 34类 服务为35 45类 商标局将尼斯分类的商品和服务项目划分类似群 并结合实际情况增加我国常用商品和服务项

随机推荐

  • SpringBoot使用多数据源时怎么解决事务不生效问题

    在使用多数据源时 如果不进行特殊处理 可能会出现事务不生效的问题 这是因为 Spring Boot 默认只会为一个数据源创建一个事务管理器 如果要使用多个数据源 就需要为每个数据源创建一个事务管理器 并在需要使用事务的方法或类上指定使用哪个
  • Istio Pilot源码学习(二):ServiceController服务发现

    本文基于Istio 1 18 0版本进行源码学习 4 服务发现 ServiceController ServiceController是服务发现的核心模块 主要功能是监听底层平台的服务注册中心 将平台服务模型转换成Istio服务模型并缓存
  • linux环境下设置用户密码过期期限

    关于密码过期时间和用户过期时间的设置 通常使用chage命令和usermod命令 设置某个用户的过期时间 accountexpires 可以用usermod e来设置 查看某个用户的密码 passwordexpires 过期时间等信息 可以
  • Java里Controller层参数不必填

    public Result
  • KNN算法的一个回归应用分析

    介绍 在我所接触的机器学习算法中 KNN是一种相对来说较容易理解的算法 但是它在实际中仍有十分广泛的应用 KNN算法可以用于分类和回归问题 在分类问题中应用较多 虽然KNN很少用于回归问题 但对于连续的变量仍有很好的效果 下面我们来介绍KN
  • 一篇打通,pytest自动化测试框架详细,从0到1精通实战(一)

    前言 pytest单元测试框架 1 什么是单元测试框架 单元测试是指在软件开发当中针对软件的最小单位 函数 方法 进行正确性的检查测试 2 单元测试框架有哪些 Java junit 和 testing python unittest 和 p
  • 整理了一些常用的免费api接口,不限次数,收藏备用~(持续更新...)

    API Application Program Interface 被定义为应用程序可用以与计算机操作系统交换信息和命令的标准集 一个标准的应用程序界面为用户或软件开发商提供一个通用编程环境 以编写可交互运行于不同厂商计算机的应用程序 AP
  • ISP_matlab

    确定输入是否为结构体数组字段 MATLAB isfield MathWorks 中国 对话框打开文件 获取路径和文件名 file path uigetfile raw RAW fid fopen fullfile path file htt
  • 百度通用翻译api使用

    官方api文档 http api fanyi baidu com api trans product apidoc springboot demo地址 https github com Blankwhiter translate 第一步 注
  • python web界面模板_Python简单轻量级Web Server模块Bottle

    Bottle 提示 使用此WEB服务器模块需要有基本的HTTP知识 简单 轻量级指的是 上手不难 容易使用 模块不大还能完成一般Web服务器的功能 Bottle是Python平台的轻量级Web Server 准确的说是HTTP Server
  • node.js在idea里运行

    Node js 是一种运行在 Chrome V8 JavaScript 引擎上的基于 JavaScript 的客户端运行时 JavaScript runtime 它可以用于构建网络应用程序和服务 在 Node js 中 你可以使用多种构建工
  • 从数据库字符串中获取数字部分,用于数据分析

    目录 前言 一 思路 1 获取字符串中的小数及整数部分 代码 效果 解析 2 获取字符串中数字部分 代码 效果 解析 二 总结 前言 在大数据时代 我们经常要分析很多非结构化的数据 同时也要分析很多非标准的数据 如 0 78吨 CYJ23w
  • 【数据结构 001】 定义

    数据 能被计算机处理 能被计算机识别 能输入计算机 数据元素 有一定意义的基本单位 数据项 数据元素的组成单位 认为是数据结构中最小组成单位 数据对象 具有相同性质的数据元素的集合 数据结构 存在特定关系的数据的集合 数据之间特定的结构关系
  • 线性表(C++实现)

    线性表的定义与基本操作 定义 具有相同数据类型的n个数据元素的有限序列 n是表长 当n为0的时候线性表是一个空表 如果用L命名线性表 那么其一般表示为 L a 1
  • 山谷 蓝桥杯

    问题描述 给定一个字母矩阵 如果矩阵中的某个位置不在四条边上 而且该位置上的字母小于其上下左右四个位 置的字母 则称为一个山谷 例如 对于如下矩阵 DDDDD CADCE FFFFA 共有两个山谷 位于第二行第二列和第四列 请注意第二行第三
  • 计算机网络-----网络编程

    网络编程 实战 网络基础 1 什么是计算机网络 2 什么是网络编程 3 网络编程中的主要问题 4 网络通信要素 5 通信协议分层思想 IP和端口号 1 IP 1 1定义 1 2IP的分类 2 端口号 2 1定义 2 2端口号的分类 网络通信
  • [转]Datagridview中的数据很多,加载完数据后滚动条自动到最下边的方法

    this dataGridView1 FirstDisplayedScrollingRowIndex this dataGridView1 Rows Count 1 转载于 https www cnblogs com aooyu archi
  • svn checkout 报 ‘svn: E000061: 执行上下文错误: Connection refused‘

    问题 svn E170013 svn E000061 svn svn checkout https xxx xxx xxx xxx 9443 svn project xxx svn E170013 Unable to connect to
  • php校验密码js,JS的密码强度校验正则表达式(附代码)

    这次给大家带来JS的密码强度校验正则表达式 附代码 使用JS的密码强度校验正则表达式注意事项有哪些 下面就是实战案例 一起来看一下 最近一直在做通行证项目 里面的注册模块中输入密码需要显示密码强度 低中高 今天就把做的效果给大家分享下 代码
  • 再谈二维数组,二级指针

    首先注意一个事实 是一个运算符 称 为 下 标 运 算 符 亦 称 变 址 运 算 符 我觉得应该是变址取内容 p n 恒 等于 p n 我们不能简单把指针看成地址 不要忽略掉数据类型 也就是说一个指针包含两方面 一个是地址 一个是数据类型