- 函数是什么
- 库函数
- 自定义函数
- 函数参数
- 函数调用
- 函数的嵌套调用和链式访问
- 函数的声明和定义
- 函数递归
函数是什么?
维基百科定义:子程序
在计算机科学中,子程序是一个大型程序中的部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
C语言中函数分类:
- 库函数
- 自定义函数
库函数
库函数学习工具:C语言库函数网站,C参考手册
常用库函数:
- IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
注:使用库函数,必须包含#include对应的头文件
自定义函数
自定义函数=返回值类型+函数名+函数参数
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type - 返回值类型
fun_name - 函数名
para1 - 函数参数
例1:取最大值
#include <stdio.h>
//定义函数
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("max = %d\n", max);
return 0;
}
例2:交换
#include <stdio.h>
void Swap(int x, int y)//void代表没有返回值
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("a = %d b = %d\n", a, b);
Swap(a, b);
printf("a = %d b = %d\n", a, b);
return 0;
}
//输出结果:
//a = 10 b = 20
//a = 10 b = 20
以上代码并不能交换a和b的值,原因是x和y存储的位置与a和b并不相同,操作结果只是交换了x和y。
改进代码:运用取地址&,和解地址*
#include <stdio.h>
void Swap2(int* pa, int* pb)
{
int tmp = 0;
tmp = *pa;
*pa = *pb;
*pb = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("a = %d b = %d\n", a, b);
Swap2(&a, &b);
printf("a = %d b = %d\n", a, b);
return 0;
}
//输出结果:
//a = 10 b = 20
//a = 20 b = 10
函数参数
实际参数
真实传给函数的参数,叫实参。实参可以是常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
形式参数
形参是指函数名后括号中的变量,形参只有在函数被调用的过程中才实例化(分配内存单元)。当函数调用完成之后形参就自动销毁了,因此形参只在函数中有效。
结论:形参其实是实参的一份临时拷贝,对形参的修改不会改变实参。
函数调用
传值调用
函数的形参和实参分别占用不同的内存块,对形参的修改不会影响实参。
传址调用
把函数外部创建变量的内存地址传递给函数参数,函数内部可以直接操作函数外部的变量。
函数的嵌套调用和链式访问
嵌套调用
一个函数中调用了另一个函数。
例1:
#include <stdio.h>
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;
}
链式访问
把一个函数的返回值作为另一个函数的参数。
例1:
#include <stdio.h>
int main()
{
int len
//普通方式
len = strlen("abc");
printf("%d\n", len);
//链式访问
printf("%d\n", strlen("abc"));
return 0;
}
例2(易错):
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
//输出结果是4321
//注:printf函数的返回值是打印函数的个数,返回值类型为int
printf("%d", printf("%d", printf("%d", 43)));//打印43,printf("%d", 43)返回2
printf("%d", printf("%d", 2));打印2,printf("%d", 2)返回1
printf("%d", 1);打印1
函数的声明和定义
函数声明
告诉编译器有这么一个函数(函数名、参数、返回类型),具体是否存在不一定
函数定义
函数具体的实现,交代函数的功能。
例1:
#include <stdio.h>
//函数声明
int Add(int x, int y);
int main()
{
int a = 10;
int b = 20;
int sum = 0;
//函数调用
sum = Add(a, b);
printf("%d\n", sum);
return 0;
}
//函数定义
int Add(int x, int y)
{
int z = x + y;
return z;
}
- 如果函数定义写在函数调用前,则无需函数声明。
- 实践中,函数声明写在头文件(如add.h)里,函数定义写在源文件(如add.c)里,函数调用前只需要在源文件(如test.c)中写上 #include"add.h"即可。
函数递归
递归的定义
程序调用自身的编程技巧称为递归。递归的主要思考方式在于“把大事化小”。
递归的两个必要条件
- 存在限制条件,满足限制条件时递归终止;
- 每次递归调用之后越来越接近限制条件。
例1:接受一个无符号整型值,按顺序打印它的每一位。如输入1234,输出1 2 3 4。
#include <stdio.h>
void print(int n)
{
if(n > 9)
{
print(n/10);
}
printf("%d", n%10);//%是mod取余的意思
}
int main()
{
unsigned int num = 0;
scanf("%d", &num);//输入1234
print(num);
return 0;
}
例2:编写函数不允许创建临时变量,求字符串的长度。
#include <stdio.h>
int my_strlen(char* str)
{
if(*str != '\0')
return 1 + my_strlen(str+1);//移动到下一个地址,即数组中下一个元素的地址
else
return 0;
}
int main()
{
char arr[] = "bit";
int len = my_strlen(arr);//arr是数组,数组传参,传过去的不是整个数组,而是第一个元素的地址
printf("len = %d\n", len);
return 0;
}
例3:求n的阶乘,不考虑溢出
#include <stdio.h>
int Fac1(int n)//循环的方式
{
int i = 0;
for(i=1; i<=n; i++)
{
ret *= i;
}
return 0;
}
int Fac2(int n)//递归的方式
{
if(n <= 1)
return 1;
else
return n*Fac2(n-1);
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d", &n);
ret = Fac1(n);//循环的方式
ret = Fac2(n);//递归的方式
printf("%d\n", ret);
return 0;
}
例4:求第n个斐波那契数,不考虑溢出
#include <stdio.h>
int Fib(int n)
{
if(n <= 2)
return 1;
else
return Fib(n-1) + Fib(n-2);
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d", &n);
ret = Fib(n);
printf("ret = %d\n", ret);
return 0;
}
上述代码的缺点在于运行速度慢,而且有许多计算是重复的,这也是改进代码的突破口。
改进后:
#include <stdio.h>
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;
int ret = 0;
scanf("%d", &n);
ret = Fib(n);
printf("ret = %d\n", ret);
return 0;
}
- 当递归和循环都能解决问题的时候,选择简单的即可。
- 递归容易出现“栈溢出”,此时改为用循环解决问题更合适。