函数这一篇章我们主要学习一下内容:
目录
函数是什么?
c语言中函数的分类:
1. 库函数
2. 自定义函数
库函数:
库函数相当于一个已经包装好的工具,我们只要用就可以了。我没有必要重复造轮子。
我们可以到
网站来看具体有哪些库函数。
- IO函数:printf, scanf, getchar, putchar
- 字符串操作函数:strcmp, strlen
- 字符操作函数:toupper
- 内存操作函数:memcpy, memcmp, memset
- 时间/日期函数:time
- 数学函数:sqrt, pow
- 其他库函数:
strcpy:
#include<string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello";
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
str 字符串, cpy = copy, 就可以简单理解为复制字符串, strcpy(a, b), 就是将b的内容复制到a的内容中。可以点击标题仔细看清细节。
memset:
翻译过来就是把起始地址,你想要更改的符号,和更改符号的数量,就能够将一个起始地址的内容更改。
int main()
{
char arr[] = "zaoshanghaowoaichibingchiling";
memset(arr, 'x', 5);
printf("%s\n", arr);
return 0;
}
自定义函数
我们的库函数始终是有限的,所以我们就需要创建一些自定义函数。
ret_type fun_name(para1, *)
{
statement;// 语句项
}
ret_type 返回类型
fun_name 函数名
paral 函数参数
比如我们想写一个哪个数字更大的函数:
int get_max(int x, int y)
{
if (x > y)
{
return x;
}
else
return y;
}
int main()
{
int a = 10;
int b = 20;
int max = get_max(a, b);
printf("%d\n", max);
return 0;
}
主函数里调用的get_max函数内的参数都是实际存在的a和b——实际参数(实参),而我们创建的函数get_max的括号内x和y都是只在函数内有用的参数——形式参数(形参),到了函数外就被销毁了。所以参数名和主函数的实参一样也没有问题。只有要调用的时候形参才会开辟空间。
交换两个数的函数:
void Swap(int x, int y)// 在被调用的时候,实参传给形参,形参是实参的一份拷贝,改变形参,不能改变实参
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
int b = 20;
// 交换2个整型变量的值
printf("交换前:a = %d, n = %d\n", a, b);
Swap(a, b);
printf("交换后:a = %d, n = %d\n", a, b);
return 0;
}
我们发现结果并没有交换。
我们通过调试我们发现,我们的形参x和y的地址和对应的a和b的地址完全不同,所以修改值的时候,我们的a和b不会被修改,所以我们就需要用指针,直接找到a和b的地址然后改变对应的值。
void Swap(int* x, int* y)
{
int tmp = 0;
tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 10;
int b = 20;
int* pa = &a;
int* pb = &b;
// 交换2个整型变量的值
printf("交换前:a = %d, n = %d\n", a, b);
Swap(pa, pb);
printf("交换后:a = %d, n = %d\n", a, b);
return 0;
}
第一个swap是传值调用,第二个swap是传址调用。
练习:
1.打印100-200的素数。
int is_prime(int i)
{
int a = 2;
for (a = 2; a <= sqrt(i - 1); a++)
{
if (i % a == 0)
{
return 0;
}
}
return 1;
}
int main()
{
int i = 0;
for (i = 100; i <= 200; i++)
{
//判断i是不是素数
int x = is_prime(i);
if (x)
{
printf("%d\n", i);
}
}
return 0;
}
2.写一个函数,实现一个整数有序数组的二分查找。
int search(int a, int arr[], int n)
{
int Left = 0;
int Right = n - 1;
int Mid = (n - 1) / 2;
while (Left <= Right)
{
if (a > arr[Mid])
{
Left = Mid + 1;
Mid = (Left + Right) / 2;
}
else if (a < arr[Mid])
{
Right = Mid - 1;
Mid = (Left + Right) / 2;
}
else if (a == arr[Mid])
return 1;
}
}
int main()
{
int a = 7;
int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int n = sizeof(arr) / sizeof(arr[0]);
if (search(a, arr, n) == 1)
{
printf("找到了");
}
else
{
printf("没找到");
}
return 0;
}
3.每调用一次,num增加1
void Add(int* x)
{
(*x)++;
}
int main()
{
int num = 0;
Add(&num);
printf("%d\n", num);
Add(&num);
printf("%d\n", num);
Add(&num);
printf("%d\n", num);
return 0;
}
函数的嵌套调用和链式访问
void test3()
{
printf("hehe\n");
}
void test2()
{
test3();
}
int main()
{
test2();
return 0;
}
但是我们不能够嵌套定义。
int main()
{
int len = strlen("abc");
printf("%d\n", len);
printf("%d\n", strlen("abc"));// 这个就是链式访问,就是一个函数的返回值作为别的函数的参数。
return 0;
}
int main()
{
char arr1[] = "abc";
char arr2[20] = {0};
printf("%s\n", strcpy(arr2, arr1));
}
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
这里的结果为什么是4321呢,我们去cplusplus网站看一下printf函数的定义。
当我们的函数定义在后面的时候,我们就需要函数的声明。
int main()
{
int a = 10;
int b = 20;
//int Add(int, int);
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
只要把主函数里面的那句注释掉的语句还原就能声明成功
int Add(int, int);
但在一般情况下,函数的声明是放在函数的头文件里面的。
我们声明的时候放在.h头文件,定义的时候放在.c文件。
然后我们要用加函数和减函数的时候在我们要用的文件里使用。
#include "add.h"
#include "Sub.h"//函数的定义放在对应的.c文件和声明放在对应的.h文件。
int main()
{
int a = 10;
int b = 20;
int c = Add(a, b);
int d = Sub(a, b);
printf("%d\n", c);
printf("%d\n", d);
return 0;
}
将声明和定义分别放在两个不同的文件的意义在于,不想被别人直接看到函数的源码。
函数的递归
函数的调用就是自己调用自己。
int main()
{
printf("%d\n", 1);
main();
return 0;
}
这里死循环的原因就是我们一直循环,没有停止循环的条件,直到栈溢出。
递归的主要思考方式:把大事化小。
递归的两个必要条件:
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件。
但即使这样递归也有可能有错误。
void test(int n)
{
if(n < 100000)
{
test(n + 1);
}
}
int main()
{
test(1);
return 0;
}
这里的原因是因为条件的达成条件过于难,不断循环导致栈溢出。
栈溢出
写递归代码的时候:
1. 不能死递归,都要有跳出条件,每次递归逼近跳出条件
2. 递归层次不能太深。
练习1:
接受一个整型值(无符号),按照顺序打印它的每一位,例如:输入:1234,输出1,2, 3, 4,
void print(unsigned int n)
{
if (n > 9)
{
print(n / 10);
}
printf("%d", n % 10);
}
int main()
{
unsigned int num = 0;
scanf("%u", &num);// 1234 unsigned int 的scanf接受&n
print(num);
return 0;
}
// print(1234)
// print(123) 4
// print(12) 3 4
// print(1) 2 3 4
// print(0) 1 2 3 4
练习2
编写函数不允许创建临时变量,求字符串长度。
// 模拟实现一个strlen函数
int MyStrlen(char* str)
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "bit";
printf("%d\n", strlen(arr));
printf("%d\n", MyStrlen(arr));
return 0;
}
这个代码并不符合要求,因为它临时创建了变量。
// 模拟实现一个strlen函数
int MyStrlen(char* str)
{
if (*str != '\0')
{
return 1 + MyStrlen(str + 1);
}
else
return 0;
}
int main()
{
char arr[] = "bit";
printf("%d\n", strlen(arr));
printf("%d\n", MyStrlen(arr));
return 0;
}
// MyStrlen("bit")
// 1 + MyStrlen("it")
// 1 + 1 + MyStrlen("t")
// 1 + 1 + 1 MyStrlen("")
// 1 + 1 + 1 + 0
练习3:
求n的阶乘。(不考虑溢出)
int Fac(int n)
{
if (n != 0)
{
return n * Fac(n - 1);
}
else
return 1;
}
int main()
{
int n = 0;
scanf("%d", &n);
int num = Fac(n);
printf("%d\n", num);
return 0;
}
练习4:
求第n个斐波那契数
int Fei(int n)
{
if (n <= 2)
return 1;
else
return Fei(n - 2) + Fei(n-1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int num = Fei(n);
printf("%d\n", num);
return 0;
}
如果我们输入50的时候,我们会发现效率非常低,结果出来的非常慢。
当我们计算这个算法在n==3的时候计算多少次的时候,输入40找第40个斐波那契数,我们发现count的数量极大。
我们发现这个问题用递归的方式,效率非常的低。
int Fei(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 num = Fei(n);
printf("%d\n", num);
return 0;
}
将前三个元素分别设为a, b, c。我们通过a和b相加的值存储到c中,b的值传到a,c的值传到b。进行传递,参数一直是只有a,b和c。
这就是迭代的方式(迭代)
但是我们发现有问题:
- 在使用fei函数的时候如果我们要计算第50个斐波那契数的时候特别耗费时间。
- 视同factorial函数求100000的阶乘(不考虑结果的正确性),程序会崩溃
函数递归的经典问题:
1. 汉诺塔问题
2. 青蛙跳台问题