学C语言,一定绕不过指针这一大难关,而指针最让人头疼的就是各种指向关系,一阶的指针还比较容易掌握,但一旦阶数一高,就很容易理不清楚其中的指向关系,现在我将通过杨辉三角为例,我会用四种方法从内存的角度简单分析动态二维数组,若有不足或错误之处,还请指出!

在讲这之前,以一维数组为例,先重新认识一下数组:

int array[5] = {1, 2, 3, 4, 5};

首先数组名称是该数组的首地址常量,即数组名称就是指针,就有&array[0] == array!

那么我们可以推出*array == array[0] == 1;

这里引入一个概念“指类”(这个概念没有在正规场合出现过,只是我为了方便分析而引入的),其表示指针所指向的空间的类型!array是一个int*类型的指针,那么它的指类就是int类型(比较容易的记忆,就是指针的类型去掉一个*就是其指类!);

其实array[0]是一个表象,其本质应该是*array ;

我们的array是局部变量,在系统堆栈中申请了sizeof(int)*5,即20字节大小的空间,用于存放5个整型数!

因为array的值是该数组首元素的地址(即首地址),那么array+1的意思就是给该数组的首地址这个值增加了一个int类型空间字节数,也就是4字节,从而array+1的值就应该是该数组的下一个int类型元素的地址,即&a[1],所以就有array+1 == &a[1];

那么array加几加几,加的实际就是多少个指类空间大小!

那么*array就可以理解成*(array + 0),同理,array[1] ==*(array + 1),array[2] == *(array + 2)......

上式还可以由加法交换律变形得到array[1] == *(1 + array),那么array[1]透过这一本质来看,其也可以变形成1[array];

*这样写编译器不但不会报错,而且连警告都不会有,但不建议这样书写!

*如果对我上文提到的系统堆栈不了解的话,强烈建议看一看下面的这个博客,后文全部涉及到内存!

*后文我不会用array[index]这种方式,而是用*(array + index)这种最本质的方式

C语言中关于形参与实参关系

这里我们先讲一讲系统堆栈和系统堆:

操作系统将内存分成:系统数据,系统功能调用(核心代码)区域;用户代码和数据区域;系统堆栈区;系统堆区;

系统堆栈是由编译器自动分配,用于存放函数的参数值,局部变量的值等;

而系统堆区是由程序员通过malloc/calloc函数自主申请的空间,系统堆的空间要远远大于系统堆栈的空间,但切记,一定要在使用完毕后,通过free函数释放掉所申请的空间。

*C语言(包括C++)不像Java那样有gc(垃圾回收)机制,gc机制大大减少了程序员的工作量,程序员在Java中通过new申请空间时,只需负责申请,gc会帮助善后(实则是Java的JVM),而C/C++需要程序员自主释放,所以java相比C++要容易掌握!

----------------------------------------------------------------------------------------------------------------------------------

杨辉三角:

关于杨辉三角如何计算得到的问题,我就不累赘了(*^_^*)

方法一:

如图,我们给出了一个六层的杨辉三角,通常的话,我们会给一个静态的二维数组用来存放这个杨辉三角:

int yangHui[row][row];

但是这会生成一个row行×row列的空间,而我们实际用到的的空间要比这小,除了最后一层,其他每一层都会有浪费的空间,为了避免这样的情况,我们就应该想到动态的数组,根据当前行数,通过malloc/calloc动态申请每一层的空间。所以,我们可以用如下的方式表示这个二维数组:

int *yangHui[row];

这种定义看起来很奇怪,其实它完全就是一个一维数组,这个一维数组的大小是row,只不过这个一维数组的每一个元素是由int *类型所组成,其本质就是一个一维的指针数组!我们可以把它定义成以下这种形式:

typedef int* type;
type yangHui[row];

这样看的话就比较好理解了,他就是一个类型为type类型的一维数组!而type就是int*,那么,这个一维数组存放的每一个元素就应该是一个int*类型的的值,那么这个值完全就可以是一个int类型的一维数组的首地址!即yangHui数组里面存放的是row个一维数组的首地址!

铺垫工作完成,下来我们就来生成杨辉三角:

*由于杨辉三角往后的数字越来越大,故以下代码都用long类型!先假定要生成的杨辉三角的层数num = 5;

void creatYangHuiOne(int num);

void creatYangHuiOne(int num) {
long *yangHui[num];
int row;
int col; for (row = 0; row < num; row++) {
*(yangHui + row) = (long *) calloc(sizeof(long), row + 1);
for (col = 0; col <= row; col++) {
*(*(yangHui + row) + col) = (row == col || col == 0) ? 1 : *(*(yangHui + row - 1) + col - 1) + *(*(yangHui + row - 1) + col);
}
}
// 关于showYangHuiTriangle函数我会在最后给出,只是为了打印好看,不做重点!
showYangHuiTriangle(yangHui, num);
for (row = 0; row < num; row++) {
free(*(yangHui + row));
}
}

多次循环得到下列关系

方法二:

void creatYangHuiTwo(int num);
void destoryYangHui(long **yangHui, int num); void creatYangHuiTwo(int num) {
long **yangHui = NULL;
int row;
int col; yangHui = (long **) calloc(sizeof(long *), num); for (row = 0; row < num; row++) {
*(yangHui + row) = (long *) calloc(sizeof(long), row + 1);
for (col = 0; col <= row; col++) {
*(*(yangHui + row) + col) = (row == col || col == 0) ? 1 : *(*(yangHui + row - 1) + col - 1) + *(*(yangHui + row - 1) + col);
}
}
showYangHuiTriangle(yangHui, num);
destoryYangHui(yangHui, num);
} void destoryYangHui(long **yangHui, int num) {
int row; for (row = 0; row < num; row++) {
free(*(yangHui + row));
}
free(yangHui);
}

方法三:

long **creatYangHuiThree(int num);

long **creatYangHuiThree(int num) {
long **yangHui = NULL;
int row;
int col; yangHui = (long **) calloc(sizeof(long *), num); for (row = 0; row < num; row++) {
*(yangHui + row) = (long *) calloc(sizeof(long), row + 1);
for (col = 0; col <= row; col++) {
*(*(yangHui + row) + col) = (row == col || col == 0) ? 1 : *(*(yangHui + row - 1) + col - 1) + *(*(yangHui + row - 1) + col);
}
} return yangHui;
}

与方法二基本一样,只不过返回值是long **类型,将creatYnaghHuiThree函数中yangHui的值即在系统堆中申请的空间的首地址addressRow作为返回值返回!该空间不会随着子函数的调用结束而消失,需要在主函数中释放!

方法四:

void creatYangHuiFour(long ***yangHui, int num);

void creatYangHuiFour(long ***yangHui, int num) {
int row;
int col; *yangHui = (long **) calloc(sizeof(long *), num); for (row = 0; row < num; row++) {
*((*yangHui) + row) = (long *) calloc(sizeof(long), row + 1);
for (col = 0; col <= row; col++) {
*(*((*yangHui) + row) + col) = (row == col || col == 0) ? 1 : *(*((*yangHui) + row - 1) + col - 1) + *(*((*yangHui) + row -1 ) + col);
}
}
}

当creatYangHuiFour函数调用结束,栈底栈顶指针回落,系统堆栈申请的子函数的局部变量都奔释放,但是主函数的yangHui空间的值通过指针运算已经由NULL变为ddressRow,而这个空间是在系统堆中申请的,不会随着子函数的调用结束而消失,即该空间还未被释放,故需要在主函数中释放!

打印函数及主函数:

void showYangHuiTriangle(long **yangHui, int num);
int getMaxNumberLength(long num); int getMaxNumberLength(long num) {
int count = 1; while (num/=10) {
++count;
} return count;
} void showYangHuiTriangle(long **yangHui, int num) {
int len = getMaxNumberLength(*(*(yangHui + num -1) + num/2));
int i;
int j;
int row;
int col; for (row = 0; row < num; row++) {
for (i = 0; i < num - row - 1; i++) {
for (j = 0; j < len; j++) {
printf(" ");
}
}
for (col = 0; col < row + 1; col++) {
printf("%ld", *(*(yangHui + row) + col));
if (getMaxNumberLength(*(*(yangHui + row) + col)) < len) {
for (j = 0; j < len - getMaxNumberLength(*(*(yangHui + row) + col)); j++) {
printf(" ");
}
}
for (j = 0; j < len; j++) {
printf(" ");
}
}
printf("\n");
}
} int main() {
long **yangHui = NULL;
int num; printf("请输入行数:\n");
scanf("%d", &num);
creatYangHuiOne(num);
creatYangHuiTwo(num);
//yangHui = creatYangHuiThree(num);
creatYangHuiFour(&yangHui, num);
showYangHuiTriangle(yangHui, num);
destoryYangHui(yangHui, num); return 0;
}

输出如图:

感谢您的阅读(*^_^*)

我在CSDN放了一份以杨辉三角为例,从内存角度简单分析C语言中的动态二维数组

以杨辉三角为例,从内存角度简单分析C语言中的动态二维数组的更多相关文章

  1. C/C++动态二维数组的内存分配和释放

    C语言: 1 //二维数组动态数组分配和释放 //数组指针的内存分配和释放 //方法一 char (*a)[N];//指向数组的指针 a = (char (*)[N])malloc(sizeof(ch ...

  2. java基础:进制详细介绍,进制快速转换,二维数组详解,循环嵌套应用,杨辉三角实现正倒直角正倒等腰三角,附练习案列

    1.Debug模式 1.1 什么是Debug模式 是供程序员使用的程序调试工具,它可以用于查看程序的执行流程,也可以用于追踪程序执行过程来调试程序. 1.2 Debug介绍与操作流程 如何加断点 选择 ...

  3. 使用for循环输出杨辉三角-还是不懂得需要复习

    package com.chongrui.test; /* *使用for循环输出杨辉三角杨辉三角形由数字排列,可以把它看作一个数字表,其基本特征是两侧的数值均为1,其他位置的数值是其正上方的数值与左上 ...

  4. 利用python打印杨辉三角

    用python打印杨辉三角 介绍 杨辉三角,是初高中时候的一个数列,其核心思想就是说生成一个数列,该数列中的每一个元素,都是之前一个数列中,同样位置的元素和前一个元素的和. 正好在python中,也就 ...

  5. ACM程序设计选修课——1031: Hungar的得分问题(二)(杨辉三角+二进制转换)

    1031: Hungar的得分问题(二) 时间限制: 1 Sec  内存限制: 64 MB 提交: 15  解决: 10 [提交][状态][讨论版] 题目描述 距离正式选秀时间越来越近了,今天Hung ...

  6. python 杨辉三角

    前提:端点的数为1. 每个数等于它上方两数之和. 每行数字左右对称,由1开始逐渐变大. 第n行的数字有n项. 第n行数字和为2n-1. 第n行的m个数可表示为 C(n-1,m-1),即为从n-1个不同 ...

  7. 趣味C程序100.9 绘制杨辉三角

    说明:1.本问题来源于<C语言经典.趣味.实用程序设计编程百例精解>,所有程序为本人自己编写.与原程序不同之处作有标记. 2.本系列所有程序均使用codeblocks编译,操作系统为Win ...

  8. 廖雪峰老师博客学习《通过生成器generator生成列表式杨辉三角》

    说明:这是我接触生成器概念后,自己对它的理解,可能比较表面,没深入理解,也可能有错误.后续校正错误认知,将有关generator作为一个tag了! 希望以后能活用. 先贴出自己写的triangles( ...

  9. python实现杨辉三角

    刚刚学python,原来用c++,Java很轻松实现的杨辉三角,现在用python实现,代码是少了,理解起来却不容易啊. 这里主要用到的Python的生成器. 我们都知道Python有列表解析功能,根 ...

随机推荐

  1. VDD,VCC,VSS,VEE,VDDA,VSSA,

    VDD是主供电电源,也是IO口输出电平的输入电源.VDDA(A表示模拟)是模拟电源,当使用到模拟信号的时候,比如AD(模数)或者DA(数模)的时候,系统会使用VDDA的电压作为参考电压来.不要求精准使 ...

  2. 设计模式之观察者模式(c++)

    Observer 模式应该可以说是应用最多.影响最广的模式之一,因为 Observer 的一个实例 Model/View/Control( MVC) 结构在系统开发架构设计中有着很重要的地位和意义, ...

  3. Android-Java-抽象类

    定义抽象类,就一定会定义抽象方法,抽象方法没有方法体{},就证明抽象方法 是不运行的,抽象方法 是给子类继承覆盖运行的, 子类继承->抽象类 就必须覆盖抽象方法,否则编译都失败: 水果案例: 定 ...

  4. 给JavaScript24条最佳实践

    作为“30 HTML和CSS最佳实践”的后续,这篇文章将回顾JavaScript的知识 !如果你看完了下面的内容,请务必让我们知道你掌握的小技巧! 1.使用 === 代替 == JavaScript ...

  5. Visual Studio Code 写Python 代码

    最近在博客园新闻里面看到微软发布的Visual Studio Code 挺好用的,现在在学习Python,查看官网发布的VSCode 是支持Python代码,自己试着安装用一下,下面是我的安装以及配置 ...

  6. linux(centos7)安装docker

    1.检查内核版本,必须是3.10及以上 uname ‐r 2.安装docker yum install docker 3.输入y确认安装 4.启动docker [root@localhost ~]# ...

  7. kali渗透windowsXP过程

    文章来源i春秋 这只是一个演示我自己搭建的环境,但是成功率非常高的,对方可以是其系统,首先我开启kali在打开kali终端输入nmap –sP 192.168.1.1/24 这里的ip是我的网关地址你 ...

  8. Android OOM 引发的思考

    一.为何会出现OOM 因为Android系统的硬件资源是相当有限的,而且分配给一个应用的资源更为有限,尤其是内存.当应用突然申请的内存大于允许的最大值的时候,就会出现OOM. 如果想要获取App的内存 ...

  9. GMM基础

    一.单成分单变量高斯模型 二.单成分多变量高斯模型 若协方差矩阵为对角矩阵且对角线上值相等,两变量高斯分布的等值线为圆形. 若协方差矩阵为对角矩阵且对角线上值不等,两变量高斯分布的等值线为椭圆形, 长 ...

  10. .NET手记-HttpClient解析GB2312乱码问题

    最近为App的服务器端卸了个爬虫程序,输出结果时发现出现乱码现象,尝试使用了几个方案发现效果并不太好,最后发现了一个很简单的用法. var result = await client.GetByteA ...