二、 函数#

1. 函数介绍#

在大多数地方,c++ 和 python的函数是一样的,都是用来包裹定义好的语句,避免重复拷贝粘贴。不过还是有些许不一样的地方。

  1. python的函数是以回车换行结尾,c++的函数是以 大括号结尾
  2. python的函数通常使用缩进方式来表示函数体, ,c++使用大括号区域来表示
  3. python是动态类型语言,而c++是静态类型语言,所以有时候需要像声明变量一样,声明函数。

2. 定义函数#

函数的定义一般可以包含以下几个部分: 方法名称方法参数返回值方法体 , 根据可有可无的设置,函数一般会有以下4种方式体现。

  • 声明并调用函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>
using namespace std;

void say_hello(){ 
    count << "hello" << endl;
}

int main(){

    say_hello();
    return 0 ;
}

1. 无返回值无参数#

1
2
3
4
5
6
7
8
void say_hello(){ 
    count << "你好 " << endl;
}

int main(){
    say_hello();
    return 0 ;
}

2. 无返回值有参数#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include<iostream>

using namespace std;

void say_hello(string name){ 
    count << "你好 "<< name << endl;
}

int main(){
    say_hello("张三");
    return 0 ;
}

3. 有返回值无参数#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include<iostream>

using namespace std;

string say_hello(){ 
   return "你好 张三";
}

int main(){
    cout << say_hello() << endl;
    return 0 ;
}

4. 有返回值有参数#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include<iostream>

using namespace std;

string say_hello(string name){ 
   return "你好 "+ name;
}

int main(){
    cout << say_hello("张三") << endl;
    return 0 ;
}

3. 函数原型#

般来说,c++的函数一般包含声明和定义两个部分。因为c++是静态类型语言,程序属于自上而下编译,所以在使用函数前,必须先表示函数的存在,告诉编译器函数所需要的参数以及函数的返回值是什么。

1. 函数定义在前#

在调用函数之前,事先先定义好函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>
using namespace std;

//函数定义 ,函数的真正实现。
int add(int a , int b){
    return a + b ; 
}

int main(){
    cout << add(1 ,2)<< endl;
    return 0 ;
}

2. 使用函数原型#

把函数分成声明和定义两部分,函数的原型定义在调用的前面,具体实现可以放在后面。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
using namespace std;

//函数声明 ,也叫函数原型 并不知道这个函数具体是如何实现的。只是有一些基本架子而已。
int add (int a , int b);

int main(){
    cout << add(1 ,2)<< endl;
    return 0 ;
}

//函数定义 ,函数的真正实现。
int add(int a , int b){
    return a + b ; 
}

4. 分离式编译#

一般说来,函数的声明 ( 函数原型 )通常都会放到头文件中,之所以称之为头文件是因为它总是在main函数的前面就引入进来。头文件一般以 .h 或者 .hpp 结尾,通常用于 写类的声明(包括类里面的成员和方法的声明)、函数原型、#define常数等,但一般来说不写出具体的实现

  • math.h

为了能够让声明和定义能够快速的被关联上,通常它们的名称会被定义成一样的,这已经成为了一种默认的规定

1
2
//函数声明
int add (int a , int b);
  • math.cpp
1
2
3
4
5
6
#include "math.h"

//函数定义 ,函数的真正实现。
int add(int a , int b){
    return a + b ; 
}
  • main.cpp
1
2
3
4
5
6
7
#include <iostream>  
#include "math.h" //这里使用"" 表示从当前目录查找

int main(){
    add(1 ,2);
    return 0 ;
}

5. 函数重载#

在许多语言中,经常会见到两个或者两个以上的函数名称是一样的,当然他们的 参数个数 或者 参数类型 或者是 参数的顺序 是不一样的。这种现象有一个学名叫做 重载 overload, 由于python属于动态类型语言,不区分数据类型,参数可以是任意类型,所以它没有重载。

下面的示例代码即是对加法运行进行了重载,以便能够针对不同的数据类型,不同的参数个数做出匹配

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
int add(int a , int b){
    return a + b ;
}

int add(int a , int b , int c){
    return a + b + c;
}


int add(double a , double b){
    return a + b ;
}

int main(){
    add(3, 3);
    add(3, 3, 3);
    add(2.5 , 2.5);

    return 0 ;
}

思考: 为什么python没有函数重载?

6. 函数参数#

python的函数,在传递参数的时候,有可变对象和不可变对象的现象,那么在C++里面也有类似的说法。只不过是另一种说辞罢了。

python中传递不可变对象,在C++中,对应的是值的拷贝,也就是传递的只是数据的一份拷贝而已。在函数内部修改数据,并不会改变外部数据

python中传递可变对象,在c++中,对应的是引用传递,也就是传递的是对象的引用,而不是拷贝。在函数内部修改数据,会导致外部数据也发生改变。

1. 值传递#

C++默认情况下,处理函数参数传递时,多数使用的是值的拷贝,少数部分除外。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include<iostream>
using namespace std;

void  scale_number(int num);

int main(){
    int number{1000};
    scale_number(number);

    //打印number 1000
    cout << number <endl;
    return 0 ;
}

void scale_number(int num){
    if(num > 100)
        num = 100;
}

2. 传递数组#

函数的参数除了能传递普通简单的数据之外,数组也是可以传递的。但是数组稍微有点特殊,这里多做讲解。

  1. 前面提过,形参实际上就是实参的一份拷贝,就是一个局部变量。
  2. 数组的数据太大,如果都进行拷贝,那么比较麻烦,也造成了浪费
  3. 所以实际上传递数组的时候,并不会进行整个数组的拷贝,而只是传递数组的第一个元素内存地址 (指针 ) 进来。
  4. 数组的数据还是在内存中,只是把第一个元素(也就是数组的起始)内存地址传进来而已。
  5. 这就造成了函数的内部根本无法知道这个数组的元素有多少个。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;

using namespace std;
//传递数组长度
void    print_array(int number[] , 5 );

int main(){
    //声明数组
    int array []{1,2,3,4,5};

    //打印数组
    print_array(array , 5);

    return 0 ;

}

//传递数组,打印数组
void print_array(int array[] , int size){
    for (int i {0} ; i < size ; i++){
        count << array[i] << endl;
    }
}

3. 传递引用#

目前为止,我们所有函数的参数传递,都是对数据进行了一份拷贝(数组除外)。那么在函数的内部是不能修改值的,因为这仅仅是一份值得拷贝而已(函数外部的值并不会受到影响)。如果真的想在函数内部修改值,那么除了数组之外,还有一种方式就是传递引用

引用实际上只是原有数据的一种别名称呼而已,使用 & 定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;


void  scale_number(int &num);

int main(){
    int number{1000};
    scale_number(number);

    //打印number100
    count << number <endl;
    return 0 ;
}

void scale_number(int &num){
    if(num > 100)
        num = 100;
}

7 . 函数是如何被调用工作的#

  1. 函数是使用函数调用栈来管理函数调用工作的。
  2. 类似盒子的栈
  3. 遵循后进先出
  4. 可以往里面执行压栈和出栈动作(push 和 pop)
  5. 栈的结构和激活记录
  6. 函数必须把它的返回值返回给调用它的函数(A ---> B)
  7. 每次函数的调用都需要创建一次激活记录,然后把它压入栈中(push)
  8. 当一个函数被调用完毕的时候,就需要从栈中弹出(pop)
  9. 函数的参数以及内部的局部变量都是存储在栈中。
  10. 函数栈有可能抛出栈溢出异常(Stack Overflow)
  11. 一旦函数调用栈中被push进来的函数记录过多,就有可能出现。(例如:无限循环调用)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void func2(int &x , int y , int z){
    x +=y+z;
}

int func1(int a , int b){
    int result{};
    result += a + b;
    func2(result , a , b );
    return result ; 
}

int main (){
    int x{10};
    int y{20};
    int z{};

    z = func1(x , y );

    count << z << endl;
    return 0 ;
}

8 . 内联函数#

函数可以使我们复用代码,但是一个函数的执行,需要开辟空间、形参和实参进行值得拷贝,还要指明函数返回、以及最后回收释放资源的动作,这个过程是要消耗时间的。

  • 作为特别注重程序执行效率,适合编写底层系统软件的高级程序设计语言,如果函数中只有简单的几行代码,那么可以使用inline 关键字来解决了函数调用开销的问题
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include<iostream>

inline int calc_Max (int a, int b)
{
    if(a >b)
        return a;
    return b;
}

int main(){

    int max = calc_Max(3, 8);
    std::cout << "max = " << max << std::endl;

    return 0 ;
}

增加了 inline 关键字的函数称为“内联函数”。内联函数和普通函数的区别在于:当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插人调用语句处,就像整个函数体在调用处被重写了一遍一样。

有了内联函数,就能像调用一个函数那样方便地重复使用一段代码,而不需要付出执行函数调用的额外开销。很显然,使用内联函数会使最终可执行程序的体积增加。以时间换取空间,或增加空间消耗来节省时间,这是计算机学科中常用的方法。

9. 范围规则#

在学习过程,我们会定义很多变量或者引用、这些变量由于定义的位置不同,所以它们的作用域范围也不同。一般会划分成几种类型: 代码块 | 局部变量 | 静态变量 | 全局变量

  • 单独代码块
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include<iostream>
using namespace std;

int main(){

    int  num{10};
    int  num1{20};

    cout << num << num1 << endl;

    {
        int num{100};
        cout << "num = "<< num << endl;
        cout << "num1 = "<< num1 << endl;
    }

}
  • 函数中局部变量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include<iostream>
using namespace std;

int num{300};
void local_example(int x){


    int num{1000};
    cout << "num =" << num << endl;

    num = x ;
    cout << "num =" << num << endl;


}
  • 静态本地变量

  • 只会初始化一次

  • 重复调用函数也只会初始化一次。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include<iostream>
using namespace std;

void static_local_example(){

    static int num{100};
    cout << "num ="<< num << endl;
    num+=100;
    cout << "num ="<< num << endl;
}

int main(){
    static_local_example();
    static_local_example();
    return 0 ;
}
  • 全局变量

通常声明在所有函数和类的外部 ,若存在局部变量和全局变量同名情况下,可以使用 域操作符 :: 来访问全局变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include<iostream>
using namespace std;

int age = 99;
int main(){

    int age =18 ;
    cout << ::age << endl;
    return 0 ;
}