【C语言进阶深度学习记录】三十 二维数组与二维指针

文章目录

    • 1 二维指针(指向指针的指针)
    • 2 二维数组
    • 3 二维数组的类型
      • 3.2 如何动态申请二维数组
    • 4 总结

1 二维指针(指向指针的指针)

  • 指针的本质是变量
  • 指针的指针是保存指针变量的地址。如下面的代码:

在这里插入图片描述

为什么需要指向指针的存在?还记得之前学习的过程中说的函数传值调用和传址调用么?当要在函数内部修改传进来参数变量的时候,需要传址调用。

同理,如果传进来的本来就是一个指针,想要修改该指针,那么就需要传指向该指针的指针了。道理是一样的。看下面的代码就明白了:

  • 代码34-1.c:函数reset为重新为某一段内存分配一段内存空间(可大可小)
#include <stdio.h>
#include <malloc.h>/* 想要修改p指向的内容,且p的地址也是会变的,就必须使用传址调用。传p的地址。就是双指针 */
int reset(char** p, int old_size, int new_size){int ret = 1;int i = 0;int len = 0;char* pp = *p;char* pt = NULL;char* tmp = NULL;if((NULL!=p) && (new_size>0)){pt = (char*)malloc(new_size);tmp = pt;len = (old_size < new_size) ? old_size : new_size;for(i=0; i<len; i++){*tmp = *pp;tmp++;pp++; }free(*p);*p=pt;}else{ret = 0;}return ret;
}int main(){char* p = (char*)malloc(5);printf("p = %p\n",p);if(reset(&p, 5, 8)){printf("reset p = %p\n",p);}else{printf("no reset!\n");}free(p);return 0;
}
  • 编译运行结果为:

p = 0x8833008
reset p = 0x8833018

分析:

上述代码中reset函数是重新为一块内存分配另一个内存空间。我们知道要分配另一块空间的话,地址肯定是会变的,那么想要最终将原来的地址p改变,就需要进行传址调用。因为p本来就是指针,所以需要传指针的指针进入reset函数。在reset函数中,进行重新分配内存空间并将原有的内存空间中的值拷贝到新的内存地址处。具体自己好好看一下reset函数就可以理解。

由运行可以看出:

  • 地址p确实改变了,说明传址调用起了作用
  • 注意理解指向指针的指针的意义与用法。需要多琢磨。

2 二维数组

在C语言中,没有二维数组的概念。它只是另一种形式的一维数组。

  • 二维数组在内存中是以一维数组的形式排布
  • 二维数组的第一维是一维数组(注意,一维数组相当于一个常量指针,也就说二维数组的第一维是存的指针)
  • 二维数组的第二维是具体存的数值
  • 既然一维数组的数组名可以看成是常量指针,那么二维数组的数组名也同样可以看成是常量指针
  • 结合上述四条看看下图中的二维数组在内存中的样式:

在这里插入图片描述
结合下面的代码来认识认识二维数组:

  • 代码:34-2.c :
#include <stdio.h>void print_array(int a[], int size){printf("print_array:sizeof(array) = %d\n",sizeof(a));int i = 0;for(i=0; i<size; i++){printf("%d ",a[i]);}printf("\n");
}
int main(){int a[3][3] = {{0,1,2},{3,4,5},{6,7,8}}; int* p = &a[0][0];int i=0,j=0;for(i=0; i<3; i++){for(j=0; j<3; j++){printf("%d, ", a[i][j]);}printf("\n");}printf("\n");for(i=0; i<3; i++){for(j=0; j<3; j++){printf("%d, ", *(*(a+i)+j));}printf("\n");}printf("a = %p, a+1 = %p\n",a,a+1);printf("&a = %p, &a+1 = %p\n",&a,&a+1);printf("p = %p, p+1 = %p\n",p,p+1);printf("\n");print_array(p,9);return 0;
}
  • 编译运行结果为:
    在这里插入图片描述

分析:

  • 上述代码并不是很难,所以不做详细分析。只做以下几点说明:
  1. 可以像这样访问二维数组:*(*(a+i)+j) 。可以想这样理解: 其中a+i 代表是一维数组的第i个元素(即指针),*(a+i)代表找到第i个一维数组的起始地址,*(a+i)+j 表示第i个一维数组中的第j个元素的地址。最终*(*(a+i)+j) 表示取出元素。可以参考上面的二维数组的内存图。

  2. 由:a = 0xbfb71b00, a+1 = 0xbfb71b0c 知道:二维数组的名字a可以看成是一个常量指针,它的值为二维数组首元素(这个首元素相当于是一个一维数组)的地址值。a+1就直接跨过一个一维数组的长度(这里是12,三个int)。

  3. 由:&a = 0xbfb71b00, &a+1 = 0xbfb71b24 知道: &a 代表整个数组的地址(这与一维数组很相似)。&a+1 就直接跨过整个数组的大小到数组末尾

  4. 由print_array 函数的参数是一维数组知道,二维数组在内存中排布是一维数组的形式。参考上图。

3 二维数组的类型

  • 之前学过以为数组的类型如下:

int a[5] ==>>> a的类型为: int*

  • 二维数组的类型为:

int a[2][5] ==>>> a的类型为:int(*)[5]

  1. 二维数组名可以看做是指向数组的常量指针
  2. 二维数组可以看成是一维数组中存的元素类型是一个同类型的一维数组

3.2 如何动态申请二维数组

从下面的代码来学习如何动态申请二维数组(参考下面的二维数组的内存模型就可以理解下面的代码):

  • 代码:34-3.c:
#include <stdio.h>
#include <malloc.h>int** malloc2d(int row, int col){int** ret = NULL;if(row>0 && col>0){ret = (int**)malloc(row*sizeof(int*)); //相当于申请一个一维数组,存的元素是指针int* p = (int*)malloc(row*col*sizeof(int));//相当于二维数组在内存中的一维排布,指针p指向这个一维排布if(NULL!=p && NULL!=ret){int i = 0;for(i=0; i<row; i++){ret[i] = p + i*col;  // ret[i]存的是一个个指针指向的数组,每个数组长度是col,可以参考下图的二维数组内存模型}}else{free(ret);free(p);ret = NULL;}}return ret;
}void free2d(int** p){if(NULL != *p){free(*p);}free(p);
}
int main(){int** a = malloc2d(3,3);int i=0,j=0;for(i=0; i<3; i++){for(j=0; j<3; j++){printf("a[%d][%d] = %d,", i,j,a[i][j]);}printf("\n");}    free2d(a);return 0;
}
  • 编译运行代码如下:

在这里插入图片描述

虽然上述的数组中的各个值都是0,但是我们要知道malloc申请后的内存中的值是不确定的,并不一定是0

分析:

  • 上述代码中的核心代码已经标注,多画图分析即可
  • 可以参考下面的二维数组的内存模型图进行分析:

在这里插入图片描述

至此,就学会了如何动态的申请二维数组了。一位数组的动态申请比较简单,之前的文章也有学习过。

4 总结

  • C语言中只支持一维数组,所谓的二维数组在内存中依然是以一维数组的形式排布
  • C语言中的数组大小,必须在编译期就作为常数确定。(毕竟数组的大小是数组类型的一部分,类型都不确定好,如何编译)
  • 二维数组就是一个一维数组存的元素是同类型的数组而已

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

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

相关文章

【C语言进阶深度学习记录】三十一 数组作为函数参数时退化为指针

之前的学习数组的文章中&#xff0c;已经知道一维数组作为函数参数的时候&#xff0c;最终会被编译器编译为指针。今天来看看二维数组的情形 文章目录1 为什么C语言中的数组作为函数参数会退化为指针&#xff1f;2 二维数组作为函数参数如何退化2.1 代码案例分析&#xff08;传…

使用HTMLParser模块解析HTML页面

HTMLParser是python用来解析html和xhtml文件格式的模块。它可以分析出html里面的标签、数据等等&#xff0c;是一种处理html的简便途径。HTMLParser采用的是一种事件驱动的模式&#xff0c;当HTMLParser找到一个特定的标记时&#xff0c;它会去调用一个用户定义的函数&#xff…

前端学习(294):rem小实例

altz转换为rem <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compatible"…

【C语言进阶深度学习记录】三十二 函数指针与使用函数指针实现回调函数

回调函数是非常重要的概念 文章目录1 函数的类型2 函数指针2.1 函数指针的使用2.2 使用函数指针实现回调函数3 总结1 函数的类型 跟以前学数组的时候是一样的&#xff0c;C语言中的数组是有自己的类型的。函数也是有自己的类型的。 函数的类型由返回值、参数的类型、参数的个…

【C语言进阶深度学习记录】三十三 C语言中动态内存分配

如何在程序运行的时候动态给程序分配内存&#xff1f; 文章目录1 动态内存分配的意义1.1 C语言中如何动态申请内存空间1.2 malloc和free的用法1.3 calloc与realloc1.31 calloc和realloc的代码案例分析2 总结1 动态内存分配的意义 在C语言中&#xff0c;一切操作都是基于内存的…

java并发实战

推荐一个Java并发编程实战的学习专栏。此专栏为极客时间收费专栏。 学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群 &#xff1a; 962535112 对于一个 Java 程序员而言&#xff0c; 能否熟练掌握并发编程是判断他优秀与否的…

a critical review of preetham skylight model 笔记

也是为了试用下Xmind。 上图是我用Xmind作的某篇文章的笔记。 感想&#xff1a; 1. 之以一直觉得这种东西没多大用处&#xff0c;回想起来大概是因为那时没有太多应用场景。 2. 如果留心&#xff0c;可以把许多事情做得更漂亮、更容易&#xff0c;这也是工具的用途。 贴一下软件…

【C语言进阶深度学习记录】三十四 C语言实现内存泄漏检测模块

上一篇文章学习了malloc系列的三个函数的使用。众所周知malloc的使用很容易导致内存泄漏。本文的目的就是使用C语言来实现内存泄漏检测模块&#xff0c;来帮忙自动检测我们写的程序中是否出现内存泄露。 文章目录1 内存泄露检测模块的实现原理1.1 各个函数模块的设计1.2 模块整…

重学前端----前端知识系统学习推荐专栏

推荐一个前端知识学习专栏。此专栏为极客时间收费专栏。 学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群&#xff1a; 962535112 文章目录1 概述2 专栏内容2.1 模块一&#xff0c;JavaScript2.2 模块二&#xff0c;HTML 和 …

【C语言进阶深度学习记录】三十五 程序中的堆、栈以及静态存储区(数据区)

学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群&#xff1a; 962535112 在我之前学习底层的知识的时候&#xff0c;也写过相关的内容。可以对比的学习&#xff1a;【软件开发底层知识修炼】二十 深入理解可执行程序的结构&a…

【C语言进阶深度学习记录】三十六 程序与进程的区别(程序的内存布局)

上一篇文章学了堆&#xff0c;栈以及静态存储区。它们实际上都是针对进程来说的。那么程序与进程有什么区别呢&#xff1f; 本文不细讲程序与进程。 1 程序与进程 1.1 什么是程序 写完的.c文件是源文件。也叫源代码。 将源代码编译后&#xff0c;会生成可执行文件程序&#…

使用Cucumber+Rspec玩转BDD(2)——邮件激活

使用CucumberRspec玩转BDD(2)——邮件激活 2009年3月2日 星期一 ### 温故知新 ###前面我们已经完成了新用户注册功能的开发&#xff0c;为了方便我们后面的开发工作且不扰乱之前的工作成果&#xff0c;我们先将这份源代码归档并做个标记。为了获得更好的阅读体验&#xff0c;读…

【C语言进阶深度学习记录】三十七 C/C++中造成程序内存错误的原因(野指针)

什么是野指针&#xff1f; 指针变量存的地址是一块非法内存地址。进而形成野指针。但是需要注意一点&#xff0c;野指针不是NULL指针。 文章目录1 野指针的概念1.1 野指针代码案例初探2 如何避免野指针2.1 野指针代码案例分析进阶3 总结1 野指针的概念 野指针变量中的值是非法…

算法补充 2011-9-12

设计一个算法将顺序表L中所有小于0的整数放前半部分&#xff0c;大于等于0的整数放在后半部分二叉树的删除设计一个算法将顺序表L中所有小于0的整数放前半部分&#xff0c;大于等于0的整数放在后半部分 思路:从左侧找出>0的元素&#xff0c;从右侧找出<0的元素,然后进行交…

【C语言进阶深度学习记录】三十八 C/C++语言中的函数声明与函数定义

文章目录1 函数的声明和定义1.1 代码分析2 总结1 函数的声明和定义 声明的意义在于告诉编译器程序单元的存在。只是告诉编译器它存在但是不在声明这里定义&#xff0c;有可能在当前文件中的其他地方或者其他文件中定义。如果在它还没有被定义之前就使用它&#xff0c;会导致编…

ASP.NET MVC3 系列教程 - 部署你的WEB应用到IIS 6.0

I:ASP.NET MVC3 部署的前期工作 1.确认部署的服务器操作系统环境 首先我们确认服务器的操作系统版本可以从系统命令行工具里输入: systeminfo 获取相关操作系统信息例如然后再确认IIS版本信息 -> 打开IIS管理工具即可接着确认.NET Framework的版本可以在系统命令行工具执行:…

【C语言进阶深度学习记录】三十九 C语言中的可变参数(参数可变的函数)

用过printf()函数的热都知道&#xff0c;printf的参数可以有多个&#xff0c;它是可变的&#xff0c;根据我们输出参数的类型以及个数的不同来确定参数。今天来学习C语言中参数可变的函数是如何实现的。 文章目录1 可变参数2 总结1 可变参数 首先我们要明白一点&#xff0c;在…

【离散数学中的数据结构与算法】一 最大公约数与最小公倍数之间的关系

文章目录1 算数基本定理2 最大公约数3 最小公倍数4 性质5 推论1 算数基本定理 设正整数 n>1&#xff0c; 则 n 可唯一地表示为&#xff1a; 其中 p1<p2<,…, <ps 是 s 个相异的素数&#xff0c; 指数ki都是正整数。 此定理又称作唯一析因定理&#xff08;unique f…

【离散数学中的数据结构与算法】二 欧几里得算法与裴蜀等式

欧几里得算法是计算两个数最大公因子算法。又称辗转相除法。本文将学习为什么辗转相除法可以求得两个数的最大公因子。同时也可以根据最大公因子计算两个数的最小公倍数。 文章目录1 欧几里得算法的理论基础1.1 欧几里得算法&#xff08;辗转相除法&#xff09;2 裴蜀等式&…