目录
4.缺省参数
4.1缺省参数的概念
4.2缺省参数分类
4.3声明和定义分离(声明使用缺省参数)
4.🐍声明和定义分离到链接
5.函数重载
5.1函数重载的概念
5.2可执行程序的形成步骤
5.3C++支持函数重载的原理—名字修饰(name Mangling)
4.缺省参数
4.1缺省参数的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。缺省参数又叫默认参数。
#include<iostream>
using namespace std;void Func(int a = 0)
{cout << a << endl;
}
int main()
{Func(); // 没有传参时,使用参数的默认值Func(10); // 传参时,使用指定的实参return 0;
}
4.2缺省参数分类
- 全缺省参数
- 半缺省参数
- 函数在给半缺省参数,必须是从右往左连续依次给出,不能间隔跳跃。(从第一个开始)
- 调用函数传参:必须从左到右连续传参,不能跳跃。(从第一个开始)
- 形式参数是实际参数的一份临时拷贝。
- 缺省参数不能在函数声明和定义中同时出现,若有声明只能在声明中出现。
- 缺省值必须是常量或者全局变量。
- C语言不支持(编译器不支持。
//全缺省参数
void Func(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;
}
//半缺省参数
void Func(int a, int b = 10, int c = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;
}
//给半缺省参数
#include<iostream>
using namespace std;
//半缺省参数
void Func2(int a, int b = 10, int c = 20)
void Func2(int a, int b , int c = 20)
void Func2(int a, int b, int c)
//❌void Func2(int a, int b = 10, int c)
//❌void Func2(int a=10, int b, int c = 20)
{cout << "a = " << a ;cout << "b = " << b ;cout << "c = " << c ;cout << endl;
}
//调用传参
#include<iostream>
using namespace std;
//全缺省参数
void Func1(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a ;cout << "b = " << b ;cout << "c = " << c ;cout << endl;
}
int main()
{Func1(1, 2, 3);Func1(1, 2);Func1(1);Func1();//Func1(, 2, );//❌return 0;
}
4.3声明和定义分离(声明使用缺省参数)
如果声明与定义位置同时出现缺省参数,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值❓
在声明处给缺省参数。因为.cpp在预处理阶段会展开头文件.h。会把.h的声明拷贝到.cpp里面。在后面编译阶段,检查语法也不会出错。
//a.hvoid Func(int a = 10);//a.cppvoid Func(int a = 20)
{///
}
// 注意:如果声明与定义位置同时有缺省参数,
//恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
4.🐍声明和定义分离到链接
tips:
从语法的角度:函数名就是函数的地址。
从底层的角度:函数调用的本质是call 函数(地址)(物理空间是连续的)
地址是函数的地址。函数底层也是一堆指令。也就是函数底层指令的第一条指令的地址。
CPU执行也是执行指令。
声明和定义分离,编译阶段检查语法,call Func(❓)里面是没有地址的,还没有链接。那编译阶段检查语法为什么不会报错。(前面预处理/编译/汇编阶段是各自走各自的)
- 编译阶段:语法检查(自定义类型/变量/函数 搜索出处)
- 在汇编阶段,编译器就只是搜索找到声明(承诺)
- 在链接阶段,形成了符号表。
- 编译器去符号表里搜索,找到函数定义(兑现承诺)
- 编译器把函数定义的地址放到 call Func(07FF7F71E12E4h)
- 符号表
//"a.cpp"
#include"a.h"
void Func(int a)
{cout << a << endl;
}//"a.h"
#pragma once
#include<iostream>
using namespace std;
void Func(int a =20);//test.cpp
#include"a.h"
int main()
{Func();Func(10);return 0;
}
在编译阶段没有找到声明:语法错误❌
在链接阶段没有找到定义:链接❌
5.函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。函数重载也就是一词多义。
比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”
5.1函数重载的概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
- C语言不允许同名函数
- 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;
}
5.2可执行程序的形成步骤
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
(前面缺省参数的声明和定义分离铺垫过了)
- 1. 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?
- 2. 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。
3.面对链接函数的地址到括号里:call 函数(函数地址)- C语言符号表:函数名 函数地址
- C++符号表的规则:函数名且包含函数参数类型等 函数地址(那么链接时,面对Add函数,链接器会使用哪个方式去符号表找呢?这里每个编译器都有自己的函数名修饰规则。只要能区分开即可)(下面细讲)
5.3C++支持函数重载的原理—名字修饰(name Mangling)
C语言不支持函数重载?C++如何支持函数重载?
>>>>>>>>> 和前面我们讲到的声明和定义分离到链接中链接步骤(符号表搜索函数地址)
>>>>>>>>>(在符号表中去搜索函数地址)这个步骤非常关键。
C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。
【C语言】
C语言在符号表中去搜索函数地址 >>>>只根据函数名搜索函数地址,当然C语言不支持函数重载。C语言链接时,直接用函数名去找地址,有同名函数,区分不开。
【C++】
祖师爷为了C++能够支持函数重载,于是把搜索规则改变了。C++在符号表去搜索函数地址,规则>>>>>>>>>>>>>>>>>>>函数名且包含函数参数类型等 函数地址,支持函数重载。
这里每个编译器都有自己的函数名修饰规则。函数名修饰规则,名字中引入参数类型,各个编译器有自己的实现一套。(下面从windows和Linux举例)
【windows下名字修饰规则】
【扩展学习:C/C++函数调用约定和名字修饰规则--有兴趣好奇的可以看看,里面
有对vs下函数名修饰规则讲解】C/C++ 函数调用约定___declspec(dllexport) void test2();-CSDN博客
#include<iostream>
using namespace std;
int Add(int left, int right);
double Add(double left, double right);
int main()
{Add(10, 20);Add(10.1, 20.2);
}
//可以去VS上只有声明没有定义,此时就会报链接错误❌
【Linux下名字修饰规则】
通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。
- 采用C语言编译器编译后结果
- 结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
- 采用C++编译器编译后结果
- 结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参
数类型信息添加到修改后的名字中。
#include<stdio.h> 2 int Add(int a,int b) 3 { 4 return a+b; 5 } 6 void func(int a,double b,int* p) 7 { 8 9 } 10 int main() 11 { 12 Add(1,2); 13 func(1,2,0); 14 return 0; 15 }
- 采用C语言编译器编译后结果
gcc -o projectC project.c
objdump -S projectC
- 采用C++编译器编译后结果
g++ -o proejctCPP project.cpp
objdump -S projectCPP(proejctCPP)
对比Linux会发现,windows下vs编译器对函数名字修饰规则相对复杂难懂,但道理都
是类似的,我们就不做细致的研究了。 🙂感谢大家的阅读,若有错误和不足,欢迎指正。