图文详解c/c++中的多级指针与多维数组

前言

首先先声明一些常识,如果你对这些常识还不理解,请先去弥补一下基础知识:

1、实际上并不存在多维数组,所谓的多维数组本质上是用一维数组模拟的。

2、数组名是一个常量(意味着不允许对其进行赋值操作),其代表数组首元素的首地址。

3、数组与指针的关系是因为数组下标操作符[],比如,int a[3][2]相当于*(*(a+3)+2) 。

4、指针是一种变量,也具有类型,其占用内存空间大小和系统有关,一般32位系统下,sizeof(指针变量)=4。

5、指针可以进行加减算术运算,加减的基本单位是sizeof(指针所指向的数据类型)。

6、对数组的数组名进行取地址(&)操作,其类型为整个数组类型。

7、对数组的数组名进行sizeof运算符操作,其值为整个数组的大小(以字节为单位)。

8、数组作为函数形参时会退化为指针。

 一、一维数组与数组指针

假如有一维数组如下:

 char a[3];

该数组一共有3个元素,元素的类型为char,如果想定义一个指针指向该数组,也就是如果想把数组名a赋值给一个指针变量,那么该指针变量的类型应该是什么呢?前文说过,一个数组的数组名代表其首元素的首地址,也就是相当于&a[0],而a[0]的类型为char,因此&a[0]类型为char *,因此,可以定义如下的指针变量:

 char * p = a;//相当于char * p = &a[0]

以上文字可用如下内存模型图表示。

大家都应该知道,a和&a[0]代表的都是数组首元素的首地址,而如果你将&a的值打印出来,会发现该值也等于数组首元素的首地址。请注意我这里的措辞,也就是说,&a虽然在数值上也等于数组首元素首地址的值,但是其类型并不是数组首元素首地址类型,也就是char *p = &a是错误的。

前文第6条常识已经说过,对数组名进行取地址操作,其类型为整个数组,因此,&a的类型是char (*)[3],所以正确的赋值方式如下:

 char (*p)[3] = &a;

注:很多人对类似于a+1,&a+1,&a[0]+1,sizeof(a),sizeof(&a)等感到迷惑,其实只要搞清楚指针的类型就可以迎刃而解。比如在面对a+1和&a+1的区别时,由于a表示数组首元素首地址,其类型为char *,因此a+1相当于数组首地址值+sizeof(char);而&a的类型为char (*)[3],代表整个数组,因此&a+1相当于数组首地址值+sizeof(a)。(sizeof(a)代表整个数组大小,前文第7条说明,但是无论数组大小如何,sizeof(&a)永远等于一个指针变量占用空间的大小,具体与系统平台有关)

二、二维数组与数组指针

假如有如下二维数组:

 char a[3][2];

由于实际上并不存在多维数组,因此,可以将a[3][2]看成是一个具有3个元素的一维数组,只是这三个元素分别又是一个一维数组。实际上,在内存中,该数组的确是按照一维数组的形式存储的,存储顺序为(低地址在前):a[0][0]、a[0][1]、a[1][0]、a[1][1]、a[2][0]、a[2][1]。(此种方式也不是绝对,也有按列优先存储的模式)

为了方便理解,我画了一张逻辑上的内存图,之所以说是逻辑上的,是因为该图只是便于理解,并不是数组在内存中实际的存储模型(实际模型为前文所述)。

如上图所示,我们可以将数组分成两个维度来看,首先是第一维,将a[3][2]看成一个具有三个元素的一维数组,元素分别为:a[0]、a[1]、a[2],其中,a[0]、a[1]、a[2]又分别是一个具有两个元素的一维数组(元素类型为char)。从第二个维度看,此处可以将a[0]、a[1]、a[2]看成自己代表”第二维”数组的数组名,以a[0]为例,a[0](数组名)代表的一维数组是一个具有两个char类型元素的数组,而a[0]是这个数组的数组名(代表数组首元素首地址),因此a[0]类型为char *,同理a[1]和a[2]类型都是char *。而a是第一维数组的数组名,代表首元素首地址,而首元素是一个具有两个char类型元素的一维数组,因此a就是一个指向具有两个char类型元素数组的数组指针,也就是char(*)[2]。

也就是说,如下的赋值是正确的:

 char (*p)[2] = a;//a为第一维数组的数组名,类型为char (*)[2]

 char * p = a[0];//a[0]维第二维数组的数组名,类型为char *

同样,对a取地址操作代表整个数组的首地址,类型为数组类型(请允许我暂且这么称呼),也就是char (*)[3][2],所以如下赋值是正确的:

 char (*p)[3][2] = &a;

三、三维数组与数组指针

假设有三维数组:

 char a[3][2][2];

同样,为了便于理解,特意画了如下的逻辑内存图。分析方法和二维数组类似,首先,从第一维角度看过去,a[3][2][2]是一个具有三个元素a[0]、a[1]、a[2]的一维数组,只是这三个元素分别又是一个"二维"数组,a作为第一维数组的数组名,代表数组首元素的首地址,也就是一个指向一个二维数组的数组指针,其类型为char (*)[2][2]。从第二维角度看过去,a[0]、a[1]、a[2]分别是第二维数组的数组名,代表第二维数组的首元素的首地址,也就是一个指向一维数组的数组指针,类型为char(*)[2];同理,从第三维角度看过去,a[0][0]、a[0][1]、a[1][0]、a[1][1]、a[2][0]、a[2][1]又分别是第三维数组的数组名,代表第三维数组的首元素的首地址,也就是一个指向char类型的指针,类型为char *。

由上可知,以下的赋值是正确的:

  char (*p)[3][2][2] = &a;//对数组名取地址类型为整个数组
  char (*p)[2][2] = a;
  char (*p) [2] = a[0];//或者a[1]、a[2]
  char *p = a[0][0];//或者a[0][1]、a[1][0]...

四:多级指针

所谓的多级指针,就是一个指向指针的指针,比如:

  char *p = "my name is chenyang.";

  char **pp = &p;//二级指针

  char ***ppp = &pp;//三级指针

假设以上语句都位于函数体内,则可以使用下面的简化图来表达多级指针之间的指向关系。

多级指针通常用来作为函数的形参,比如常见的main函数声明如下:

 int main(int argc,char ** argv)

因为当数组用作函数的形参的时候,会退化为指针来处理,所以上面的形式和下面是一样的。

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

argv用于接收用户输入的命令参数,这些参数会以字符串数组的形式传入,类似于:

 char * parm[] = {"parm1","parm2","parm3","parm4"};//模拟用户传入的参数

 main(sizeof(parm)/sizeof(char *),parm);//模拟调用main函数,实际中main函数是由入口函数调用的(glibc中的入口函数默认为_start)

多级指针的另一种常见用法是,假设用户想调用一个函数分配一段内存,那么分配的内存地址可以有两种方式拿到:第一种是通过函数的返回值,该种方式的函数声明如下:

 void * get_memery(int size)
 {
  void *p = malloc(size);
  return p;
  }

第二种获取地址的方法是使用二级指针,代码如下:

 int get_memery(int** buf,int size)
 {
  *buf = (int *)malloc(size);
  if(*buf == NULL)
   return -1;
  else
   return 0;
 }
  int *p = NULL;
  get_memery(&p,10);

总结

关于多级指针的用法很多,尤其以二级指针应用最为广泛,后续的有时间再进行补充。c/c++中的多级指针与多维数组的内容到这就基本结束了,希望本文的内容对大家能有所帮助。

(0)

相关推荐

  • C/C++中获取数组长度的方法示例

    学过C/C++的人都知道,在C/C++中并没有提供直接获取数组长度的函数,对于存放字符串的字符数组提供了一个strlen函数获取其长度,那么对于其他类型的数组如何获取他们的长度呢? 其中一种方法是使用sizeof(array) / sizeof(array[0]), 在C语言中习惯上在使用时都把它定义成一个宏,比如: #define GET_ARRAY_LEN(array,len) {len = (sizeof(array) / sizeof(array[0]));} 而在C++中则可以使用模板

  • C/C++ 动态数组的创建的实例详解

    C/C++ 动态数组的创建的实例详解 在C++语言中,二维动态数组主要使用指针的方法建立,以建立一个整数二维数组为例: #include<iostream> #include<string> #include<malloc.h> using namespace std; int main(int argc,char **argv) { ///*int a[2][3]={{1,2,3},{4,5,6}}; //cout<<sizeof(a+1)<<

  • Lua教程(五):C/C++操作Lua数组和字符串示例

    本文将介绍如何在C/C++里面操作Lua的数组和字符串类型,同时还会介绍如何在C/C++函数里面存储Lua状态(registry和upvalue),而registry在使用C/C++自定义类型时非常有用,可以方便地为userdata指定metatable. C/C++操作Lua数组 Lua数组Overview 在Lua里面,数组只不过是key为整数的table而已.比如一个table为array = {12,"Hello", "World"},它是一个数组,可以用下

  • C/C++ 数组和指针及引用的区别

    C/C++ 数组和指针及引用的区别 1.数组和指针的区别 (1)定义 数组是一个符号,不是变量,因而没有自己对应的存储空间.但是,指针是一个变量,里面存储的内容是另外一个变量的地址,因为是变量所以指针有自己的内存空间,只不过里面存储的内容比较特殊. (2)区别 a.对于声明和定义,指针和数组是不相同的,定义为数组,则声明也应该是数组,不可混淆 b.当作下标操作符时,指针和数组是等价的.a[i]会被编译器翻译成*(a+i). c.当数组声明被用作函数形参的时候,数组实际会被当作指针来使用. (3)

  • 浅析C/C++,Java,PHP,JavaScript,Json数组、对象赋值时最后一个元素后面是否可以带逗号

    1 C,C++,Java,PHP都能容忍末尾的逗号 C,C++,Java中对数组赋值时,最后一个元素末尾的逗号可有可无.下面两行代码对这些语言来说是等效的. int a[] = {1,2,3}; /* 正确 */ int a[] = {1,2,3,}; /* 正确 */ PHP这一点也继承了C的特点,下面的两行代码等效. $a = array(1,2,3); /* 正确 */ $a = array(1,2,3,); /* 正确 */ 2 JavaScript视末尾逗号为语法错误! 然而到了Jav

  • 图文详解c/c++中的多级指针与多维数组

    前言 首先先声明一些常识,如果你对这些常识还不理解,请先去弥补一下基础知识: 1.实际上并不存在多维数组,所谓的多维数组本质上是用一维数组模拟的. 2.数组名是一个常量(意味着不允许对其进行赋值操作),其代表数组首元素的首地址. 3.数组与指针的关系是因为数组下标操作符[],比如,int a[3][2]相当于*(*(a+3)+2) . 4.指针是一种变量,也具有类型,其占用内存空间大小和系统有关,一般32位系统下,sizeof(指针变量)=4. 5.指针可以进行加减算术运算,加减的基本单位是si

  • 图文详解HTTP头中的SQL注入

    目录 1.HTTP头中的注入介绍 2.HTTP User-Agent注入 3.HTTP Referer注入 4.sqlmap安全测试 5.HTTP头部详解 总结 HTTP头中的SQL注入 1.HTTP头中的注入介绍 在安全意识越来越重视的情况下,很多网站都在防止漏洞的发生.例如SQL注入中,用户提交的参数都会被代码中的某些措施进行过滤. 过滤掉用户直接提交的参数,但是对于HTTP头中提交的内容很有可能就没有进行过滤. 例如HTTP头中的User-Agent.Referer.Cookies等. 2

  • 详解C语言中的常量指针和指针常量

    概述 对于新手来说,指针在c语言里总是一个非常难以理解的概念.在这篇文章中,我们将解释常量指针,指针常量,const pointer to const(ps:楼主以为这可以翻译成指向常量的常量指针)的区别 常量指针 让我们先来理解什么是常量指针.常量指针是指指针指向的地址是常量.换句话说,一旦常量指针指向了一个变量,你不能让该常量指针指向其他变量了 常量指针的声明方法如下: <type of pointer> * const <name of pointer> 常量指针声明示例:

  • JavaScript中浅讲ajax图文详解

    1.ajax入门案例 1.1 搭建Web环境 ajax对于各位来说,应该都不陌生,正因为ajax的产生,导致前台页面和服务器之间的数据传输变得非常容易,同时还可以实现页面的局部刷新.通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新. 对于JavaWeb项目而言,ajax主要用于浏览器和服务器之间数据的传输. 如果是单单地堆砌知识点,会显得比较无聊,那么根据惯例,我先不继续介绍ajax,而是来写一个案例吧. 打开

  • Eclipse中导入Maven Web项目并配置其在Tomcat中运行图文详解

    今天因为实习的关系需要讲公司已经开发的项目导入进Eclipse,而公司的项目是用Maven来构建的所以,需要将Maven项目导入进Eclipse下. 自己因为没有什么经验所以搞了得两个多小时,在这里和大家分享一下自己的经验已经在这之中遇到的一些问题. 首先我通过svn将公司的项目checkout到了本地. 因为Maven遵循的是规约比配置重要的原则,所以Maven项目的结构一般是进入目录后是一个pom.xml文件和一个src文件夹,当然可能还存在一些README之类的这些都不重要,最关键的就是p

  • Android Studio 中运行 groovy 程序的方法图文详解

    Groovy简介 Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python.Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码.由于其运行在 JVM 上的特性,Groovy也可以使用其他非Java语言编写的库. Groovy 是 用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言.使用该种语言不必编写过多的代码,同时又具有闭包和动态语

  • 在idea中将创建的java web项目部署到Tomcat中的过程图文详解

    在idea中将创建的java web项目部署到Tomcat中 采用的工具idea 2018.3.6 Tomcat7 1.先创建第一个新项目secondweb(注意勾选JavaEE下的web Application(4.0),窗口下的version对应为4.0,并且保证create web.xml已经被勾选) 2.在创建好的web项目的web/WEB-INF目录下创建两个文件夹:classes和lib.classes用来存放编译后输出的class文件,lib用来存放第三方jar包(下图显示的是创建

  • 在IDEA(2020.2)中配置Git及使用Git的图文详解

    一. idea中配置git 先配置好git的本地地址,然后test,出现版本号说明测试成功! 二. idea中使用git 可以直接在idea中使用命令操作git 1.初始化本地仓库 选好项目点击OK即可. 2.添加到暂存区 3.提交到本地仓库 也可以在这里提交,效果一样只是位置不一样 4.推送至远程仓库 5.直接克隆项目到本地 6.拉取项目到本地 7.创建分支 这时候就切换到了新创建的分支 到此这篇关于在IDEA(2020.2)中配置Git及使用Git的图文详解的文章就介绍到这了,更多相关IDE

  • pycharm中leetcode插件使用图文详解

    1.安装插件步骤 2.点击OK确认之后,提示IDE需要重启,选择重启: 3.设置leetcode插件,用户名.密码: 4.点击右下角的leetcode: 题库就出来了,双击进入开发编辑界面: 做完题之后,可选择运行.测试.提交,查看执行情况: 到此这篇关于pycharm中leetcode插件使用图文详解的文章就介绍到这了,更多相关pycharm中leetcode插件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

  • 最详细的docker中安装并配置redis(图文详解)

    一.找到一个合适的docker的redis的版本 可以去docker hub中去找一下 https://hub.docker.com/_/redis?tab=tags 二.使用docker安装redis sudo docker pull redis 安装好之后使用docker images即可查看 truedei@truedei:~$ truedei@truedei:~$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE redis

随机推荐