端午节不休息,肝出万字“粽”量级长文:一文搞懂C++函数

这篇具有很好参考价值的文章主要介绍了端午节不休息,肝出万字“粽”量级长文:一文搞懂C++函数。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在C++中,函数是一种重要的编程构造,可将代码组织成可重用的模块,从而提高代码的可读性和可维护性。

1. 函数的定义与声明

(1)函数的定义

C++函数定义的基本形式如下:

返回类型 函数名(参数列表){
    // 函数体
    return 返回值;
}

各个部分的含义如下:

  • 返回类型: 指定了函数返回值的数据类型。如果函数不需要返回值,则返回类型为void。
  • 函数名: 这是对函数的一种标识,用于调用该函数。
  • 参数列表: 指定了函数的参数类型和名称。如果该函数没有输入参数,则参数列表为空。
  • 函数体: 这是函数的主体部分,包含了具体的实现逻辑,可以访问函数的参数以及其它变量。
    返回值: 函数执行完毕后返回的结果。

e.g.

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

该函数的名称为sum,接受两个整数类型的参数ab,并返回ab的和。函数返回类型指定了函数返回值的数据类型为int

(2)函数的声明

函数的声明是指在代码中提前声明函数名、参数列表和返回值类型等信息,但不实现具体的函数功能。这样做可以让编译器在编译时知道有该函数的存在,方便后续调用,一般出现在头文件中。

需要注意的是,如果函数需要被其他文件调用,则必须进行函数声明。同时,函数声明与函数定义中的参数必须保持一致,否则编译会出错。

e.g.

// 函数声明
int sum(int a, int b);

2. 函数的调用

函数调用实际上是对栈空间的操作过程

  • 建立被调用函数的栈空间
  • 保护调用函数的运行状态和返回地址
  • 传递函数实参给形参
  • 执行被调用函数函数体内语句
  • 将控制权和返回值交给调用函数

函数的参数传递可以使用传值调用(Pass by Value)、地址调用(Pass by Address)和引用调用(Pass by Reference)三种方式。

  • 传值调用适用于只需用实参的副本进行计算而不影响实参本身的场景
  • 地址调用适用于需要通过指针进行进一步操作的情况
  • 引用调用在写代码时更方便和直观,同时也能提高程序的效率,常用于需要修改实参值的情况,也可避免拷贝大型对象的开销。

(1)传值调用

传值调用中,函数参数通过复制的方式传递给函数。即,在函数调用时,实参的值被复制到形参,函数内部对形参的修改不会影响实参的值

e.g.

#include <iostream>

void square(int num) {
    num *= num;
    std::cout << "Inside square function: " << num << std::endl;
}

int main() {
    int number = 5;
    square(number);
    std::cout << "After function call: " << number << std::endl;
    return 0;
}

上述示例定义了一个名为 square() 的函数,该函数接受一个整型参数 num。在 main() 函数中,调用 square() 函数并将变量 number 的值作为实参传递给函数。在函数内部,对形参 num 进行平方运算,并输出结果。但是在函数调用后,打印 number 的值时,发现 number 的值没有改变。这是因为值调用只是将实参的值复制给了形参,函数内部的修改不会影响到实参本身。

(2)传址调用

传址调用中,函数通过传递变量的地址或指针来访问或修改实参的值。

#include <iostream>

void incrementByOne(int *numPtr) {
    (*numPtr)++;
}

int main() {
    int number = 5;
    incrementByOne(&number);
    std::cout << "After function call: " << number << std::endl;
    return 0;
}

上述示例定义了一个名为incrementByOne()的函数,该函数接受一个整型指针参数 numPtr。在 main() 函数中,定义了一个变量 number 并将其地址传递给incrementByOne()函数。在函数内部,我们使用指针间接修改实参 number 的值。因此,在函数调用后,打印 number 的值时,发现其值已经被增加1。

(3)引用调用
引用调用中,函数通过引用参数来访问或修改实参的值。与地址调用类似,但是使用引用可以更直接地操作变量。

#include <iostream>

void incrementByOne(int &numRef) {
    numRef++;
}

int main() {
    int number = 5;
    incrementByOne(number);
    std::cout << "After function call: " << number << std::endl;
    return 0;
}

上述示例中定义了一个名为 incrementByOne() 的函数,该函数接受一个整型引用参数 numRef。在 main() 函数中,定义了一个变量 number,并将其作为实参传递给 incrementByOne() 函数。在函数内部,直接操作引用 numRef,实际上就是操作了实参 number。因此,在函数调用后,打印 number 的值时,发现其值已经被增加1。

3. 函数的递归调用

函数的递归调用是指函数调用自身的过程。在函数内部,通过调用自身来解决更小规模的问题,最终达到解决原始问题的目的。常用于解决求最大公约数、阶乘计算、斐波那契数列、树的遍历、链表操作等问题

在递归调用过程中,系统使用堆栈(stack)来保存每个函数的局部变量、返回地址和其他信息。每次递归调用时,系统将创建一个新的栈帧(stack frame)以保存当前函数的状态,并将其推入堆栈顶部。当递归结束时,系统将按照先进后出的顺序弹出栈帧,恢复之前的函数状态。递归调用可以使代码更加简洁和易读,但也可能导致栈溢出等问题。

e.g. 求最大公约数

// 函数定义
int gcd(int a, int b) {
    if (b == 0) {
        return a;  // 当第二个数为0时,第一个数即为最大公约数
    } else {
        return gcd(b, a % b);  // 递归调用欧几里得算法
    }
}

gcd使用欧几里得算法(辗转相除法)来求解最大公约数

e.g. 计算阶乘

int factorial(int n)
{
    if (n == 0)
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}

factorial函数计算一个整数n的阶乘,它使用了递归调用的方式。当n等于0时,函数返回1;否则,函数返回n和factorial(n-1)的乘积。在递归调用过程中,每次调用都会将n的值减1,直到n等于0为止。

e.g. 斐波那契数列

int fibonacci(int n) {
    if (n <= 1) {
        return n;  // 当 n <= 1 时,直接返回 n
    } else {
        return fibonacci(n - 1) + fibonacci(n - 2);  // 递归调用计算斐波那契数列
    }
}

4. 内联函数

C++的内联函数是一种函数声明方式,用于对函数进行优化以提高程序的执行效率。通过使用内联函数,编译器会将函数的代码插入到调用该函数的每个位置,而不是像普通函数那样进行函数调用和返回。

使用内联函数可以减少函数调用的开销,因为它避免了函数调用时的栈帧保存和恢复操作。内联函数适用于执行时间较短、被频繁调用的函数,例如简单的数学计算或者访问对象的成员函数或成员变量。

C++中使用inline关键字来声明内联函数。在函数定义或者函数原型前加上inline关键字,告诉编译器将该函数进行内联展开。需要注意的是,编译器对于是否真正将函数内联展开有权决定,它可能根据一些规则(例如函数体的大小)来判断是否进行内联展开。同时,内联函数的定义必须放在头文件中,以便在调用处进行内联展开。

e.g.

#include <iostream>

// 内联函数的定义
inline int add(int a, int b) {
    return a + b;
}

int main() {
    int x = 5;
    int y = 3;
    
    // 调用内联函数
    int sum = add(x, y);
    
    std::cout << "Sum: " << sum << std::endl;
    
    return 0;
}

在上面的例子中,add函数被声明为内联函数。编译器会将函数体的代码插入到调用add函数的地方,而不是进行函数调用。这样可以减少函数调用的开销,提高程序的执行效率。

5. 函数重载

函数重载(Function Overloading)是指在同一个作用域内,定义多个具有相同名称但参数列表不同的函数。通过函数重载,可以根据不同的参数类型或参数个数来调用不同的函数,相同的函数名对于不同的参数会被编译器当作不同的函数进行处理。

函数重载的条件:

  • 函数名称必须相同。
  • 参数列表必须不同,包括参数类型、参数顺序或参数个数的不同。
  • 返回值类型可以相同也可以不同。

函数重载的优点:

  • 提供更直观的接口:使用相同的函数名字来表示功能相似但参数不同的函数,可以提供更直观和易懂的接口。
    代码复用:通过函数重载,可以避免定义多个类似- 功能的函数,提高代码的复用性。
  • 增加代码可读性:函数重载使得程序代码更加清晰、易读,提高了代码的可维护性。

e.g.

#include <iostream>

// 重载函数的声明和定义
void print(int n) {
    std::cout << "Integer: " << n << std::endl;
}

void print(double x) {
    std::cout << "Double: " << x << std::endl;
}

void print(const char* str) {
    std::cout << "String: " << str << std::endl;
}

int main() {
    int a = 10;
    double b = 3.14;
    const char* c = "Hello";
    
    // 调用不同版本的print函数
    print(a);        // 调用print(int)
    print(b);        // 调用print(double)
    print(c);        // 调用print(const char*)
    
    return 0;
}

上述例子中,print函数被重载了三次,分别接受整数、浮点数和字符串作为参数。根据调用时提供的参数类型,编译器会选择调用合适的重载函数进行输出。

6. 标识符作用域

标识符作用域(Identifier Scope)指的是标识符(如变量名、函数名、类名等)在程序中可见和有效的范围。

通过标识符作用域,可以更好地组织代码结构、避免名称冲突,并能够灵活应用各种作用域规则来编写更加清晰、可读性高的程序。

C++中存在以下几种作用域:

  • 全局作用域(Global Scope):在任何函数或代码块之外定义的标识符具有全局作用域。全局作用域中的标识符在整个程序中都是可见和有效的。
  • 标识符作用域:C++中的命名空间提供了一种逻辑上的作用域封装机制。通过将相关的实体(如变量、函数、类等)放置在同一个命名空间内,可以限定它们的作用域范围。命名空间作用域中的标识符在该命名空间内是可见和有效的。
  • 类作用域(Class Scope):类作用域指的是类的成员变量、成员函数和嵌套类的作用域。
    类作用域中的标识符在类定义内部是可见和有效的。
  • 局部作用域(Local Scope):局部作用域指的是在函数、代码块或循环等内部定义的标识符的范围。局部作用域中的标识符只在其所在的函数、代码块或循环等内部是可见和有效的。

作用域规则:

  • 名称屏蔽(Name Hiding):在内部作用域中定义了与外部作用域相同名称的标识符时,会隐藏外部作用域中的标识符。当需要访问外部作用域的标识符时,可以使用作用域解析运算符(::)来显式指定。
  • 嵌套作用域(Nested Scope):在一个作用域内部可以嵌套其他作用域,内部作用域中的标识符优先级更高。
  • 生命周期(Lifetime):标识符的有效期取决于其作用域。一旦超出了其作用域,该标识符就不再可见,其内存空间可能被释放或重新分配。

e.g.

#include <iostream>

int globalVar = 10; // 全局作用域

namespace MyNamespace {
    int localVar = 20; // 命名空间作用域

    class MyClass {
    public:
        static int classVar; // 类作用域

        void func() {
            int count = 5; // 局部作用域
            std::cout << "count: " << count << std::endl;
        }
    };
}

int MyNamespace::MyClass::classVar = 30; // 类静态成员的定义与初始化

int main() {
    int localVar = 15; // 局部作用域

    std::cout << "globalVar: " << globalVar << std::endl; // 访问全局作用域下的变量
    std::cout << "localVar: " << localVar << std::endl; // 访问局部作用域下的变量
    std::cout << "namespace localVar: " << MyNamespace::localVar << std::endl; // 访问命名空间作用域下的变量
    std::cout << "classVar: " << MyNamespace::MyClass::classVar << std::endl; // 访问类作用域下的变量

    MyNamespace::MyClass obj;
    obj.func(); // 调用类作用域下的成员函数

    return 0;
}

输出结果:

globalVar: 10
localVar: 15
namespace localVar: 20
classVar: 30
count: 5

7. 存储类

C++ 中的变量存储类用于指定变量在内存中的存储方式和生命周期。

存储类提供了灵活的方式来管理变量的生命周期和存储方式。根据需求选择合适的存储类可以提高程序的执行效率和内存管理。

C++ 提供了以下四种存储类:

(1)auto(自动类)

  • auto 是 C++ 中默认的存储类。
  • 自动存储类(auto storage class)用于指定局部变量的存储方式。
  • 自动存储类的变量的生命周期与其所在的作用域相同。
  • 在函数内部定义的局部变量默认为 auto 类型。

(2)register(寄存器类)

  • register 关键字用于请求将变量存储于 CPU 寄存器中,以便更快地访问变量。
  • 由于寄存器数量有限,编译器可以忽略 register 请求,而将变量视为 auto 变量。
  • register 变量的地址是不可获取的,也无法对其应用运算符 &

(3)static(静态变量)

  • static 关键字用于声明静态变量。
  • 静态变量在程序执行期间一直存在,其生命周期从首次被初始化开始,直到程序结束。
  • 静态变量在内存中分配一次,并且初始化只会执行一次。
  • 静态局部变量在函数内部定义,但生命周期延长到函数调用结束后。
  • 静态全局变量在文件作用域内定义,对整个文件都是可见的。

(4)extern(外部变量)

  • extern 关键字用于声明外部变量或函数。
  • extern 可以用于引用其他源文件中定义的全局变量或函数。
  • 在一个源文件中使用 extern 声明一个全局变量时,该变量实际上是在其他源文件中定义的,编译器会在链接时解析其引用。

e.g.

#include <iostream>

int globalVar; // 外部链接的全局变量

void func() {
    int localVar = 10; // 自动变量(默认)
    register int regVar = 20; // 寄存器变量
    static int staticVar = 30; // 静态局部变量

    globalVar++; // 访问全局变量

    std::cout << "localVar: " << localVar << std::endl;
    std::cout << "regVar: " << regVar << std::endl;
    std::cout << "staticVar: " << staticVar << std::endl;

    localVar++; // 自动变量的值可以改变
    regVar++; // 寄存器变量的值可以改变
    staticVar++; // 静态局部变量的值可以改变
}

int main() {
    globalVar = 100; // 初始化全局变量

    for (int i = 0; i < 5; i++) {
        func();
    }

    return 0;
}

输出结果:

localVar: 10
regVar: 20
staticVar: 30
localVar: 10
regVar: 20
staticVar: 31
localVar: 10
regVar: 20
staticVar: 32
localVar: 10
regVar: 20
staticVar: 33
localVar: 10
regVar: 20
staticVar: 34文章来源地址https://www.toymoban.com/news/detail-496011.html

到了这里,关于端午节不休息,肝出万字“粽”量级长文:一文搞懂C++函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 端午节使用Threejs实现数字人3D粽子

    个人主页: 左本Web3D,更多案例预览请点击==》 在线案例 个人简介:专注Web3D使用ThreeJS实现3D效果技巧和学习案例 💕 💕积跬步以至千里,致敬每个爱学习的你。喜欢的话请三连,有问题请私信或者加微信   1,功能介绍 Threejs实现加载粽子模型,使用AI生成数字人并进行介

    2024年02月16日
    浏览(35)
  • 端午节出行的小贴士——行之安,乐之逍

    亲爱的朋友们, 随着端午节的到来,想必许多人已经开始期待那份出游的快乐与解脱。无论你是期待漫步在宁静的田野小径,还是在繁华的城市中探索,这篇文章都会给你一些实用的端午节出行建议,帮助你尽情享受旅程。 首先,不可忽视的就是做好天气预报的关注。端午

    2024年02月09日
    浏览(36)
  • 【端午节】用Vue3写粽子——从零开始

    在端午节即将到来之际,我们来一起写一个粽子组件来庆祝这个传统节日。 准备工作 首先,我们需要安装Vue3及其相关依赖,这里使用Vue CLI来创建项目。 接下来,我们需要安装一些必要的依赖。 创建组件 在/src/components目录下创建一个Zongzi.vue文件,并编写以下代码。 这段代

    2024年02月09日
    浏览(44)
  • 【端午节快乐】用Angular框架开发粽子小游戏

    端午节,让我们开始用Angular框架来开发一款有趣的关于粽子的网页小游戏吧! 在这个小游戏中,我们需要开发一个简单的互动界面,让用户通过点击屏幕来控制粽子的移动,从而躲避障碍物和收集道具,最终获得分数。接下来,我们将依次介绍如何使用Angular框架来实现这个

    2024年02月10日
    浏览(31)
  • 带你用Python制作7个程序,让你感受到端午节的快乐

    名字:阿玥的小东东 学习:Python、C/C++ 主页链接:阿玥的小东东的博客_CSDN博客-pythonc++高级知识,过年必备,C/C++知识讲解领域博主 目录 前言 程序1:制作粽子

    2024年02月09日
    浏览(42)
  • 【前端】1. HTML【万字长文】

    HTML 代码是由 “标签” 构成的. 形如: 标签名 (body) 放到 中 大部分标签成对出现. body 为开始标签, /body 为结束标签. 少数标签只有开始标签, 称为 “单标签”. 开始标签和结束标签之间, 写的是标签的内容. (hello) 开始标签中可能会带有 “属性”. id 属性相当于给这个标签设置了

    2024年04月27日
    浏览(29)
  • 【万字长文】前端性能优化实践

    从一个假死页面引发的思考: 作为前端开发,除了要攻克页面难点,也要有更深的自我目标,性能优化是自我提升中很重要的一环; 在前端开发中,会偶遇到页面假死的现象, 是因为当js有大量计算时,会造成 UI 阻塞,出现界面卡顿、掉帧等情况,严重时会出现页面卡死的

    2024年02月05日
    浏览(52)
  • C++ 万字长文,链表详解

    什么是链表? 什么是链式存储? 线性存储线性表 链式存储 链表 初始化 分析真实下标 获取长度 改查(getset) 尾部增删节点 清空链表元素 迭代器 任意位置增删节点 I/O操作 数据填充 数据置空(数据初始化) 数据交换 链表复制 拷贝列表部分 链表合并 链表高级操作(统计

    2024年02月05日
    浏览(47)
  • 计算机启动过程(万字长文)

    当电源通电后,计算机系统的启动过程始于主板上的固件,通常是BIOS(基本输入/输出系统)或UEFI(统一可扩展固件接口)。基本流程如下: 1、电源通电 用户按下计算机电源按钮,电源开始供给计算机各个组件。此时,CPU并没有直接开始执行指令。 2、BIOS/UEFI 自检 : 电源

    2024年02月02日
    浏览(66)
  • 万字长文硬核AQS源码分析

    阅读本文前,需要储备的知识点如下,点击链接直接跳转。 java线程详解 Java不能操作内存?Unsafe了解一下 一文读懂LockSupport AQS即 AbstractQueuedSynchronizer 的简称,翻译过来就是抽象队列同步器的意思,由Doug Lea大神开发的。说他抽象是因为它提供的是一个基于队列的同步器框架

    2024年02月11日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包