「C++系列」函数/内置函数

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站:人工智能教程

一、函数

C++ 中的函数是完成特定任务的一段独立代码块。函数可以接受输入(称为参数)并返回输出(称为返回值)。函数是代码重用的基础,它们使得代码更加模块化、易于理解和维护。

1. 函数的基本结构

一个基本的C++函数包括返回类型、函数名、参数列表(可选)和函数体。

返回类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...) {
    // 函数体
    // 可以包含局部变量、控制结构等
    return 返回值; // 如果有返回值的话
}

2. 示例

无参数无返回值函数

#include <iostream>
using namespace std;

// 声明一个无参数无返回值的函数
void sayHello() {
    cout << "Hello, World!" << endl;
}

int main() {
    sayHello(); // 调用函数
    return 0;
}

有参数有返回值函数

#include <iostream>
using namespace std;

// 声明一个有参数有返回值的函数
int add(int a, int b) {
    return a + b; // 返回两个整数的和
}

int main() {
    int sum = add(5, 3); // 调用函数,并将返回值赋给变量sum
    cout << "Sum is: " << sum << endl;
    return 0;
}

3. 函数的其他特性

  • 函数重载:允许在同一个作用域内定义多个同名函数,只要它们的参数列表(参数的数量、类型或顺序)不同即可。
  • 函数指针:可以存储函数地址的变量。通过函数指针,可以在运行时调用函数。
  • 递归函数:直接或间接地调用自身的函数。递归函数必须有一个明确的终止条件,以防止无限递归。
  • 内联函数:使用inline关键字声明的函数。编译器会尝试在调用点内联展开这些函数,以减少函数调用的开销。但请注意,这是向编译器发出的请求,编译器可能会忽略它。
  • Lambda 表达式(C++11及以后):提供了一种定义匿名函数对象的方式,这些对象可以捕获它们所在作用域的变量。

注意事项

  • 函数定义和函数声明可以分开。通常,函数声明(也称为函数原型)放在头文件中,而函数定义放在源文件中。
  • 函数参数在函数被调用时通过值传递(pass by value)或引用传递(pass by reference)传递给函数。默认情况下,C++使用值传递,但你可以通过传递参数的引用(使用&符号)来避免不必要的复制,从而提高效率。
  • 在使用函数时,需要确保已经包含了相应的头文件(如果是自定义函数且声明在头文件中)并且链接了包含函数定义的源文件。

二、函数重载

C++ 中的函数重载(Function Overloading)是一种特性,它允许在相同的作用域内定义多个具有相同名称但参数列表不同的函数。参数列表的不同可以是参数的数量不同、参数的类型不同或者参数的顺序不同(对于某些特定类型的参数,如用户自定义类型,可能需要考虑运算符重载的情况)。函数重载是编译时多态的一种形式,编译器根据函数调用时提供的参数类型和数量来选择最合适的函数版本进行调用。

1. 函数重载的规则

  1. 函数名必须相同:重载函数的名称必须相同。
  2. 参数列表必须不同:重载函数的参数列表必须不同,可以是参数的类型不同、参数的数量不同,或者参数类型的顺序不同。
  3. 函数的返回类型可以不同:但是返回类型不能作为函数重载的依据。即,仅仅返回类型不同而其他都相同的函数不能构成重载。
  4. 函数体可以不同:由于重载函数是不同的函数,所以它们的函数体可以完全不同。

2. 示例

#include <iostream>
using namespace std;

// 第一个版本:打印整数
void print(int i) {
    cout << "Printing int: " << i << endl;
}

// 第二个版本:打印浮点数
void print(double f) {
    cout << "Printing float: " << f << endl;
}

// 第三个版本:打印字符串
void print(const char* c) {
    cout << "Printing character: " << c << endl;
}

int main() {
    print(7);       // 调用第一个版本
    print(7.1);     // 调用第二个版本
    print("Hello"); // 调用第三个版本
    return 0;
}

3. 注意点

  • 函数重载与函数模板不同。函数模板在编译时根据传入的参数类型生成函数实例,而函数重载是在编译时根据传入的参数类型选择已经定义的函数。
  • 在进行函数重载时,需要特别注意避免二义性。如果编译器无法根据调用时提供的参数类型和数量唯一确定一个函数版本,那么就会出现编译错误。
  • 对于引用类型(如int&const int&)和默认参数值,也可以用于函数重载,但需要谨慎处理以避免二义性。
  • 函数重载是C++多态性的一种表现,但它与面向对象编程中的多态性(通过虚函数实现)是不同的概念。函数重载发生在编译时,而多态性(通过虚函数)发生在运行时。
    在这里插入图片描述

三、函数指针

C++ 中的函数指针是一种特殊的指针,它指向一个函数而非数据。使用函数指针,你可以在程序运行时调用函数,而不仅仅是在编写程序时通过函数名来调用。这提供了一种灵活的方式来处理函数,例如将它们作为参数传递给其他函数,或者将它们存储在数据结构中以便后续使用。

1. 定义函数指针

定义函数指针时,你需要指定它所指向的函数的返回类型和参数列表(不包括函数名)。语法类似于函数声明,但用指针声明符(*)代替函数名。

// 定义一个指向函数的指针,该函数没有参数,返回类型为int
int (*funcPtr)();

// 定义一个指向函数的指针,该函数接受一个int参数,返回类型为void
void (*funcPtrWithParam)(int);

// 定义一个指向函数的指针,该函数接受两个int参数,返回类型为int
int (*funcPtrWithTwoParams)(int, int);

2. 赋值给函数指针

你可以将函数名(不带括号和参数)赋值给相应的函数指针。这是因为函数名在表达式中会被转换为指向该函数的指针。

#include <iostream>

// 示例函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 声明并赋值给函数指针
    int (*funcPtr)(int, int) = add;

    // 通过函数指针调用函数
    std::cout << "The sum is: " << funcPtr(5, 3) << std::endl;

    return 0;
}

3. 使用函数指针作为参数

函数指针也可以作为参数传递给其他函数,这允许你编写更加通用和灵活的代码。

#include <iostream>

// 一个接受函数指针作为参数的函数
void applyFunction(int (*funcPtr)(int, int), int a, int b) {
    std::cout << "Result: " << funcPtr(a, b) << std::endl;
}

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

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 调用applyFunction,传入不同的函数指针
    applyFunction(add, 10, 5);
    applyFunction(subtract, 10, 5);

    return 0;
}

注意事项

  • 函数指针的类型必须与它所指向的函数的类型严格匹配,包括返回类型和参数列表。
  • 当你通过函数指针调用函数时,不需要(也不能)使用括号包围函数指针变量名,除非你想解引用函数指针然后立即调用其指向的函数(即(*funcPtr)(args))。然而,在大多数情况下,直接写funcPtr(args)就足够了,因为编译器会理解你的意图。
  • 函数指针的解引用(使用*)在调用函数时不是必需的,但在某些需要函数指针地址的上下文中(如将函数指针作为参数传递给需要函数指针地址的函数时)可能是必需的。然而,在C++中,这种情况相对较少见。

四、递归函数

C++ 中的递归函数是一种特殊的函数,它直接或间接地调用自身。递归函数是解决可以分解为更小相似问题的问题的一种强大工具。递归通常与分而治之的策略结合使用,即将问题分解成更小的子问题,直到子问题变得足够简单,可以直接解决。

1. 递归函数的基本结构

递归函数通常包含两个主要部分:

  1. 基本情况(Base Case):这是递归的终止条件,当满足这个条件时,函数将停止调用自身并返回一个值。基本情况必须确保递归能够最终停止,否则将导致无限递归,最终引发栈溢出错误。

  2. 递归步骤(Recursive Step):在这一步中,函数会调用自身,但会以一个或多个参数的不同值来调用,这些参数值使得问题规模减小,向基本情况靠近。

2. 示例:计算阶乘

阶乘是一个很好的递归函数示例。n 的阶乘(记作 n!)是所有小于或等于 n 的正整数的乘积,特别地,0! = 1。

#include <iostream>

// 递归函数计算阶乘
long long factorial(int n) {
    // 基本情况
    if (n == 0) {
        return 1;
    }
    // 递归步骤
    else {
        return n * factorial(n - 1);
    }
}

int main() {
    int number;
    std::cout << "Enter a number: ";
    std::cin >> number;
    std::cout << number << "! = " << factorial(number) << std::endl;
    return 0;
}

注意事项

  • 确保有基本情况:没有基本情况的递归函数将导致无限递归。
  • 考虑递归深度:递归调用会占用调用栈的空间,如果递归深度过大,可能会导致栈溢出错误。
  • 优化递归:有时,递归不是最高效的解决方案。在某些情况下,使用迭代或尾递归优化(在支持尾递归优化的编译器中)可以提高性能。
  • 可读性:递归代码可能难以理解和调试,特别是对于复杂的递归逻辑。确保递归逻辑清晰明了,并适当添加注释。

递归是一种强大的编程技术,但它也需要谨慎使用,以避免常见的问题,如无限递归和栈溢出。

五、内联函数

C++ 中的内联函数(Inline Function)是一种请求编译器尽可能在每个调用点上“内联展开”该函数的特殊函数。内联展开意味着编译器会在每个调用该函数的地方直接插入(或替换)该函数的代码体,而不是像通常那样进行函数调用(即生成调用指令、保存调用状态、跳转到函数体执行、恢复调用状态并返回)。这样做的好处是可以减少函数调用的开销,特别是对于那些体积小、调用频繁的函数,可以显著提高程序的执行效率。

1. 声明内联函数

在 C++ 中,可以使用 inline 关键字来声明一个函数为内联函数。但是,需要注意的是,inline 关键字仅仅是对编译器的“请求”或“建议”,编译器有权忽略这个请求。编译器会根据函数的复杂度、调用频率以及目标代码的大小等因素来决定是否真正地将函数内联展开。

inline int max(int a, int b) {
    return (a > b) ? a : b;
}

注意事项

  1. 函数体大小:虽然理论上任何函数都可以声明为内联函数,但实际上,编译器通常只会对函数体较小(如几行代码)的函数进行内联展开。如果函数体过大,编译器可能会忽略 inline 请求。
  2. 递归函数:递归函数通常不适合声明为内联函数,因为递归调用会导致编译器无法确定展开到何时为止。
  3. 虚函数和构造函数/析构函数:虚函数、构造函数和析构函数通常不会(也不能)被自动内联,尽管你可以在构造函数或析构函数的定义前加上 inline 关键字,但编译器通常会忽略它。
  4. 链接性:内联函数在多个源文件中使用时,需要在每个源文件中都定义它(除非它被定义为静态的)。但是,由于内联函数的目的是减少函数调用的开销,所以通常建议将内联函数的定义放在头文件中,并通过 #include 指令在需要使用的地方包含它。
  5. 调试困难:由于内联函数在编译时会被展开到每个调用点上,所以在调试时可能无法直接跳转到内联函数的定义处进行调试,这可能会增加调试的难度。

六、内置函数

C++中的内置函数主要指的是编译器自带的函数,这些函数通常用于执行常见的数学计算、字符串操作、类型转换等任务。不过,需要注意的是,C++标准本身并不直接定义“内置函数”这一术语,但我们可以将那些无需显式包含特定头文件即可使用的函数(如标准库中的函数),以及通过编译器优化技术(如内联函数)实现的函数视为广义上的“内置函数”。

1. 数学函数

这些函数通常定义在<cmath>(或C风格的<math.h>)头文件中,用于执行数学计算。

  • sin(double x):计算x(以弧度为单位)的正弦值。
  • cos(double x):计算x(以弧度为单位)的余弦值。
  • tan(double x):计算x(以弧度为单位)的正切值。
  • sqrt(double x):计算x的平方根。
  • pow(double base, double exponent):计算base的exponent次幂。
  • abs(int x):计算整数x的绝对值(注意,对于浮点数,应使用fabs)。
  • ceil(double x):向上取整(不小于x的最小整数)。
  • floor(double x):向下取整(不大于x的最大整数)。

2. 字符串函数

虽然C++标准库中没有直接称为“内置”的字符串函数,但<string>库中的函数提供了丰富的字符串处理能力。

  • std::string::length() / std::string::size():返回字符串的长度。
  • std::string::substr(size_t pos = 0, size_t len = npos):返回字符串的一个子串。
  • std::string::find(const std::string& str, size_t pos = 0):在字符串中查找子串,并返回第一次出现的位置。
  • std::to_string(int val) / std::to_string(double val)等:将整数、浮点数等转换为字符串。

3. 类型转换函数

这些函数通常用于在不同数据类型之间进行转换。

  • static_cast<T>(expression):用于基本数据类型的静态转换。
  • dynamic_cast<T*>(expression):用于类的层次结构中的安全向下转换。
  • reinterpret_cast<T>(expression):用于进行底层重新解释转换,如指针类型的转换。
  • const_cast<T>(expression):用于去除类型的const或volatile修饰符。

4. 输入输出函数

这些函数定义在<iostream>头文件中,用于标准输入输出。

  • std::cin:标准输入流对象。
  • std::cout:标准输出流对象。
  • std::cerr:标准错误输出流对象。
  • std::clog:另一个标准错误输出流对象,通常用于记录日志。

5. 内联函数

虽然内联函数不是严格意义上的“内置函数”,但它们是编译器优化技术的一部分,可以通过在函数声明或定义前添加inline关键字来请求编译器将其内联展开。

  • inline关键字是一种向编译器发出的请求,用于提高函数调用的效率,但编译器可能会忽略这个请求。

注意事项

  • 并非所有C++编译器都支持相同的内置函数集。然而,上述提到的数学和字符串函数以及输入输出流对象是广泛支持的。
  • 对于特定于编译器或平台的内置函数,您可能需要查阅该编译器的文档。
  • C++标准库中的函数(如<cmath><string>等)通常不是严格意义上的“内置函数”,但它们在C++程序中非常常见且易于使用。
    在这里插入图片描述

七、Lambda 函数与表达式

在C++中,Lambda函数(也称为Lambda表达式)是一种定义匿名函数对象的方式。它们提供了一种简洁的方式来编写可以在需要函数对象的场合使用的短小函数。Lambda表达式可以捕获其所在作用域的变量,并且可以被用作算法的参数或者存储在std::function对象中。

1. Lambda表达式的基本语法

Lambda表达式的基本语法如下:

[capture](parameters) mutable -> return_type {
    // 函数体
}
  • capture:捕获列表,用于指定Lambda表达式体内部可以访问的外部变量列表。捕获方式可以是值捕获(通过=)或引用捕获(通过&),也可以明确指定捕获每个变量的方式。如果不写捕获列表,则默认不能访问外部变量。
  • parameters:参数列表,与普通函数的参数列表类似,可以省略,此时Lambda表达式可以视为一个无参函数。
  • mutable:可选的,表示Lambda表达式体内的代码可以修改捕获的变量(注意,这仅对以值捕获的变量有意义)。
  • return_type:返回类型,如果Lambda表达式体只有一个返回语句,且该语句的类型可以明确推断出来,则可以省略返回类型。
  • 函数体:包含Lambda表达式要执行的代码。

2. 示例

无参数无返回值

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 使用Lambda表达式输出每个元素
    std::for_each(vec.begin(), vec.end(), [](int x) {
        std::cout << x << std::endl;
    });

    return 0;
}

有参数有返回值

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 使用Lambda表达式计算并返回每个元素的两倍
    std::vector<int> doubled;
    std::transform(vec.begin(), vec.end(), std::back_inserter(doubled), [](int x) {
        return 2 * x;
    });

    for (int n : doubled) {
        std::cout << n << std::endl;
    }

    return 0;
}

捕获外部变量

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    int factor = 2;
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 使用Lambda表达式捕获外部变量并计算每个元素与factor的乘积
    std::vector<int> multiplied;
    std::transform(vec.begin(), vec.end(), std::back_inserter(multiplied), [&factor](int x) {
        return x * factor;
    });

    for (int n : multiplied) {
        std::cout << n << std::endl;
    }

    return 0;
}

在这个例子中,Lambda表达式通过捕获列表[&factor]捕获了外部变量factor的引用,并在函数体内使用了它。

Lambda表达式是C++11及以后版本中引入的一个重要特性,它们极大地增强了C++的表达能力,使得编写简洁、灵活的代码变得更加容易。
在这里插入图片描述

八、相关链接

  1. Visual Studio Code下载地址
  2. Sublime Text下载地址
  3. 「C++系列」C++简介、应用领域
  4. 「C++系列」C++ 基本语法
  5. 「C++系列」C++ 数据类型
  6. 「C++系列」C++ 变量类型
  7. 「C++系列」C++ 变量作用域
  8. 「C++系列」C++ 常量知识点-细致讲解
  9. 「C++系列」C++ 修饰符类型
  10. 「C++系列」一篇文章说透【存储类】
  11. 「C++系列」一篇文章讲透【运算符】
  12. 「C++系列」循环
  13. 「C++」判断
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

·零落·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值