Bootstrap

【C语言】数组与指针

定义一个指针变量指向数组

  既然指针能够指向一个整型变量,那数组就一定能够指向一个数组变量,在这里我们就要谈到 数组的首地址 这个名词。数组的首地址,无非就是数组的第一个元素的地址,它照样可以用取整型变量的地址那样去取它的首地址,即 &arr[0] 。除了这种方式,还有一种说法,就是 数组名就是数组的首地址 ,即 arr 就是数组的首地址,具体怎么用呢?我们看一下代码:

#include<stdio.h>

int main()
{
	int arr[4] = {0,1,2,3};
	
	int *p1;
	int *p2;
	
	p1 = &arr[0];	//数组的首地址就是首个元素的地址
	p2 = arr;		//数组名就是数组的首地址
	
	printf("arr[0] = %d\n",*p1);
	printf("arr[0] = %d\n",*p2);
	
	return 0;
}

运行结果为:
在这里插入图片描述
  该段代码就实现了两种方法去取 数组arr 的首地址,然后将它们保存到指针变量 ∗ p 1 *p1 p1 ∗ p 2 *p2 p2 中,最终将数组首个元素的值打印出来。

指针偏移遍历数组

  我们都知道,数组是一片连续的空间,那么既然连续,就一定能够一个一个地去访问其中的元素。既然指针元素连续,那么一个数组一定在一片连续的地址空间中,如图:
在这里插入图片描述
这样我们就可以通过指针一个一个地向下偏移去一个一个地去数组中的每个元素的值。我们看一下代码:

#include<stdio.h>

int main()
{
	int arr[4] = {0,1,2,3};
	
	int *p;
	
	p = arr;		//数组名就是数组的首地址
	
	printf("arr[0] = %d\n",*p);
	printf("arr[1] = %d\n",*(p+1));
	printf("arr[2] = %d\n",*(p+2));
	
	return 0;
}

运行结果为:
在这里插入图片描述
  以上代码就实现了我们想要的结果,但是这样的访问方式未免太笨,当访问一个数组元素量非常大的数组时,往往我们就需要写很多输出语句,所以我们就采用循环的方式去访问,看以下代码:

#include<stdio.h>

int main()
{
	int arr[4] = {0,1,2,3};
	int *p;
	p = arr;		//数组名就是数组的首地址
	
	for(int i = 0;i<4;i++)
	{
		printf("arr[%d] = %d\n",i,*(p+i));
	}

	return 0;
}

运行结果:
在这里插入图片描述
  通过循环的方式便使数组的遍历显的更加灵活。

  通过以上学习,我们掌握了两种遍历数组的方法,第一种就是通过下标法遍历数组,第二种方法就是使用指针偏移的方式去遍历数组。呢么,这两种方法的有什么优劣呢?
  其实,系统在用下标法访问数组的时候,系统开销比较大,指针的访问效率是远远大于下标访问效率的,但是只有 指针正确访问 的时候,才比下标法更有效率。下标法更容易理解,在可读性方面也更有优势,具体怎么选择,也没有一定的说法。但是以现在的系统运行速度,这两者效率上的差别是微乎其微的。

见怪不怪的数组名(面试)

1.指针遍历数组的见怪不怪

  以上 f o r for for 循环的部分还可以写成以下形式:

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

  其中, ∗ p *p p 的运算符的优先级更高,取值以后,指针++,这样就可以实现指针的偏移。同样还可以写成以下形式:

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

2.指针和数组名的见怪不怪

  我们看一段通过下标法打印数组中元素值的代码:

#include<stdio.h>

int main()
{
	int arr[4] = {0,1,2,3};
	int *p = arr;
	
	printf("%d\n",*arr);
	
	return 0;
}

运行结果:
在这里插入图片描述
  这段代码就是 数组名就是数组的首地址 这句话的完美印证, a r r arr arr 就是数组的首地址,用 ∗ * 运算符进行取值即把第一个元素的值取出来。见怪不怪。

  再看一个例子:

#include<stdio.h>

int main()
{
	int arr[4] = {0,1,2,3};
	int *p = arr;
	
	printf("%d\n",p[0]);
	
	return 0;
}

运行结果:
在这里插入图片描述
  这段代码将数组名 a r r arr arr 用指针变量名 p p p 代替,同样也是可以正常通过下标访问数组中的元素。

  还有一种见怪不怪,就是拿数组名来加,具体我们看代码:

#include<stdio.h>

int main()
{
	int arr[4] = {0,1,2,3};
	int *p = arr;
	
	for(int i = 0;i<3;i++){
		printf("%d ",*(p+i));
	//  printf("%d ",*(arr+i)); //这行代码和上一行代码等效
	}
	
	return 0;
}

运行结果:
在这里插入图片描述
  在上面我们讲到过,数组名和指向数组首个元素的指针是可以替换的,既然这样,我们就可以通过 ∗ ( a r r + i ) *(arr+i) (arr+i) ∗ ( p + i ) *(p+i) (p+i) 去实现指针偏移来遍历数组。知道这个道理以后,我们以后见到拿数组名来加也就见怪不怪了。

  既然以上可以实现,那么数组名 a r r arr arr 可以直接用来 + + ++ ++ 吗?我们接着看以下代码:

#include<stdio.h>

int main()
{
   int arr[4] = {0,1,2,3};
   int *p = arr;
   
   for(int i = 0;i<3;i++){
   	printf("%d ",*arr++);
   }
   
   return 0;
}

运行结果:
在这里插入图片描述
  很明显,这里编译出错。为什么呢?原因是因为 a r r arr arr 既然是数组的首地址,那么它永远都是数组的首地址,它是一个指针常量,不可变,而 p p p 作为指针变量,它的指向是随意的,想指哪里指哪里,所以它就可以进行自增运算。所以在以后的编程中我们就要区分指针变量和指针常量的区别。
  下面,我们用 s i z e o f ( ) sizeof() sizeof() 比较一下两者的区别,看以下代码:

#include<stdio.h>

int main()
{
   int arr[4] = {0,1,2,3};
   int *p = arr;
   
   printf("size of p is %d\n",sizeof(p)); // os 用8个字节表示一个地址
   printf("size of arr is %d\n",sizeof(arr)); // 4*4=16
   printf("size of int is %d\n",sizeof(int));
   printf("size of pointer is %d\n",sizeof(int*));
   printf("size of pointer is %d\n",sizeof(char*));
   
   return 0;
}

运行截图:
在这里插入图片描述

通过以上比较,我们知道了 a r r arr arr 的字节长度是整个数组的长度,而 p p p 的字节长度是操作系统中规定的地址的长度,而且不同类型的指针的长度都相同。

操练1:利用指针操作函数完成数组的初始化

#include<stdio.h>

void initArray(int *parr, int size)
{
	int i;
	for(i = 0;i<size;i++){
		printf("请输入第%d个元素的值:\n",i+1);
		scanf("%d",parr++); //数组指针本身就是一串地址,省略取地址符号
	}
}

void printArray(int *parr, int size)
{
	int i;
	for(i = 0;i<size;i++){
		printf("%d ",*parr++); //指针偏移遍历数组
	}
}

int main()
{
	int arr[5];
	int size = sizeof(arr)/sizeof(arr[0]);
	
	initArray(arr, size); //数组名就是数组首元素的地址
	printArray(arr, size);
	
	return 0;
}

在这里插入图片描述
动动小手敲一下效果更好哦。

操练2:将数组中的n个元素按逆序存放

#include<stdio.h>

void initArray(int *parr, int size)
{
	int i;
	for(i = 0;i<size;i++){
		printf("请输入第%d个元素的值:\n",i+1);
		scanf("%d",parr++); //数组指针本身就是一串地址,省略取地址符号
	}
}

void printArray(int *parr, int size)
{
	int i;
	for(i = 0;i<size;i++){
		printf("%d ",*parr++); //指针偏移遍历数组
	}
	putchar('\n');
}

void revangeArray(int *parr, int size)
{
	int i,j;
	int temp;
	
	for(i = 0;i<size/2;i++){
		j = size - i - 1;
		temp = *(parr+i);
		*(parr+i) = *(parr+j);
		*(parr+j) = temp;
	}
}

int main()
{
	int arr[5];
	int size = sizeof(arr)/sizeof(arr[0]);
	
	initArray(arr, size); //数组名就是数组首元素的地址
	printArray(arr, size);
	revangeArray(arr, size);
	printArray(arr, size);
	
	return 0;
}

在这里插入图片描述

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;