从零开始学编程---第一步-C语言(二十五)
我们现在用二维数组打印一次杨辉三角,之前用一维数组做过了,详看133楼
现在用二维数组打印杨辉三角,应该容易理解的多
我们还是打印这样的吧
首先打印这个
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
这个其实就是个表格数据,有行有列,每一行等于它的列数,就是说第n行就有n个数据
定义一个二维数组 int a[5][5]
你可能会奇怪,竟然是每行5个数据,为什么前4行不是5个数据也这样定义
我们不一定要对数组所有元素全部赋值,我们只要在打印的时候打印出赋了值的元素就可以了,其他没赋值的元素就自动为0了
现在怎么把这个三角数据存储到数组里??
首先看,每行的第一个元素和最后一个元素都是1
那我们就先给每行的第一个元素和最后一个元素赋值1吧
for(i=0;i<5;i++)
{
a[i][0]=1;
a[i][i]=1;
}
然后是中间的数,这个三角有个规律,中间的某个数等于它左上角的数和右上角的数相加的结果
比如这里
第3行第3个数是3,就等于第2行第2个数和第3个数相加
所以 a[3][2]=a[2][1]+a[2][2]
则 a[i][j]=a[i-1][j-1]+a[i-1][j]
那么求中间数的循环为
for(i=2;i<5;i++) 表示从第3行开始 ,因为1,2行没中间数
for(j=1;j<i;j++) 表示从第2个数开始,因为第一个数都是1,并且元素个数不能大于行数
a[i][j]=a[i-1][j-1]+a[i-1][j];
好了,全部存储到位,该打印了,怎么打印呢?
很简单,每行就打印行数的元素
比如说第1行,就打印第一个元素
第2行,就打印第1,2个元素
。。。
就是这样
for(i=0;i<5;i++)
{
for(j=0;j<=i;j++)
printf("%d ",a[i][j]);
printf("\n");
}
整个程序如下:
#include <stdio.h>
main()
{
int a[5][5],i,j;
for(i=0;i<5;i++) //存储两边的1
{
a[i][0]=1;
a[i][i]=1;
}
for(i=2;i<5;i++) //存储中间的数子
for(j=1;j<i;j++)
a[i][j]=a[i-1][j-1]+a[i-1][j];
for(i=0;i<5;i++) //打印
{
for(j=0;j<=i;j++)
printf("%d ",a[i][j]);
printf("\n");
}
}
下面我们输入n,就打印n行,并且成三角形打印
程序如下:
#include <stdio.h>
main()
{
int a[20][20]={0},i,j,k,n;
scanf("%d",&n);
for(i=0;i<n;i++)
a[i][0]=1; //第一列全部赋值为1
for(i=1;i<n;i++)
for(j=1;j<=i;j++)
a[i][j]=a[i-1][j-1]+a[i-1][j];
for(i=0;i<n;i++) //打印
{
for(k=n-1;k>i;k--)
printf(" ");
for(j=0;j<=i;j++)
printf("%d ",a[i][j]);
printf("\n");
}
}
这个和楼上的程序有点不一样
for(i=0;i<n;i++)
a[i][0]=1;
只给了第一列全部赋值为1,那么最后一列的1到哪去了
看这里
for(i=1;i<n;i++) 这里就是从第2行开始了,不再是第3行
for(j=1;j<=i;j++)
a[i][j]=a[i-1][j-1]+a[i-1][j];
每一行的最后一个1,其实就是上一行的最后一个1加上0
不懂就看这里
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
比如说第3行的最后一个1,就是第2行的最后一个1加上后面的那个数,后面那个数也就是 a[1][2],但这上面是不显示的,但在数组里实际存在,a[1][2]等于多少呢,不赋值的话就是为0
需要特别强调的是,如果单纯定义int a[20][20]这样一个二维数组,你不初始化赋值,它里面的所有元素都不是为0,而是未知的
只有int a[20][20]={0},这样不管你赋值多少元素,其他未赋值元素才自动为0
int a[20][20]={0},这里给第一行第一个元素赋值0,赋值之后所有元素都为0,当然,你可以随便赋值一个数字,但是这里只能赋值一个元素,如果赋值两个元素的话,会产生结果错误,因为第2行的最后一个数字1是第一行第一个数加上后面那个数的结果,所以后面那个数必须为0
还有一方法可以不初始化赋值让这个数组所有元素自动为0,就是设为静态数组,以后会介绍
static int a[20][20];
呃,这个程序只能打印20行之内的数据
还有一种方法,稍作了改动
#include <stdio.h>
main()
{
int a[20][20]={1},i,j,k,n;
scanf("%d",&n);
for(i=1;i<n;i++)
{
a[i][0]=1; //第一列全部赋值为1
for(j=1;j<=i;j++)
a[i][j]=a[i-1][j-1]+a[i-1][j];
}
for(i=0;i<n;i++) //打印
{
for(k=n-1;k>i;k--)
printf(" ");
for(j=0;j<=i;j++)
printf("%d ",a[i][j]);
printf("\n");
}
}
看
for(i=1;i<n;i++)
{
a[i][0]=1; //第一列全部赋值为1
for(j=1;j<=i;j++)
a[i][j]=a[i-1][j-1]+a[i-1][j];
}
这里是从第2行开始,第一个元素赋值1,然后接着赋值后面的数
那么第一行去哪了?
在这里
int a[20][20]={1}
给第一行第一个元素赋值1,而这里就只能赋值1,不能赋值别的数了,不然第一行就会显示你赋值的那个数
从3个程序你应该看出一个比一个简练,说明问题不只一种解决方法,我们要学会举一反三,选择更快代码更少且更有效率的方法
想必大家对二维数组稍微熟悉了,什么三维数组的其实也没必要去了解,多维数组可以由二维数组推导而成,等你完全理解深入数组的概念,你会明白的
一般而说,掌握二维数组就足够了,三维以上数组你可能根本用不到的
好了,下面开始深入二维数组的内容,即开始理解二维数组的指针,会有点难,一定要逐个理解
首先,要明白如果你定义一个二维数组int a[2][2]
然后定义一个指针来指向 int *p=a 这是绝对错误的!
先让我们来回顾一维数组的指针
int a[2];
int *p=a;
指针p指向数组a 数组名a就是地址,而且是a[0]的地址,a+1就是指向a[1]
指针变量p存储a自身的地址,p+i 就是指向a[i]
其中数组名a要好好说下
计算机内存有两个概念:空间(存储单元)和地址,每个地址对应一个空间,空间就相当于一个盒子,我们把数据存储到这个盒子里面,每个盒子对应一个地址,我们可以根据地址来找到盒子来存储,但是要记住地址太麻烦了,于是我们就可以给空间取一个名字(这就是变量名或常量名),我们就可以不需要知道空间的地址只根据空间的名字来存储数据。
现在说说常量和字面量
常量就是不可改变的量,就是说这个盒子里的内容怎么样都不会被改变
怎么定义? 用 const 这个关键字 如const int a=1 a就会一直为1,不可更改,如果你a=2会出错
字面量也就是常量,就是有空间,有地址,但没常量名
比如说int a=1 ,1就是字面量
还有printf("hello world"); 中的hello world 也是字面量
这样是能够运行的
#include <stdio.h>
main()
{
"hello world";
}
不过这样没什么意义
现在回到数组名a,之前说数组名就是常量指针,指针就是地址,这2句并不是完全正确
说指针就是地址,其实就是说的指针变量它存储了地址
数组名a就是地址,不是常量名,也不是常量指针(只能说相当于吧),这个a它既没有地址也没有存储空间,它就是地址本身,相当于地址的别名
比如a[0]的地址是1000,那么这个1000就是a,就是给这个地址取了个名字,a即1000,1000即a
千万不要理解成a存储了1000,a指向&a[0],是错的,a是没地址和空间的
那么a+1,a+2这些就相当于a[1],a[2]地址的别名
给地址取别名,只有数组才有这样的特权
在一组连续的数据中,要有一个名字,说明某几个成员是一个组合
于是就把第一个成员的地址做为这个组合的入口,然后给这个地址取个名字作为组合的名字
所以 int a[5]; int a
你应该把第一个a看作一个整体,a就是一个整型数组,而不是代表&a[0]
就像第2个a 一样,代表一个盒子,而第一个a只是代表5个盒子而已,而a[0]就是代表第一个盒子
另外,数组也是有地址的(实际上没有,只是象征性),就像变量a有自己的地址一样 &a
那么数组a肯定也有自己的地址,一个整体的地址
int a[5],
&a就是取这个数组的地址,你猜的到,这个地址结果和a的结果一样,都是1000
本来就是地址,再取地址不矛盾吗,不矛盾,因为这里你要把a看作是数组名,而不是地址1000,总之,很晕很晕吧
a代表的是一个数组,在这里你不要管它就是地址1000,就当它是个名称吧
&a代表这个数组的一个整体的地址,虽然结果1000,但概念不一样
现在程序分析
#include <stdio.h>
main()
{
char a[5]={1,2,3,4,5};
printf("%d\n",a);
printf("%d\n",&a);
printf("%d\n",a+1);
printf("%d\n",&a+1);
}
这里用char是因为一个字符占一个字节也就是一个地址,方便理解
假设a是1000
最后输出
1000
1000
1001
1005
a &a都是1000
a+1 是1001这个没错
但&a为什么是1005
因为&a是数组a的地址,这个数组总共占5个字节,所以这个数组的地址是5个字节的空间,加上1就超出了数组空间,所以是1005
&a并没有确切的地址,地址1000只是象征性,概念不一样,实际上数组的地址也只是象征性的存在,并非实实在在有,唉,好像越说越糊涂了
总之,
int a[5]
a是这个数组的名称 做为一个数组的标志,它的1000是作为数组的入口
&a是这个数组的地址 它的1000是作为数组地址的存在(象征性的地址)
而a[0]的地址1000,那就是实实在在a[0]的地址
虽然a &a &a[0]都是1000,但是意义都不一样,一定要注意
说了怎么多,这个不去往深处研究是很难弄清楚的
不一定所有人都能彻底明白,当然也包括本人
实在感觉晕,也不需要深究了,这里了解数组名就是地址本身,和数组可以取地址也差不多了,有些东西通过实践积累会慢慢明白!
下面开始介绍二维数组的指针
先定义一个二维数组
int a[2][3];
这是一个2行3列的二维数组
它的所有元素是
a[0][0],a[0][1],a[0][2]
a[1][0],a[1][1],a[1][2]
总共6个元素
可以这样理解
a是一个数组名,a数组包含2个元素,a[0],a[1]
而每个元素又是一个一维数组,它包含3个元素(列元素)
从二维数组的角度来看,a代表整个二维数组的首地址,即第1行的首地址,第一行的第一个元素,a指向的是a[0][0]的地址,也可以说a就是a[0][0]的地址
那么a+1 就是第2行的首地址,即a[1][0]的地址
a[0],a[1]是一维数组名,且数组名代表数组的首地址
所以 a[0]就是&a[0][0],a[1]就是&a[1][0]
注意,a[0],a[1]就只是地址,并不占用内存单元,因为它们是数组名
a[0][1]的地址怎么表示呢
这其实就是第一行第2个元素的地址,按一维数组来看,就是数组名+1
所以a[0][1]地址就是a[0]+1
那么二维数组里的a+1代表什么?
数组a有2个元素,a[0],a[1] ,而这两个元素中又包含3个元素
二维数组应该怎么看?
首先应该把行元素看成一个一维数组,然后再把每个行元素又看成一个一维数组
int a[2][3]
先不要看[3],那么它是个一维数组
然后看元素
a[0] 中有3个元素
a[1] 中有3个元素
所以a+2 就是第2行的首地址,即a[1]对应的地址
其实二维数组就是一个“总”一维数组和若干个“分”一维数组组成
现在再来探索
int a[2][3]
*a代表什么?如果是一维数组,*a表示的首元素地址的值,也就是a[0]的值
二维数组里,*a当然也表示首元素地址的值,首元素是a[0],而a[0]这里也是个数组名,是a[0][0]的地址,这里取a[0]的值,由于a[0]没有空间,也就不存在什么值,取a[0]的值实际就是取地址的值,地址有值吗?取地址的值得到的也只能是地址
所以 *a 得到的就是a[0]的值(只能怎么说),a[0]是a[0][0]的地址,最后得到的值就是a[0][0]的地址
实际上,a就是a[0][0]的地址,因为a[0]是a[0][0]的地址,a又是a[0]的地址
所以,a和*a结果一样
a和a[0]都是a[0][0]的地址,相当于a[0][0]有两个别名
需要注意的是,二维数组的“总”一维数组和普通数组不是一样的
int a[2] 这是普通的一维数组
a就是a[0]的地址 a[0],a[1]都有地址和空间(值)
int a[2][3] 这是2维数组
分解一个“总”一维数组
a={a[0],a[1]}
这个一维数组的所有元素是没有值的(空间),它们是数组名,就是一个个地址,它们没有自己的地址和空间,它们只是个名称
所以不能完全说a是a[0]的地址,你只能这么看待
而且,a又等于a[0],因为a[0]没有值(空间)嘛,在普通一维数组里,绝对没有什么a等于a[0]的
总的来说,二维数组里
a等于a[0]的地址,又等于a[0]的值,a[0]地址和值则都是&a[0][0](注意,只是这样看待,a[0]实际没值也没地址)
所以,a和*a结果相同,意义却不同
如果要表示a[0][0]的值怎么做
可以直接取值它的数组名,*a[0]
还可以取值两次二维数组名,得到a[0][0]的值
*a 这是取值第一次 取a[0]的值 而a[0]就是首元素a[0][0]的地址
所以*a就相当于a[0] *a==a[0]
*(*a) 由于*a的结果是地址,而不是值,所以还可以用*取值
这样就等于是取值a[0][0]的地址,获得a[0][0]的值
*(*a)可以写为**a(这就是二级指针,指针的指针)
表示a[0][1]的值则是*(a[0]+1)
那么用二维数组名a如何表示?
*a就是a[0],所以*a+1 就是a[0][1]的地址
用*号取地址的值 *(*a+1) 就是a[0][1]的值
注意,不能写成**(a+1)
因为a+1表示的是第2行第一个元素,也就是a[1]的地址
*(a+1)取a[1]的值,结果还是地址,这里*(a+1)就等于a[1]了
所以**(a+1)是取a[1]的值,也就是取a[1][0]的值
取a[1][1]的值,则是*(*(a+1)+1),注意不要写成*(*(a+2))。
总之,二维数组取值和首元素等价
这个和一维数组是一样的,*a就是等价于a[0]
二维数组 第n行 就是 a+n (0就是第一行)
a+n就是第n行的数组名地址
*(a+n)就等价于数组名
如*(a+3)就等价于a[3]
*(*(a+3)+2)就是取a[3][2]的值
*(a[i]+j)或*(*(a+i)+j)就是a[i][j]的值
*(a+i)和a[i]是等价的
二维数组里,a,*a,a+i,a[i],*(a+i),*(a+i)+j,a[i]+j都是地址
其中*(a+i)和a[i]是等价的,表示分支一维数组的数组名
*(a+i)+j和a[i]+j是等价的,表示a[i][j]的值
a表示a[0]的地址,两者都是a[0][0]的地址
*a表示a[0]的值,这里*a等价于a[0],*a同样表示a[0][0]的地址
a和*a的结果相同,但意义不相同,前者表示a[0]的地址,后者表示a[0]的值
a[0]的地址和值都是a[0][0]的地址(实际上a[0]没有地址也没值)
a+i和*(a+i)的原理和a和*a一样
a+i表示a[i]的地址,而*(a+1)等价于a[i]
这样看你就懂了
a=&a[0] *a=a[0]
a+i=&a[i] *(a+i)=a[i]
&a[i]和a[i]结果一样
其实和一维数组名一样的道理
&a和a的结果一样
但&a表示数组的地址 a表示数组名 间接表示数组的入口地址,即a[0]的地址
这样说明了数组的首地址&a[0]既是数组的入口地址 也是整个数组的地址
但你还是要清楚一点,数组实际上是没地址的
说了这么多,大家也许很糊涂,有些东西表示这样,为什么实际上没有
这些需要更深了解数组的原理,目前表示这样你就看成这样就可以了,但是心里还是要有个底,要清楚它的真正面目
这些也许很麻烦,但一定要记下来
二维数组里
a+i=&a[i]=a[i]=*(a+1)=&a[i][0]
它们都是i行的首地址
二维数组如果行下标为1或者列下标为1,那么它就类似于一个一维数组
如a[2][1],2行1列,a[1][2],1行2列
a[2][1]只有两个元素,a[0][0],a[1][0]
而用*a[0],*a[1]可以直接表示
a[1][2]也是2个元素,a[0][0],a[0][1]
要用*a[0],*(a[0]+1)表示
a[2][1]相当于只有一个行数组,a[1][2]相当于只有一个列数组(注意,行列数组还是有的,只不过只有一个元素而已)
不过a[2][1],a[1][2]都不等于a[2],这点要记住
接下来的学习你会发现,二维数组的“总”一维数组(行数组)其实就类似于指针数组
现在说明一下,行数组只有一个,行数组的元素有几个,列数组就有几个(列数组即分支一维数组)
现在正式开始二维数组指针的学习
如何定义一个指针指向二维数组?
首先我们先看下面
#include <stdio.h>
main()
{
int a[2][3]={{1,2,3},{4,5,6}};
int *p=a;
}
定义一个指针p指向二维数组a,但int *p=a;是错误的
a只是行数组的数组名,也就是说a只包括a[0],a[1]
但是后面的[3],就是列数组a[0],a[1]的内容,这是指针p指向不到的
另外,a指向&a[0],a[0]并不是变量,它也是个指针,它指向&a[0][0]
所以a类似于指针的指针(只能这样理解,另外数组实际上不完全是指针)
而指针变量p只能存储一个整型变量的地址,而非一个地址的地址
怎么定义一个指针来指向二维数组呢?
显然不能用指针变量,得用数组指针
数组指针,就是指向数组的指针,在228楼介绍过一次,指向一维数组可能让你难以明白,但指向二维数组可能会明白很多
指向数组,就是指向数组的地址,数组的地址就是&数组名
int (*p)[3] 这就是定义了一个数组指针
数组指针是专门指向数组的地址的指针,这跟普通指针指向数组首地址是不同的,虽然它们指向的都是一样的地址
int a[3] 这是个一维数组
用数组指针指向这个数组就是 int (*p)[3]=&a
意思就是一个数组指针p指向一个长度为3的数组
int a[2][3] 这是一个二维数组
用数组指针指向这个数组就是 int (*p)[3]=a
为什么这里不需要&a?
其实二维数组a就是一个数组的地址,a=&a[0] a[0]这个数组的地址
a的行数组就是数组的数组,行数组里全部都是数组
其实也可以说a就相当于一个数组指针,它指向的就是一个数组的地址(这里你要明白,数组名并不是完全的指针,说是指向地址,其实就是地址本身)
而它指向的这个数组,这个数组并不是单独一个,它是一个数组**中的一个,而且是第一个,这个数组**你先要理解为它一个个都是一个数组的整体,a指向a[0]就是指向一个数组
而a[0]这个数组的地址同时也是这个数组**的入口地址,a指向它就成了数组名(只是这样理解,你可以认为一个指针指向数组的首地址都能作为数组名使用,仅仅是使用而已,不能代替数组名,这里的a是实实在在的数组名,因为a只是个地址)
a是一个数组的数组的数组名(就是一个数组里的元素都是数组)
a指向a[0]这个数组,由于a[0]也是数组里的一部分
所以a+1 就可以指向下一个数组 a[1] a+1=&a[1]
int (*p)[3]=a 就是数组指针p指向a,而a就是数组a[0]的地址
也就是说数组指针指向了a[0]这个数组,但由于a[0]是数组的一部分,而且是首地址
p指向这个数组的第一个元素数组的地址,就有了数组名a的功能
所以可以p+1,p-1,还可以做数组名不能做的 p++ p--
这跟普通指针变量指向一个有数组的第一个元素变量一个道理,指向了那个变量即可获取这个数组**的所有变量元素,这里指向了那个数组,也即可获取这个数组**的所有数组元素
可以说,p具备了a的功能,而且还多了a没有的自增自减功能
int (*p)[3]=a 就表示int (*p)[3] 和a[2][3]一样了
注意[3]是表示指向的那个数组的长度,不能省略的,小括号是为了区分指针数组,没了小括号就是指针数组了
一个指针指向一个数组中的任何一个元素,都能访问数组的所有元素,只需要指针前后移动就可(加和减)
因为数组是一段连续的地址,指针本身具有移动的功能,它指向一个地址,只要加1或者减1,就能指向这个地址前面的地址或者后面的地址,指针指向数组其中一个元素,加1或者减1就可以指向这个元素前面一个或后面一个元素的地址,如果加多了或者减多了,则会数组越界,指向数组之外的地址,指针不只是指向数组中的元素才可以加减,指向一个变量的地址也可以加减,只不过指向的地址是未知类型的
看程序
#include <stdio.h>
main()
{
int a[5]={1,2,3,4,5};
int *p=&a[2];
printf("%d",*(p+1));
}
最后打印4
数组名为什么不用数组其他元素的地址,而用首元素的地址呢
把首地址作为数组入口,就可以按顺序从第一个元素到最后一个元素
而不把中间一个元素地址或最后一个元素地址作为数组入口,自己想想也知道了
指针可以不经过数组入口,指向任何一元素就可以访问其他元素,这跟指向一个变量其实是一样的,数组元素就是个变量,只不过刚好它周围的地址都是同类型的变量
数组其实并不存在什么入口,说这样只是为了方便理解,但要知道要访问一个数组就要得到它的首地址,而不是什么数组中的某个元素的地址,把首地址做为一个大门和名称,也并不只有数组才有,例如函数也是这样的
现在再回到二维数组指针,用一个指针变量也可以指向a[2][3]里的数组名
例如:
#include <stdio.h>
main()
{
int a[2][3]={{1,2,3},{4,5,6}};
int *p=a[0];
printf("%d",*(p+1));
}
这是p指向a[0]这个数组名 p+1则是指向a[0]这个数组第2个元素的地址
当然,p也可指向这个数组的任何一个元素的地址
最新内容
热点内容
- QQ群
-
微信
- 返回首页
- 返回顶部