C语言拯救者 (函数与递归--4)

目录

1.1:库函数

练习1:写一个函数可以交换两个整形变量的内容练习2:写一个函数可以判断一个数是不是素数

练习2:写一个函数可以判断一个数是不是素数

练习3:写一个函数,实现一个整形有序数组的二分查找。

2.1:函数的嵌套调用和链式访问

2.2练习:以下代码输出的结果是什么

3.1:函数的声明+导入静态库

4.1:递归与迭代

练习4.1:接受一个整型值(无符号),按照顺序打印它的每一位

 练习4.2:编写函数不允许创建临时变量,求字符串的长度

4.3:求n的阶乘(只要知道数学公式,它就是递归代码)

4.4:求第n个斐波那契数(前两个数相加等于第三个数)


1.1:库函数

在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率。所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发,提高开发效率,标准化代码

1.2:自定义函数

自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。


练习1:写一个函数可以交换两个整形变量的内容练习2:写一个函数可以判断一个数是不是素数

当实参传给形参的时候,形参是实参的一份临时拷贝,对形参的修改不会影响实参



void swap2(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);

	printf("交换前:a=%d,b=%d\n", a, b);

	int* p1 = &a;
	int* p2 = &b;

  swap2(&a, &b);

	printf("交换后:a=%d,b=%d\n", a, b);

	return 0;
}

传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。

这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。


练习2:写一个函数可以判断一个数是不是素数

int is_prime(int n)
{
	int j = 0;
	for (j = 2; j <= n-1; j++)
	{
		if (n % j == 0)
		{
			return 0;
		}
	}
	return 1;
}

int main()
{
	//打印100~200之间的素数
	int i = 0;
	for (i = 100; i <= 200; i++)
	{
		//判断i是否为素数
		if (is_prime(i) == 1)
		{
			printf("%d ", i);
		}
	}
	return 0;
}

练习3:写一个函数,实现一个整形有序数组的二分查找。

int binary_search(int arr[], int k, int sz)
{
	int left = 0;
	int right = sz - 1;

	while (left<=right)
	{
		int mid = left + (right - left) / 2;
		if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else
		{
			return mid;
		}
	}

	return -1;//找不到
}


int main()
{
	//
	//数组在传参的时候,传递不是整个数组,传递是数组首元素的地址
	//
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 0;
	scanf("%d", &k);//要查找的元素
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = binary_search(arr, k, sz);
	if (-1 == ret)
		printf("找不到\n");
	else
		printf("找到了,下标是:%d\n", ret);

	return 0;
}


2.1:函数的嵌套调用和链式访问

函数可以嵌套调用(例如main函数调用其他函数),不可以嵌套定义

void test()
{
}


int main()
{
   return 0;    可以
}



int main()
{
     void test()
{

}

   return 0;    不可以
}

链式访问(把一个函数的返回值作为另一个函数的参数)

int main()
{
	//int len = strlen("abcdef");
	//printf("len = %d\n", len);
    
    上面两句代码可以写成:

	printf("len = %d\n", strlen("abcdef"));

	return 0;
}

2.2练习:以下代码输出的结果是什么

int main()
{
	
	printf("%d", printf("%d", printf("%d", 43)));

	return 0;
}

答案:4321

解释:首先打印43,(printf返回值是int类型,printf返回打印字符的个数).再依次返回2,1


3.1:函数的声明+导入静态库

//int Add(int x,int y);   //让编译器不再报警告,相当于告诉编译器我这里有一个Add函数
                          //返回类型int,函数名Add,函数参数int x,int y



int main()
{
	int a = 10;
	int b = 20;
	int sum = Add(a, b);//函数的使用
	printf("%d\n", sum);
	return 0;
}

int Add(int x, int y)
{
	return x + y;
}

Add函数在main函数之后,编译器从main函数开始没找到Add函数,报警告.

想让编译器不报警报,我们需要在主函数前写一个函数申明

我们也可以新建一个头文件Add.h,把函数申明放到头文件中

不想暴露自己写好的代码,我们可以把源文件编译成静态库(右击源文件->属性)

静态库里面存放的是二进制信息,找到Add.lib文件和Add.h文件

使用Add函数,既不暴露自己所写源文件,又让别人可以使用

#include "add.h" //包含头文件

#pragma comment(lib, "add.lib")//导入静态库

库函数的原理也是一样,(你已经学会了使用!接下来写一个编译器吧)


4.1:递归与迭代

什么是递归?

程序调用自身的编程技巧称为递归

通常把一个大型复杂问题层层转化为一个和原问题相似,规模较小的问题来求解

少量的代码解决多次重复运算,减少代码量

递归主要思考方式:大事化小

递归的两个必要条件

1:存在限制条件,当满足这个限制条件的时候,递归便不再继续。

2:每次递归调用之后越来越接近这个限制条件。


练习4.1:接受一个整型值(无符号),按照顺序打印它的每一位

例如1234,输出1 2 3 4

void print(int n)
{
	if (n > 9)  //1:存在限制条件,当满足这个限制条件的时候,递归便不再继续。
	{
		print(n/10);  //2:每次递归调用之后越来越接近这个限制条件。
	}
	printf("%d ", n % 10);
}


int main()
{
	unsigned int num = 0;
	scanf("%d", &num);//1234
    
    //解题步骤:
    //1234%10=4   得到4
	//1234/10=123
	//123%10=3    得到3
	//...
	//[4 3 2 1]   得到4 3 2 1
	//

	print(num);//print函数可以把num的每一位按照顺序打印出来

	return 0;
}

递归

 递归结束返回


 练习4.2:编写函数不允许创建临时变量,求字符串的长度

 不使用递归,一般写法


int my_strlen(char* str)
{
	int count = 0;//统计字符的个数
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}


int main()
{
	char arr[] = "abcd";
	//char* str = arr;
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

使用递归

//my_strlen("abcdef")
//1+my_strlen("bcdef")
//1+1+my_strlen("cdef")
//1+1+1+ my_strlen("def")
//1+1+1+1+ my_strlen("ef")
//1 + 1 + 1 + 1 +1+my_strlen("f")
//1 + 1 + 1 + 1 + 1 + 1+ my_strlen("")
//1 + 1 + 1 + 1 + 1 + 1 + 0 = 6


int my_strlen(char* str)
{
	if (*str != '\0')
		return 1 + my_strlen(str+1);
	else
		return 0;
}

int main()
{
	char arr[] = "abcd";
	//char* str = arr;
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

 


4.3:求n的阶乘(只要知道数学公式,它就是递归代码)

   n<=1 时 ,n =1;

   n>1 时  n!=nx(n-1)

int fac1(int n)
{
	if (n <= 1)
		return 1;
	else
		return n * fac(n - 1);  
}

int fac(int n)
{
	int i = 0;
	int ret = 1;
	for (i = 1; i <= n; i++)
	{
		ret = ret * i;
	}
	return ret;                 //非递归,迭代
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fac(n);
	printf("%d\n", ret);

	return 0;
}

4.4:求第n个斐波那契数(前两个数相加等于第三个数)

递归
int fib(int n)

	if (n <= 2)
		return 1;
	else
		return fib(n - 1) + fib(n - 2);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d\n", ret);
	return 0;
}

当要求的斐波那契数很大,重复计算的过程也就很大,这时递归的算法没有那么好,计算第50个斐波那契数,编译器要执行的时间非常久

求解F(n),必须先计算F(n-1)和F(n-2),计算F(n-1)和F(n-2),又必须先计算F(n-3)和F(n-4)。。。。。。以此类推,直至必须先计算F(1)和F(0),然后逆推得到F(n-1)和F(n-2)的结果,从而得到F(n)要计算很多重复的值,在时间上造成了很大的浪费,算法的时间复杂度随着N的增大呈现指数增长,时间的复杂度为O(2^n),即2的n次方

非递归
int fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;

	while (n>2)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}

	return c;
}


int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d\n", ret);
	return 0;
}

 从n(>2)开始计算,用F(n-1)和F(n-2)两个数相加求出结果,这样就避免了大量的重复计算,它的效率比递归算法快得多,算法的时间复杂度与n成正比,即算法的时间复杂度为O(n).

猜你喜欢

转载自blog.csdn.net/weixin_63543274/article/details/123287040