当前位置: 首页 > news >正文

03【C++ 入门基础】函数重载

文章目录

  • 引言
  • 函数重载
    • 函数重载的使用
    • 函数重载的原理
      • extern “C”
    • 静态多态
  • 总结


引言

通过00【C++ 入门基础】前言得知,C++是为了解决C语言在面对大型项目的局限而诞生:

C语言面对的现实工程问题(复杂性、可维护性、可扩展性、安全性)

C语言为了实现不同类型的加法,只能定义多个函数:

//C语言:
int add_int(int a, int b)              //对两int类型相加
{return a + b;
}
double add_double(double a, double b)  //对两double类型相加
{return a + b;
}
int add_10_int_ptr(void *a)      //对void*类型强转之后加10
{return *a + 10;
}
int main()
{add_int(1, 2);add_double(1.2, 2.3);return 0;
}

平时写写还可以,面对大型项目问题就暴露出来了:

  1. 多个类似功能(加法)的函数,在各个角落被调用,但是函数名称不同,命名冗余,代码可读性下降。​(对调用者来说复杂)
  2. C语言的泛型,由于对void*没有检查,使用起来不安全(安全性):
#include <stdio.h>
// 类型不安全的打印函数
void unsafe_print(void* data, char type)   //C语言使用void*,也实现了一个函数接收不同类型(泛型编程),但是使用起来不安全。
{switch(type) {case 'i': // 假设 'i' 代表 intprintf("Integer: %d\n", *(int*)data);break;case 'f': // 假设 'f' 代表 floatprintf("Float: %f\n", *(float*)data);break;default:printf("Unknown type\n");}
}
int main() 
{int a = 10;float b = 3.14;// 正确使用(需要确保类型匹配)unsafe_print(&a, 'i');unsafe_print(&b, 'f');// 类型错误的使用(编译器不会警告)unsafe_print(&b, 'i'); // 错误!把 float 当作 int 解释unsafe_print(&a, 'f'); // 错误!把 int 当作 float 解释return 0;
}

函数重载

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

函数重载的使用

#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}
double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return left + right;
}
// 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;
}

函数重载,需要参数类型不同、参数个数不同、参数类型顺序不同。

思考:

  1. 以下函数构成重载吗?
int Add(int a, char b)
{cout << "Add(int a, char b)";
}
int Add(int b, char a)
{cout << "Add(int b, char a)";
}

不构成,我们的参数的顺序类型个数都没有变化,参数名不起决定作用。

  1. 以下函数构成重载吗?
int f()
{cout << "f()";
}
int f(int a = 0)
{cout << "f(int a = 0)";
}

构成,但是有错,因为调用的时候有歧义:

int main()
{f();      //有多个 重载函数 "f" 实例与参数列表匹配:return 0;
}
  1. 以下函数构成重载吗?
void Func(int a = 10)
{cout<<"void Func(int a = 10)"<<endl;
}
void Func(int a)
{cout<<"void Func(int a)"<<endl;
}

不构成,不满足重载条件,虽然不传参只能调用到第一个带缺省值的函数,但是如果传参的话就会有调用冲突:“Func”: 重定义默认参数 : 参数 1

函数重载的原理

在从代码变为可执行程序的过程的汇编阶段,会生成符号表,符号表主要功能之一就是帮助后续的链接器找到和解析符号(如函数和全局变量)。
编译链接过程
那么每个函数都会生成它自己对应的符号,C++在这里用了采用了有别于C语言的符号生成方式,才得以实现函数重载:

  • 采用C语言编译器编译后结果:
    C语言编译后的结果
  • 采用C++编译器编译后结果:
    在这里插入图片描述

可以看到,C++最终生成的符号表,是不同于C语言的,其实它生成符号表的时候,考虑了函数的参数,也就最终导致,参数类型不同、参数个数不同、参数类型顺序不同的同名函数生成的符号不相同,那么最后链接器去找到对应函数的符号进行链接的时候,就可以区分重载的函数!!!

疑问:我们只是换了个编译器,为什么说是C++语言的行为?
这由C++语言规范明确规定的机制,强制所有的C++编译器执行。

windows下vs编译器对函数名字修饰规则相对复杂难懂,所以我们使用Linux演示,但道理都是类似的,我们就不做细致的研究了。

extern “C”

由于C和C++编译器对函数名字修饰规则的不同,在有些场景下可能就会出问题,比如:

  1. C++中调用C语言实现的静态库或者动态库,反之亦然。
  2. 多人协同开发时,有些人擅长用C语言,有些人擅长用C++。

在这种混合模式下开发,由于C和C++编译器对函数名字修饰规则不同,可能就会导致链接失败,在该种场景下,就需要使用extern “C”。在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。下面演示一个在C++工程中使用C语言静态库的例子:

///cal.h/
#pragma once
/*
* 注意:
* 在实现该库时,并不知道将来使用该静态库的工程是C语言工程还是C++工程
* 为了能在C/C++工程中都能使用,函数声明时需加上extern "C"
*
* __cplusplus:是C++编译器中定义的宏,即用该宏来检测是C工程还是C++工程
*
* 
* #ifdef __cplusplusextern "C"{#endif// 要导出函数的声明#ifdef __cplusplus}#endif作用:如果是C++工程,编译器已经定义_cplusplus宏,编译时该宏是可以被识别的,被声明的函数就被
extern "C"修饰了,此时C++编译就知道,静态库中的函数是按照C的方式编译的,这样在链接时就会按照C的方式找函
数名字如果是C工程,编译器未定义_cplusplus宏,编译时该宏无法被是被,则条件编译就无效,函数就
不会被extern "C"修饰
*/#ifdef __cplusplus
extern "C"
{
#endifint Add(int left, int right);int Sub(int left, int right);
#ifdef __cplusplus
}
#endif
///cal.cpp/
#include "cal.h"int Add(int left, int right)
{return left + right;
}
int Sub(int left, int right)
{return left - right;
}
///libUser.cpp/
#include "./../../StaticLib/StaticLib/cal.h"
#pragma comment(lib, "./../../StaticLib/Debug/StaticLib.lib")
#include <iostream>
using namespace std;int main()
{int ret = Add(10, 20);cout << ret << endl;ret = Sub(30, 20);cout << ret << endl;return 0;
}

如果在实现静态库时,cal.h中的函数没有使用extern "C"修饰就会报错:

#pragma once
int Add(int left, int right);
int Sub(int left, int right);

1>TestCalc.cpp
1>TestCalc.obj : error LNK2019: 无法解析的外部符号 “int __cdecl Add(int,int)” (?
Add@@YAHHH@Z),函数 _main 中引用了该符号
1>TestCalc.obj : error LNK2019: 无法解析的外部符号 “int __cdecl Sub(int,int)” (?
Sub@@YAHHH@Z),函数 _main 中引用了该符号
1>D:\WorkStations\Gitee\cppLesson\TestCalc\Debug\TestCalc.exe : fatal error LNK1120: 2
个无法解析的外部命令

思考:

  1. C语言中为什么不能支持函数重载?
    C语言没有生成符号表是考虑参数的规范,所以链接器最后会将所有同名不同参的函数,认为是同一个。
  2. C++中能否将一个函数按照C的风格来编译?
    可以,为了向前兼容,即即使有了C++,C语言的代码也可以被C++的编译器运行。
  3. C++程序中被extern "C"修饰的函数,就会按照C语言规则来编译,用C语言的方式来生成符号表,那么它是不是不可以做函数重载了?
    是的,C语言规则生成符号表时不考虑函数的参数,所以被修饰的函数将不可被重载。

静态多态

多态,就是同一个接口(函数调用)可以根据对象类型的不同而执行不同的操作,说白了就是看人下菜碟。
多态又分为静态多态和动态动态,静态多态在编译时绑定,动态多态在运行时绑定。

我们的函数重载,就正是多态的一种,因为它根据参数类型变化,可以执行不同操作,它在我们编译时,便通过符号表确定,所以它是一种静态多态!!

总结

1.函数重载,解决C语言的同功能但不同命函数的命名冗余和一些安全性问题。
2.函数重载的使用方式,是定义一个同名,但是参数类型不同、参数个数不同、参数类型顺序不同的函数。
3.函数重载的原理是:C++汇编时生成的符号表,每个函数的符号都考虑了函数的参数去生成,所以参数类型不同、参数个数不同、参数类型顺序不同的同名函数之间可以被区分。
4.函数重载,是一种多态,静态多态。


本文章为作者的笔记和心得记录,顺便进行知识分享,有任何错误请评论指点:)。

相关文章:

  • 使用ros2服务实现人脸检测4-客户端(适合0基础小白)
  • 通达信【MACD趋势增强系统】幅图(含支撑压力位)
  • D-FiNE:在DETR模型中重新定义回归任务为精细粒度分布细化
  • MySQL数据库的增删改查
  • SpringCloud系列(41)--SpringCloud Config分布式配置中心简介
  • 模拟多维物理过程与基于云的数值分析-AI云计算数值分析和代码验证
  • CppCon 2017 学习:The Asynchronous C++ Parallel Programming Model
  • 在线之家官网入口 - 免费高清海外影视在线观看平台
  • STM32之28BYJ-48步进电机驱动
  • 思二勋:算法稳定币的发展在于生态场景、用户和资产的丰富性
  • 打造地基: App拉起基础小程序容器
  • 大事件项目记录12-文章管理接口开发-总
  • 现代 JavaScript (ES6+) 入门到实战(一):告别 var!拥抱 let 与 const,彻底搞懂作用域
  • Spark Web UI从0到1详解
  • SpringSecurity6-授权-动态权限
  • (NIPS-2024)CogVLM:预训练语言模型的视觉专家
  • 大事件项目记录13-接口开发-补充
  • 深入剖析 Linux 内核网络核心:sock.c 源码解析
  • 现代 JavaScript (ES6+) 入门到实战(四):数组的革命 map/filter/reduce - 告别 for 循环
  • 数据挖掘、机器学习与人工智能:概念辨析与应用边界