【C++】深入理解C++中的函数与运算符重载

news2025/5/26 3:04:02

文章目录

  • 前言
    • 一、什么是重载?
      • 1.1 函数重载
        • 1.1.1 函数重载的规则
        • 1.1.2 示例:函数重载
      • 1.2 运算符重载
        • 1.2.1 运算符重载的规则
        • 1.2.2 示例:运算符重载
      • 1.2.3 运算符重载的注意事项
    • 二、重载的注意事项
      • 2.1 重载的二义性
      • 2.2 默认参数和重载
      • 2.3 运算符重载的成员函数与非成员函数
      • 2.4 重载与继承
      • 虚函数与重载
    • 三、重载的一些问题
      • 3.1 函数重载如何确定应该调用哪个函数?在编译期间还是运行期间?
      • 3.2 C语言支持函数重载吗?为什么?
      • 3.3 C++底层怎么支持函数重载的?
      • 3.4 为什么仅仅返回值不同不能形成重载?
      • 3.5 extern "C" 的作用?
      • 3.6 如何在C语言中写出函数重载的效果?

前言

C++中 重载 主要体现在两种形式:函数重载运算符重载。下面会分别进行介绍

一、什么是重载?

重载(Overloading) 在同一个作用域内,允许使用相同名称的函数或运算符,但参数列表或类型不同

参数列表指的是 参数类型、参数个数、参数类型次序不同;

1.1 函数重载

函数重载是指在同一个作用域中,允许存在多个函数名相同但参数列表不同的函数。编译器会根据函数调用时传入的参数个数、类型等信息来区分并调用相应的函数。

1.1.1 函数重载的规则
  • 函数名相同:重载的函数必须具有相同的函数名。
  • 参数个数或类型不同:重载的函数必须有不同的参数个数、类型或顺序。参数的类型、数量或顺序任何一种发生变化,都会被视作不同的函数重载。
  • 返回类型无关:返回类型不能作为区分重载函数的依据,重载的函数必须通过不同的参数列表来区分。
1.1.2 示例:函数重载
#include <iostream>
using namespace std;

class Printer {
public:
    // 打印整数
    void print(int i) {
        cout << "打印整数: " << i << endl;
    }

    // 打印浮点数
    void print(double d) {
        cout << "打印浮点数: " << d << endl;
    }

    // 打印字符串
    void print(const string& s) {
        cout << "打印字符串: " << s << endl;
    }
};

int main() {
    Printer p;
    p.print(10);         // 打印整数
    p.print(3.14);       // 打印浮点数
    p.print("Hello!");   // 打印字符串
    return 0;
}

输出

打印整数: 10
打印浮点数: 3.14
打印字符串: Hello!

对于上面的代码,print 函数通过参数类型的不同实现了重载。编译器根据传入的参数类型来选择对应的函数版本。

1.2 运算符重载

运算符重载是指为自定义类型(如类)定义运算符的行为,使得这些类型可以像内置类型一样使用常见的运算符(例如 +-* 等)。

1.2.1 运算符重载的规则
  • 运算符重载必须是成员函数或者非成员函数
  • 运算符重载不会改变运算符的优先级和结合性。
  • 可以重载大多数的运算符,但有一些运算符不能被重载(如 ::sizeoftypeid 等)。
  • 运算符重载必须遵循特定的语法规则,通常通过类成员函数或全局函数实现。
1.2.2 示例:运算符重载
#include <iostream>
using namespace std;

class Complex {
private:
    double real;
    double imag;

public:
    // 构造函数
    Complex(double r, double i) : real(r), imag(i) {}

    // 重载+运算符
    Complex operator+(const Complex& other) {
        return Complex(real + other.real, imag + other.imag);
    }

    // 输出复数
    void print() const {
        cout << real << " + " << imag << "i" << endl;
    }
};

int main() {
    Complex c1(3.0, 4.0);
    Complex c2(1.0, 2.0);
    
    Complex c3 = c1 + c2;  // 使用重载的+运算符
    c3.print();  // 输出: 4.0 + 6.0i

    return 0;
}

输出

4.0 + 6.0i

对于上面的代码,定义了一个Complex类表示复数,并重载 + 运算符使可以通过 + 来相加两个复数对象。重载后的 + 运算符实现了两个复数的实部和虚部相加。

1.2.3 运算符重载的注意事项

  • 运算符重载只是改变了运算符的行为,而不改变运算符的基本语义。
  • 并非所有运算符都可以被重载,C++标准规定了一些不能被重载的运算符,比如 ::(作用域解析运算符)、sizeof(大小运算符)、typeid(类型信息运算符)等。
  • 重载运算符时需要特别注意运算符的结合性和优先级,这些是固定的,无法改变的。

二、重载的注意事项

在使用重载时,有一些注意事项需要特别留意:

2.1 重载的二义性

有时重载可能会导致编译器无法确定使用哪个函数或运算符。这种情况通常称为二义性。例如,假设有两个重载函数,它们的参数类型很相似,编译器可能无法决定调用哪一个函数。

void func(int x);
void func(double x);

如果此时传入一个 float 类型的参数,编译器可能不知道该调用 int 版本还是 double 版本的函数。这种情况下可以通过强制类型转换来解决。

2.2 默认参数和重载

默认参数不能作为区分重载的依据。如果两个函数的参数只有默认值不同,那么它们仍然会被认为是同一个函数,而不能构成重载。

void func(int x = 5);
void func(int x = 10);  // 错误:参数默认值不同,不构成重载

2.3 运算符重载的成员函数与非成员函数

  • 运算符可以通过成员函数或者非成员函数来重载。成员函数可以访问类的私有数据,而非成员函数(通常是全局函数)需要通过传参来访问类的数据。

  • 通常来说,如果运算符重载涉及到修改类内部状态,则使用成员函数;如果只是执行操作,则可以使用非成员函数。

2.4 重载与继承

重载的函数或运算符如果在基类和派生类中都有定义,派生类可以覆盖基类的重载版本

比如对于下面的代码:

#include <iostream>
using namespace std;

// 基类
class Shape {
public:
    // 基类中的重载函数
    virtual void draw(int radius) {
        cout << "Drawing a circle with radius: " << radius << endl;
    }

    virtual void draw(double side) {
        cout << "Drawing a square with side: " << side << endl;
    }

    // 虚拟函数,允许派生类重写
    virtual ~Shape() {}
};

// 派生类
class Circle : public Shape {
public:
    // 重写基类的重载函数
    void draw(int radius) override {
        cout << "Circle class: Drawing a circle with radius: " << radius << endl;
    }
};

class Square : public Shape {
public:
    // 重写基类的重载函数
    void draw(double side) override {
        cout << "Square class: Drawing a square with side: " << side << endl;
    }
};

int main() {
    Shape* shape1 = new Circle();
    Shape* shape2 = new Square();

    // 使用基类指针调用派生类的覆盖版本
    shape1->draw(5);   // 调用 Circle 类中的 draw(int)
    shape2->draw(3.5); // 调用 Square 类中的 draw(double)

    delete shape1;
    delete shape2;

    return 0;
}

会有如下的输出结果:

Circle class: Drawing a circle with radius: 5
Square class: Drawing a square with side: 3.5

虚函数与重载

如果重载的函数是虚函数,那么编译时选择哪个重载版本并不会受到虚函数机制的影响。虚函数机制(多态性)是基于运行时的动态绑定来确定调用哪个函数,而重载是根据函数签名在编译时进行解析的。


三、重载的一些问题

3.1 函数重载如何确定应该调用哪个函数?在编译期间还是运行期间?

  • A:函数重载的选择是在 编译期间 确定。具体的说,编译器根据函数的参数列表来决定应该调用哪个重载版本。这个过程被称为静态绑定早期绑定

3.2 C语言支持函数重载吗?为什么?

  • C语言不支持,C语言编译器种没有名字修饰(Name Mangling)机制,C++ 编译器可能会将 func(int) 和 func(double) 分别变成 func_int 和 func_double,从而使它们在符号表中具有不同的名字。
  • 而C 的符号表通常只有函数的名称,而没有参数类型的附加信息。

3.3 C++底层怎么支持函数重载的?

  • 同上一问,C++编译器会将函数的参数信息也加载到名字中;在符号表中的名字就不同了。

3.4 为什么仅仅返回值不同不能形成重载?

  • 同上面的问题,编译器不会将函数的返回值加载到名字中,仅仅返回值不同,符号表上依然是相同的

3.5 extern “C” 的作用?

  1. extern “C” 是用于 C++ 中的声明,作用是 告诉编译器以 C 语言的方式来处理函数或变量的链接方式,即 禁用 C++ 的名字修饰(name mangling)

使用 extern "C" 修饰函数或变量,可以确保这些符号按照 C 的规则进行链接,使得 C++ 编译器生成与 C 编译器兼容的符号,从而允许 C++ 和 C 代码混合编程,进行跨语言调用。

3.6 如何在C语言中写出函数重载的效果?

C语言中可以通过利用特性实现函数重载的效果,比如 变参函数

可以利用宏根据不同的参数类型调用不同的函数:

#include <stdio.h>

#define PRINT(x) _Generic((x), \
    int: print_int, \
    double: print_double)(x)

void print_int(int x) {
    printf("Integer: %d\n", x);
}

void print_double(double x) {
    printf("Double: %f\n", x);
}

int main() {
    PRINT(10);        // 调用 print_int
    PRINT(3.14);      // 调用 print_double
    return 0;
}
  1. 变参函数

C 语言允许定义变参函数,即函数的参数数量和类型可以在调用时动态变化。例如:

#include <stdio.h>
#include <stdarg.h>

void print(const char *format, ...) {
    va_list args;
    va_start(args, format);
    
    if (strcmp(format, "int") == 0) {
        int x = va_arg(args, int);
        printf("Integer: %d\n", x);
    } else if (strcmp(format, "double") == 0) {
        double x = va_arg(args, double);
        printf("Double: %f\n", x);
    }
    
    va_end(args);
}

int main() {
    print("int", 10);
    print("double", 3.14);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2385793.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【读代码】BAGEL:统一多模态理解与生成的模型

一、项目概览 1.1 核心定位 BAGEL是字节跳动推出的开源多模态基础模型,具有70亿激活参数(140亿总参数)。该模型在统一架构下实现了三大核心能力: 多模态理解:在MME、MMBench等9大评测基准中超越Qwen2.5-VL等主流模型文本生成图像:生成质量媲美SD3等专业生成模型智能图像…

隧道自动化监测解决方案

行业现状 隧道作为一种重要的交通运输通道&#xff0c;不管是缓解交通压力&#xff0c;还是让路网结构更趋于完善&#xff0c;它都有着不可估量的作用。隧道在运营过程中&#xff0c;由于受到材料退化、地震、人为因素等影响会发生隧道主体结构的损坏和劣化。若不及时检修和维护…

游戏引擎学习第307天:排序组可视化

简短谈谈直播编程的一些好处。 上次结束后&#xff0c;很多人都指出代码中存在一个拼写错误&#xff0c;因此这次我们一开始就知道有一个 bug 等待修复&#xff0c;省去了调试寻找错误的时间。 今天的任务就是修复这个已知 bug&#xff0c;然后继续排查其他潜在的问题。如果短…

java接口自动化初识

简介 了解什么是接口和为什么要做接口测试。并且知道接口自动化测试应该学习哪些技术以及接口自动化测试的落地过程。 一、什么是接口 在这里我举了一个比较生活化的例子&#xff0c;比如我们有一台笔记本&#xff0c;在笔记本的两端有很多插口。例如&#xff1a;USB插口。那…

NVM安装使用及问题解决

目录 一、前言 二、NVM安装 三、配置下载源 四、nvm使用 五、安装nvm list available没有的版本 六、问题解决 一、前言 如果你开发 Node.js 项目&#xff0c;可能会遇到这些问题&#xff1a; ①新项目需要 Node.js 18&#xff0c;但老项目只能用 Node.js 14&#xff0c;…

C++学习之STL学习:string类使用

在之前的学习中&#xff0c;我们初步了解到了STL的概念&#xff0c;接下来我们将深入学习STL中的string类的使用&#xff0c;后续还会结合他们的功能进行模拟实验 目录 为什么要学习string类&#xff1f; 标准库中的string类 string类&#xff08;了解&#xff09; auto和范围…

5月24日day35打卡

模型可视化与推理 知识点回顾&#xff1a; 三种不同的模型可视化方法&#xff1a;推荐torchinfo打印summary权重分布可视化进度条功能&#xff1a;手动和自动写法&#xff0c;让打印结果更加美观推理的写法&#xff1a;评估模式 作业&#xff1a;调整模型定义时的超参数&#x…

Linux(7)——进程(概念篇)

目录 一、基本概念 二、描述进程——PCB 1.task_struct——PCB的一种 2.task_struct的内容分类 三、查看进程 1.通过系统目录查看 2.通过ps命令查看 四、通过系统调用获取进程的PID和PPID 五、通过系统调用创建进程 1.fork函数创建子进程 2.使用if来引出问题 六、L…

前端流行框架Vue3教程:24.动态组件

24.动态组件 有些场景会需要在两个组件间来回切换&#xff0c;比如 Tab 界面 我们准备好A B两个组件ComponentA ComponentA App.vue代码如下&#xff1a; <script> import ComponentA from "./components/ComponentA.vue" import ComponentB from "./…

Unity3D仿星露谷物语开发48之显示树桩效果

1、目标 砍完橡树之后会露出树桩&#xff0c;然后树桩可以用斧头收割&#xff0c;并将创建一个新的砍树桩的粒子效果。 这里有&#xff1a;一种作物收获后创造另一种作物的逻辑。 2、分析 在SO_CropDetailsList中&#xff0c;Harvested Transform Item Code可以指定收获后生…

[Datagear] 实现按月颗粒度选择日期的方案

在使用 Datagear 构建数据分析报表时,常常会遇到一个问题:如果数据的目标颗粒度是“月”,默认的日期控件却是精确到“日”的,这在用户交互和数据处理层面会带来不必要的复杂度。本文将分享两种解决方案,帮助你更好地控制日期控件的颗粒度,实现以月为单位的日期筛选功能。…

漏洞检测与渗透检验在功能及范围上究竟有何显著差异?

漏洞检测与渗透检验是确保系统安全的重要途径&#xff0c;这两种方法各具特色和功效&#xff0c;它们在功能上有着显著的差异。 目的不同 漏洞扫描的主要任务是揭示系统内已知的安全漏洞和隐患&#xff0c;这就像是对系统进行一次全面的健康检查&#xff0c;看是否有已知的疾…

DB-GPT扩展自定义Agent配置说明

简介 文章主要介绍了如何扩展一个自定义Agent&#xff0c;这里是用官方提供的总结摘要的Agent做了个示例&#xff0c;先给大家看下显示效果 代码目录 博主将代码放在core目录了&#xff0c;后续经过对源码的解读感觉放在dbgpt_serve.agent.agents.expand目录下可能更合适&…

家政维修平台实战09:推送数据到多维表格

目录 1 API调试2 创建云函数3 前端调用整体效果总结 上一篇我们搭建了服务分类的后台功能&#xff0c;对于分类的图标通过集成TOS拿到了可以公开访问的地址&#xff0c;本篇我们将写入的数据推送至多维表格中。 1 API调试 要想推送多维表格的数据&#xff0c;首先要利用官方的…

前端框架token相关bug,前后端本地联调

今天我搭建框架的时候&#xff0c;我想请求我自己的本地&#xff01;然后我自己想链接我自己的本地后端&#xff0c;我之前用的前端项目&#xff0c;都是链别人的后端&#xff0c;基本上很少情况会链接自己的后端&#xff01;所以我当时想的是&#xff0c;我前后端接口一样&…

卷积神经网络(CNN)可视化技术详解:从特征学到演化分析

在深度学习领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;常被称为“黑箱”&#xff0c;其内部特征提取过程难以直接观测。而 可视化技术 是打开这一“黑箱”的关键工具&#xff0c;通过可视化可直观了解网络各层学到了什么、训练过程中如何演化&#xff0c;以及模型…

QT之INI、JSON、XML处理

文章目录 INI文件处理写配置文件读配置文件 JSON 文件处理写入JSON读取JSON XML文件处理写XML文件读XML文件 INI文件处理 首先得引入QSettings QSettings 是用来存储和读取应用程序设置的一个类 #include "wrinifile.h"#include <QSettings> #include <QtD…

微信小程序调用蓝牙API “wx.writeBLECharacteristicValue()“ 报 errCode: 10008 的解决方案

1、问题现象 问题:在开发微信小程序蓝牙通信功能时,常常会遇到莫名其妙的错误,查阅官方文档可能也无法找到答案。如在写入蓝牙数据时,报了这样的错误: {errno: 1500104, errCode: 10008, errMsg: "writeBLECharacteristicValue:fail:system error, status: UNKNOW…

【Java基础笔记vlog】Java中常见的几种数组排序算法汇总详解

Java中常见的几种排序算法&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09;选择排序&#xff08;Selection Sort&#xff09;插入排序&#xff08;Insertion Sort&#xff09;希尔排序&#xff08;Shell Sort&#xff09;归并排序&#xff08;Merge Sort&#xff09…

WebRTC与RTSP|RTMP的技术对比:低延迟与稳定性如何决定音视频直播的未来

引言 音视频直播技术已经深刻影响了我们的生活方式&#xff0c;尤其是在教育、医疗、安防、娱乐等行业中&#xff0c;音视频技术成为了行业发展的重要推动力。近年来&#xff0c;WebRTC作为一种开源的实时通信技术&#xff0c;成为了音视频领域的重要选择&#xff0c;它使得浏览…