函数总结
函数总结
1.为什么要用函数
函数是C源码程序中最基本的功能单位,是一个可以从程序其它地方调用执行的语句块。
C语言是一种结构化程序设计语言,结构化程序设计思想是“分解”大问题,依次解决小问题,通过小问题解决实现大问题的解决,描述“小问题”解决方法的工具即是函数。
函数的定义格式如下:
type name ( argument1, argument2, ...) statement
说明:
type 是函数返回的数据的类型
name 是函数被调用时使用的名
argument 是函数调用需要传入的参量(可以声明任意多个参量)。每个参量(argument)由一个数据类型后面跟一个标识名称组成,就像变量声明中一样(例如,int x)。参量仅在函数范围内有效,可以和函数中的其它变量一样使用, 它们使得函数在被调用时可以传入参数,不同的参数用逗号(comma)隔开.
statement 是函数的内容。它可以是一句指令,也可以是一组指令组成的语句块。如果是一组指令,则语句块必须用花括号{}括起来,这也是我们最常见到情况。其实为了使程序的格式更加统一清晰,建议在仅有一条指令的时候也使用花括号,这是一个良好的编程习惯。
2.为什么要用函数重载
两个以上的函数具有相同的函数名,但是形参的个数或者类型不同,编译器根据食实参和形参的类型及个数的最佳匹配自动确定调用哪一个函数,这就是函数的重载。
如果没有重载机制,那么对不同类型的数据进行相同的操作,也需要定义名称完全不同的函数,例如定义加法函数我就必须这样对整数的加法和浮点数的加法使用不同的函数名:
int iadd (int x,int y);
float fadd (float x,float y);
这在调用时实在不方便。
c++允许功能相近的函数在相同的作用域内以相同的函数名定义,从而形成重载。方便使用,便于记忆。
include
using namespace std;
int sumofsquare(int a,int b)
{
return aa+bb;
}
double sumofsquare(double a,double b)
{
return aa+bb;
}
int main()
{
int m,n;
cout << "输入两个整数:" ;
cin >> m >> n;
cout << "两个数字的平方和为:" << sumofsquare(m,n)<<endl;
double x,y;
cout << "输入两个小数:";
cin >> x >> y;
cout << "两个小数的平方和为:" << sumofsquare(x,y)<<endl;
return 0;
}
3.为什么要用值传递地址传递
昨天看了内存管理的有关内容,有一点了解,但不是很深入,发现之前写代码时有很多细节问题没有注意到,只知道这样做可以实现功能,却不知道为什么可以这样,对于采用自己的方法造成的隐患也未知,更不晓得还有其他方法可以实现,我们知道C++强大的一个原因是因为对于一个问题的答案多种解答方法或思路,我想着也许就是它难学的原因。
c或C++中函数的参数传递包括:值传递、指针传递、引用传递这三种方法,这三种方法在《程序员面试宝典》中说的很明了,这里加上我自己的理解。
5 #include <iostream>
6
7 using namespace std;
8
9 //值传递
10 void swap1(int p,int q)
11 {
12 int temp;
13 temp=p;
14 p=q;
15 q=temp;
16 }
17
18 //指针传递,函数体内只有指针值的变化
19 void swap2(int *p,int *q)
20 {
21 int temp;
22 temp=*p;
23 *p=*q;
24 *q=temp;
25 }
26
27 //指针传递,函数体内只有指针的变化
28 void swap3(int *p,int *q)
29 {
30 int *temp;
31 temp=p;
32 p=q;
33 q=temp;
34 }
35
36 //引用传递
37 void swap4(int &p,int &q)
38 {
39 int temp;
40 temp=p;
41 p=q;
42 q=temp;
43 }
44
45 int main()
46 {
47 int a=1,b=2;
48 swap1(a,b);
49 //swap2(&a,&b);
50 //swap3(&a,&b);
51 //swap4(a,b);
52 cout<<a<<" "<<b<<endl;
53 return 0;
54 }
共有四个函数,其中有两个是指针传递,但函数体内的实现不一样。下面具体分析
1.值传递
swap1函数实现的值传递,值传递传递的是实际参数的一个副本,如果对这句话不理解,那一步步调试看下内存分配情况。
接着进入swap1函数体内,
p和q的地址和a与b的地址不一样,只是把a和b的值拷贝过去了,在swap1中对p和q操作只是对临时分配的栈中内容进行操作,函数执行完后形参就消失了,对原来的a和b不产生任何影响。所以swap1不能完成交换a和b值的功能
2.指针传递
swap2和swap3都是指针传递,swap2函数体内交换了p和q指向地址的值,swap3函数体内交换了p和q指向的地址。
先说swap2,进入swap2函数体内,、
形参指针p和q指向的是a和b的地址,而不是像值传递那样将实参的值拷贝到另外分配的地址中,运行到函数尾时
可以看到、指针p和q指向的地址没变,但地址中的值变了,也即a和b地址中的变了,就是a和b的值成功交换,继续调试可以看到正确的结果,
再来看swap3,swap3运行到函数尾时p和q交换了地址,但最后函数执行完后的结果,a和b的值并未交换,这是为什么呢?
swap3中,形参p和q会保存在栈中,p指向a的地址,q指向b的地址,使用temp指针完成了p和q的地址交换,即p指向b的地址,q指向了a的地址,但a和b地址中的值并未发生变化,这与swap2不同,swap2中是p指向的地址中的值(就是a)与q指向的地址中的值(b)交换,所以swap2执行完后a和b的值是交换了的。
3.引用传递
引用传递时,对形参的操作等同于对实参的操作,即传递的不会是实参的副本,而就是实参,进入swap4函数体内,最后会交换a和b的值。
4.递归函数
递归可以相当于循环,所以想结束递归,就必须有终止递归的条件测试部分,否则就会出现无限递归(即无限循环)。同时,这也是使用递归的难点。
例子:
include <stdio.h>
void recur(int);
int main (void)
{ recur(1); return 0;}
void recur(int n) //递归函数
{ printf("第%d级调用\n", n); //#1
if (n < 4)
{ recur(n+1); //递归 }
printf("第%d级返回\n", n); //#2}
解析:从结果可以看出,#1和#2相当于循环体,当符合测试条件(即n<4)时,#1部分循环;当测试条件为false时,#2部分循环。在递归函数中,位于递归调用之前的语句(即#1部分),按被调函数(即recur())的顺序执行;位于递归调用之后的语句(即#2部分),按被调函数相反的顺序执行。每级函数调用都有自己的变量,递归调用就相当于又从头开始执行函数的代码。每次函数调用都会返回一次,并且按顺序逐级返回递归。
递归(优缺点)与循环
- 使用循环的地方都可以使用递归
- 缺点:
- 递归快速耗内存
- 不方便阅读和维护
- 效率低
- 优点:
- 简洁
- 适合解决阶乘、涉及相反顺序的编程问题
hanoi塔问题:
代码如下:
include
using namespace std;
void Move(char src,char dest)
{
cout << src << "-->" << dest <<endl;
}
void hanoi(int n,char src,char medium,char dest)
{
if(n==1)
Move(src,dest);
else
{
hanoi(n-1,src,dest,medium);
Move(src,dest);
hanoi(n-1,medium,src,dest);
}
}
int main()
{
int m;
cout << "输入有几个盘子:" << endl;
cin >> m;
cout << "步骤为如下:" << endl;
hanoi(m,'A','B','C');
return 0;
}