二维数组本质以及列指针与行指针以及数组和指针数组注意事项

下面是关于二维数组,行指针列指针,数组注意事项,指针数组的介绍

二维数组的二维是逻辑上的二维,实际上不论是几维在实际存储上都是一维存储(按行存储),例如int a[2][2],那么该二维数组在内存中的排列为
a[0][0] (低地址)
a[0][1]
a[1][0]
a[1][1](高地址)
可以看出,在内存中的排列方式表现类似于一条线。

那么如何在物理内存上的一维实现逻辑上的二维呢,那便是让二维数组的行与列分别有不同的地址,如a[2][2],a为第0行的地址(a[0]的地址),a[0]则表示第0行第0列的地址(a[0][0]的地址),这些地址不同但都指向了这一串数据,再加上让他们的步长不同,即在该数组中:在行地址下,一次移动8个字节(假设int型为4个字节),而在列地址下一次移动4个字节。这就实现了逻辑上的二维。比方说这个数存储了学生的姓名与他们的各科成绩,一次8个字节便是姓名之间跨越,一次4个字节便是科目成绩之间的移动。
语文a[0][0] a[0]为小红
数学a[0][1]
语文a[1][0] a[1]为小明
数学a[1][1]

同理,若实现一个三维数组,只需再赋加第三维的地址并配上相应的更大跨度的步长(每次移动的字节数)。

以下为实例:
a表示第0行的地址,
a+1(一次移动8个字节)所以a+1表示第一行的地址。
数组的地址为该数组第一个元素的地址,数组中某一行的地址为该行第一个元素的地址。
* (a+1)为第一行第一列的的地址。
到这里问题就来了,按以上三句的意思,a+i和 * (a+i)的地址居然是一样的。为什么会这样呢,其实他们的值确实是一样的,但类型不同,
a+i为二维指针,*(a+i)为一维指针,他们的本质区别还在于步长(每次移动的字节数), 在a[2][2]中二维指针每次移动8字节,一维指针每次移动4字节。

继续以上的实例,* (a+1)+1表示第一行第一列的地址,* (* (a+1)+1)表示第一行第一列的值,即a[1][1]。
再举两个例子:int a[4][4],那么在该数组中二维指针一次移动16个字节,一维指针一次移动4个字节。
char b[3][5],那么该数组二维指针一次移动5个字节,一维指针一次移动1个字节。

下面介绍关于列指针与行指针的区别:
列指针:int a[4][3]; 初始化 int* p;,p=a[0];或p=* a;或p=&a[0][0];。
* a=* (a+0)+0
即p以列为移动单位,一次移动4个字节,p表示为第0行0列的地址,p+2表示第0行第2列的地址,p+4表示第1行第1列的地址。
所以使用时,首先确定偏移量,及数据为第几行第几列,若在第i行,第j列,每行有n
个,则a[i][j]=* (p+i*n+j)
行指针 : int( * p)[3];初始化:p=a;或p=&a[0];
因为p=a,所以可以参照前面关于a+1, *(a+1)+1的描述。
即 p+i表示第i行的地址
* (p+i)为第i行第0列的地址
* (p+i)+j表示第i行第j列的地址
* ( *(p+i)+j)表示第i行第j列的元素值
行指针与列指针的本质区别在定义时产生,即他们的步长不同,列指针p为int *型(一维指针)一次4个字节,行指针为int *[3]类型(二维指针),一次移动12个字节。(本质上是指针的类型不同)。

结合上述所有,我们会发现,列指针的一条线式的处理方式更偏向于二维数组存储方式,行指针的处理方式更偏向于二维数组取值方式。

有一个易错点:在二维数组中,a[0],a[1]可以单独使用表示一个地址,但不能做加减操作,这是不合法的。

下面介绍数组中指针应用的一些注意事项:
我们结合一些题目来理解:
1.#include <stdio.h>
int main(void)
{ int a[5] = { 1, 2, 3, 4, 5 };
int ptr = (int)(&a + 1);
printf("%d %d\n", *(a + 1), *(ptr - 1));
return 0;}

那么这个代码输出的结果是2和5,因为a表示这个数组的首地址,那么&a便指向了int[5]类型,一次移动20个字节,&a+1在a[5]位置,但该数据最大为a[4],所以该位置是非法的,但因为 *(ptr-1)减一使指针指向a[4]即5((int * )强转将int * [5]类型变为了int * 类型,步长由20字节变为4字节)
2.void foo(int[][3]);
int main(void)
{
int a[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
foo(a);
printf("%d\n", a[2][1]);
return 0;
}
void foo(int b[][3])
{
++b;
b[1][1] = 9;
}

该代码的输出结果为9。首先进入了foo函数,b+1后b表示第1行的地址,所以b[1]现在指向了{7,8,9}这一行,b[1][1]=8,把9赋给b[1][1],在被调函数中,数组中的值改变,主调函数数组中的值也会改变,在被调函数被释放后,a[2]指向了{7,9,9},a[2][1]值为9.
3.#include <stdio.h>
int main(void)
{
int a[][3] = {1, 2, 3, 4, 5, 6};
int (*ptr)[3] = a;
printf("%d %d “, (*ptr)[1], (*ptr)[2]);
++ptr;
printf(”%d %d\n", (*ptr)[1], (*ptr)[2]);
return 0;
}

该代码的输出结果为2 3 5 6.首先代码中定义了一个行指针ptr,( * ptr)[1]==a[0][1];这两种表达形式是完全等价的,因为ptr ==a, * ptr ==a[0];
4.
#include <stdio.h>
int main(void)
{
char p;
char buf[10] = {1, 2, 3, 4, 5, 6, 9, 8};
p = (buf + 1)[5];
printf("%d\n", p);
return 0;
}

该代码的输出结果为9.关键在于如何解读(buf+1)[5],他就等价于 * (buf+1+5),buf指向a[0],所以 *(buf+1+5)指向了a[6],即9.

在这里我们可以进一步去理解数组的运算规则, 有以下一个现象:
a[i]=i[a],比上述题目的形式还要离谱,这种现象的原理即数组的运算是a[i]= *(a+i) ,i[a]= *(i+a),所以最终结果都是一样的,也就是4题中的(buf+1)[5]还可以写作 5[buf+1],是等价的但是并没有优势,反而会使代码显的复杂,实用性并不好。
下面是关于二维字符数组和指针数组的对比介绍:

#include<stdio.h>
int main()
{ char a[4][5]={"abcd","ef","gh","ijk"};
printf("%s",a[0]);
return 0;} 

以上代码为二维字符数组,缺点是内存的浪费,因为字符串的长度是不同的,数组每列的最小长度必须以最长的字符串为标准。
输出a[0] 输出结果为 abcd
输出a[0][0] 输出结果为 a
这里有一个易错点:a[4][5]而不能是a[4][4],必须留出空字符的位置,否则输出a[0]结果为abcdef,把a[1]也输出了,因为a[0]中没有空字符,无法停止,而a[1]中除去ef还有剩余空间给空字符,所以输出a[1]后才停止。

#include<stdio.h>
int main()
{char *a[]={"abcd","ef","gh","ijk"};
printf("%s",a[0])
}

以上代码为指针数组,这4个字符串并不保存在这个数组中,这个数组中a[0],a[1]等都是字符串地址。
输出a[0] 输出结果abcd
输出* a[0] 输出结果为a

————————————————————————————————

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值