# 函数 --- ## 一、函数的定义 常量:`const int a=10;` 变量: `int a;` ```c++ int sum = 0; for(int i=1; i <= 5; ++i) { sum = sum + i; } cout << sum; ``` --- 这个只能做从1加到5,我们想从1加到10,加到n呢?我们只能改程序中的for的条件, ```c++ int sum = 0; int n = 5; for(int i=1; i <= n; ++i) { sum = sum + i; } cout << sum; ``` --- 爸爸给小明买了一个复读机,不管小明说什么,复读机都能一摸一样的读出来。 在程序里,复读机可以设计成一个函数,每次调用这个函数,传入一句话(字符串)作为参数,函数就会输出这句话。 ```c++ void repeat(string s) { cout << s << endl; } ``` --- #### 1、函数的简介 函数是实现程序模块化结构的重要手段,用来封装重复代码,提炼成某一类特定的任务,供程序调用。 * 有些函数系统已经封装好了,可以直接调用,例如cmath类库里的min函数求最小值,sqrt,abs,fabs,strlen。 * 有些函数则需要根据自己的需求进行封装,例如封装一个输出5个星号的函数。 --- #### 2、函数的声明 ```c++ 返回值类型 函数名(参数列表); int min(int a, int b); ``` 函数的声明由以下几部分组成: * 返回值类型:声明这个函数要返回的结果的类型,void表示无返回。 * 函数名:描述函数的意思和用途,调用的时候要一摸一样。 * 参数列表:小括号里面,表示函数要传入哪些参数。 --- #### 3、函数的调用 根据函数的声明,就可以对函数进行调用: ```c++ 返回值类型 变量 = 函数名(传入参数列表); int a = min(3, 5); ``` `void`:void类型表示无返回,调用的时候,不需要返回 ,例如: ```c show(10); ``` --- #### 4、函数的定义 如果自己封装一个函数,就要在声明的基础上进行定义(增加函数体部分): ```c++ 返回值类型 函数名(参数列表) { //函数体,在这里实现函数的代码,并且返回结果 } ``` --- * 函数体里面默认定义了参数列表里的局部参数。 * 使用return 语句返回结果,类型要和声明的返回值类型一致,例如return 0。 * 一般return语句在最后执行,如果中间被执行到,则后面的语句不会继续执行。 * void表示不需要返回,但也可以在函数体中间直接使用return语句返回。 `注意`:不能在函数里面定义函数。 --- #### 5、先定义后调用 * c++语言采用先定义、后调用的方式,函数定义需要在调用代码之前。 * 假如想把定义放到调用之后,可以把函数的声明先放到调用前面,然后在调用后面定义函数。 ```c++ //函数声明 int abs(int a); int main() { int a = abs(3); //调用 cout << a << endl; return 0; } //函数定义 int abs(int a) { //函数体实现 } ``` --- #### 6、函数定义与编译过程(选) 计算机只能识别和执行机器语言,`编译`就是指将高级语言翻译成机器语言的过程。 --- 编译包括:预处理、编译、链接三个过程。 * 预处理:处理#include(包含)、#define(宏定义)等。 * 编译:将每个代码文件翻译成目标文件(后缀是.o)。 * 链接:将多个目标文件链接起来,组成可执行文件(win下后缀是.exe) --- 代码的运行就是指调用最后链接成的可执行文件。 函数声明后,可以调用,此时能通过编译,但是在链接的时候会报错(因为函数具体的实现没有),导致可执行文件不能生成。 --- #### 7、重名错误(选) 如果有变量和函数重名了,就会报重名的错误。 例如: * 如果有个变量名为min,再调用min函数,就会报错。 ```c++ int min=0; int a = min(3, 5); ``` * 但如果变量名在函数调用后面,就不会报错。 ```c++ int a = min(3, 5); int min = 0; ``` --- #### 8、例题:一本通p1150 参考代码: ```c++ #include
#include
using namespace std; int n; int isFullNum(int k); int isFullNum(int k) { // k = a * b ,a <= b; int sum = 1; for (int i = 2; i * i <= k; ++i) { if (k % i == 0) { sum = sum + i + k / i; } } if (sum == k) { return 1; } return 0; } int main(void) { cin >> n; //2...n 的每一个数都去看一看,这个数的因子和是否与这个数相同 for (int i = 2; i <= n; ++i) { //i是这个数,i是否是完全数 if (isFullNum(i)) { cout << i << endl; } } return 0; } ``` --- 一本通1152 ```c++ #include
#include
#include
#include
using namespace std; int a, b, c;//全局变量 int max3(int x, int y, int z);// 函数声明 int main(void) { cin >> a >> b >> c; float m; float m1, m2, m3; m1 = max3(a, b, c); m2 = max3(a + b, b, c); m3 = max3(a, b, b + c); m = m1 / (m2 * m3); printf("%.3f\n", m); return 0; } int max3(int x,int y,int z) // 函数定义 { return max(max(x, y), z); } ``` --- ## 二、函数的参数 #### 1、形参和实参 * `形参`:函数定义的参数称之为形式参数(简称形参),形参的名字是可以自由修改的,相当于在函数体里定义变量。 ```c++ int add(int a, int b) { return a + b; } ``` --- * `实参`:调用函数的时候,传递给函数的为实际参数(简称实参)。 - 实参要与形参个数一致、顺序一致、类型一致(会自动转换)。 - 调用时,实参前面不需要加参数类型, - 实参可以是变量、常量、表达式、函数调用等。 ```c++ add(1, 2);//变量 add(n, 3); //常量 add(5*4, 10); //表达式 add(add(3, 4), 5); //函数调用 ``` --- * 默认值:可以给形参指定默认值,如果调用时不传入该实参,则使用默认值。 ```c++ int add(int a, int b=10) { return a+b; } int sum = add(3); //结果是3+10 = 13 ``` * 内存:当函数被调用的时候,为形参分配内存,把实参的值赋给形参(值传递),当函数结束时,内存被释放。多次函数调用时,分配的是多个内存地址。 --- #### 2、值传递 实参和形参的传递有几种值传递、引用传递、指针传递等。 * 值传递:在调用时,把实参的值拷贝给形参,这种传递是单向的,在函数体里面修改了形参的值,函数结束后不会影响到实参变量的值。 * 引用传递和指针传递:这两种传递方式是双向的,函数体里面修改了形参的值,函数结束后会影响到实参变量的值。 --- 值传递和引用传递: ```c++ void change(int a) {//值传递 //void change(int &a) {//引用传递 a++; } int n = 3; change(n); cout << n; //n还是3,没有改变 ``` --- #### 3. 局部变量和全局变量 ##### 3.1 局部变量: * 函数体里定义的变量,为局部变量,函数外面不能调用这个变量。 * 形参可以看成是函数的局部变量。 * 代码块(用大括号框起来的)里定义的变量,只能在该代码块中调用。 * 局部变量需要显式初始化,否则初始值不确定。 --- ##### 3.2 全局变量: * 在函数外面定义的变量,为全局变量,后面的所有函数都能调用这个全局变量。 * 全局变量会被自动初始化为$0$。 --- ##### 3.3 同名冲突 * 两个全局变量同名,会报错。 * 同一个函数里两个局部变量同名,会报错。 * 函数的局部变量和函数里代码块的局部变量同名,会报错。 * 不同函数的局部变量同名,没关系,调用自己函数的局部变量。 * 两个代码块里面的局部变量同名,没关系,调用自己代码块的局部变量。 * 全局变量与函数局部变量同名,没关系,函数里调用的是局部变量,函数外面调用的是全局变量。 * 如果函数里想调用同名的全局变量,使用::变量名。 --- ## 三、函数的嵌套 #### 1、函数嵌套 函数可以继续调用其它函数,但main函数不能给别的函数调用。 --- 调用函数的时候,就会经历一次保存现场、进入调用函数、调用函数返回、恢复现场继续往下执行的过程,如果产生多级嵌套调用,那么保存现场和恢复现场的过程就会形成一种深V的结构。 ```c++ enter f1 f1 return enter f2 f2 return enter f3 f3 return enter f4 return ``` --- #### 2、函数嵌套的主要作用 函数封装、嵌套的主要作用:代码封装、模块化、重用,小步编码和测试。 --- ## 四、函数递归 #### 1、递归调用 函数直接或间接调用了自己,称之为递归调用。 如果递归调用一直没有停止,则无限次保存现场(没有恢复)的结果就会导致栈溢出,所以,正常的递归一定要有停止条件,我们可以理解成“有去有回”。 ![12](https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3242572376,489213452&fm=26&gp=0.jpg) --- #### 2、递归函数 正常递归的两大要素: * 递归关系式:描述本次调用和上次调用的关系。 * 停止条件:当递归到某一个条件时,不需要继续递归下去,可直接处理结果。 --- 例子: f(n)= 1 + 2 + 3 + 4 +... + n。 * 递归关系式: f(n) = n + f(n-1); n > 1 * 停止条件: f(1) = 1; n = 1 这两点合起来,就可以定义为为递归函数。 --- #### 3、代码框架 ```c++ int f(int n) { if (n == 1) return 1; //最小问题,直接返回 return n + f(n-1); //问题范围变小 } ``` --- #### 4、递归和循环的差异?(选) * 循环递推:从前往后推,如果没有结束条件会死循环。 * 递归函数:从后往前推,函数自己调用自己,有入栈出栈的操作,如果没有终止条件会栈溢出。 --- #### 5、主要场景(选) 函数递归的主要场景:解问题,将大问题转化为小问题,直到可直接求解的规模,再依次返回。 --- #### 6、尾递归(选) 如果递归的最后一步是直接返回下一次运算结果,例如:return f(n-1)。则编译器会进行优化,使之底层通过循环递推来实现。 如果最后一步还需要在下一次运算结果上进行运算,例如:return 1 + f(n-1)。则编译器没法优化,需要占用入栈出栈的操作。 --- ## 五、例题 ####一本通 1160:倒序数 [【1160:倒序数】](http://ybt.ssoier.cn:8088/problem_show.php?pid=1160) 解法一:(字符串) ```c++ #include
#include
#include
#include
using namespace std; int main() { string s; cin >> s; for (int i = s.length()-1; i >= 0; --i) { cout << s[i]; } return 0; } ``` --- 解法二:(递推) ```c++ #include
#include
#include
#include
using namespace std; int a; int main() { cin >> a; while(a) { cout << a % 10; a = a / 10; } return 0; } ``` --- 解法三:(递归) ```c++ #include
#include
#include
#include
using namespace std; int a; void f(int n); void f(int n) { if(n <= 10) { cout << n; return; } cout << n % 10; f(n / 10); } int main() { cin >> a; f(a); return 0 } ``` ---