C语言复习(4)——数组和指针的那些事儿

在这里插入图片描述


1.数组名的值为指针常量

也就是数组第一个元素的地址。如果要修改这个指针常量,唯一可行的操作就是把整个数组移动到内存的其他位置。但是,当程序链接后,内存中数组的位置便固定了,当运行时就不能移动数组了。


2.只有在两种场合下,数组名不作指针常量来使用

1)sizeof(数组名)——返回整个数组的长度。而不是指向数组的指针的长度。

2)&数组名——代表一个指向整个数组的指针,而非指向一个数组头元素的指针

举个栗子:

int a[10];
	int b[10];
	int* c;
	c = &a[0];

&a[0]是指向数组第一个元素的指针,同时也是数组名本身的值,所以下面的这条赋值语句也是一样的

c=a;

这里可以正确认识到数组名,是首元素的指针,c所指向的元素是a数组的第一个元素。但是下面的赋值语句是非法的:

b=a;

不能用赋值符把整个数组复制到另一个数组中去,必须使用循环。

考虑下面语句

a=c;

看上去像是指针赋值,把c赋值给a,但是是非法的,因为a的值是常量不能被修改。


3.下标与间接引用

a[x]=*(a+x)

举个栗子

int a[10];
int *ap=a+2; 

以下ap的表达式能否改写为关于a的表达式

  1. ap——a+2 ,&a[2]

  2. *ap—— *(a+2),a[2]

  3. ap[0]——*(ap+0)= *(a+2)=a[2] 。C语言的下标引用和间接访问表达式是一样的,此处并不在意ap是否为数组。

  4. ap+6——(a+8),&a[8]

  5. *ap+6—— *(a+2)+6,a[2]+6

  6. *(ap+6)—— *(a+8),a[8]

  7. ap[6]——*(ap+6)=a[8]


4.注意以下例子也是合法的

2[a];

等于 *(2+a),但是不要这么写,影响可读性。


5.数组与指针

int a[5];
int *b;

a和b都是指针,都可以进行间接访问和下标引用操作,但仍存在区别。

声明一个数组时,编译器会根据数组元素数量和元素类型为其分配内存空间,然后将数组名指向这段空间的起始位置。声明指针变量时,编译器只为指针本身保留内存空间,并不为任何整型值分配内存空间,且b不会初始化。
在这里插入图片描述

所以表达式*a是合法的,但 *b是非法的,因为 *b将访问内存中一个不确定的位置。

另一方面,b++可以通过编译,但是a++不行,因为a是常量。


6.作为函数参数的数组名

将数组名作为一个参数传递给函数时,函数如果执行了下标引用,实际上是对这个指针进行间接访问操作,通过这种间接访问函数可以访问和修改调用程序的数组元素,就是传址调用。

那么数组的传值调用体现在什么地方呢?那就是数组名指针本身,函数可以自由操作指针形参,因为形参作为一份实参拷贝不会影响到实参的指针!

函数的所有参数本质上是通过传值传递的,当然如果传递的是指针,且函数又对其执行了间接访问,那么函数就可以修改指针指向的那个变量了。数组名作为参数时,函数得到的是该指针的一份拷贝,它可以被修改,但实参不受影响。


7.数组传入函数的声明

函数的形参实际上是个指针,编译器也接受数组形式的函数形参:

下面的函数原型是相等的:

int func(int* a);

int func(int a[]);

但是要注意,两者的函数声明虽然相等,但数组名与指针还是存在区别(sizeof,&),如果在函数中出现表达式 sizeof (a),此时的长度则为指针的长度而非整个数组的长度了,所以在数组传入函数时还是以指针形式更为准确!

这样你就清楚为什么一维数组形参无需写明元素数目了,因为函数不为数组参数分配内存空间。形参只是个指针,它指向的是在其他地方分配好的内存空间。另一方面,函数无法知道数组的长度,如果需要则必须作为一个显式的参数传递给函数。


8.自动计算数组长度

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

如果声明中没有给出数组的长度,编译器就把数组的长度设置为刚好容纳所有初始值的长度。


9.整型数组的初始化

int main()
{
	int a[5];
	static int b[5];
	int c[3]={1,2};
	printf("a[1]=%d\n", a[1]);
	printf("b[1]=%d\n", b[1]);
	printf("c[2]=%d\n", b[1]);
	return 0;
}

在这里插入图片描述

static变量未初始化时,数组元素的初始值将设为0,而局部数组变量,存放在栈中,每次运行时给其分配的内存空间随机,编译器没有办法对这些位置进行初始化。这些缺省static的变量如果未赋值初始化,则其中存放为随机值。

如果局部数组初始化一部分,未初始化的值默认为0。


10.自动计算数组长度

int a[]={0,1,2,3,4};

如果声明中没有给出数组长度,编译器就把数组长度设置为刚好能够容纳所有初始值的长度。


11.字符数组的初始化

观察下列三行代码:

char message[]={'H','e','l','l','o',0};
char message1[]="Hello";
char* message2="Hello";

第一行和第二行完全一致,第二行尽管看上去像字符常量,但它并不是。它只是第一行初始化的另一种快速写法。但第三行则是不同意义,他是一个真正的字符串常量,这个指针变量被初始化为指向这个字符串常量的地址。
在这里插入图片描述


12.多维数组

int a;
int b[10];
int c[6][10];

a是一个整型,b是一维数组其中包含10个整型元素,c是一个包含6个元素的数组,而每个元素又是包含10个整型的数组。


13.数组的存储顺序

int a[3];

包含3个元素,

在这里插入图片描述

如果每个元素又是包含六个元素的数组,则有如下声明

int a[3][6];

它在内存中的存储形式:

在这里插入图片描述

实线分割的是第一维的三个元素,虚线又将一维的三个元素各划分为6个元素。从左到右的元素标为

a[0] [0],a[0] [1],a[0] [2],a[0] [3],a[0] [4],a[0] [5],a[1] [0],a[1] [1],a[1] [2],a[1] [3],a[1] [4],a[1] [5],

a[2] [0],a[2] [1],a[2] [2],a[2] [3],a[2] [4],a[2] [5]

可见多维数组的存储顺序按照右边下标率先变化的原则。


14.数组名

一维数组名的值是指针常量,类型是”指向元素类型的指针“,它指向数组的第一个元素。那二维数组自然也是如此,但是二维数组的第一个元素是个一维数组。例如以下声明:

int a[3][10];

a是该二维数组的数组名,a是一个指向第一个元素指针,第一个元素是个包含10个整数的数组,所以a是一个指向10个元素的数组指针。它的值如下

在这里插入图片描述

对于表达式a+1,也是一个”指向包含10个元素的数组的指针“,它指向a之后的一行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H62KqLXe-1626781209551)(C:\Users\14025\AppData\Roaming\Typora\typora-user-images\image-20210713155827048.png)]

为什么?因为1这个值根据包含10个int元素的数组长度进行调整。

如果对其进行间接访问

*(a+1)

他是一个包含10个int元素的一维数组的数组名,类型是“指向整型的指针:

在这里插入图片描述

*(a+1)虽然指向第二行的数组的第一个元素,但是在sizeof和&场合下,其表示整个第二行的数组,

int main()
{
	int a[3][6]; 
	printf("%d\n", sizeof(*(a + 1)));
	return 0;
}

在这里插入图片描述

再看一行表达式:

*(a+1)+5

根据以上我们知道这是一个指针(指向的是一个整型),所以5这个值应该根据整型的长度进行调整,他所指向的位置如图所示:

在这里插入图片描述

对其进行间接访问操作:

*(*(a+1)+5)

访问的就是图中所指的那个元素,如果换成下标访问则为

*(a[1]+5)

a[1]选定了一个子数组,所以它的类型是指向整型的指针。再次下标访问

a[1][5]

15. 指向数组的指针

先看两行声明

int vector[10],*vp=vector;
int matrix[3][10],*mp=matrix;

第一行是合法的,vector和vp都是指向整型的指针,vp初始化为指向vector数组第一个元素的指针。

第二行的声明则是非法的,mp是指向整型的指针,但是mp的初始化不正确,因为matrix不是一个指向整型的指针,而是一个指向拥有10个整型元素的数组的指针。

那么如何声明一个指向数组的指针呢?

int (*p)[10];

p为指向整型数组的指针,下标访问优先级比间接访问高需加上圆括号,在声明中加上初始化:

int (*p)[10]=matrix;

它使p指向matrix的第一行。

p是一个指向拥有10个整型元素的数组的指针,当把p与一个正数相加时,该整数值首先根据10个整数长度进行调整,然后执行加法。所以我们可以使用这个指针一行一行的在matrix中移动。

如果你需要一个指针逐个访问整型元素而不是逐行在数组中移动,该怎么声明这个指针呢

下面的三个声明都创建了简单的整型指针,并以不同方式进行初始化,指向了a这个二维数组的第一个整型元素

int *pi=a[0];
int *pi=*a;
int *pi=&a[0][0];

增加这个指针将使它指向下一个整形元素


16.作为函数参数的二维数组

将二维数组名作为函数参数的传递方式和一维数组名相同——实际上传递的是指向数组的第一个元素的指针。
但是,两者的区别在于二维数组的首元素是另一个数组,编译器需要他的维数,举个例子:

首先来看下一维数组的函数传参

int vector[10];
...
func1(vector);

参数vector为数组名,类型是指向整形的指针,所以func1的原型可以声明为下面两种的任何一种:

void func1(int vec[]);
void func1(int* vec);

作用于vec上面的指针运算把整型的长度作为调整因子
现在观察二维数组的函数传参

int matrix[3][10];
...
func2(matrix);

参数matrix的类型是指向包含10个整型元素的数组的指针,func2的声明为下面两种的任何一种:

void func2(int mat[][10]);
void func(int (*mat)[10]);

在这个函数中,mat的第1个下标根据包含10个元素的整型数组的长度进行调整,接着第二个下标根据整型的长度进行调整
记住func2这样的声明是错误的

void func2(int **mat);

这个声明把mat声明为一个指向整型指针的指针,他和指向整型数组的指针并不是一回事。


彩蛋

int (* parr[10])[5];

parr是一个数组,拥有10个元素
每个元素的类型是 int (*)[5],
即parr的每个元素是一个指针,指向拥有5个整型的数组。

数组序列类型
parr[i] (0<=i<10)int (*)[5]
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值