C++入门1(下)

简介: C++入门1

4.函数重载

C++是支持函数重载的,

所谓函数重载就是指:两个函数名称相同,但是参数的个数或者类型或者类型顺序不同,即构成函数重载

注意:如果两个函数只有返回值不同的话,并不会构成函数重载

1.函数重载的形式

这里以Add函数为例

int Add(int num1, int num2)
{
  return num1 + num2;
}
double Add(double num1, double num2)
{
  return num1 + num2;
}
int main()
{
  cout << Add(1, 2) << endl;
  cout << Add(1.1, 1.2) << endl;
  //cout << Add(1, 1.2) << endl;//err
  return 0;
}

大家注意到了这一行了吧

cout << Add(1, 1.2) << endl;//err
• 1

为什么会报错呢?

首先,在C语言的学习中,我们知道int和double类型是可以发生隐式类型转换的

也就是说在Add(1,1.2)这个函数调用中,有可能会发生两种情况:

(1)(int类型的1)被隐式转换为double类型,去匹配double Add(double num1, double num2)

(2)(double 类型的1.2)被隐式转换为int类型,去匹配int Add(int num1, int num2);

也就是说这个函数调用存在歧义,所以编译器会报错

也就是说哪怕我们去掉其中的一个Add函数,这个函数调用就不会报错了

下面是函数重载的几种错误形式

1.两个函数仅有返回值不同

2.参数仅有名称不同或者仅有参数名称顺序不同

注意:并不是说两个函数构成重载之后对它们的调用就不会产生歧义

2.函数重载和缺省参数的联系

下面请大家看一下下面两个函数能不能构成函数重载

void f(int num)
{
  cout << "f(int num)" << endl;
}
void f(int num = 1)
{
  cout << "f(int num = 1)" << endl;
}

答案是:不构成重载,因为参数的个数,类型,类型顺序均相同

那么在请大家看一下这两个函数会不会构成重载呢?

void f()
{
  cout << "f()" << endl;
}
void f(int num = 1)
{
  cout << "f(int num = 1)" << endl;
}

答案是:构成重载

但是这意味着这两个函数在调用时就真的没有歧义了吗?

这种情况下是可以的

但是这种情况下就不行了:

因为在调用的时候产生了歧义

这就像是一个经典的问题:

你妈妈和你老婆掉水里,你只能救一个,你救谁?

C++懒得跟你折腾,直接在语法上不允许这样做,直接报错

3.函数重载的底层原理:函数名修饰规则

还有一个问题:函数的参数不同就能构成函数重载,那我返回值不同凭什么就无法构成函数重载呢?

这就要谈一下函数名修饰规则了

在谈这个之前,我们先建立一个Func.h,一个Func.cpp,一个Test.cpp文件

并且复习一下C语言阶段的编译链接的知识

现在我们有Func.h,Func.cpp,Test.cpp这三个文件
其中在Func.cpp文件中定义了两个重载的func函数
我们知道编译分为4个阶段:
1.预处理:
头文件展开
宏替换
去注释
条件编译
Fun.h在Func.cpp和Test.cpp中展开
Func.cpp中同时拥有func函数的声明和定义,Test.cpp中拥有func函数的声明和具体调用
Fun.cpp ->  Fun.i
Test.cpp -> Test.i
2.编译:
把.i文件进行语法检查(语法分析,词法分析,语义分析)生成汇编代码(.s文件)
Fun.i  ->  Fun.s
Test.i ->  Test.s
3.汇编阶段:
把汇编代码转换为二进制机器码,生成目标文件(.o文件)
Fun.s -> Fun.o
Test.s -> Test.o
4.链接阶段:
合并.o目标文件,链接一些没有确定的函数的地址,合并段表,符号表的重定位等等
生成可执行程序:
Windows: .exe,
Linux:   a.out
而这个函数名修饰规则就是在链接阶段进行的
在预处理阶段结束后
Func.cpp中同时拥有func函数的声明和定义,Test.cpp中拥有func函数的声明和具体调用
所谓声明就是一种承诺,是承诺,就要兑现
而链接阶段就是这个承诺兑现的时候
怎么兑现呢?让我的Test.cpp能够找到func函数的定义,就是兑现
怎么找到呢?
(通过函数声明去找地址)
.o文件中有一个东西叫做符号表,符号表中存储了函数名跟函数地址的一种映射关系
在链接阶段,通过符号表跟一种映射关系找到函数的地址,就能进行函数的调用了
而对于这个符号表来说
在C语言中,符号表中只会建立函数名跟函数地址的映射关系,
也就决定了在C语言中不允许存在同名函数,否则在链接阶段会找到多个函数地址,产生歧义
而对于C++来说
有函数名修饰规则:把函数的参数代入进去对符号表中的映射进行修饰
在Linux下可以尝试看到这个名字
Linux下函数名修饰规则:
_Z4func(_Z是前缀+函数名的字符个数+函数名+参数首字母)
_Z4funcdi(第一个参数类型:double,第二个:int)
_Z4funcid(第一个参数类型:int,第二个:double)
int* :Pi
有兴趣,可以自己在Linux下去试试

下面我们在Linux下给大家看一看

我们在Linux下建立了两个文件:

test.c

test.cpp

先看.c:

cpp:

然后我们用gcc把test.c编译出来,编译成testc

然后用这个命令查看testc的内容

这个 < func>前面的就是它的地址,也就是第一个指令的地址,

而且我们发现这个函数名根本就没有被修饰

下面我们来看一下.cpp文件

先用g++把test.cpp编译成了testcpp

还是用这个命令进行查看

然后我们发现C++编译后的函数名的确被修饰了

修饰成了

_Z4funcid
• 1

大家也可以试一下其他的类型,指针的话:

例如int* 类型的是:会被修饰为Pi

下面我们就可以回答这个问题了:

为什么C语言不支持函数重载,而C++支持函数重载呢?

因为在链接阶段

C语言文件的函数地址是完全只通过函数名去进行查找的,如果存在同名函数,会导致找到多个函数地址,让函数调用产生歧义

而C++文件的函数名经过了修饰,那么如果两个函数名相同,但是参数不同就可以进行区分了

所以对于构成重载的函数,只会找到那唯一一个,不会产生歧义

Linux下函数名修饰规则:

_Z4func(_Z是前缀+函数名的字符个数+函数名+参数首字母)

注意:不同编译器下的函数名修饰规则是不同的

那么为什么我刚才不在windows下演示呢?

因为windows下的函数名修饰太过复杂

我们把Func.cpp中的代码注释掉,让它发生链接错误

看到蓝色标注的地方,windows下也是一个类型对应一个符号,不过不像Linux下那么直观

以上就是C++入门1的全部内容,希望能对大家有所帮助

相关文章
|
2月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
47 2
C++入门12——详解多态1
|
2月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
38 3
|
2月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
43 2
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
87 1
|
2月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
81 1
|
2月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
29 1
|
2月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
46 1
|
2月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
59 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
25 0
|
2月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
36 0