c语言深度剖析(29)—指针和数组分析(下)

问题:数组名可以当作指针常量使用,那么指针是否也可以当作数组来使用呢?

  • 通过下标的形式访问数组中的元素
int main()
{
    int a[5] = {0};
    a[1] = 3;
    a[2] = 5;

    return 0;
}
  • 通过指针的形式访问数组中的元素
int main()
{
    int a[5] = {0};
    *(a + 1) = 3;
    *(a + 2) = 5;

    return 0;
}

 1. 下标形式VS指针形式

  •  指针以固定增量在数组中移动,效率高于下标的形式; 
  • 指针增量为1,且硬件具有硬件增量模型时,效率更高;
  • 下标形式与指针形式的转换:a[n] <==> (a + n) <==> (n + a) <==> n[a]
  • note: 现代编译器的生成代码优化率已大大提高,在固定增量时,下标形式的效率已经和指针形式的相当;但从代码可读性和代码维护性的角度来看,下标形式的更优。
#include <stdio.h>

int main()
{
	int a[5] = { 0 };
	int *p = a;
	int i = 0;

	for (i = 0; i < 5; i++)
	{
		p[i] = i + 1;  // 将指针当做数组名来使用
	}

	for (i = 0; i < 5; i++)
	{
		printf("a[%d] = %d\n", i, *(a + i));
	}

	printf("\n");

	for (i = 0; i < 5; i++)
	{
		i[a] = i + 10;
	}

	for (i = 0; i < 5; i++)
	{
		printf("p[%d] = %d\n", i, p[i]);
	}

	return 0;
}
  • 运行结果:
  • 总结: 通过上面程序验证可知:指针可以当作数组名使用。

2. 数组和指针的不同点

  • 数组和指针是不同的
#include <stdio.h>

int main()
{
    
    extern int a[];
    //extern int *a;
    
    printf("&a = %p\n", &a);
    printf("a = %p\n", a);
    printf("*a = %d\n", *a);

    
    return 0;
}
  • ext.c文件
int a[] = {1, 2, 3, 4, 5};
  • 当上述程序第5行为extern int a[];时,编译运行结果如下:

 

  • 当第5行改为extern int *a;时,结果如下:

 

  • 出现了段错误。
  • 当编译ext.c的时候,在内存中出现了如下的情况,编译器在内存中分配20个字节:

  • 编译器编译ext.c,分配了内存,给了a一个地址0x804a014。编译器将a这个标识符映射到了0x804a014这个地址值上面。在内存中是没有变量和标识符的,只有地址。
  • 编译器在编译到主程序的第5行时,看到a是在外部定义的,那么就认为在外部,编译器已经给了它一个地址值了,第7行取a的地址值,自然得到了0x804a014。
  • 编译到第8行时,a被解释为指针,a这个变量在编译器中分配的地址为0x804a014,这个地址其实对程序员是不可见的。而a的值并不是这个地址,而是这个地址中存放的值,因此,编译器会到0x804a014这个地址里面取四个字节,这四个字节就是a的值。
  • 根据以上的分析,第9行就会去访问0x01这个地址的数据,因此,会出现段错误。
  • 可以得到结论,数组不是指针,指针也不是数组。只不过某些情况下,它们可以等价。

3. a和&a的区别

  • a为数组首元素的地址 
  • &a为整个数组的地址 
  • 区别在于指针运算 
    • a + 1 <==> (unsigned int)a + sizeof(* a) 
    • &a + 1 <==> (unsigned int)a + sizeof(* &a) <==> (unsigned int)a + sizeof(a)
#include<stdio.h>

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

    int* p1 = (int*)(&a + 1);      // p1指向数组最后一个元素的后一个地址
    int* p2 = (int*)((int)a + 1);  // p2指向的值为:将a的地址值强制转化为int型后+1
    int* p3 = (int*)(a + 1);       // p3指向第二个元素 

    printf("%d, %d, %d \n", p1[-1], p2[0], p3[1]);

    return 0;
}
  • 运行结果:
  • gcc64位:
  • gcc32位:

4. 数组参数

  • 数组作为函数参数时,编译器将其编译成对应的指针: 
    • void f(int a[]) <==> void f(int *a) 
    • void f(int a[5]) <==> void f(int *a) 
  • 总结:一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标识数组的大小。
  • 当数组作为参数时,不管带不带大小,都会退化为指针。一般传数组参数时,我们会另外带一个int型参数表示数组的大小。
#include <stdio.h>

void func1(char a[5])
{
	printf("In func1: sizeof(a) = %d\n", sizeof(a));

	*a = 'a';

	a = NULL;
}

void func2(char b[])
{
	printf("In func2: sizeof(b) = %d\n", sizeof(b));

	*b = 'b';

	b = NULL;
}

int main()
{
	char array[10] = { 0 };

	func1(array);

	printf("array[0] = %c\n", array[0]);

	func2(array);

	printf("array[0] = %c\n", array[0]);

	return 0;
}
  • 运行结果:
  • 可见数组参数是虚幻的,会退化为指针。

5.小结

  • 数组名和指针仅使用方式相同,注意数组名的本质不是指针; 指针的本质不是数组; 
  • 数组名并不是数组的地址,而是数组首元素的地址; 
  • 函数的数组参数退化为指针
  • int *a[10] :指针数组。数组a里存放的是10个int型指针
  • int (*a)[10]:数组指针,a是指针,指向一个数组。此数组有10个int型元素​​​​​​​
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值