目录
函数是什么
函数是一个大型程序中的某部分代码,由一个或多个语句块组成,它负责完成某项特定的项任务,而且相较于其他代码,具备相对的独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏,这些代码通常被集成为软件库。
C语言中的函数
库函数
自定义函数
库函数
C语言已经封装的好的函数,通过头文件引用后就可以使用。
自定义函数
函数的格式
return_value fun_name(para,......)
{
statement;
}
return_value 返回值
fun_name 函数名
para 参数
statement 语句项(函数体)
举例
//找出两个整数中最大的值
int get_max(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int a = 10, b = 20, c;
c=get_max(a,b);
printf("%d", c);
system("pause");
return 0;
}
上面的代码输出的结果是20,在参数传递后,get_max函数通过比较得到了最大的值。
//交换两个整型变量的内容
void swap(int a, int b)//一个函数只能有一个返回值,所以需要改变多个参数的结果时,返回值没有意义
{
int x;
x = a;
a = b;
b = x;
}
int main()
{
int a = 10, b = 20;
printf("交换前%d %d\n", a, b);
swap(a, b);
printf("交换后%d %d\n", a, b);
system("pause");
return 0;
}
上面的代码结果并没有对a和b进行交换,原因在于swap函数只交换了x和y的值,但是x,y与a,b的地址并不相同,是完全不同的变量,所以这样的改变没有意义,同时也说明了函数的形参只是实参的临时拷贝。
函数的参数
实际参数(实参)
真实传递给函数的参数叫实参。
实参的类型是多样的,可以是常量,变量,表达式,函数等。
无论实参是何种类型的量,在传递给函数的时候必须是确定的值,以便把值传递给形参。
上面的例子中a和b就是实参。
形式参数(形参)
形式参数是函数名后括号中的变量,在函数被调用时才实例化(分配内存单元)。
形参当函数调用完之后就销毁了,因此形参只有在函数中才有效。
形参是实参的一份临时拷贝。
//交换两个整型变量的内容
void swap(int a, int b)//a 和 b 是形参,在调用时,a和b才被创建
int x;
x = a;
a = b;
b = x;
}
int main()
{
int a = 10, b = 20;
printf("交换前%d %d\n", a, b);
swap(a, b);
printf("交换后%d %d\n", a, b);
system("pause");
return 0;
}
函数的调用
传值调用
把值传递给形参,这种传递方式,形参与实参拥有不同的内存空间,对形参的修改不影响实参。
int add(int x,int y)
{
return x+y;
}
int main()
{
int a=10,b=20;
int c=add(a,b);
printf("%d",c);
return 0;
}
传址调用
传址调用就是把函数外部创建的变量的内存地址传递给函数的一种调用方式,该方式可以使函数和函数外部的变量建立真正的联系,在函数内部的修改可以影响到函数外部的变量。
void add(int *x,int *y)
{
*x=5;
*y=7;
}
int main()
{
int a=10,b=20;
add(&a,&b);
return 0;
}
传值调用与传址调用的使用,需要改变外部参数时,选择传址调用,不改变外部参数时,选传值调用。
函数的嵌套调用和链式访问
嵌套调用
嵌套调用就是在函数内调用另一个函数。
void print()
{
printf("hehe\n");
}
void fun(int num)
{
int i = 0;
for (i = 0; i < num; i++)
{
print();
}
}
int main()
{
int num = 10;
fun(num);
system("pause");
return 0;
}
上面是打印10个hehe的代码,从main函数进入,调用fun函数,再从fun函数调用print函数,最终实现打印,从fun调用print函数就是函数的嵌套调用。
需要注意的是,函数可以嵌套调用,但是不能嵌套定义。
链式访问
链式访问就是用一个函数的返回值,作为另一个函数的参数。
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
system("pause");
return 0;
}
该代码的结果是4321,从最内层的43打印,然后printf函数返回打印的数字2作为中间层printf函数的参数,最后返回被打印数字的个数1作为最外层函数的参数。
函数的声明和定义
函数声明
函数的声明是向编译器指出有这个函数,说明函数的参数类型,返回类型,但是并不是函数具体存在。
函数的声明一般会出现在函数之前,函数的使用是满足先声明后使用。
函数的声明一般要放在头文件中。
函数定义
函数定义是向编译器指出函数的具体实现方式。
void print();//函数声明,说明了print函数返回类型是void,不需要参数。
void fun(int num);//函数声明,说明了函数返回类型是void,也就是没有返回类型,需要一个int类型的参数
int main()
{
int num = 10;
fun(num);
system("pause");
return 0;
}
void print()//函数的定义,函数的具体实现方式,定义了print函数打印hehe的功能。
{
printf("hehe\n");
}
void fun(int num)//定义了fun函数循环调用print函数的功能。
{
int i = 0;
for (i = 0; i < num; i++)
{
print();
}
函数声明与定义的正确用法
函数声明与定义是将函数模块化的一种工具。
下面是一个简单的加减乘除计算函数,每个功能分为了不同的文件封装,这样做有三个好处:
一、每个人可以做不同的功能,最终只要合并到一起就可以
二、便于代码的管理和修改
三、便于代码隐藏
//test.c文件
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include"add.h"
#include"sub.h"
#include"mul.h"
#include"div.h"
int main()
{
int num1, num2;
int(*fun[5])(int x, int y) = { 0, Add, Sub, Mul, Div };
int i=1;
while (i)
{
printf("1,加法,2,减法,3,乘法,4,除法,0,退出,请输入->");
scanf("%d", &i);
if (i > 0 && i < 5)
{
printf("输入需要计算的数x ,y\n");
scanf("%d %d", &num1, &num2);
printf("%d\n", fun[i](num1, num2));
}
else if (i==0)
{
printf("退出计算器\n");
}
else
{
printf("重新输入\n");
}
}
system("pause");
return 0;
}
//add.h文件
int Add(int x, int y);
//add.c文件
int Add(int x, int y)
{
return x + y;
}
//sub.h文件
int Sub(int x, int y);
//sub.c文件
int Sub(int x, int y)
{
return x - y;
}
//mul.h文件
int Mul(int x, int y);
//mul.c文件
int Mul(int x, int y)
{
return x * y;
}
//div.h文件
int Div(int x, int y);
//div.c文件
int Div(int x, int y)
{
return x / y;
}
代码隐藏
下面以add.c文件为例
首先将配置类型调整为静态库
其次生成静态库
静态库的使用
新项目引入静态库
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include"add.h"
#pragma comment(lib,"8-27.lib")
int main()
{
int num1, num2;
scanf("%d %d", &num1, &num2);
printf("%d", Add(num1, num2));
system("pause");
return 0;
}
函数递归
什么是递归
递归(recursion):就是函数调用自身的技巧。
递归的主要思考方式在于把大事化小。
递归的两个必要条件
限制条件,当递归满足于限制条件时,递归便不再继续。
每次调用后,越来越接近限制条件。
递归实例
接收一个无符号整型值,按照顺序打印每一位
输入 1234,输出1 2 3 4
void print(int num)
{
if (num > 9)
{
print(num / 10);
}
printf("%d ", num % 10);
}
int main()
{
int num;
scanf("%d", &num);
print(num);
system("pause");
return 0;
}
编写函数不允许创建临时变量,求字符串长度
思路,字符串长度的结尾是'\0',计算字符串长度将指针不断后移,并记录移动的次数就可以。
以下是包含临时变量的方法
int my_strlen(char *str)
{
int count = 0;
while (*str != '\0')
{
str++;
count++;
}
return count;
}
int main()
{
char str[] = "abcde";
int ret=my_strlen(str);
printf("%d", ret);
system("pause");
return 0;
}
按照题目要求,不包含临时变量的方法
int my_strlen(char *str)
{
if (*str != '\0')
{
return 1+my_strlen(str + 1);
}
else
{
return 0;
}
}
int main()
{
char str[] = "abcdef";
int ret =my_strlen(str);
printf("%d", ret);
system("pause");
return 0;
}
汉诺塔的问题
问题:有三根柱子,和N个圆盘,要将sou柱子上的三个圆盘全部放到tar柱上去,需要几步,每步怎么移动。
要求,每次只能移动一个圆盘,每根柱子上的圆盘必须大的在下,小的在上。
思路:将圆盘移动,可以分为两种情况:
一:非最下面的圆盘,这些圆盘首先要移动到辅助柱aux上。
二:最下层圆盘,该圆盘直接从sou移动到tar柱子上。
三:将辅助柱aux上的圆盘移动到tar柱上。
用代码表示
//以下是简化的代码,用来说明圆盘的移动
hanoi(num,sou,tar,aux)//这里是开始时,num表示有三个圆盘,
最左边的sou表示圆盘所在的位置,中间的tar表示需要移动的位置,
最右边aux表示辅助的位置。
{
if(num==1)//当num为1时,说明已经是最下层的圆盘,
这时圆盘可以直接从sou柱子移动到tar柱子上。
{
printf("%c->%c",sou,tar);
}
else
{
hanoi(num,sou,aux,tar);//当num不为1也就是最下层的圆盘时,
需要将圆盘从sou移动到aux上。
hanoi(num,aux,tar,sou);//当最下层的圆盘移动完毕,
其他的圆盘从aux上移动到tar柱上。
}
}
代码实现
void hanoi(int num, char sou, char tar, char aux)
{
static i = 0;
if (num == 1)
{
printf("第%d次移动:从%c移动到%c\n", ++i, sou, tar);
}
else
{
hanoi(num - 1, sou, aux, tar);
printf("第%d次移动:从%c移动到%c\n", ++i, sou, tar);
hanoi(num - 1, aux, tar, sou);
}
}
int main()
{
int num = 3;
hanoi(3, 'A', 'B', 'C');
system("pause");
return 0;
}
递归与迭代
递归于迭代时编程时两种不同的思路,循环就是属于迭代的一种,事实上有的问题适合于递归,有的问题适合于迭代,使用时要以具体问题的性质来决定。
比如求n的阶层
//迭代方法
int main()
{
int num;
int s = 1;
scanf("%d", &num);
for (num; num >= 1; num--)
{
s *= num;
}
printf("%d", s);
return 0;
}
//递归方法
int fun(int num)
{
if (num == 1)
{
return 1;
}
else
{
return num*fun(num - 1);
}
}
int main()
{
int num;
scanf("%d", &num);
int ret =fun(num);
printf("%d", ret);
system("pause");
return 0;
}
递归的方法中代码并没有简化,运行时函数不断的调用还占据了栈空间。
求斐波那契数列
问题:求出数列中的第n个数的值
思路:斐波那契数列的第n个数就是(n-1)与(n-2)两个数字的和,简化成公式n=(n-1)+(n-2)。
迭代思路:从第三为开始,循环求解下一位的数,直到达到要求的数为止,迭代中只需要3个变量循环保存n,n-1,n-2三个数就可以。
int main()
{
int num1=1, num2=1, num3,i;
scanf("%d", &i);
if (i <= 2)
{
num3 = 1;
}
while (i>=3)
{
num3 = num1 + num2;
num1 = num2;
num2 = num3;
i--;
}
printf("%d", num3);
return 0;
}
递归方法:递归与迭代的思路相同,利用公式n=(n-1)+(n-2)。
int c = 0;
int fun(int num)
{
if (num == 3)
{
c++;
}
if (num < 3)
{
return 1;
}
else
{
return fun(num - 1) + fun(num - 2);
}
}
int main()
{
int num;
scanf("%d", &num);
int ret=fun(num);
printf("%d\n", ret);
printf("%d\n", c);
system("pause");
return 0;
}
但是使用迭代时,fun函数被反复调用了多次,而且num越大,计算的越慢,当num超过一定的数值,函数会栈溢出。
造成该问题的原因在于,内存的空间是有限的,每次函数调用都要在栈区上申请空间,当递归的层次太深,申请的空间太多时,就会出现stack overflow的错误。
所以,递归虽然简单,但不是岁申明情况都能够使用,解决递归栈溢出的问题,也是将变量设置为静态变量,这样变量只在静态区,可以部分缓解栈区的压力。