C++函数详解
什么是函数
主函数
主函数是程序的入口,它也是一个函数。
int main(int argc,char *argv[]){
/*这里写代码*/
return 0;
}
自定义函数
int y(int x){
int temp=2*x+3;
return temp;
}
可以看到函数的构造和主函数是一样的。
那么我们来分析一下函数的构造。
函数的构造
返回值类型 函数名(参数){
函数体
返回值
}
函数名:对函数命名,以后可以通过函数名进行调用。比如上面的y就是函数名。
参数:输入值,可以有多个。比如上面的x就是输入值。(注意要写明参数的类型)
函数体:经过一系列运算得到返回值。比如上面的int temp=2*x+3;
返回值:输出值,只能输出一个。比如上面的return temp;
返回值类型:是返回值的数据类型,在函数定义时需要写明。
一个函数的实例
#include<iostream>
using namespace std;
int max(int num1, int num2)
{
// 局部变量声明
int result;
if (num1 > num2)
result = num1;
else
result = num2;
//返回值
return result;
}
int main()
{
// 调用函数来获取最大值
int ret = max(100, 200);
cout << "Max value is : " << ret << endl;
return 0;
}
代码解释:
- 这是一个判断最大值的函数,传入num1和num2,判断他们之间较大的值后返回。
- 函数是和主函数平行的。即写在主函数的外面。
- 自定义数是先写在主函数的前面的。
使用函数的好处
封装性。
就是把代码封装起来,像一个黑箱,我们无需知道箱子里是什么,只要会使用这个箱子就可以。
就像手机一样,我们无需知道手机内部的构造,也不用知道CPU的原理是什么,我们只要会使用手机就行了。代码重用。
我们在使用这一段代码的时候不需要再复制一遍了,只要调用这个函数就可以了。这样的代码也会显得简洁。
在修改代码的时候,只要修改函数体就可以了。
函数声明
前面讲到了函数和具有封装性,我们无需知道函数里面是什么,怎么实现的。
所以,我们将函数体写在前面显得多余,且不方便阅读。(函数可能有上百个)
那么,我们可以使用函数声明:先声明再调用,最后定义函数。
#include<iostream>
using namespace std;
int max(int num1, int num2); //声明
void main(){
cout<< max(100, 200) <<endl; //调用
}
int max(int num1, int num2) { //定义
/*这里省略了函数体*/
}
函数声明:函数头加分号,省略了函数体。
声明中省略参数名:
参数的名字只在函数体中被使用。那么我们同样无需知道参数名是什么。在声明中可以省略。
int max(int, int); //省略了参数名
注意:在函数定义中一个都不能省略,因为最终执行的代码还是函数定义中的函数体。
默认参数值
我们可以对函数的参数定义默认值,这个时候就不用传入相应的值了,程序会将参数赋值为默认值。
这是一个计算年龄的函数,传入出生日期和年份就可以得到这个年份的年纪。
如果我不想给定年份,默认计算今年的年纪,这样的逻辑也是可以实现的。
int get_age(int birth,int year=2019){
/*函数体省略*/
}
int main(){
cout<<get_age(19901018)<<endl; //调用的时候只需要传入出生日期就可以了。(默认计算的就是今年的年纪)
cout<<get_age(19901018,2030)<<endl; //如果给定了相应的值,那么默认值就会被覆盖,这里使用的是2030,而不是默认值2019。
}
如果给定了相应的值,那么默认值就会被覆盖,上面使用的是2030,而不是默认值2019。
形参与实参
形参
形式参数的缩写。
在函数头部定义的就是形式参数。比如int get_age(int birth,int year=2019)
中的birth和year。实参
实际参数的缩写。
在调用的时候给的实际的值就是实际参数。比如get_age(19901018,2030)
中的19901018和2030。
/*省略了函数定义*/
int main(){
int birthday=19901018;
int thisYear=2030;
cout<<get_age(birthday,thisYear)<<endl;
}
形参作用域
形参其实就是变量,是在函数中定义的变量,所以在函数外不能使用。(因为形参的作用域只在函数内部,就像是for循环中的变量作用域一样)
函数的调用过程
函数调用 --> 传值(将实参赋值给形参) --> 找到函数相应的函数体后执行。
这里的“将实参赋值给形参”是重点,形参和实参不是同一个变量,作用域也不一样。
我们来看一个有趣的现象:
#include<iostream>
using namespace std;
int func(int a){
a += 1;
return a;
}
int main(){
int a = 3;
cout << func(a) << endl;
cout << a << endl;
}
输出的结果:
4
3
解释:
程序会从main函数作为入口,并从main中的第一条语句开始执行。
当执行到func()函数时,将a的值传入函数。注意这里是将main函数中a赋值给函数定义头部的a。
所以说,两个a不是同一个,而且作用域不一样。
执行函数体后,返回4,函数体中的a被删除(内存空间被回收)。
最终,得到了返回值4。由于两个a不是同一个a。所以函数中的a影响不到main中的a。main中的a还是3。
小贴士:
main函数执行完后,其中定义的变量也会被删除回收。
执行完后我们发现,实参的值和形参的值是互不影响的。
如果想要实参值随着形参改变,那么我们可以将形参设置为指针或者引用。关于指针和引用,请听下回分解。
特殊的函数
没有返回值(void)
#include<iostream>
using namespace std;
void func(){
for (int i = 0; i < 5; i++)
cout << "hello world " << i << endl;
}
int main(){
func();
}
- 上面的函数没有参数。
- 上面的函数没有返回值。这时候返回值类型写void。
函数重载
有这样一种场景,我们需要实现一种功能,但参数的个数和类型不确定,这样我们就需要给每一种可能的场景编写一个函数。
知识点:
C++用什么区分不同的函数?函数名+参数个数+参数类型。(注意:与返回值类型无关)
比如,我们需要实现几个数相加的函数(int+int,int+int+int,int+double)。由于这几个函数的参数类型和个数都不一样,我们就要设置不同的函数,这样设置多个函数名显得有点麻烦和累赘。
如何进行函数重载,实现几个数相加:
int sum(int a,int b){
return a + b;
}
int sum(int a,int b,int c){
return a + b + c;
}
double sum(int a,double b){
return a + b;
}
void main(){
cout << "sum(1, 2)的值为: " << sum(1, 2) << endl; //值为3
cout << "sum(1, 2, 3)的值为: " << sum(1, 2, 3) << endl; //值为6
cout << "sum(1, 2.5)的值为: " << sum(1, 2.5) << endl; //值为3.5
}
解释:
由于三个函数的参数个数和类型不一样,所以即使名字都叫sum,C++也不会混淆。
调用时,程序会自动根据参数个数和参数类型选择相应的函数调用。
函数默认值和函数重载
在上一个例子中,如果代码有默认值:
int sum(int a,int b,int c=20)
。那么,如果调用时只给出两个参数,那么C++就不知道调用int sum(int a,int b)
,还是int sum(int a,int b,int c=20)
。
所以,这样的函数默认值是非法的。
比如:
int sum(int a,int b){
return a + b;
}
int sum(int a,int b,int c=20){
return a + b + c;
}
void main(){
cout << "sum(1, 2)的值为: " << sum(1, 2) << endl; //报错!
}
解释:
由于调用函数时只给了2个参数。程序不知道调用的是sum(1,2)还是sum(1,2,20),所以程序报错。
递归
什么是递归函数?
函数直接或间接调用函数本身,则该函数称为递归函数。
递归函数的特点?
1.直接或间接调用函数本身。
2.有结束逻辑。(即停止调用的逻辑)直接或间接调用函数本身是递归函数最直观的特点。
如果没有结束逻辑,递归函数就会无限调用自己,就会产生栈溢出。
![QQ截图20191103192931.png QQ截图20191103192931.png](https://i.loli.net/2019/11/03/HIeViYDwMt1odLf.png)
例子:计算阶乘n!
int Leo(int n){
int sum = 1;
if (1 == n) //递归终止条件(结束逻辑)
return 1;
sum = n * Leo(n - 1);
return sum; //返回阶乘的总和
}
void main(){
int num;
cin >> num;
cout << Leo(num) << endl; //输出该数的阶乘
}
解释:
在求X的阶乘时,可以利用递归的思想:把大问题转化成小问题,再把小问题转化成更小的问题,最后得解。
C++内置函数
数学:
![UTOOLS1573214354532.png UTOOLS1573214354532.png](https://i.loli.net/2019/11/08/RXneK6mkj7vlwQy.png)
日期和时间
关于日期和时间,有四个与时间相关的数据类型:clock_t,time_t,size_t和tm。
clock_t,time_t,size_t使用某种整数表示时间。tm使用结构体保存时间。
我们无需知道这几个数据类型的细节,只要会用即可。
需要包含头文件:#include<ctime>
下面简单介绍一下系统定义好的tm类型
struct tm{
int tm_sec; // 秒,正常范围从 0 到 59,但允许至 61
int tm_min; // 分,范围从 0 到 59
int tm_hour; // 小时,范围从 0 到 23
int tm_mday; // 一月中的第几天,范围从 1 到 31
int tm_mon; // 月,范围从 0 到 11
int tm_year; // 自 1900 年起的年数
int tm_wday; // 一周中的第几天,范围从 0 到 6,从星期日算起
int tm_yday; // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
int tm_isdst; // 夏令时
};
解释:
代码无需我们定义,系统已经写好,我们只要用tm即可。
tm_year是自1900年起的年数,而不是时间戳中的1970年。(但是我们会使用你1970年的时间戳来初始化tm)
tm_wday等数据成员的范围是从0开始的。也就是说,如果返回3,代表是星期四。
实例1-获取格式化时间:
time_t now = time(NULL); //自1970年1月1日以来经过的秒数
//获取当前时间
char* dt = ctime(&now); //把now转换为字符串形式
cout << "本地日期和时间:" << dt << endl;
//获取UTC时间
tm *gmtm = gmtime(&now);
dt = asctime(gmtm);
cout << "UTC日期和时间:"<< dt << endl;
//输出:
本地日期和时间:Fri Nov 08 20:11:06 2019
UTC日期和时间:Fri Nov 08 12:11:06 2019
实例2-获取结构化时间:
time_t now = time(NULL);
tm *ltm = localtime(&now);
cout << "年: "<< 1900 + ltm->tm_year << endl;
cout << "月: "<< 1 + ltm->tm_mon<< endl;
cout << "日: "<< ltm->tm_mday << endl;
cout << "时间: "<< ltm->tm_hour << ":";
cout << ltm->tm_min << ":";
cout << ltm->tm_sec << endl;
//输出:
年: 2019
月: 11
日: 8
时间: 20:12:40
随机函数
C++使用函数rand()产生随机数。
但是只使用rand()的话,每一次运行产生的随机数都是一样的。因为种子值是一样的。
可以使用函数srand(int)函数设置种子。但是,如果种子值固定,那么每一次产生的随机数也是一样的。所以我们可以配合ctime中的time(NULL),每次运行可以得到不同的种子值。
#include<iostream>
#include<ctime>
using namespace std;
int main(){
srand(time(0)); //设置随机种子值(每次启动程序的时间戳都不一样)
for (int i = 0; i < 20; i++)
cout << rand() << endl; //输出20次随机数
}
实例:得到[1,37]的随机数
srand(time(NULL));
int num = rand() % 37 + 1;
cout << num << endl;
睡眠
背景
有的时候,我们需要让程序睡眠指定时间。
比如:广告之间的替换。各个广告之间的时间间隔是一样的。使用方法
Sleep(2);表示睡眠2ms,即2ms之后再执行后面的代码。
windows系统中需要包含头文件:#include <windows.h>。注意:不同的操作系统包含的头文件不一样。
实例-指定时间后输出Hello world:
#include<iostream>
#include<ctime>
#include <windows.h>
using namespace std;
int main(){
Sleep(2000);
cout << "Hello world!" << endl; //2s之后输出Hello world!
}
实例-广告之间的替换:
while (true){ //2s替换一个宣传词
cout << "富强" << endl;
Sleep(2000);
cout << "民主" << endl;
Sleep(2000);
cout << "文明" << endl;
Sleep(2000);
cout << "和谐" << endl;
Sleep(2000);
}
数组排序
很多场景中需要我们对数组进行排序,从大到小或者从小到大。
排序算法有很多:选择排序、冒泡排序、堆排序、希尔排序…
对于简单的数组,我们可以不必考虑排序效率,直接调用C++自带的排序函数:sort()
语法:
//需要包含头文件:#include <algorithm>
sort(<数组开始地址>,<数组结束地址>) //这是默认的排序:升序(从小到大)
sort(<数组开始地址>,<数组结束地址>,自定义排序函数)
实例-自定义排序:
#include<iostream>
#include <algorithm>
using namespace std;
bool compare(int a, int b){ //自定义排序函数
return a > b; //降序
}
int main(){
int arr[5] = { 5, 3, 6, 9, -1 };
sort(arr, arr + 5,compare);
for (int i = 0; i < 5; i++)
cout << arr[i] << " ";
cout << endl;
}
解释:
如果直接使用
sort(arr, arr + 5)
,那么这是从小到大的排序。
如果使用sort(arr, arr + 5,compare)
,sort函数就会调用compare函数来判断比较大小,这样就实现了自定义的排序。
比如:有一个人,有年龄和体重身高,我们可以根据年龄比较两个人的大小,也可以根据体重身高比较两个人的大小。这个时候就需要我们自定义排序。