函数
1.函数分类
a.库函数
IO函数
字符串操作函数
字符操作函数
内存操作函数
时间/日期函数
数学函数
其他库函数
b.自定义函数
注:但是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件。
2.自定义函数
自定义函数和库函数一样,有函数名,返回值和函数参数
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1 函数参数
//get_max函数设计-找出两个整数最大值
int get_max(int x, int y)
{
return (x > y) ? (x) : (y);
}
int main()
{
int num1 = 10;
int num2 = 20;
int max = get_max(num1, num2);
printf("max=%d\n", max);
return 0;
}
//交换整形
void swap1(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
void swap2(int *px, int *py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int num1 = 0;
int num2= 2;
swap1(num1, num2);
printf("swap1:num1=%d num=%d\n", num1, num2);
swap2(&num1, &num2);
printf("swap2:num1=%d num=%d\n", num1, num2);
return 0;
}
3.函数的参数
3.1实际参数(实参)
真实传给函数的参数,叫实参;
实参:常量、变量、表达式、函数;
无论实参是何种类型的变量,在进行函数调用时,他们都必须有确定的值,以便把这些值传给形参。
3.2形式参数(形参)
形参是指函数后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用后就自动销毁了。因此形式参数只有在函数中才有效。
上述swap1和swap2函数中的参数x,y,px,py都是形参。
main函数中的num1和num2,&num1和num2都是实参。
代码对应的内存分配如下:
这里可以看到 Swap1 函数在调用的时候, x , y 拥有自己的空间,同时拥有了和实参一模一样的内容。
所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。
4.函数的调用
4.1传值调用
函数的形参和实参分别占有不同的内存块,对形参修改不会影响实参。
4.2传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参函数可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
4.3联系
- 写一个函数可以判断一个数是不是素数。
素数的定义:只能被常数1或自己整除,不能被其他整数整除的正整数。
https://blog.csdn.net/weixin_52133421/article/details/122860206添加链接描述
int main()
{
int x;
int isprime = 1;//定义变量isprime并赋初值为1,x为素数;
scanf_s("%d", &x);
for (int i = 2; i < x; i++)
{
if (x % i == 0)
{
isprime = 0;
break;
}
}
if (isprime == 0)
printf("%d is not a prime number",x);
else
printf("%d is a prime number", x);
return 0;
}
-
写一个函数判断一年是不是闰年。
-
写一个函数,实现一个整形有序数组的二分查找。
程序:添加链接描述
int erfen(int arr[], int k, int sz)
{
int left = 0;
int right = sz - 1;
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;
}
}
int main()
{
int k = 8;
int arr[] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
int sz = sizeof(arr) / sizeof(arr[0]);
int ret = erfen(arr, k, sz);//用ret存储找到数字的下标
if (ret == -1)
{
printf("找不到指定的数字\n");
}
else
{
printf("找到了,下标是:%d\n", ret);
}
return 0;
}
- 写一个函数,每调用一次这个函数,就会将 num 的值增加1。
void add(int *i)
{
(*i)++;
}
int main()
{
int num = 0;
add(&num);
printf("%d\n", num);
add(&num);
printf("%d\n", num);
return 0;
}
void:函数调用不需要返回值时;
int:函数调用返回一个int型数;
5.函数的嵌套调用和访问
函数和函数之间可以根据实际需求进行组合,也就是互相调用;
5.1嵌套调用
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i = 0;
for(i=0; i<3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
函数可以嵌套使用,但不能嵌套定义。
5.2链式访问
把函数的返回值作为另一个函数的参数。
int main()
{
char arr[20] = "hello";
int ret = strlen(strcat(arr, "bit"));
//strlen所作的是一个计数器的工作,它从内存的某个位置
//(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,
//直到碰到第一个字符串结束符'\0'为止,然后返回计数器值(长度不包含'\0')。
//strcat将两个char类型连接。
printf("%d\n", ret);
printf("%d\n", printf("%d", printf("%d", 43)));
结果是啥?
注:printf函数的返回值是打印在屏幕上字符的个数
return 0;
}
//strlen所作的是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符’\0’为止,然后返回计数器值(长度不包含’\0’)。
//strcat将两个char类型连接。
注:printf函数的返回值是打印在屏幕上字符的个数
6.函数的声明和定义
6.1函数声明
1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
2.函数的声明一般出现在函数使用之前,要满足先声明后使用。
3.函数的声明一般要放在头文件中。
6.2函数定义
函数的定义市值函数的具体实现,交代函数的功能实现。
test.h的内容
放置函数的声明
#ifndef __TEST_H__
#define __TEST_H__
//函数的声明
int Add(int x, int y);
#endif //__TEST_H__
test.c的内容
放置函数的实现
#include "test.h"
//函数Add的实现
int Add(int x, int y)
{
return x+y;
}
这种分文件的书写形式
7.函数递归
7.1什么是递归
1.程序调用自身的编程技巧称为递归。
2.递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的
3.一种方法,它通常把一个大型的问题层层转化成一个原问题相似的规模较小的问题求解,递归策略
4.只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
7.2递归的两个必要条件
1.存在限制条件,当满足这个限制条件之后,递归便不再继续。
2.每次递归调用之后越来越接近这个条件。
7.2.1练习1:
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4.
void print(int n)
{
if(n>9)
{
print(n/10);
}
printf("%d ", n%10);
}
int main()
{
int num = 1234;
print(num);
return 0;
}
#### 7.2.2练习2:
编写函数不允许创建临时变量,求字符串的长度。
strlen(“abc”)
第一个不是斜杠0,那就说明为1+strlen(“bc”);
1+1+strlen(“c”);
1+1+1+strlen(“”);
1+1+1+0=3
```c
#incude <stdio.h>
int Strlen(const char*str)
{
if(*str == '\0')
return 0;
else
return 1+Strlen(str+1);
}
int main()
{
char *p = "abcdef";
int len = Strlen(p);
printf("%d\n", len);
return 0;
}
return 1+Strlen(str+1);//str+1→下一个字母的地址;
![在这里插入图片描述](https://img-blog.csdnimg.cn/9607f5a79e4744cfa070454557e7574b.png)str+1(指向地址变了,+1后str没变,传给的值变了)→str++(后置++:先使用再++,传进去以后,即+1之前的,所以+1的效果没有,所以不行)
→++str(不鼓励,带有副作用,确实+1之后传递了,但是会改变留下的str)
### 7.3递归与迭代
#### 7.3.1练习3:求n的阶乘。(不考虑溢出)
```c
int factorial(int n)
{
if (n <= 1)
return 1;
else
return n * factorial(n - 1);
}
int main()
{
int n, t;
scanf_s("%d", &n);
t = factorial(n);
printf("%d", t);
return 0;
}
7.3.2练习4:求第n个斐波那契数
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
int main()
{
int n, t;
scanf_s("%d", &n);
t = fib(n);
printf("%d", t);
return 0;
}
但是我们发现有问题;
在使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。
使用 factorial 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。
为什么呢?
我们发现 fib 函数在调用的过程中很多计算其实在一直重复。
如果我们把代码修改一下:
int count = 0;//全局变量
int fib(int n)
{
if(n == 3)
count++;
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
改进:
1.在调试factorial函数的时候,如果你的参数比较大,那就会报错:stack overflow(栈溢出)这样的信息;
系统分配给程序的占空间是有限的,但是如果出现了死循环忙活着死递归,这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们成为栈溢出。
如何解决上述问题:
1.将递归改写成非递归;
2.使用static对象代替nonstatic局部对象。在递归函数设计中,可以使用static对象了代替nonstatic局部(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
n的阶乘:
int factorial(int n)
{
int result = 1;
while (n > 1)
{
result *= n;
n -= 1;
}
return result;
}
int main()
{
int n, t;
scanf_s("%d", &n);
t = factorial(n);
printf("%d\n", t);
return 0;
}
i
斐波那契数
nt fib(int n)
{
int result;
int pre_result;
int next_older_result;
result = pre_result = 1;
while (n > 2)
{
n-= 1;
next_older_result = pre_result;
pre_result = result;
result = pre_result + next_older_result;
}
return result;
}
int main()
{
int n, t;
scanf_s("%d", &n);
t = fib(n);
printf("%d\n", t);
return 0;
}
提示:
-
许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
-
但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
-
当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开
销。