函数重载:在相同作用域中的多个函数,具有相同的名字而形参表不同。
不能仅仅基于不同的返回类型而实现函数重载。返回值是不影响函数签名的。
C++函数重载底层实现原理是C++利用name mangling(倾轧)技术,来改名函数名,区分参数不同的同名函数。
编译器通过函数名和其参数类型识别重载函数。为了保证类型安全的连接(type-safe linkage),编译器用参数个数和参数类型对每一个函数标识符进行专门编码,这个过程有时称为“名字改编”(name mangling)或“名字修饰”(name decoration)。类型安全的连接使得程序能够调用合适的重载函数并保证了参数传递的一致性。编译器能够检测到并报告连接错误。
查看符号表
nm 目标文件
objdump -t 目标文件
readelf -s 目标文件
strings 目标文件
gcc
编译
test.c
int func(int a){};
- 编译
gcc -c test.c -o test.o
- 查看符号
objdump -t test.o
函数名 | 名字改编 |
---|---|
int func(int a); |
func |
g++
编译
test.cpp
int func(int a){};
int func(int a,int b){};
- 编译
g++ -c test.cpp -o test.o
- 查看符号
objdump -t test.o
函数名 | 名字改编 |
---|---|
int func(int a); |
_Z4funci |
int func(int a,int b); |
_Z4funcii |
_Z
是规定前缀,4
是函数名的字符个数,i
是参数列表类型i
的首字母
注意:不同系统和编译器的命名倾轧方式是有所不同的。
命名反倾轧
- 名字改编转化成函数名
使用c++filt
命令可以很容易把名字改编转换成函数名。
例如:c++filt _Z4funci
- 查看反倾轧的符号表
nm -C 目标文件
禁用命名倾轧
C++命名倾轧的函数是无法被C语言调用的。C++的函数必须是没有倾轧的才能调用。
使用声明extern "C"
的函数会禁止命名倾轧,这样C++的函数就可以被C语言调用。
例如:
- sum.h
#ifndef __CPP_H_
#define __CPP_H_
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
int sum(int* arr,int n);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // __CPP_H_
通常为了头文件可以同时被C++/C使用,通常把extern "C"放在
__cplusplus
条件宏定义中。
- sum.cpp
#include "cppFunc.h"
#include <numeric>
using namespace std;
int sum(int* arr,int n){
return accumulate(arr,arr+n,0);
}
- sum_test.c
#include <stdio.h>
#include "cppFunc.h"
int main(){
int arr[] = {1,2,3,4,5};
printf("%d\n",sum(arr,5));
}
编译
g++ -c sum.cpp
gcc sum_test.c sum.o
练习
把vector<int>
封装成C语言可调用的顺序表。
gcc调用找不到C++标准库符号时,
-lstdc++
链接C++标准库。
- 头文件部分内容
#ifdef __cplusplus
extern "C"{
#endif // __cplusplus
void* seq_create();
void seq_destroy(void* seq);
void seq_append(void* seq,int val);
void seq_prepend(void* seq,int val);
int seq_size(void* seq);
int seq_get(void* seq,int index);
#ifdef __cplusplus
}
#endif // __cplusplus
- 源文件部分内容
int max_element(int* arr,int n){
return max_element(arr,arr+n) - arr;
}
void* seq_create(){
return new vector<int>();
}
void seq_destroy(void* seq){
delete reinterpret_cast<vector<int>*>(seq);
}
void seq_append(void* seq,int val){
reinterpret_cast<vector<int>*>(seq)->push_back(val);
}
void seq_prepend(void* seq,int val){
vector<int>* p = reinterpret_cast<vector<int>*>(seq);
p->insert(p->begin(),val);
}
int seq_size(void* seq){
return reinterpret_cast<vector<int>*>(seq)->size();
}
int seq_get(void* seq,int index){
return reinterpret_cast<vector<int>*>(seq)->at(index);
}
原理小结
空类
1. 空类的大小是多少?
2. 编译器会给空类自动生成几个成员函数?
- 默认(缺省的)构造函数
- 析构函数
- 拷贝构造函数
- 赋值运算符重载函数
- 两个取址运算符重载函数
class Empty {
public:
Empty(){} //缺省构造函数
Empty(const Empty &rhs){} //拷贝构造函数
~Empty(){} //析构函数
Empty& operator=(const Empty &rhs){} //赋值运算符重载函数
Empty* operator&(){} //取址运算符重载函数
const Empty* operator&() const{} //取址运算符重载函数(const版本)
};
调用时机
Empty *e = new Empty(); //缺省构造函数
delete e; //析构函数
Empty e1; //缺省构造函数
Empty e2(e1); //拷贝构造函数
Empty e3 = e1; //拷贝构造函数
e2 = e1; //赋值运算符
Empty *pe1 = &e1; //取址运算符重载函数(非const)
const Empty *pe2 = &e2; //取址运算符重载函数(const)