在C语言中,函数又叫子程序。
1.函数的分类
C语言中的函数分为库函数和自定义函数。对于库函数的学习我们只能通过不停地使用才能学习到他们,可以通过网站进行学习,上网查询库函数,了解和学习库函数的用法。
C语言的常见函数分类:IO函数,字符串操作函数,字符操作函数,内存操作函数,时间/日期函数.,等等。
推荐学习网站:Reference - C++ Referencehttp://cplusplus.com/reference/
1.1自定义函数
自定义函数和库函数一样,都有函数名,返回类型,函数参数。根据我们的需要自己来设计函数。
函数的基本组成:
ret_type fun_name(para1.*)
{ ------------------------
statement;//语句 ------------------------函数体
} ------------------------
ret_type返回值类型
fun_name函数名称
para1函数参数
例如我们写一个获得两个数中较大的那个值的函数:
int get_max(int x, int y)
{
int z = (x > y ? x :y);
return z;
}
几点说明:
1.返回值有int,char,float,void。如果需要返回值,函数体重应该有return a(整形数或浮点型数)。如果不需要返回值,那么函数体中可以写return(有时会有巧妙的作用),也可以不写。举个例子
void test()
{
int a = 5;
printf("hehe\n");
if(a == 5)
return;
printf("haha\n")
}
//在这个函数中,如果满足了a==5,它就只会打印hehe。
//return直接跳出函数,程序不会再执行return后面的程序
2.返回值,参数可以为空,void。根据需求来判断,如果需要与外界交互,就需要添上;
3.调用时,形参实参类型必须相同。
2.函数的参数
函数的参数分为实参和形参。实参就是真实传给函数的值;形参就是指函数名后括号的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完后就自动销毁了,因此形式参数只在函数中有效。
这里要说明实参可以是常量,变量,表达式,函数等。但无论是何种的量,在函数调用时,都必
须是确定的值以便传给形参。
get_max(11+5,3); //实参为表达式形式
get_max(8,get_max(55,30); //实参为函数的形式
3.函数调用方式
函数的调用方式可以分为传值调用和传址调用。传值调用:函数的形参和实参分别占有不同的内存块,对形参的修改不会影响到实参; 传址调用:传址调用是把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式,这种传参方式可以让函数和函数外边的变量建立起真正的联系,月就是函数内部可以直接操作函数外部的变量。
在这里举个例子以便读者朋友更好地理解。
我们想得到利用这个Swap函数交换两个数的值。看结果是否交换:
结果就与所期望的不符了。是因为这个进行了传值调用。当函数调用时只是把实参a和b值传到了形参x和y内。形参x和y其实只是实参a,b的一份临时拷贝,对形参x,y的修改不会影响实参a,b。因此,打印还是打印原ab的值。如果想要完整地实现这个函数就必须使用传址调用。我们这样修改代码:
4.函数的嵌套调用和链式访问
4.1嵌套调用
函数和函数之间是可以相互调用的:
void test1()
{
printf("hello world);
}
void test2()
{
int i;
for(i = 0; i < 3; i++)
{
test1();
}
}
int main()
{
test2();
return 0;
}
//函数之间的相互调用
但是要注意,函数是不可以嵌套定义的:
void test1()
{
void test2()
{
int i;
for(i = 0; i < 3; i++)
{
printf(“hello world”);
}
}
printf("hello world);
}
//这样定义函数是错误的!!!
4.2链式访问
即拿一个函数的返回值作为另一个函数的参数:
int main()
{
printf("%d\n", strlen("abc");//在这里,strlen函数的返回值做printf函数的参数
return 0;
}
int main()
{
printf("%d\n",printf("%d\n",printf("%d\n",43)));
printf("%d",printf("%d",printf(“%d”,43)));
return 0;
}
这个题他为什么打印这些呢?这就涉及到链式访问了。我们知道printf函数的返回值是打印字符的个数:
所以第一行第一次打印了43\n三个字符,返回3,打印3\n,返回2,打印2\n。第二行的printf同理,不过就是少了个字符\n,不换行了而已。
5.函数的声明和定义
5.1函数声明
1.告诉编译器有一个函数叫什么,参数,返回类型是什么,但是具体是不是存在,函数声明解决不了;2.函数的声明一般出现在函数的使用之前,要满足先声明后使用;3.函数的声明一般都放在头文件里面。
5.2函数定义
函数的定义是指函数的具体实现,交代函数的功能实现。
int Add (int x, int y); //函数的声明
int main()
{
int a, b;
printf("%d %d",Add(a, b));
return 0;
}
int Add (int x, int y) //函数的定义
{
int z = x + y;
return z;
}
在实际工程中,函数的定义一般放在一个源文件中,函数的声明放到一个头文件中,然后在要调用函数的文件中引用自定义函数头文件。大大提高了效率。
6.函数递归
6.1函数的递归
程序调用自身的编程技巧就叫做递归。递归是在一个过程或函数在其定义或说明中直接或间接调用自身的一种方法。它就是把一个很复杂的问题转化成一个和原问题相似的但规模较小的问题来解决的方法。能够用最少的程序描绘出很复杂的计算。
写一个很简单的递归(不考虑栈溢出):
int main()
{
printf("hehe\n");
main();
return 0;
}
//它会不停地打印hehe,但是会栈溢出,所以写程序时不能这么写
递归的必要条件:存在限制条件,当满足这个限制条件的时候,递归便不再继续,而且每次递归调用之后必须越来越接近这个限制条件。
6.2两个例子
来看两道题道题来加深我们对函数递归的理解:1.用递归,将无符号整数的每一位打印出来。举个例子234:
void Print(unsigned int x) //将{234}化为{23}4、{2}34、234,越小位的数应该越往后打印
{
if (x > 9)
{
Print(x / 10);
}
printf("%d ", x % 10);
}
int main()
{
unsigned int a = 0;
scanf("%u", &a);
Print(a);
return 0;
}
2.计算字符串的长度(函数内不能创建临时变量)
//这道题就是让我们自己动手创建一个和strlen函数功能相同的函数
//初级版本
int Strlen (char* p) //数组名就是指针,指向数组的第一个字符。
{
int count = 0;
while(*p != '\0') //先创建一个有临时变量的函数,一步一步达到我们想要的效果。
{
p++; //不论是什么指针,只要指向一段连续的空间。p+1--就是往后偏移一个元素的位置,即
//拿到下一个元素。p-1--就是往前偏移一个元素的位置 即拿到前一个元素。
count++; //count加一,即实现字符串长度
}
return count;
}
int main()
{
char ch[] = "abc";
printf("%d\n",Strlen(ch));
return 0;
}
//进阶版本
int Strlen(char* p)
{
if(*p == '\0')
return 0;
else
return 1 + Strlen(p + 1);
int main()
{
char ch[] = "abc";
printf("%d\n",Strlen(ch));
return 0;
}
6.3 stackoverflow(栈溢出)
用函数递归可以解决的问题多数也可以用循环来解决。例:打印n的阶乘
//循环
int Fac(int n)
{
int i= 0;
int ret = 1;
for (i = 1; i <= n; i++)
{
ret = ret * i;
}
return ret;
}
int main()
{
int a = 0;
scanf("%d", &a);
printf("%d\n", Fac(a));
return 0;
}
//函数的递归
int Fac(int n)
{
if (n == 1)
return 1;
else
return n * Fac(n - 1);
}
int main()
{
int a = 0;
scanf("%d", &a);
printf("%d\n", Fac(a));
return 0;
}
但是在某些时候,函数的递归是很低效的!比如我们想打印求第n个斐波那契数。(不考虑溢出)
int fib(int x)
{
if (x <= 2)
return 1;
else
{
return fib(x - 1) + fib(x - 2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d\n", fib(n));
return 0;
}
//当n比较小时,还可以计算出,但是当n为50时,计算机执行相同的步骤就会很多,速度也就会变慢
//效率也就会变低
//我们这样来看 创建一个全局变量,n为50,当运行到x为3时,看看函数被调用了几次。
int count = 0;
int fib(int x)
{
if (x == 3)
count++;
if (x <= 2)
return 1;
else
{
return fib(x - 1) + fib(x - 2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d\n", fib(n));
printf("%d\n",count);
return 0;
}
//运行结束后,count是一个非常非常大的数,说明被重复调用了很多次,因此是低效的
这时我们就可以利用循环来解决问题:
int fib(int x)
{
int a = 1;
int b = 1;
int c = 1;
while (x > 2)
{
c = a + b;
a = b;
b = c;
x--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d\n", fib(n));
return 0;
}
因此递归在某时也不是万能的,根据实际情况来选择代码的写法。
如果老铁感觉还不错的话一键三连哦!谢谢各位。