Record02—数组做函数参数的退化问题

目录

数组退化

总结

总体代码

 

写在前面:该篇针对C编译器进行记录,至于是否适用于C++编译器,日后考证

数组退化

本次以选择排序算法代码为例,来探讨关于数组做函数参数导致的退化问题。那么先看下下面的这个代码(main01),是实现选择排序算法的版本一(main01)。可以看出来这个代码在许多地方可以实现功能,但是,非常的低下。一个合格的程序员不仅是将代码的功能实现出来(或者说这只是最最基础的一个要求),更重要的是,将代码简洁,高效的实现出来。所以,要对版本一(main01)的代码进行改进。

那先按版本一的思路来解释下代码的实现原理,选择排序,就是在乱序序列中,挑选一个整个序列中最小的值,把它和第一个位置放的值进行交换,然后,再挑选从第二个值开始到序列末尾中最小的值,把它再和第二个位置放的值进行交换,一次类推,把第三个值开始到最末尾的值之中的最小值,第四个值开始到最末尾的值之中的最小值和第三,四位置的值进行交换,以此类推,将整个数列的值都来一遍,最后的序列,肯定就是从小到大排的一个有序数列了(长度为n的序列,进行交换的次数仅为n-1次,因为最后一次的时候就已经是排好了,不用交换了)。

这个地方想在记一笔的是,在设计算法前,临机想出一种关于算法维度的考量,就选择排序算法,应该算是一种2维算法,横维(内层)上进行横向的逐个元素进行比较;纵维(外层)上进行横维的循环。一横一纵反应到代码里就是内外两个循环。

那么版本一中的实现过程,有那些地方不简洁,不高效呢?

1.在实现打印这个功能(printf)的环节,前后实现了两次。高效的方式是将其封装成函数来进行调用。

2.同样,在排序的核心循环部分,也封装成函数,定义好接口,可以应付不同长度的序列。

void main01()
{
	int		i = 0,j = 0;
	int		tmp = 0;
	int a[] = {33,654,4,455,6,33,4};

	printf("排序之前\n");
	for (i=0; i<7; i++)
	{
		printf("%d ", a[i]);
	}
	
	//排序

	//外层循环	当i=0的时候, 让j从1===N进行变化
	//外层循环	当i=1的时候, 让j从2===N进行变化
	//			当i=2的时候, 让j从3===N进行变化
	//结论: 按照一个变量i不变,让另外一个变量j进行变化;下一轮 依次进行

	for(i=0; i<7; i++)  
	{
		for (j=i+1; j<7; j++)  //内层循环: a[i] 和 a[j]比较
		{
			if (a[i] > a[j])
			{
				tmp = a[i];
				a[i]= a[j];
				a[j] = tmp;
			}
		}
	}

	printf("排序之后\n");

	for (i=0; i<7; i++)
	{
		printf("%d ", a[i]);
	}

	printf("hello...\n");
	system("pause");
}

经过改进,得到下面的版本二(main22),分别将上面说的两个部分封装成两个函数(printArray,sortArray),那么这其中的过程就需要有一些琢磨了,首先,在封装成函数的过程中,为什么是第三个最后,前两个分别有什么特点呢?

//void printArray(int a[7])
//void printArray(int a[7], int num)
//void printArray(int a[], int num)
void printArray(int *a, int num)

那么就着这个来针对这三个来分析一下:先看第一个,很明显存在bug,假设某一天,a的数组长度有变化了,超过7了,那就出现了错误,那么就改,改成第二个那样的形式;再看第二个,经过添加一个num的变量来存序列长度,来应对序列长度变化这种情况的发生。发现程序可以很顺利的运行:

那么,问题来了,那第二个中的“int a[7]”的"7"到底有用没用啊?那我就进行一个尝试,把“7”给去掉,再运行一次,

发现还是能正常运行,没有报错,就得到了第三个;这就引出了我们这篇博文的主题,即数组做函数参数会退化为一个指针。正确的处理方法是:把数组的内存首地址和数组的有效长度传给被调用函数(应用到当前例子当中,“int a[]”就是内存首地址,“num”就是数组的有效长度)。那么我再继续往前想一步,既然当数组做函数参数的时候会退化为一个指针,那我要直接填一个指针进去呢?不也应该是可以的吗?于是我就把“int a[]” 给替换成了“int *a”(表示数组a的指针),这就是第四个。同样,也能顺利的运行。这就印证了之前的猜想是正确的。

 

形参定义:形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。

实参定义:实参出现在主调函数中,进入被调函数后,实参变量也不能使用。

比如,下面的例子:

那么如何从理论上将这个退化过程给证明一下?确确实实是数组退化成了指针了呢?那么分别定义num和num2两个变量,来进行调试:

发现num的值(形参,直接通过外部传入)是8,num2的值(通过将数组a的长度除以数组a第一个元素的长度来求数组a包含几个元素)是1,出现这两个变量不同值是因为:实参的a是一个数组,形参的a是一个指针,两个变量是不同类型的数据类型。

最开始的,写在实参位置的“int a[7]”,表面上是数组,实际上编译器只认为其只是一个指针,只会给它分配四个字节的内存,而不会分成4*7个字节的内存。那这是为什么呢?这是因为如果编译器把“int a[7]”看作是数组了,就会把数组a中的所有数据给调用到内存当中,这是一种低效的方式,降低了编译器的处理效率。

最后补充一点,需要记的:

形参写在函数的括号里和写到函数的第一行,效果是一样的。只不过是,写在括号里的时候,形参具有对外的属性而已。
 

总结

(1)作为一个程序员,要知道,你写代码是给编译器看的,要站在编译器的角度上去思考代码问题,才可以将代码的逻辑把握好。

(2)C语言的特色:就是可以在主调用函数和被调用函数之间,直接通过指针来操作内存。

(3)C语言的特色:实参a和形参a的数据类型本质不一样。

(4)C语言的特色:形参中的数组会被编译器先处理成指针来处理。

 

 

 

//void printArray(int a[7], int num)
//void printArray(int a[], int num)
void printArray(int *a, int num)
{
	int i = 0;
	for (i=0; i<num; i++)
	{
		printf("%d ", a[i]);
	}
}

void sortArray(int a[7], int num)
//void sortArray(int a[], int num)
//void sortArray(int *a, int num)
{
	int i , j , tmp ;
	int		num2 = 0;

	num2 = sizeof(a)/sizeof(a[0]);
	printf("num:%d \n", num2);
	//实参的a 和 形参的a 的数据类型本质不一样
	//形参中的数组 ,编译器会把它当成指针处理 这是C语言的特色
	for(i=0; i<num; i++) 
	{
		for (j=i+1; j<num; j++)  //内层循环: a[i] 和 a[j]比较
		{
			if (a[i] > a[j])
			{
				tmp = a[i];
				a[i]= a[j];
				a[j] = tmp;
			}
		}
	}
}

//数组做函数参数的退回问题  退回为一个指针, 
//1 正确做法:把数组的内存首地址和数组的有效长度传给被调用函数
//2 //实参的a 和 形参的a 的数据类型本质不一样
	//形参中的数组 ,编译器会把它当成指针处理 这是C语言的特色
	//排序 本质也剖析 
//3 形参写在函数上,和写在函数内是一样的,只不过是具有对外的属性而已.
void main22()
{
	int		i = 0,j = 0;
	int		tmp = 0;
	int		num = 0;
	int a[] = {33,654,4,455,6,33,4,3333};
	num  = 7;

	num = sizeof(a)/sizeof(a[0]);
	printf("num:%d \n", num);

	printf("排序之前\n");
	printArray(a, num);

	//排序

	//外层循环	当i=0的时候, 让j从1===N进行变化
	//外层循环	当i=1的时候, 让j从2===N进行变化
	//			当i=2的时候, 让j从3===N进行变化
	//结论: 按照一个变量i不变,让另外一个变量j进行变化;下一轮 依次进行

	sortArray(a, num);

	printf("排序之后\n");
	printArray(a, num);

	printf("hello...\n");
	system("pause");
}

总体代码

#include "stdlib.h"
#include "string.h"
#include "stdio.h"

//排序
void main01()
{
	int		i = 0,j = 0;
	int		tmp = 0;
	int a[] = {33,654,4,455,6,33,4};

	printf("排序之前\n");
	for (i=0; i<7; i++)
	{
		printf("%d ", a[i]);
	}
	
	//排序

	//外层循环	当i=0的时候, 让j从1===N进行变化
	//外层循环	当i=1的时候, 让j从2===N进行变化
	//			当i=2的时候, 让j从3===N进行变化
	//结论: 按照一个变量i不变,让另外一个变量j进行变化;下一轮 依次进行

	for(i=0; i<7; i++)  
	{
		for (j=i+1; j<7; j++)  //内层循环: a[i] 和 a[j]比较
		{
			if (a[i] > a[j])
			{
				tmp = a[i];
				a[i]= a[j];
				a[j] = tmp;
			}
		}
	}

	printf("排序之后\n");

	for (i=0; i<7; i++)
	{
		printf("%d ", a[i]);
	}

	printf("hello...\n");
	system("pause");
}


//void printArray(int a[7], int num)
//void printArray(int a[], int num)
void printArray(int *a, int num)
{
	int i = 0;
	for (i=0; i<num; i++)
	{
		printf("%d ", a[i]);
	}
}

void sortArray(int a[7], int num)
//void sortArray(int a[], int num)
//void sortArray(int *a, int num)
{
	int i , j , tmp ;
	int		num2 = 0;

	num2 = sizeof(a)/sizeof(a[0]);
	printf("num:%d \n", num2);
	//实参的a 和 形参的a 的数据类型本质不一样
	//形参中的数组 ,编译器会把它当成指针处理 这是C语言的特色
	for(i=0; i<num; i++) 
	{
		for (j=i+1; j<num; j++)  //内层循环: a[i] 和 a[j]比较
		{
			if (a[i] > a[j])
			{
				tmp = a[i];
				a[i]= a[j];
				a[j] = tmp;
			}
		}
	}
}

//数组做函数参数的退回问题  退回为一个指针, 
//1 正确做法:把数组的内存首地址和数组的有效长度传给被调用函数
//2 //实参的a 和 形参的a 的数据类型本质不一样
	//形参中的数组 ,编译器会把它当成指针处理 这是C语言的特色
	//排序 本质也剖析 
//3 形参写在函数上,和写在函数内是一样的,只不过是具有对外的属性而已.
void main22()
{
	int		i = 0,j = 0;
	int		tmp = 0;
	int		num = 0;
	int a[] = {33,654,4,455,6,33,4,3333};
	num  = 7;

	num = sizeof(a)/sizeof(a[0]);
	printf("num:%d \n", num);

	printf("排序之前\n");
	printArray(a, num);

	//排序

	//外层循环	当i=0的时候, 让j从1===N进行变化
	//外层循环	当i=1的时候, 让j从2===N进行变化
	//			当i=2的时候, 让j从3===N进行变化
	//结论: 按照一个变量i不变,让另外一个变量j进行变化;下一轮 依次进行

	sortArray(a, num);

	printf("排序之后\n");
	printArray(a, num);

	printf("hello...\n");
	system("pause");
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值