理解数组和指针以及两者之间的关系

前言

关于C语言,数组和指针的关系是密不可分的,如果我们没能理解其中的关系或者一些细节,就可能在以后的学习中遇到大麻烦。

1、数组

  1. C语言中只有一维数组,定义一个数组的时候必须要指明大小或者初始化。可能这是会有人问这个int a[2][3]怎么解释?因为在C语言中,数组的元素类型可以是int,double,struct等等。换言之,数组的元素类型可以是任意类型,当然也可以是另一个数组。所以,int a[2][3]表示该数组有2个元素,每个元素都是一个拥有2个整形元素的数组。因此,定义出一个二维数组就是一件轻而易举的事。

  2. 对于一个数组,我们只能做两件事:确定数组的大小以及获取指向该数组的第一个元素(下标为0)的指针。对于其它的有关数组的操作,哪怕他看上去像是在操作下标,其实实际是在对指针进行操作,因为它们呈现的是一种一一对应的关系。

要理解C语言中数组的运算机制,首先我们要能学会如何声明一个数组,以及如何理解它,例如:int a[5]; 这个语句声明了变量a是一个有5个整型元素的数组。double b[2];  这个语句声明了变量b是一个有2个双精度浮点型元素的数组。

类似地,

struct student
{
   char name[10];
   int age;
}Stu[15];

声明了 Stu是拥有15个元素的数组,每个元素都是一个结构体,该结构体包括了一个拥有10个字符型元素的数组(名字为name)和一个整形变量(名字为age)。

再来看一个例子:int c[10][15]; 这个语句声明了c是一个数组,该数组拥有10个数组类型的元素,每个元素都是一个拥有15个整型元素的数组(而不是一个组拥有15个数组类型的元素,每个元素都是一个拥有10个整型元素的数组).因此sizeof(c)的值是150(15*10)和sizeof(int)的乘积。这里,如果c不是sizeof的操作符,而是用于其他场景,c将被转化成为一个指向该数组首元素指针。如果要很好的理解这句话,我们必须对指针也要有一定的了解。

2、指针

指针是一个标识,就像地址一样,用这个地址就可以找到一个确切的地点。所以指针可以指向任何类型的变量,不过前提得是两者的类型必须保持一致。例如,我们声明一个指针变量int* p,此时指针p并没有被初始化,因此p里面放的就是一个随机的地址,所以我们不能对这个指针进行任何解引用操作,否则编译器会发出警告或者报错,甚至有可能带来毁灭性的后果。再如,我们声明 int i = 0;那么我们就可以把i的地址赋给指针p:p = &i;我们也可以通过指针p将i的值改为10:*p =10。但是,如果你要把一个双精度浮点类型变量的地址赋给指针p,例如:double j = 0; p = &j;这种操作是非法的,因为它们的类似不匹配。

如果我们定义一个数组:int week[7] = {1, 2, 3, 4, 5, 6, 7}; 众所周知,数组名是一个地址(指针),但是它到底是数组首元素的地址还是整个数组的地址,大部分人都很混淆,以为这两者是相同的概念,实则不然。此时,我们就可以直接利用一段简单的代码来告诉我们真正的答案:

#include<stdio.h>
int main()
{
	int week[7] = { 1, 2, 3, 4, 5, 6, 7 };
	printf("%p\n", &week[0]);
	printf("%p\n", week);
	printf("%p\n", &week);
	return 0;
}

看到这里,大部分人都会认为数组名就是整个数组的地址,也就等价于数组首元素的地址,这样的理解其实是错误的,我们再来看下一段代码

#include<stdio.h>
int main()
{
	int week[7] = { 1, 2, 3, 4, 5, 6, 7 };
	printf("%p->", &week[0]);
	printf("%p\n", &week[0] + 1);

	printf("%p->", week);
	printf("%p\n", week + 1);

	printf("%p->", &week);
	printf("%p\n", &week + 1);
	return 0;
}

 而这段代码的结果却是:

不难发现,对数组名进行取地址操作,再加1,使它指向了数组的最后一个元素的后一个地址。数组名加1,使它指向了数组中第2个元素的地址,两者相差了24个字节,这就证实了数组名是首元素的地址。此时,我们就可以定义一个指针变量,用来存放数组week的地址了,例如: int*p = week;这样的含义就是把数组week中下标为0的元素的地址赋给p。可是,我们并没有写成:int* p = &week;这种写法是非法的,因为&week是一个指向数组的指针,而p是一个指向整型变量的指针,它们的类型不匹配。

这使我们联想到给指针加上一个整数或者减去一个整数。例如:int a[5]={1,2,3,4,5}; int* p = a+1;    同样的也可以让两个指向同一个数组中的元素的地址相减,这样做是有意的,例如:int* q = p+i;    那么我们就可以通过q-p得到i的值,通俗的讲,i就是q和p之间的元素个数。值得我们注意的是:

         1.必须是大地址减去小地址,否则将得到的是一个负数   

         2.如果p和q指向的是不同的数组中元素的地址,即使它们所指向的地址在内存中正好间隔一个数组元素的整数倍,所得的结果也是一种耦合,无法保证其中的正确性。更有甚者,p和q指向的甚至是不同类型元素的数组,这样的结果更难预料。

我们定义这样一个数组:int arr[5] = {1,2,3,4,5};再将数组中第一个元素的地址赋给p:int* p = arr;此时,我们可以通过数组下标来访问数组的每一个元素:arr[0],arr[1],以此类推。因为p等同于arr,我们也可以这样:p[0],p[1],以此类推。因为p是一个指针,指向的是数组arr中的第一个元素,也就是下标为0的元素,因此我们可以这样:*p,*p+1,以此类推。同样的,我们也可以*arr,*arr+1,以此类推。

#include<stdio.h>
int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ",arr[i]);
	}
	printf("\n");
	for (i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}
	printf("\n");
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *p+i);
	}
	printf("\n");
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *arr+i);
	}
	printf("\n");
	return 0;
}

 这段代码很好的证明了以上的写法是正确的。所以arr[i]=p[i]=*p+i=*arr+i。现在,我们来讨论一个非常有趣的细节,p+i=i+p -> *(p+i)=*(i+p),那么p[i] 是否等于i[p]呢,或者是arr[i]是否等于i[arr],又或者是arr[i],i[arr],p[i],i[p]这种写法是否都相等呢?

事实告诉我们是相等的。实际上,因为i+p和p+i的含义相同,因此p[i]和i[p]也具有相同的意义,但是我们推荐p[i]这种写法,因为这种写法让我们明确知道这是在对下标为i的元素进行引用。

现在,我们来讨论二维数组。首先我们先声明这样一个二维数组:int calendar[12][31];然后声明一个变量int i;最后声明一个指针int* p;首先,我们先思考一下calendar[4]的含义。因为calendar是一个拥有12个数组类型元素的数组,所以calendar[4]是calendar数组的第5个元素,而calendar的每个元素都是一个有着31个整型元素的数组,同样的calendar[4]也是一个有着31个整型元素的数组。例如:sizeof(calendar[4])的结果是31于sizeof(int)的乘积。又如:p = calendar[4];这个语句表明了将calendar这个数组的首元素的地址赋给p,总的看来,就相当于把calendar[4][0]这个元素的地址赋给p。因为calendar[4]是一个数组,我们可以通过下标的方式将这个数组的第6个元素赋值给i:i = calendar[4][5]。其次就是,我们也可以通过指针的方式:i = *(calendar[4]+5)。这个语句进一步可以写成这样:i = *(*(calendar+4)+5);从中我们可以发现,用下标比用指针要明了许多。

我们再看:p = calendar;前面的例子已经告诉我们这种写法是错误的了,因为calendar是一个指向数组的指针,而p是一个指向整型变量的指针,类型不匹配。如果我们要将calendar这个指针赋给另一个指针,我们就必须重新再声明一个数组指针,例如:int (*pp)[31] ;这个语句告诉我们这样一个信息:pp是一个指针,该指针指向了一个拥有10个整型元素的数组。所以,这样的写法才是正确的:pp = calendar;

总结

对于编程人员,我们应该深入理解数组和指针的关系,有着融会贯通的能力,这样才能在学习和工作中少走弯路,写出优秀的代码,不至于错误连篇。

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值