【算法竞赛】C++函数详解:从定义、调用到高级用法

🔭 个人主页: 散峰而望

《C语言:从基础到进阶》《编程工具的下载和使用》《C语言刷题》《算法竞赛从入门到获奖》《人工智能AI学习》《AI Agent》
愿为出海月,不做归山云


🎬博主简介

文章目录

  • 前言
  • [1. 函数是什么](#1. 函数是什么)
  • [2. 函数的分类](#2. 函数的分类)
    • [2.1 库函数](#2.1 库函数)
      • [2.1.1 库函数介绍](#2.1.1 库函数介绍)
      • [2.1.2 库函数使用举例](#2.1.2 库函数使用举例)
    • [2.2 自定义函数](#2.2 自定义函数)
      • [2.2.1 函数的语法形式](#2.2.1 函数的语法形式)
      • [2.2.2 函数定义](#2.2.2 函数定义)
  • [3. 函数参数和返回值](#3. 函数参数和返回值)
    • [3.1 实参和形参](#3.1 实参和形参)
      • [3.1.1 实参](#3.1.1 实参)
      • [3.1.2 形参](#3.1.2 形参)
      • [3.1.3 实参和形参的关系](#3.1.3 实参和形参的关系)
    • [3.2 函数传参](#3.2 函数传参)
      • [3.2.1 数组做函数参数](#3.2.1 数组做函数参数)
      • [3.2.2 字符串做函数参数](#3.2.2 字符串做函数参数)
      • [3.2.3 全局变量不用传参](#3.2.3 全局变量不用传参)
    • [3.3 返回值](#3.3 返回值)
  • [4. 函数的声明和调用](#4. 函数的声明和调用)
    • [4.1 函数声明](#4.1 函数声明)
    • [4.2 函数调用](#4.2 函数调用)
      • [4.2.1 传值调用](#4.2.1 传值调用)
      • [4.2.2 引用](#4.2.2 引用)
        • [4.2.2.1 引用概念](#4.2.2.1 引用概念)
        • [4.2.2.2 引用特性](#4.2.2.2 引用特性)
      • [4.2.3 传址(引用)调用](#4.2.3 传址(引用)调用)
      • [4.2.4 传值、传址效率比较](#4.2.4 传值、传址效率比较)
  • [5. 函数重载](#5. 函数重载)
    • [5.1 重载概念](#5.1 重载概念)
    • [5.2 重载举例](#5.2 重载举例)
  • [6. 练习](#6. 练习)
  • 结语

前言

在算法竞赛和程序开发中,函数是构建高效、可维护代码的核心工具之一。通过函数,复杂的逻辑可以被拆解为独立的模块,提升代码的复用性和可读性。C++作为算法竞赛的主流语言,其函数机制不仅支持基础的封装与调用,还提供了诸如函数重载、引用传参等高级特性,帮助开发者优化性能并简化设计。

本文将从函数的基础概念出发,系统介绍C++函数的定义、分类、参数传递、返回值机制以及高级用法。内容涵盖库函数与自定义函数的应用场景、实参与形参的交互原理、传值与传址的效率对比,以及函数重载的实现方式。通过理论解析与代码示例结合,帮助读者掌握函数的核心技术,并能在算法竞赛中灵活运用。

无论是初学者希望夯实基础,还是有经验的选手寻求性能优化,本文均能提供清晰的指导与实践参考。

1. 函数是什么

数学中我们其实就见过函数的概念,比如:一次函数 y = kx + b,k 和 b 都是常数,给一个任意的 x,就得到一个 y 值。其实在 C/C++ 语言中引入了 函数(function) 的概念,有些翻译为:子程序 ,子程序这种翻译更加准确一些。函数就是一个完成某项特定的任务的一小段代码,这段代码是有特殊的写法和调用方法的。

C/C++ 语言程序其实是由若干个小的函数组合而成的,也可以说:一个大的计算任务可以分解成若干个较小的函数(对应较小的任务)完成。同时一个函数如果能完成某项特定任务的话,这个函数也是可以复用的,提升了开发软件的效率。

如果我们要完成这样下面这样的任务:

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
    int arr[10] = { 0 };
    //打印数组的内容 
    
    //给数组的元素赋值为1~10 
    
    //打印数组 
    
    return 0;
}
cpp 复制代码
//代码1 
#include<iostream>
using namespace std;
int main()
{
    int arr[10] = { 0 };
    //打印数组的内容 
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
    //给数组的元素赋值为1~10 
    for (i = 0; i < 10; i++)
    {
        arr[i] = i + 1;
    }
    //打印数组 
    for (i = 0; i < 10; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
    return 0;
}

可以看到,如果没有函数,我们能写出的代码如上的代码 1,其中有一部分代码是重复的,目的就是打印数组的内容,那如果我们封装成一个函数,在需要的时候调用,就很方便,就是下面的代码 2。

cpp 复制代码
//代码2 
#include<iostream>
using namespace std;
void print_arr(int arr[])
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
}
int main()
{
    int arr[10] = { 0 };
    //打印数组的内容 
    print_arr(arr);
    //给数组的元素赋值为1~10 
    for (int i = 0; i < 10; i++)
    {
        arr[i] = i + 1;
    }
    //打印数组 
    print_arr(arr);
    return 0;
}

在代码 2 中 print_arr 就是我们自己定义的一个函数,使用函数能有什么好处呢?

  1. 模块化开发,一个大的功能,总能拆分成各种子功能,每个子功能都可以设计成一个函数,每个函数可以作为一个独立的模块存在,程序的逻辑更加清晰,逻辑关系更加明确。
  2. 代码可以复用,只要根据需要定义出一个函数,需要这个功能的地方,直接调用函数就行,降低了代码的冗余,提升了开发效率。
  3. 方便多个程序员之间协作开发,方便程序的多个模块之间互相交互。
  4. 熟悉函数的使用之后,代码的编写、阅读、调试、维护都变得更加容易。

2. 函数的分类

在 C/C++ 中,函数一般分为库函数自定义函数,那我们接下来就介绍一下。

2.1 库函数

2.1.1 库函数介绍

前面内容中学到的 printf、scanf 都是库函数,库函数是标准库中提供的现成的函数,我们只要学习函数的功能,就能直接使用。有了库函数,一些常⻅的功能就不需要程序员自己实现了,一定程度提升了效率;同时库函数的质量和执行效率上都更有保证。

编译器的标准库 中提供了一系列的库函数,这些库函数根据功能的划分,都在不同的头文件中进行了声明。

库函数的学习和查阅工具很多,比如:

C/C++官方参考手册:https://en.cppreference.com/w/cpp/header.html

C/C++第三方网站:https://legacy.cplusplus.com/reference/(个人更推荐这个)

库函数中有数学相关的,有日期相关的,有算法相关的等等。这些库函数相关的信息都有自己对应的头文件,每一个头文件中都包含了相关的一组函数和类型等信息。

C++ 是兼容 C 语言,所以在 C++ 中也包含了一些来自 C 语言的头文件,这些头文件的后缀是 .h ,如果需要也可以直接包含使用;有一些来自 C 语言的头文件,在 C++ 中会在原来 C 语言的头文件进行了封装,在 C++ 程序中更推荐 C++ 的头文件写法,以下是常见 C 和 C++ 对应的头文件:

解释 C 头文件 C++ 头文件
常用数学函数 < math.h > < cmath >
浮点类型的极限 < float.h > < cfloat >
C 风格输入输出 < stdio.h > < cstdio >
字符串处理 < string .h > < string >

2.1.2 库函数使用举例

举例: sqrt

cpp 复制代码
double sqrt (double x);

解释:

  • sqrt 是函数名
  • x 是函数的参数,表示调用 sqrt 函数需要传递一个 double 类型的值
  • double 是返回值类型,表示函数计算的结果是 double 类型的值

功能:

Compute square root 计算平方根

Returns the square root of x.(返回平方根)

头文件包括:

库函数是在标准库中对应的头文件中声明的,所以库函数的使用,务必包含对应的头文件,不包含是可能会出现一些问题的。

实践:

cpp 复制代码
#include <iostream>
#include <cmath>       //数学函数头文件,不包含则无法使用sqrt函数 
using namespace std;
int main()
{
    double d = 16.0;
    double r = sqrt(d);
    cout << r << endl;
    return 0;
}

运行结果:

2.2 自定义函数

了解了库函数,其实库函数的功能是有限的,实际开发过程中还是得根据需要将代码封装成自定义的函数;自定义的函数就是自己设计和实现的函数。

2.2.1 函数的语法形式

其实自定义函数和库函数是一样的,形式如下:

cpp 复制代码
ret_type fun_name(形式参数)
{
}

语法说明如下:

  • ret_type 是用来表示函数计算结果的类型,有时候返回类型可以是 void,表示什么都不返回
  • fun_name 是为了方便使用函数;就像人的名字一样,有了名字方便称呼,函数有了名字方便调用,所以函数名尽量要根据函数的功能起的有意义。
  • 函数的参数就相当于工厂加工需要的原材料,函数的参数也可以是 void,明确表示函数没有参数。如果有参数,多个参数用逗号隔开,每个参数要分别交代清楚参数的类型和名字。
  • { } 括起来的部分被称为函数体,函数体就是完成计算的过程。

我们可以把函数想象成小型的一个加工厂,工厂得输入原材料,经过工厂加工才能生产出产品,那函数也是一样的,函数一般会输入一些值(可以是 0 个,也可以是多个),经过函数内的计算,得出结果。

2.2.2 函数定义

那函数是如何定义的,接下来我们举一个实际的例子,来讲解。

例如:写一个加法函数,完成 2 个整型变量的加法操作。

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
    int a = 0;
    int b = 0;
    //输入 
    cin >> a >> b;
    //任务:调用加法函数,完成a和b的相加 
    //求和的结果放在r中 
    //to do
    
    //输出 
    cout << r << endl;
    return 0;
}

我们根据要完成的功能,给函数取名:Add,函数 Add 需要接收 2 个整型类型的参数,函数计算的结果也是整型。

所以我们根据上述的分析写出函数:

cpp 复制代码
#include<iostream>
using namespace std;
//这就是函数的定义 
int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}
int main()
{
    int a = 0;
    int b = 0;
    //输入 
    cin >> a >> b;
    //调用加法函数,完成a和b的相加 
    //求和的结果放在r中 
    int r = Add(a, b);
    //输出 
    cout << r << endl;
    return 0;
}

Add 函数也可以简化为:

cpp 复制代码
int Add(int x, int y)
{
    return x + y;
}

函数的参数部分需要交代清楚:参数个数,每个参数的类型是啥,形参的名字叫啥。

上面只是一个例子,未来我们是根据实际需要来设计函数,函数名、参数、返回类型都是可以灵活变化的。函数的实现就是函数的定义。

3. 函数参数和返回值

3.1 实参和形参

实际上,在函数定义和使用的过程中,函数的参数要被分为两种:

  • 实际参数,简称实参
  • 形式参数,简称形参

为了更好进行解释上面的概念,先回顾我们前面写的代码:

cpp 复制代码
#include<iostream>
using namespace std;

int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int a = 0;
    int b = 0;
    cin >> a >> b;
    int r = Add(a, b);
    cout << r << endl;
    return 0;
}

3.1.1 实参

实际参数就是真实传递给函数的参数。

在上面代码中,第 4~9 行是 Add 函数的定义,有了函数后,再第 16 行调用 Add 函数的。我们把第 16 行调用 Add 函数时,传递给函数的参数 a 和 b,称为实际参数 ,简称实参

3.1.2 形参

在上面代码中,第 4 行定义函数的时候,在函数名 Add 后的括号中写的 x 和 y,称为形式参数 ,简称形参

为什么叫形式参数呢?实际上,如果只是定义了 Add 函数,而不去调用的话,Add 函数的参数 x 和 y 只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形式的实例化。

3.1.3 实参和形参的关系

虽然我们提到了实参是传递给形参的,他们之间是有联系的,但是形参和实参各自是独立的内存空间。

这个现象是可以通过调试来观察的。请看下面的代码和调试演示:

cpp 复制代码
#include<iostream>
using namespace std;

int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int a = 0;
    int b = 0;
    cin >> a >> b;
    int r = Add(a, b);
    cout << r << endl;
    return 0;
}

在调试的可以观察到,x 和 y 确实得到了 a 和 b 的值,但是 x 和 y 的地址和 a 和 b 的地址是不一样的,当 a 和 b 传参给形参 x 和 y 的时候,x 和 y 只是得到了 a 和 b 的值,他们得有自己独立的空间。所以我们可以理解为形参是实参的一份临时拷贝

3.2 函数传参

3.2.1 数组做函数参数

在使用函数解决问题的时候,难免会将数组作为参数传递给函数,在函数内部对数组进行操作。

比如:写一个函数将一个整型数组的内容,全部置为 1,再写一个函数打印数组的内容。

简单思考一下,基本的形式应该是这样的:

cpp 复制代码
#inculude <iostream>
using namespace std;
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    set_arr();                  //设置数组内容为-1 
    print_arr();                //打印数组内容 
    return 0;
}

这里的 set_arr 函数要能够对数组内容进行设置,就得把数组作为参数传递给函数,同时函数内部在设置数组每个元素的时候,也得遍历数组,需要知道数组的元素个数。所以我们需要给 set_arr 传递 2 个参数,一个是数组,另外一个是数组的元素个数。仔细分析 print_arr 也是一样的,只有拿到了数组和元素个数,才能遍历打印数组的每个元素

cpp 复制代码
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    int sz = sizeof(arr)/sizeof(arr[0]);
    set_arr(arr, sz);            //设置数组内容为1 
    print_arr(arr, sz);          //打印数组内容 
    return 0;
}

数组作为参数传递给了 set_arr 和 print_arr 函数了,那这两个函数应该如何设计呢?

这里我们需要知道数组传参的几个重点知识:

  • 函数的实参的名字和形参的名字可以相同,也可以不同
  • 函数的形式参数要和函数的实参个数匹配
  • 函数的实参是数组,形参也写成数组形式的
  • 形参如果是一维数组,数组大小可以省略不写
  • 形参如果是二维数组,行可以省略,但是列不能省略
  • 数组传参,形参是不会创建新的数组的
  • 形参操作的数组和实参的数组是同一个数组(因为如果不是同一个数组的话,创建一个新的空间不创建数组,那么数组内部的大小可有可无)

更详细的可以见:
基本魔法语言函数(一)
你真的熟练掌握函数的高阶玩法了吗?
提示:

数组传参时候,实参数组名就行

根据上述的信息,我们就可以实现这两个函数:

cpp 复制代码
void set_arr(int arr[], int sz)
{
    int i = 0;
    for(i = 0; i < sz; i++)
    {
        arr[i] = 1;
    }
}
void print_arr(int arr[], int sz)
{
    int i = 0;
    for(i = 0; i < sz; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
}

3.2.2 字符串做函数参数

那如果将字符串做函数参数呢?其实也很简单,直接在形参的部分使用字符串来接收就可以。这里的形参 s 也是实参 s 的一份临时拷贝,对形参的修改不能影响实参。

cpp 复制代码
void test(string s) 
{
    cout << s << endl;
}
int main()
{
    string s("hello world");
    test(s);
}

3.2.3 全局变量不用传参

全局变量的作用域很大,在整个程序中都可以使用,那么只要把变量、数组等定义成全局变量,在函数中使用,是可以不用传参的,在竞赛中为了方便经常使用,但是在软件工程中很少这么使用。

cpp 复制代码
#include <iostream>
using namespace std;
int arr[10] = { 0 };
void print_arr()//这里可以不传参
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
}
int main()
{
    //打印数组的内容 
    print_arr();
    //给数组的元素赋值为1~10 
    for (int i = 0; i < 10; i++)
    {
        arr[i] = i + 1;
    }
    //打印数组 
    print_arr();
    return 0;
}  

当然,有时候变量或者数组,定义成全局的时候,是不能解决问题,比如:递归等场景,这时候,就得考虑传参的问题。

3.3 返回值

我们在设计的函数的时候,函数在经过计算后,有时候需要带回一些计算好的数据,这时候往往使用 return 来返回,这里我们就讨论一下使用 return 返回。

  1. return 后边可以是一个数值,也可以是一个表达式,如果是表达式则先执行表达式,再返回表达式的结果。函数返回的值,可以使用变量来接收,如果不需要这个返回值,也可以不接收。
cpp 复制代码
// 这里使用简化版本的加法函数 
int Add(int x, int y)
{
    return x + y;       // 1. 先执行x+y,得到该表达式计算的结果 2. 执行return,返回结果 
}
//在main函数中使用return 0; 返回的就是一个数值
  1. return 后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是 void 的情况。
cpp 复制代码
void test(int n)
{
    if(n == 2)
        return; //只要执行这里的return,函数就提前返回,不再打印数据 
    cout << n << endl;
}
  1. return 返回的值和函数返回类型不一致,系统会自动将返回的值的类型隐式转换为函数的返回类型。
cpp 复制代码
#include <iostream>
using namespace std;
 
int test()
{
    return 3.14;
}
int main()
{
    int ret = test();
    cout << ret << endl;
    return 0;
}

演示结果:

  1. return 语句执行后,函数就彻底返回,后边的代码不再执行。
cpp 复制代码
#include <iostream>
using namespace std;
void print_arr(int arr[], int n)
{
    int i = 0;
    for(i=0; i<n; i++)
    {
        if(i == 5)
            return;//对比换成break 
        cout << arr[i] << " ";
    }
    cout << endl;
    cout << "打印完毕" << endl;
}
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    print_arr(arr, 10);
    return 0;
}
cpp 复制代码
#include <iostream>
using namespace std;
void print_arr(int arr[], int n)
{
    int i = 0;
    for(i=0; i<n; i++)
    {
        if(i == 5)
            break;
        cout << arr[i] << " ";
    }
    cout << endl;
    cout << "打印完毕" << endl;
}
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    print_arr(arr, 10);
    return 0;
}

演示结果:

可以看到 return 直接结束函数,而 break 还会打印打印完毕

即如果函数中 return 语句被执行,则函数直接返回,即使函数中 return 语句后还有其他代码也不再执行。

4. 函数的声明和调用

4.1 函数声明

一般在使用函数的方式写代码的时候,函数定义好之后,在后续的代码中对函数进行调用,例如代码 1。有时候我们也可能将函数定义放在了函数调用的后边,例如代码2,这时候编译器在编译的时候就会有警告出现(如下图),提示这个函数可能不存在,为了消除这种警告,我们一般在函数的调用之前先声明一下这个函数,这就是函数声明。函数声明就是告诉编译器,有一个函数名字叫什么,参数是什么,返回类型是什么,至于这个函数是否真的存在,是要看函数的定义了。函数调用必须满足先声明后使用。

cpp 复制代码
//代码1 
#include <iostream>
using namespace std;
//函数定义 
int Add(int x, int y) 
{
    return x + y;
}
int main()
{
    int a = 10;
    int b = 20;
    int c = Add(a, b);
    cout << c << endl;
    return 0;
}
cpp 复制代码
//代码2 
#include <iostream>
using namespace std;
//函数声明 
int Add(int x, int y);
int main()
{
    int a = 10;
    int b = 20;
    int c = Add(a, b);
    cout << c << endl;
    return 0;
}
int Add(int x, int y) //函数定义 
{
    return x + y;
}

代码 2 的演示结果:

4.2 函数调用

函数调用的方式,一般会分为两类:传值调用和传址(引用)调用 ,我们前面讲过的都是传值调用。因为暂时还没有学习指针,这里下面我们会讲解一下传址调用

4.2.1 传值调用

案例 1: 写一个函数 Max,求两个整数的较大值。

cpp 复制代码
#include <iostream>
using namespace std;
int Max(int x, int y)
{
    return x > y ? x : y;
}
int main()
{
    int a = 0;
    int b = 0;
    cin >> a >> b;
    int c = Max(a, b);
    cout << c << endl;
    return 0;
}

第 14 行调用 Max 函数时,就是传值调用。传值调用就是将实参的数据直接传递给形参。这个过程其实是将实参的值拷贝一份给 Max 函数使用,这份副本其实就是形参变量。这时形参和实参是不同的变量,所以对形参的修改,不会影响实参。这种情况下参数传递的方式只能从实参到形参,也就是单向传递。为了理解什么是单向传递,我们看一下案例 2。

案例 2: 写一个函数 Swap,交换两个整型变量的值。

如果我们按照常规的思路,写出下面的代码。

cpp 复制代码
#include <iostream>
using namespace std;
void Swap(int x, int y)
{
    int z = x;
    x = y;
    y = z;
}
int main()
{
    int a = 0;
    int b = 0;
    cin >> a >> b;
    cout << "交换前, a = " << a << " b = " << b << endl;
    Swap(a, b);
    cout << "交换后, a = " << a << " b = " << b << endl;
    return 0;
}

运行程序你会发现,其实 Swap 函数中的 x 和 y 确实会交换,但是 main 函数的中的 a 和 b 其实没有交换。这就是值传递的特点,形参和实参是不同的内存空间,对形参的修改不会影响实参,数据仍然是单向传递的,所以交换的效果没有达到。

  1. 传值调用的方式在什么时候使用?
    其实传值调用的方式一般应用的场景就是:仅需要通过传参的方式将实参的值传递给被调函数,被调函数就可以完成工作,而不需要改变实参的值。比如,案例 1 中的 Max 函数,将 a 和 b 的值传递给形参 x 和 y,在 Max 函数中使用 x 和 y 来求较大值,就能得到 a 和 b 中的较大值。
  2. 如何改造 Swap 函数才能实现交换的效果呢?
    这里可以使用指针,也可以使用引用。

4.2.2 引用

4.2.2.1 引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量是同一块内存空间。

引用使用格式:

类型 & 引用变量名 = 引用实体

cpp 复制代码
void TestRef()
{
    int a = 10;
    int& ra = a;            // 定义引用类型 
    //通过printf输出a,ra的地址 
    printf("%p\n", &a);
    printf("%p\n", &ra);
}
4.2.2.2 引用特性
  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体
cpp 复制代码
#include <iostream>
#include <cstdio>
using namespace std;

void TestRef()
{
    int a = 10;
    int b = 20;
    // int& ra;   // 该条语句编译时会出错 
    int& ra = a;
    int& rra = a;
    ra = b;//这是赋值不是修改引入
    printf("%p %p %p\n", &a, &ra, &rra);
}
int main()
{
	TestRef();
	return 0;
 } 

演示结果:

4.2.3 传址(引用)调用

有了前面引用的学习,那就可以利用引用来重新改造前面写的 Swap 函数了。

cpp 复制代码
#include <iostream>
using namespace std;
void Swap(int& x, int& y)
{
    int z = x;
    x = y;
    y = z;
}
int main()
{
    int a = 0;
    int b = 0;
    cin >> a >> b;
    cout << "交换前, a = " << a << " b = "<< b << endl;
    Swap(a, b);
    cout << "交换后, a = " << a << " b = "<< b << endl;
    return 0;
}

上面这种实现 Swap 函数实现方式就是函数的传址调用。这种调用方式的本质是将实参变量的地址传递给了形参,而形参使用指针直接找到实参来进行操作。所以修改形参的时候,直接改变的就是实参,这就是引用的应用。当然这里也可以使用指针来实现,比引用稍微复杂一些,这里暂时不讲,等学习到指针,自然就明白了。

拓展:swap 函数

其实在 C++ 的 STL 中也提供了一个库函数 swap,这个函数可以用来交换两个变量,也可以交换两个数组(容器的值),如果掌握了,我们就不需要自己再自己实现交换的逻辑,直接使用现成的函数。

swap 函数需要的头文件 < utility >

cpp 复制代码
#include <iostream>
#include <utility>
using namespace std;
int main()
{
    int a = 0;
    int b = 0;
    cin >> a >> b;
    cout << "交换前, a = " << a << " b = " << b << endl;
    swap(a, b);//直接使用库函数swap交换两个变量 
    cout << "交换后, a = " << a << " b = " << b << endl; 
    return 0;
}

演示结果:

使用 swap 交换两个数组也是可以的:

cpp 复制代码
#include <iostream>    
#include <utility>      //swap函数需要 
using namespace std;
int main () 
{
    int arr1[4];                      
    int arr2[] = {10,20,30,40};       
    swap(arr1, arr2);                 
    
    for (int e: arr1) 
        cout << e << " ";
    cout << endl;
    return 0;
}

演示结果:

那么字符串能不能使用引用的方式传参呢?当时是可以的。

cpp 复制代码
#include <iostream>
using namespace std;
void test(string& s) 
{
    cout << s << endl;
    s = "haha";
}
int main()
{
    string s("hello world");
    test(s);
    cout << s << endl;
    return 0;
}

演示结果:

4.2.4 传值、传址效率比较

cpp 复制代码
#include<iostream>
#include<ctime>
using namespace std;
//定义全局字符串s 
string s("hello world");
void TestFunc1(string s) {}
void TestFunc2(string& s) {}
void Test()
{
    // 以值作为函数参数 
    size_t begin1 = clock();
    for (size_t i = 0; i < 10000000; ++i)
    {
        TestFunc1(s);
    }
    size_t end1 = clock();
    // 以引用作为函数参数 
    size_t begin2 = clock();
    for (size_t i = 0; i < 10000000; ++i)
    {
        TestFunc2(s);
    }
    size_t end2 = clock();
    // 分别计算两个函数运行结束后的时间 
    cout << "TestFunc1(string)-time:" << end1 - begin1 << endl;
    cout << "TestFunc2(string&)-time:" << end2 - begin2 << endl;
}
int main() 
{
    Test();
    return 0;
}

演示结果:

采用传值调用过程中,函数传参,将实参传递给形参的时候,形参会创建新的空间,再将实参的数据给形参拷贝一份;但是引用传参的方式,就不存在数据的拷贝,只是在形参的部分建立引用的关系,形参就是实参。所以引用传参的效率要高于传值调用的方式。

提示:

数组在传参的时候,形参和实参本来就是同一个数组,所以数组传参的时候,不需要使用引用参数。

5. 函数重载

5.1 重载概念

引入:

比如:我们现在要写一个函数,求两个整数的和,那么我们可以这么写:

cpp 复制代码
#include <iostream>
using namespace std;
int IntAdd(int x, int y)
{
    return x + y;
}
int main()
{
    int a = 0;
    int b = 0;
    cin >> a >> b;
    int c = IntAdd(a, b);
    cout << c << endl;
    return 0;
}

那如果还想要实现一个函数求两个 double 类型浮点数的和,怎么办呢?

cpp 复制代码
double DoubleAdd(double x, double y)
{
    return x + y;
}

那么上面的 IntAddDoubleAdd 函数的功能是类似的,都是完成求两个数的求和,只是参数类型有差异而已,既然功能是类似的,能不能函数名字统一一下都叫 Add()呢?这样使用 Add() 函数的人,不需要记忆很多名字,就比较方便。这是 C++ 中引入的函数重载的功能。

函数重载: C++ 中的函数重载(Function Overloading)是指在同一个作用域中可以有多个同名函数,它们的函数名称相同,但是参数列表不同。

cpp 复制代码
函数返回类型  函数名(参数1, 参数2,...);

这里的"不同"指的是参数的数量、类型或顺序至少有一个不同。函数的返回类型并不影响函数的重载,因为 C++ 编译器不会根据返回类型来区分不同的函数.

5.2 重载举例

cpp 复制代码
#include<iostream>
using namespace std;
// 1、参数类型不同 
int Add(int a, int b)
{
    return a + b;
}
double Add(double a, double b)
{
    return a + b;
}
// 2、参数个数不同 
void f()
{
    cout << "f()" << endl;
}
void f(int a)
{
    cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同 
void f(int a, char b)
{
    cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
    cout << "f(char b, int a)" << endl;
}
int main()
{
    Add(10, 20);
    Add(10.1, 20.2);
    f();
    f(10);
    f(10, 'a');
    f('a', 10);
    return 0;
}

6. 练习

  1. 简单算术表达式求值

  2. 最大数 max(x,y,z)

  3. 最高分与最低分之差

  4. 歌唱比赛

  5. 求正整数2和n之间的完全数

  6. 甲流病人初筛

  7. 素数个数


结语

函数作为C++程序设计的核心组成部分,在算法竞赛中扮演着关键角色。从基础的函数定义与调用,到参数传递、返回值处理,再到高级特性如函数重载,掌握这些内容能够显著提升代码的模块化程度和复用性。

理解库函数与自定义函数的差异,能够帮助开发者合理利用现有资源,同时灵活扩展功能。参数传递机制(传值、传址、引用)的选择直接影响程序效率和逻辑正确性,尤其在处理大型数据结构时需谨慎权衡。

函数重载通过简化接口设计,让代码更易读且适应多变的需求场景。未来在更复杂的算法实现中,这些基础知识将成为嵌套调用、递归设计乃至面向对象编程的基石。

持续练习函数相关的应用,结合具体算法问题深化理解,是迈向高效竞赛编程的必经之路。

愿诸君能一起共渡重重浪,终见缛彩遥分地,繁光远缀天

相关推荐
冷凝雨2 小时前
复数乘法(C & Simulink)
c语言·开发语言·信号处理·simulink·dsp
CoderCodingNo2 小时前
【GESP】C++五级真题(贪心思想考点) luogu-B4071 [GESP202412 五级] 武器强化
开发语言·c++·算法
我有一些感想……2 小时前
An abstract way to solve Luogu P1001
c++·算法·ai·洛谷·mlp
前端小L2 小时前
双指针专题(三):去重的艺术——「三数之和」
javascript·算法·双指针与滑动窗口
0和1的舞者2 小时前
Spring AOP详解(一)
java·开发语言·前端·spring·aop·面向切面
MoonBit月兔2 小时前
年终 Meetup:走进腾讯|AI 原生编程与 Code Agent 实战交流会
大数据·开发语言·人工智能·腾讯云·moonbit
智航GIS2 小时前
8.2 面向对象
开发语言·python
小小星球之旅2 小时前
CompletableFuture学习
java·开发语言·学习
智者知已应修善业3 小时前
【求等差数列个数/无序获取最大最小次大次小】2024-3-8
c语言·c++·经验分享·笔记·算法