C++基础语法-重载、extern

函数重载

今天我们来深入学习下C++的基础语法函数重载。函数重载定义:只要函数名相同,参数个数不同,参数类型不同,参数顺序不同就构成C++的函数重载。其本质是编译器采用一种叫name mangling也叫name decoration的一种技术对函数名修饰装饰达到函数名唯一的的目的,它提供了在函数、结构体、类、或其它的数据类型的名字中编码附加信息一种方法,用于从编译器中向链接器传递更多语义信息。先来看一下C++函数重载的列子:

#include 
#include 
using namespace std;
void print() {
    cout<<"print() "<
}
void print(int a) {
    cout<<"print(int a) "<< a << endl
}
void print(long a) {
   cout<<"print(long a) "<< a << endl
}
void print(double a) {
   cout<<"print(double a) "<< a << endl
}
int main(int argc,const char* argv[]) {
    print(); //  __Z5printv:
    print(10);//  __Z5printi:
    print(10l);// __Z5printl:
    print(10.1);//__Z5printd:
    return 0;
}

由于C不支持重载,但是C++经过编译器name mangling技术,函数名字已经被修饰成具有唯一函数名的函数,然后各自分别调用,每个编译器修饰的结果不一样,我这里采用的编译器是Apple Clang。下面再列举一个维基百科上面name mangling的应用,代码如下:

#include 
#include 
using namespace std;
namespace wikipedia
{
    class article
    {
    public:
        std::string format (void)
        {
            return "";
        }
        /* = __ZN9wikipedia7article6formatEv */
        
        bool print_to (std::ostream&)
        {
            return true;
        }
        /* = __ZN9wikipedia7article8print_toERSo */
        
        class wikilink
        {
        public:
            wikilink (std::string const& name){}
            /* = __ZN9wikipedia7article8wikilinkC1ERKSs */
        };
    };
}
int main(int argc, const char * argv[]) {
    wikipedia::article art;
    art.format();
    art.print_to(cout);
    wikipedia::article::wikilink link("link_name");
    return 0;
}

编译以上代码通过相关工具查看其二进制代码,我这里是采用Hopper Disassembler工具,定义在类内部的成员函数通过name mangling都进行了转换,转换后的函数名保持唯一(查看函数代码注释),以上述转换后的函数__ZN9wikipedia7article6formatEv为例:以__Z开头,N表示命名空间,紧接着9是命名空间的长度,再跟上命名空间,7是类的类名长度,再跟上类名,6是类成员函数名字长度,再跟上函数名,E表示结束符,v表示函数返回值。这样不同的类的不同的函数通过编译器都被唯一确定,程序运行的时候找到的函数地址都是唯一。正向开发的时候,我们基本不用管函数最终转换成了哪个函数名,但是逆向的时候,我们只能拿到别人二进制文件,看到的很多函数名字都是通过name mangling之后的函数名,我们可以借助相关工具c++filt终端执行还原函数名:

c++filt __ZN9wikipedia7article8wikilinkC1ERKSs
wikipedia::article::wikilink::wikilink(std::string const&)

extern "C"

C++文件如果要调用C语言文件,经常我们要制定C++按照C的方式编译,通常项目C文件要被C++和C都可以调用,C++才有exten "C",C没有extern "C"的定义,C++中自己定义了个宏__cplusplus。鉴于上述特征,可以在要调用的C头文件函数声明的时候做一个判断处理,同时支持C++和C的调用,示例代码如下:

#ifdef __cplusplus
extern "C"{
#endif
//此函数既可以被C调用也可以被C++调用
int print(void);
#ifdef __cplusplus
}
#endif

关于extern在C语言中的用法这里做一个补充,extern关键字一般用于声明变量或者函数,告诉程序如何去找到它,不分配内存,可以多次声明,而定义会分配内存且只能定义一次。下面我们来分别看看函数和变量的extern用法:
1.函数:函数的extern一般在声明或者定义编译器会隐式声明,我们自己不用加,比如我们这样写:

int foo(int arg1, char arg2);

编译器会这样翻译:

 extern int foo(int arg1, char arg2);

加了exten后,这个函数在整个程序中可见,只要提供了这个函数声明,可以在其它地方使用或者调用,编译器会自动去找函数的定义并且进行编译。
2.变量;变量跟函数exten用法不太一样,变量的声明和定义如下:

int age;       //既声明又定义,编译器默认赋值为0
int age = 100; //既声明又定义,手动赋值为100
extern age; //只声明,编译器会去找相关定义拿到相关的值
extern int age = 100;  //显示声明和定义,手动赋值为100

参考文献:
https://en.wikipedia.org/wiki/Name_mangling
https://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

你可能感兴趣的:(C++基础语法-重载、extern)