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();
}
  1. 上面的函数没有参数。
  2. 上面的函数没有返回值。这时候返回值类型写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

例子:计算阶乘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

日期和时间

关于日期和时间,有四个与时间相关的数据类型: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函数来判断比较大小,这样就实现了自定义的排序。
比如:有一个人,有年龄和体重身高,我们可以根据年龄比较两个人的大小,也可以根据体重身高比较两个人的大小。这个时候就需要我们自定义排序。


posted @ 2019-09-09 15:47  NetRookieX  阅读(34)  评论(0编辑  收藏  举报