42 函数讲解
库函数
库函数集合网址:cplusplus.com - The C++ Resources Network
学会看文档查找学习库函数的使用
C/C++官网:cppreference.com
自定义函数
函数返回类型的地方写:void,表示这个函数不返回任何值,也不需要返回
函数的参数
实参:实际参数简称“实参”。在调用有参函数时,函数名后面括号中的参数称为“实际参数”,实参可以是常量、变量或表达式。
形参:自定义函数中的“形参”全称为"形式参数" 由于它不是实际存在变量,所以又称虚拟变量。实参和形参可以重名。是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
当实参传递给形参时,形参其实是实参的一份临时拷贝,对形参的修改是不会改变实参的。
45 函数的调用
传值调用:函数的形参和实参分别占有不同的代码块,对形参的修改不会影响实参。
传址调用:把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式,函数内部可以直接操作函数外部的变量。
//判断素数函数(打印100-200之间的素数)
#include<math.h>
int is_prime(int n)
{
int i = 0;
for (i = 2; i <= sqrt(n); i++) {
if (n % i == 0)
return 0;
}
return 1;
}
int main()
{
int i = 0;
for (i = 1; i <= 200; i++)
{
if (is_prime(i) == 1)
printf("%d ", i);
}
return 0;
}
函数的功能单一足够独立比较好,可用性强
//判断闰年函数
int isaddyear(int year)
{
if (year % 100!=0&&year % 4 == 0)
return 1;
else if (year % 400 == 0)
return 1;
else
return 0;
}
int main()
{
int year = 0;
for (year = 1000; year <= 2000; year++)
{
if (isaddyear(year) == 1)
{
printf("%d ", year);
}
}
return 0;
}
//二分查找函数
//本质上arr是一个指针
int binary_search(int arr[], int k,int sz)//这里的arr不是数组,传递过去的是arr首元素的地址,本质上是个指针
{
//int sz = sizeof(arr) / sizeof(arr[0]);不能在这里求数组个数
int left = 0;
int right = sz;
while (left <= right)
{
int mid = (left + right) / 2;//不能放到循环外
if (arr[mid]> k)
{
right = mid-1;
}
else if (arr[mid] < k)
{
left = mid + 1;
}
else
return mid;
}
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int k = 17;
int sz = sizeof(arr) / sizeof(arr[0]);
//arr传递过去的是数组首元素地址
int ret=binary_search(arr, k,sz);
if (ret == -1)
{
printf("找不到\n");
}
else
printf("找到了,数组下标为%d", ret);
return 0;
}
如果函数内部需要参数部分传入某个数组元素的个数,一定是在外部求好传入的,函数内部无法求出数组元素个数
//函数每次调用sum自加1
void Add(int* p)
{
(*p)++;
}
int main()
{
int sum = 0;
Add(&sum);
printf("%d\n", sum);
Add(&sum);
printf("%d\n", sum);
Add(&sum);
printf("%d\n", sum);
}
47 函数的嵌套调用和链式访问
- 函数的嵌套调用:函数和函数之间可以有机的组合
- 函数的链式访问:把一个函数的返回值作为另外一个函数的参数
函数的声明和定义
48 函数递归
函数递归:程序调用自身
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法
主要思考方式:大事化小
- 递归容易栈溢出
递归的两个必要条件:
- 存在限制条件,满足后不在继续
- 每次递归调用后越来越接近这个限制条件
栈区:局部变量、函数形参
堆区:动态开辟的内存(malloc,calloc等)
静态区:全局变量,static修饰的变量
//接受一个无符号整型值,按照顺序打印它的每一位。(例如输入:123,输出:1 2 3)
void print(int n)
{
if (n>9)
{
print(n/10);
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 0;
scanf("%d", &num);
print(num);//递归
return 0;
}
//模拟实现strlen函数
int my_strlen(char* str)
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "skyesun";
int len = my_strlen(arr);//arr是数组,数组传参,传过去的不是整个数组,而是第一个元素的地址
printf("len=%d\n", len);
return 0;
}
//要求不创建临时变量:递归的办法(编写函数不允许创建临时变量,求字符串的长度)
int my_strlen(char* str)
{
if (*str != '\0')
return 1 + my_strlen(str + 1);
else return 0;
}
递归与迭代
递归和迭代的区别:
一、含义不同:
递归是重复调用函数自身实现循环。迭代是函数内某段代码实现循环,循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值。
递归循环中,遇到满足终止条件的情况时逐层返回来结束。迭代则使用计数器结束循环。当然很多情况都是多种循环混合采用,这要根据具体需求。
二、结构不同:
递归与迭代都是基于控制结构:迭代用重复结构,而递归用选择结构。 递归与迭代都涉及重复:迭代显式使用重复结构,而递归通过重复函数调用实现重复。
递归与迭代都涉及终止测试:迭代在循环条件失败时终止,递归在遇到基本情况时终止,使用计数器控制重复的迭代和递归都逐渐到达终止点:迭代一直修改计数器,直到计数器值使循环条件失败;递归不断产生最初问题的简化副本,直到达到基本情况。
//递归求n的阶乘
int factorial(int n)
{
if (n <= 1)
return 1;
else
return n * factorial(n - 1);
}
//求第n个斐波那契数
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);//会有重复大量的计算,效率低
}
//用循环解决效率高
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;
}
课后思考题
1.汉诺塔问题
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
当数量为3时的动态演示:
算法分析:递归
第一次移动,要把A柱子上的前n-1个移动到B柱子上;(图1)
第二次移动,直接把A柱子上的最后一个移动到C柱子上;(图2)
第三次移动,把B柱子上的n-1个柱子通过柱子A移动到柱子C上。(图3)
代码实现:
void Hanoi(int n, char a, char b, char c);
void Move(int n, char a, char b);
int count;
int main()
{
int n = 8;
printf("The number of floors of the Tower of Hanoi:\n");
scanf(" %d", &n);
Hanoi(n, 'A', 'B', 'C');
return 0;
}
void Hanoi(int n, char a, char b, char c)
{
if (n == 1)
{
Move(n, a, c);
}
else
{
Hanoi(n - 1, a, c, b);//把A柱子上的前n-1个移动到B柱子上
Move(n, a, c);//把A柱子上的最后一个移动到C柱子上
Hanoi(n - 1, b, a, c);//把B柱子上的n-1个移动到柱子C上
}
}
void Move(int n, char a, char b)
{
count++;
printf("%d step Move %d: Move from %c to %c !\n", count, n, a, b);
}
2.青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
如果n=1,只有一种跳法,那就是1
如果n=2,那么有两种跳法,2,[1,1]
如果n=3,那么有三种跳法,[1,1,1],,[1,2],[2,1]
如果n=4,那么有五种跳法,[1,1,1,1],[1,1,2],[1,2,1],[2,1,1],[2,2]
如果n=5,那么有八种跳法,[1,1,1,1,1],[1,1,1,2],[1,1,2,1],[1,2,1,1],[2,1,1,1],[2,2,1],[2,1,2],[1,2,2]
结果为1,2,3,5,8 ,是斐波那切数列
递归做法:
public static int jump(int n){
if (n==0)
return 0;
if (n==1)
return 1;
if (n==2)
return 2;
return jump(n-1)+jump(n-2);
}
非递归做法:
public static int jump2(int n){
if (n==0)
return 0;
if (n==1)
return 1;
if (n==2)
return 2;
int n1=1;
int n2=2;
int count=2;
while (count++<=n){
int tmp=n1;
n1=n2;
n2=tmp+n2;
}
return n2;
}
思考题参考链接:
汉诺塔递归调用(C语言实现)_一禅的师兄的博客-CSDN博客_汉诺塔c语言
算法—青蛙跳台阶问题汇总_zhangshk_的博客-CSDN博客_青蛙跳台阶
53 函数作业讲解
//字符串逆序(递归实现)编写一个函数reserve_string(char * string)(递归实现)
int my_strlen(char* str)
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
//void reverse_string(char* str)
//{
// int left = 0;
// int right = my_strlen(str)-1;
//
// while (left<right)
// {
// char tmp = str[left];
// str[left] = str[right];
// str[right] = tmp;
// left++;
// right--;
// }
//}
//void reverse_string(char* str)
//{
// int left = 0;
// int right = my_strlen(str) - 1;
//
// while (left < right)
// {
// char tmp = *(str + left);//str[left];
// *(str + left) = *(str+right);
// *(str+right) = tmp;
// left++;
// right--;
// }
//}
void reverse_string(char* str)
{
char tmp = *str;//1
int len = my_strlen(str);
*str = *(str + len - 1);//2
*(str + len - 1) = '\0';//3
//判断条件
if(my_strlen(str+1)>=2)
{
reverse_string(str + 1);//4
}
*(str + len - 1) = tmp;//5
}
int main()
{
char arr[] = "abcdef";
reverse_string(arr);//数组名arr是数组arr首元素的地址
printf("%s\n", arr);//fedcba
return 0;
}
//计算一个数的每位之和(递归实现)
int DigitSum(int n)
{
if (n > 9)
{
return DigitSum(n / 10) + n % 10;
}
else
{
return n;
}
}
//编写一个函数实现n的k次方,使用递归实现
double Pow(int n, int k)
{
if (k == 0)
return 1.0;
else if (k > 0)
return n * Pow(n, k - 1);
else
return 1.0 / (Pow(n, -k));
}