文章目录
- 引言
- 函数重载
- 函数重载的使用
- 函数重载的原理
- 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;
}
平时写写还可以,面对大型项目问题就暴露出来了:
- 多个类似功能(加法)的函数,在各个角落被调用,但是函数名称不同,命名冗余,代码可读性下降。(对调用者来说复杂)
- 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;
}
函数重载,需要参数类型不同、参数个数不同、参数类型顺序不同。
思考:
- 以下函数构成重载吗?
int Add(int a, char b)
{cout << "Add(int a, char b)";
}
int Add(int b, char a)
{cout << "Add(int b, char a)";
}
不构成,我们的参数的顺序类型个数都没有变化,参数名不起决定作用。
- 以下函数构成重载吗?
int f()
{cout << "f()";
}
int f(int a = 0)
{cout << "f(int a = 0)";
}
构成,但是有错,因为调用的时候有歧义:
int main()
{f(); //有多个 重载函数 "f" 实例与参数列表匹配:return 0;
}
- 以下函数构成重载吗?
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++编译器执行。
windows下vs编译器对函数名字修饰规则相对复杂难懂,所以我们使用Linux演示,但道理都是类似的,我们就不做细致的研究了。
extern “C”
由于C和C++编译器对函数名字修饰规则的不同,在有些场景下可能就会出问题,比如:
- C++中调用C语言实现的静态库或者动态库,反之亦然。
- 多人协同开发时,有些人擅长用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
个无法解析的外部命令
思考:
- C语言中为什么不能支持函数重载?
C语言没有生成符号表是考虑参数的规范,所以链接器最后会将所有同名不同参的函数,认为是同一个。 - C++中能否将一个函数按照C的风格来编译?
可以,为了向前兼容,即即使有了C++,C语言的代码也可以被C++的编译器运行。 - C++程序中被extern "C"修饰的函数,就会按照C语言规则来编译,用C语言的方式来生成符号表,那么它是不是不可以做函数重载了?
是的,C语言规则生成符号表时不考虑函数的参数,所以被修饰的函数将不可被重载。
静态多态
多态,就是同一个接口(函数调用)可以根据对象类型的不同而执行不同的操作,说白了就是看人下菜碟。
多态又分为静态多态和动态动态,静态多态在编译时绑定,动态多态在运行时绑定。
我们的函数重载,就正是多态的一种,因为它根据参数类型变化,可以执行不同操作,它在我们编译时,便通过符号表确定,所以它是一种静态多态!!
总结
1.函数重载,解决C语言的同功能但不同命函数的命名冗余和一些安全性问题。
2.函数重载的使用方式,是定义一个同名,但是参数类型不同、参数个数不同、参数类型顺序不同的函数。
3.函数重载的原理是:C++汇编时生成的符号表,每个函数的符号都考虑了函数的参数去生成,所以参数类型不同、参数个数不同、参数类型顺序不同的同名函数之间可以被区分。
4.函数重载,是一种多态,静态多态。
本文章为作者的笔记和心得记录,顺便进行知识分享,有任何错误请评论指点:)。