C语言学习小结_3(递归最强解析)

1、写一个函数实现有序数组的二分查找(前提是数组为有序而且要已知是升序还是降序)

```c
#include<stdio.h>
#include<windows.h>
int BinSearch(int arr[], int length, int targetNum){
    
    
	int left = 0;
	int right = length - 1;
	int mid = 0;
	while (left <= right){
    
    
		mid = left + (right - left)  >>1;
		//数组元素为降序排列,若为升序,left和right更新方式发生改变
		if (targetNum<arr[mid]){
    
    
			left = mid + 1;
		}
		else if (targetNum>arr[mid]){
    
    
			right = mid - 1;
		}
		else{
    
    
			return mid;
		}
	}
	return -1;//数组中没有此元素
}
int main(){
    
    
	int arr[] = {
    
     99, 88, 77, 66, 55, 44, 33, 22, 11, 1 };
	int length = sizeof(arr) / sizeof(arr[0]);
	int targetNum = 99;
	int ret = BinSearch(arr, length, targetNum);
	if (-1 == ret){
    
    
		printf("没有此元素!");
	}
	else{
    
    
		printf("找到了,元素下标为%d\n", ret);
	}
	system("pause");
	return 0;
}

2、goto语句的使用:
原则上应尽量避免使用goto,因为会导致代码的逻辑混乱。
但是某些场合下goto语句还是可以用的着的,比如终止程序在某些深度嵌套的结构处理过程,例如一次跳出两层或者多层循环。LinuxOS源代码大量使用了goto语句

3、传址传参VS传值传参
1)实际参数(实参):
真实传给函数的参数,叫实参,可以是常量、变量、表达式、函数等。无论实参是何种类型的,在进行函数调用的时候,他们都必须有确定的值,以便把这些值传递给形参。
2)形式参数(形参):
形式参数指的是函数名后括号中的变量,因为形式参数只有函数被调用的过程中的才实例化(分配内存单元),所以叫形式参数。形式参数在函数调用完成后,就自动销毁了。因此形式参数只在函数中有效。
具体来说:
形参实例化的发生时间是调用函数后执行函数体之前。
下面结合代码来进行说明

#include <stdio.h>
#include <windows.h>
void Swap1(int x, int y){
    
    
	int temp = 0;
	temp = x;
	x = y;
	y = temp;
}
void Swap2(int *px, int *py){
    
    
	int temp = 0;
	temp = *px;
	*px = *py;
	*py = temp;
}
int main(){
    
    
	int num1 = 23;
	int num2 = 24;
	/*Swap1(num1, num2);
	printf("Swap1::num1 = %d num2 = %d\n", num1, num2);*/

	Swap2(&num1, &num2);
	printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
	
	system("pause");
	return 0;
}

在这里插入图片描述
这里可以看到Swap1在函数调用的时候,x和y拥有自己的空间,同时拥有和实参一模一样的内容。所以可以这样认为:形参实例化后相当于实参的一份临时拷贝。
函数的调用:传值调用,传址调用。
1)传值调用:函数的形参和实参分别占有不同的内存块,对形参的修改(比如函数体中执行某些操作)不会影响实参。
2)传址调用:看下图:
在这里插入图片描述
Swap中的参数类型是int *,为指针变量,传址调用把函数外部创建创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种参数传递方式可以让函数和函数外部的变量建立真正的联系,即函数内部可以直接操作函数外部的变量。
在这里插入图片描述
最后的结果就是这样。
4、C语言中并没有字符串这种数据类型;但是有字符串;可以用字符数组和指针表示

char str[64] = "chenzhihao";
char *str = "chenzhihao";

5、递归:
recursion作为一种算法在程序设计语言中广泛应用。一个过程或者函数有直接或者间接调用自身的一种方法。它通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题求解,递归策略只需要少量的程序可以表示解决问题过程中所需要的多次重复运算,大大减少代码的程序立量。递归主要的思考方式在于把大事化小。tioajian
递归的两个必要条件:
1)存在限制条件,当满足这个限制条件时,递归不再继续。
2)每次递归调用后越来越接近这个限制条件。
下面我们来举例说明下递归的原理(绝对全网最清晰的哦!看好辣!)
我们先做一道题:
接收一个整型无符号的值,按照顺序打印它的每一位,例如:输入1234,输出1 2 3 4

//用递归实现输入1234,输出1 2 3 4
int PrintInt(int n){
    
    
	if (n > 10){
    
    
		PrintInt(n/10);//方法内调用自己	
	}
	printf("%d ", n %10);

}
在main函数中调用:
int a = 1234;
	PrintInt(a);

首先说明一些先验知识:
在C语言中每个程序对应一个地址空间图
对于一个C语言开发工程师来说,一个程序的内存区域的逻辑划分为下图所示:
在这里插入图片描述
由图可知,一个c程序的地址空间可以由代码区、字符常量区,全局数据区、堆、栈。从上到下从高地址到底地址。只要程序被加载进内存这个地址空间就一直存在。我们平时定义的全局变量在全局数据区中,所以只要程序在运行,全局变量的生命周期是和程序或者是项目一致的。堆这个东西我打算在后续讲。为了讲清楚递归这个算法的原理,讲清楚栈是必须的。我们看下面的图:
在这里插入图片描述
在程序运行时候,进入程序入口main 函数,会在栈内存中开辟一块内存区域叫栈帧,栈帧中由我们在函数中定义的局部变量,int n = 1234;
接着调用PrintInt方法,在main栈帧的下面,再开辟另一块内存区域(PrintInt的栈帧);然后按照程序的逻辑开始执行PrintInt(123),同理在再开辟属于它的栈帧,最后PrintInt(1);执行printf("%d",n%10);
然后释放它的栈帧,返回紧挨着它的上一个函数的执行结果,PrintInt(12),同理接着返回PrintInt(123),之后PrintInt(1234);完事了。
最后的执行结果是
在这里插入图片描述
然后我们可以用给递归实现阶乘和斐波那契数列。
1)求n!
方法一:使用循环

int Fact1(int n){
    
    
	int ret = 1;
	for (int i = n; i > 1; i--){
    
    
		ret *= i;
	}
	return ret;
}

方法二:使用递归

int Fact2(int n){
    
    
	if (n <= 2){
    
    
		return n;
	}
	return n*Fact2(n-1);
}

2)求斐波那契数列中的第n个数(n>=2):(使用递归效率很低,因为进行了很多重复的函数的调用,栈帧的开辟和释放也是会花费时间的),可以使用循环进行优化。

int Fib(int n){
    
    
	
	if (n <= 2){
    
    
		return 1;
	}
	/*if (n == 3){
		count++;
	}*/
	return Fib(n - 1) + Fib(n - 2);
}

总而言之,就是递归将一个复杂的计算问题,一步步拆分成相同逻辑的但是更小的计算问题,比如5!,求5!等价于54!,4! = 43!,3! = 3 * 2!,…。所以5!被拆分成最小的计算单元2!因为2!和1!就是自己作为递归的出口。
这是刚好验证了最开始我说的递归问题的两个必要条件:
1)存在限制条件当满足限制条件时候递归不再继续。
2)每次递归调用后都会越来越接近这个限制条件。

猜你喜欢

转载自blog.csdn.net/CZHLNN/article/details/109150475