第八章 函数探幽

这篇具有很好参考价值的文章主要介绍了第八章 函数探幽。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

8.1 C++内联函数

提出的目的:为了提高程序运行速度。
内联函数和普通函数的区别:

  1. 编译方式

    • 内联函数在编译时会被直接替换到调用处,而不是像普通函数那样通过函数调用的方式执行。这样可以减少函数调用的开销,提高程序执行效率。
    • 普通函数则是通过函数调用的方式执行,会涉及函数栈的压栈和出栈操作。
  2. 代码复制

    • 内联函数会在每个调用处直接插入函数代码,因此可能会导致代码冗余增加,尤其对于较大的函数来说。
    • 普通函数只在内存中存储一份代码,多次调用时共享这一份代码。
  3. 适用场景

    • 内联函数适合用于简单的、频繁调用的函数,可以减少函数调用带来的开销。
    • 普通函数适合用于复杂的、功能复用性强的函数,可以提高代码的可读性和维护性。
  4. 代码大小

    • 使用内联函数可能会增加最终生成的可执行文件的大小,因为函数代码会被复制到每个调用处。
    • 普通函数会减少可执行文件的大小,因为函数代码只需要存储一份。

总的来说,内联函数可以提高程序的执行效率,但可能会增加代码的大小,空间换时间;普通函数更适合用于复杂的功能或需要多次重复调用的场景。在实际编程中,要根据具体情况选择使用内联函数还是普通函数。
下面是一个内联函数的代码例子:

#include <iostream>

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

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

在上述代码中,我们定义了一个内联函数 add,用于计算两个整数的和。在 main 函数中,我们通过 add 函数进行求和,并输出结果。

内联函数的关键字 inline 告诉编译器将该函数内联展开,而不是通过函数调用的方式执行。这样可以减少函数调用的开销,提高程序的执行效率。需要注意的是,编译器会根据具体情况决定是否真正内联展开函数代码,因此并非所有带有 inline 关键字的函数都会被内联展开。

请注意,内联函数适用于简单的、频繁调用的函数。在实际编程中,应根据需求和具体情况来决定是否使用内联函数。

如果使用C语言的宏执行了类似的函数定义功能,应该考虑替换为c++内联函数。

8.2 引用变量

引用时已定义的变量的别名,其主要用途时用作函数的形参。

8.2.1 创建引用变量

下面是demo

#include <iostream>

int main() {
    int num = 10;   // 定义一个整数变量
    int& ref = num;  // 创建一个对整数变量的引用

    std::cout << "num: " << num << std::endl;  // 输出 num 的值
    std::cout << "ref: " << ref << std::endl;  // 输出引用 ref 的值

    ref = 20;  // 通过引用修改原变量的值

    std::cout << "num after modification: " << num << std::endl;  // 输出修改后的 num 的值
    std::cout << "ref after modification: " << ref << std::endl;  // 输出修改后的引用 ref 的值

    return 0;
}
image-20240312195026188

注意: 引用必须在声明引用的时候就将其初始化,而指针是可以先声明,后赋值。

8.2.2 将引用用作函数参数

当使用按引用传递时,被调用的函数可以通过引用参数访问和修改调用函数中的变量。

下面是一个相关的例子:

#include <iostream>

// 按引用传递的函数
void multiplyByTwo(int &num) {
    num *= 2;  // 通过引用修改调用函数中的变量
}

int main() {
    int number = 5;

    std::cout << "Original value: " << number << std::endl;

    multiplyByTwo(number);  // 通过引用传递调用函数中的变量

    std::cout << "Value after function call: " << number << std::endl;

    return 0;
}

在这个示例中,我们定义了一个名为 multiplyByTwo 的函数,它以引用方式接收一个整数参数,并将该参数乘以2。在 main 函数中,我们创建了一个整数变量 number,然后调用 multiplyByTwo 函数并将 number 作为参数传递。

由于 multiplyByTwo 函数的参数是按引用传递的,它可以直接修改 main 函数中的 number 变量的值。因此,在输出 "Value after function call" 时,我们会看到 number 的值已经被修改为原来的两倍。

这个例子展示了按引用传递允许被调用的函数访问和修改调用函数中的变量的情况。当然上述情况也能用指针解决。

8.2.3 引用的属性和特别之处

左值参数:

左值参数有两种,一是指可以出现在赋值操作符的左边的表达式的常规变量,它们是可以被修改的。

​ 二是const 变量,不可被修改。

如果函数的目的不是为了修改传递的值,可使用const。

8.2.7 何时使用引用参数

使用原因:

1.能够修改调用函数中的数据对象。

2.通过传递引用而不是整个数据对象,可以提高效率。

什么时候应使用引用、什么时候应使用指针呢?什么时候应按值传递呢?

下面是一些指导原则:

对于使用传递的值而不作修改的函数。
1.如果数据对象很,如内置数据类型或小型结构,则按值传递
2.如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。
3.如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间。
4.如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。

对于修改调用函数中数据的函数:
如果数据对象是内置数据类型,则使用指针。如果看到诸如 fxit(&x)这样的代码(其中x是 imnt),则很明显,该函数将修改 x。
如果数据对象是数组,则只能使用指针
如果数据对象是结构,则使用引用或指针
如果数据对象是类对象,则使用引用

8.3 默认参数

默认参数是指当函数调用时少了实参时自动使用的一个值。

如何设置: 必须使用函数原型

char * left(const char * str,int n = 1);如果不对n赋值时,n的值默认为1

注意:对于带参数列表的函数,必须从右往左添加默认值

8.4 函数重载

函数重载和函数多态是一回事儿。

函数重载是指在同一个作用域内,可以定义多个函数名相同但参数列表不同的函数。通过函数重载,可以根据函数参数的不同来调用不同版本的函数,从而提高代码的灵活性和可复用性。

下面是一个简单的示例来说明函数重载:

#include <iostream>

// 函数重载示例
void printNumber(int num) {
    std::cout << "Integer number: " << num << std::endl;
}

void printNumber(double num) {
    std::cout << "Double number: " << num << std::endl;
}

int main() {
    int a = 10;
    double b = 3.14;

    printNumber(a);  // 调用第一个 printNumber 函数,传入整数参数
    printNumber(b);  // 调用第二个 printNumber 函数,传入双精度浮点数参数

    return 0;
}

在上面的示例中,定义了两个名为 printNumber 的函数,一个接受 int 类型的参数,另一个接受 double 类型的参数。这两个函数具有相同的函数名但参数列表不同,因此它们构成了函数重载。

main 函数中,分别传入整数和双精度浮点数参数来调用 printNumber 函数。由于参数类型不同,编译器可以根据传入参数的类型选择调用相应版本的函数,并输出不同类型的数字信息。

通过函数重载,我们可以根据不同的参数类型或参数个数来区分函数的行为,使代码更加灵活,同时避免在命名上进行过多的差异化,提高代码的可读性和维护性。

何时用:仅当函数基本上执行相同的任务,但使用不同形式的数据时,才采用函数重载。

8.5 函数模板(重要)

函数模板是一种通用的函数定义,通过引入类型参数,可以实现多个类型的数据进行相同的操作。

#include <iostream>

// 函数模板定义
template <typename T> // 常用为
template <class T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    int num1 = 10, num2 = 20;
    double num3 = 3.14, num4 = 2.7;

    // 调用函数模板
    int maxInt = getMax(num1, num2);
    double maxDouble = getMax(num3, num4);

    std::cout << "Max integer: " << maxInt << std::endl;
    std::cout << "Max double: " << maxDouble << std::endl;

    return 0;
}

8.5.1 重载的模板

当函数模板和函数重载结合时,可以实现更加灵活的代码设计。下面是一个示例,演示了同时使用函数模板和函数重载的情况:

#include <iostream>

// 函数模板重载示例
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

// 函数重载:针对 char* 类型的特殊处理
const char* getMax(const char* a, const char* b) {
    return strcmp(a, b) > 0 ? a : b;
}

int main() {
    int num1 = 10, num2 = 20;
    double num3 = 3.14, num4 = 2.7;
    const char* str1 = "apple";
    const char* str2 = "banana";

    // 调用函数模板
    int maxInt = getMax(num1, num2);
    double maxDouble = getMax(num3, num4);

    // 调用函数重载
    const char* maxStr = getMax(str1, str2);

    std::cout << "Max integer: " << maxInt << std::endl;
    std::cout << "Max double: " << maxDouble << std::endl;
    std::cout << "Max string: " << maxStr << std::endl;

    return 0;
}

在上面的示例中,我们首先定义了一个函数模板 getMax,用于比较两个相同类型的数据并返回较大值。然后,我们又定义了一个针对 const char* 类型的函数重载版本,用于比较字符串的大小并返回较大的字符串。

main 函数中,我们声明了几个变量,包括整数、双精度浮点数和字符串。然后分别调用 getMax 函数模板和函数重载版本,根据参数的类型来选择合适的函数进行处理。

8.5.3 显式具体化

当处理一些特殊结构的特殊操作时候,可能无法使用模板函数,例如下面的结构体job,若定义job a,job b,如果要交换a,b里面的floor值则无法正常使用模板函数,此时提供了显式具体化的办法实现。

struct job{
    char name[40];
    double salary;
    int floor;
}
//模板化
template<class T>
void Swap(T &a, T &b);
//对job类型的显示具体化
template <> void Swap<job>(job &a,job &b);
int main(){
    double u,v;
    
	Swap(u,v);
    job s,w;
    Swap(s,w);
}

8.5.4 实例化和具体化

实例化:是生成一个具体的函数,无论调不调用都存在

具体化:指开辟一个特殊的模板通道,使用时不走普通模板,走此特殊通道。文章来源地址https://www.toymoban.com/news/detail-839330.html

8.6 总结

	C++扩展了C语言的函数功能。通过将imnline 关键字用于函数定义,并在首次调用该函数前提供其函数定义,可以使得C+编译器将该函数视为内联函数。也就是说,编译器不是让程序联到独立的代要受以执行函数,而是用相应的代码替换函数调用。只有在函数很短时才能采用内联方式。
	引用变量是一种伪装指针,它允许为变量创建别名(另一个名称)。引用变量主要被用作处理结构和类对象的函数的参数。通常,被声明为特定类型引用的标识符只能指向这种类型的数据;然而,如果一个类(如ofstream)是从另一个类(如ostream)派生出来的,则基类引用可以指向派生类对象。
	C++原型让您能够定义参数的默认值。如果函数调用省略了相应的参数,则程序将使用默认值:如果函数调用提供了参数值,则程序将使用这个值(而不是默认值)。只能在参数列表中从右到左提供默认参数。因此,如果为某个参数提供了默认值,则必须为该参数右边所有的参数提供默认值。
	函数的特征标是其参数列表。程序员可以定义两个同名函数,只要其特征标不同。这被称为函数多态或函数重载。通常,通过重载函数来为不同的数据类型提供相同的服务。函数模板自动完成重载函数的过程。只需使用泛型和具体算法来定义函数,编译器将为程序中使用的特定参数类型生成正确的函数定

到了这里,关于第八章 函数探幽的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 第八章:多线程

    目录 八:多线程 8.1:基本概念 8.2:线程的创建与使用         8.2.1:Thread类的有关方法         8.2.2:线程的调度         8.2.3:两种创建线程方式的比较 8.3:线程的生命周期 8.4:线程的同步         8.4.1:同步代码块同步方法         8.4.2:单例模式的懒汉式修改

    2024年02月09日
    浏览(25)
  • 第八章 常见Linux命令

    1 了解Linux帮助类命令 2 熟悉开关机命令 3 熟练文件目录类命令 4 熟悉时间日期类命令 5 熟悉用户管理命令 6 熟悉组管理命令 7 熟练文件权限命令 8 熟悉搜索查找类命令 9 熟练压缩和解压缩命令 10 熟悉磁盘分区类命令 11 熟练进程线程类命令 12 了解系统定时任务命令 man获取帮

    2024年02月11日
    浏览(29)
  • 第八章 Gateway网关

    gitee:springcloud_study: springcloud:服务集群、注册中心、配置中心(热更新)、服务网关(校验、路由、负载均衡)、分布式缓存、分布式搜索、消息队列(异步通信)、数据库集群、分布式日志、系统监控链路追踪。 1. 概述简介 官网:Spring Cloud Gateway Gateway该项目提供了一个构

    2024年02月04日
    浏览(37)
  • 第八章 C#脚本(上)

    脚本是使用 Unity 开发的所有应用程序中必不可少的组成部分。大多数应用程序都需要脚本来响应玩家的输入并安排游戏过程中应发生的事件。游戏对象的行为由附加的组件控制。虽然Unity内置了许多组件,但是我们仍然可以使用脚本来创建自定义组件。Unity支持C#编程脚本语言

    2024年02月01日
    浏览(86)
  • C国演义 [第八章]

    力扣链接 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润 返回你可以从这笔交易中获取的最大利润。如果你不能获

    2024年02月15日
    浏览(30)
  • 第八章 图像压缩

    数据冗余R为 R = 1 − 1 C R=1-cfrac1C R = 1 − C 1 ​ C为压缩率,定义为 C = b b ′ C=cfrac{b}{b\\\'} C = b ′ b ​ 二维灰度阵列受如下可被识别和利用的三种主要类型的数据冗余的影响: 编码冗余。编码是用于表示信息实体或事件集合的符号系统(字母、数字、比特和类似的符号等)。每个信

    2024年02月10日
    浏览(32)
  • 第八章:Linux信号

    linux信号是OS的重要功能。 使用kill -l查看所有信号。使用信号时,可使用信号编号或它的宏。 1、Linux中信号共有61个,没有0、32、33号信号。 2、【1,31】号信号称为普通信号,【34,64】号信号称为实时信号。 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中

    2024年02月13日
    浏览(36)
  • 考研C语言第八章

    这个东西看着像数据库里面属性的定义,也像java里面的类的定义 关于结构体里面scanf读取输入的数据,并进行相关的存储, 这里面字符串就像之前的,取数据时可以和前面的不加空格,可以不加取地址符号 (但是为了好记和规范,建议直接所有的一视同仁) 就想数据库在输

    2024年02月10日
    浏览(28)
  • 考研数据结构:第八章 排序

    2.1.1算法思想 插入排序的思想很简单,就是不断的把一个个带排序的记录,按的大小插入到前面已经排好序的子序列中。直到全部序列插入完成。 比如我们现在要对下面的序列进行排序, 刚开始我们从1号位开始 我们会认为当前处理的这个元素之前都是有序的 现在把

    2024年02月11日
    浏览(27)
  • ChatGPT入门到高级【第八章】

    第一章:Chatgpt的起源和发展 1.1 人工智能和Chatbot的概念 1.2 Chatbot的历史发展 1.3 机器学习技术在Chatbot中的应用 1.4 Chatgpt的诞生和发展 第二章:Chatgpt的技术原理 2.1 自然语言处理技术 2.2 深度学习技术 2.3 Transformer模型 2.4 GPT模型 第三章:Chatgpt的应用场景 3.1 智能客服 3.2 智能问

    2024年02月05日
    浏览(26)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包