- chjshen 的博客
C++语法基础6-函数
- 2023-11-21 9:08:45 @
函数
一、函数的定义
常量:const int a=10;
变量: int a;
int sum = 0;
for(int i=1; i <= 5; ++i)
{
sum = sum + i;
}
cout << sum;
这个只能做从1加到5,我们想从1加到10,加到n呢?我们只能改程序中的for的条件,
int sum = 0;
int n = 5;
for(int i=1; i <= n; ++i)
{
sum = sum + i;
}
cout << sum;
爸爸给小明买了一个复读机,不管小明说什么,复读机都能一摸一样的读出来。
在程序里,复读机可以设计成一个函数,每次调用这个函数,传入一句话(字符串)作为参数,函数就会输出这句话。
void repeat(string s) {
cout << s << endl;
}
1、函数的简介
函数是实现程序模块化结构的重要手段,用来封装重复代码,提炼成某一类特定的任务,供程序调用。
- 有些函数系统已经封装好了,可以直接调用,例如cmath类库里的min函数求最小值,sqrt,abs,fabs,strlen。
- 有些函数则需要根据自己的需求进行封装,例如封装一个输出5个星号的函数。
2、函数的声明
返回值类型 函数名(参数列表);
int min(int a, int b);
函数的声明由以下几部分组成:
- 返回值类型:声明这个函数要返回的结果的类型,void表示无返回。
- 函数名:描述函数的意思和用途,调用的时候要一摸一样。
- 参数列表:小括号里面,表示函数要传入哪些参数。
3、函数的调用
根据函数的声明,就可以对函数进行调用:
返回值类型 变量 = 函数名(传入参数列表);
int a = min(3, 5);
void
:void类型表示无返回,调用的时候,不需要返回 ,例如:
show(10);
4、函数的定义
如果自己封装一个函数,就要在声明的基础上进行定义(增加函数体部分):
返回值类型 函数名(参数列表)
{
//函数体,在这里实现函数的代码,并且返回结果
}
- 函数体里面默认定义了参数列表里的局部参数。
- 使用return 语句返回结果,类型要和声明的返回值类型一致,例如return 0。
- 一般return语句在最后执行,如果中间被执行到,则后面的语句不会继续执行。
- void表示不需要返回,但也可以在函数体中间直接使用return语句返回。
注意
:不能在函数里面定义函数。
5、先定义后调用
- 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函数,就会报错。
int min=0; int a = min(3, 5);
-
但如果变量名在函数调用后面,就不会报错。
int a = min(3, 5); int min = 0;
8、例题:一本通p1150
参考代码:
#include <iostream>
#include <cstring>
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
#include <iostream>
#include <cstdio>
#include<cmath>
#include <cstring>
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、形参和实参
-
形参
:函数定义的参数称之为形式参数(简称形参),形参的名字是可以自由修改的,相当于在函数体里定义变量。int add(int a, int b) { return a + b; }
-
实参
:调用函数的时候,传递给函数的为实际参数(简称实参)。- 实参要与形参个数一致、顺序一致、类型一致(会自动转换)。
- 调用时,实参前面不需要加参数类型,
- 实参可以是变量、常量、表达式、函数调用等。
add(1, 2);//变量 add(n, 3); //常量 add(5*4, 10); //表达式 add(add(3, 4), 5); //函数调用
-
默认值:可以给形参指定默认值,如果调用时不传入该实参,则使用默认值。
int add(int a, int b=10) { return a+b; } int sum = add(3); //结果是3+10 = 13
-
内存:当函数被调用的时候,为形参分配内存,把实参的值赋给形参(值传递),当函数结束时,内存被释放。多次函数调用时,分配的是多个内存地址。
2、值传递
实参和形参的传递有几种值传递、引用传递、指针传递等。
- 值传递:在调用时,把实参的值拷贝给形参,这种传递是单向的,在函数体里面修改了形参的值,函数结束后不会影响到实参变量的值。
- 引用传递和指针传递:这两种传递方式是双向的,函数体里面修改了形参的值,函数结束后会影响到实参变量的值。
值传递和引用传递:
void change(int a) {//值传递
//void change(int &a) {//引用传递
a++;
}
int n = 3;
change(n);
cout << n; //n还是3,没有改变
3. 局部变量和全局变量
3.1 局部变量:
- 函数体里定义的变量,为局部变量,函数外面不能调用这个变量。
- 形参可以看成是函数的局部变量。
- 代码块(用大括号框起来的)里定义的变量,只能在该代码块中调用。
- 局部变量需要显式初始化,否则初始值不确定。
3.2 全局变量:
- 在函数外面定义的变量,为全局变量,后面的所有函数都能调用这个全局变量。
- 全局变量会被自动初始化为。
3.3 同名冲突
- 两个全局变量同名,会报错。
- 同一个函数里两个局部变量同名,会报错。
- 函数的局部变量和函数里代码块的局部变量同名,会报错。
- 不同函数的局部变量同名,没关系,调用自己函数的局部变量。
- 两个代码块里面的局部变量同名,没关系,调用自己代码块的局部变量。
- 全局变量与函数局部变量同名,没关系,函数里调用的是局部变量,函数外面调用的是全局变量。
- 如果函数里想调用同名的全局变量,使用::变量名。
三、函数的嵌套
1、函数嵌套
函数可以继续调用其它函数,但main函数不能给别的函数调用。
调用函数的时候,就会经历一次保存现场、进入调用函数、调用函数返回、恢复现场继续往下执行的过程,如果产生多级嵌套调用,那么保存现场和恢复现场的过程就会形成一种深V的结构。
enter f1 f1 return
enter f2 f2 return
enter f3 f3 return
enter f4 return
2、函数嵌套的主要作用
函数封装、嵌套的主要作用:代码封装、模块化、重用,小步编码和测试。
四、函数递归
1、递归调用
函数直接或间接调用了自己,称之为递归调用。
如果递归调用一直没有停止,则无限次保存现场(没有恢复)的结果就会导致栈溢出,所以,正常的递归一定要有停止条件,我们可以理解成“有去有回”。
2、递归函数
正常递归的两大要素:
- 递归关系式:描述本次调用和上次调用的关系。
- 停止条件:当递归到某一个条件时,不需要继续递归下去,可直接处理结果。
例子: f(n)= 1 + 2 + 3 + 4 +... + n。
- 递归关系式: f(n) = n + f(n-1); n > 1
- 停止条件: f(1) = 1; n = 1
这两点合起来,就可以定义为为递归函数。
3、代码框架
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:倒序数
解法一:(字符串)
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
string s;
cin >> s;
for (int i = s.length()-1; i >= 0; --i)
{
cout << s[i];
}
return 0;
}
解法二:(递推)
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
int a;
int main()
{
cin >> a;
while(a)
{
cout << a % 10;
a = a / 10;
}
return 0;
}
解法三:(递归)
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
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
}