目录
缺省参数
基本概念:在调用函数时,如果没有指定实参则采用该形参的缺省值(默认值),否则使用指定的实参
#include <iostream>
using std::cout;
using std::endl;
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(10);
Func();
return 0;
}
注意事项:不允许跳跃式的传递参数
#include <iostream>
using std::cout;
using std::endl;
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func(1,2,3);
Func(1,2);
Func(1);
Func();
Func(, 1, ); //wrong
Func(, , 2); //wrong
Func(, 1, 2); //wrong
return 0;
}
缺省参数的分类
全缺省参数
特点:所有缺省参数都被赋值
void Func(int a = 10, int b = 20, int c = 30)
半缺省参数
特点:不是所有缺省参数都被赋值
void Func(int a, int b = 20, int c = 30)
void Func(int a, int b = 20, int c = 30); //right
void Func(int a = 20, int b = 30, int c); //wrong
2、源文件和头文件分开的情况下,缺省参数不能在函数声明和定义中同时出现,否则会起冲突
//a.h文件
void Func(int a = 10);
// a.cpp文件
void Func(int a = 20)
{
......
}
//wrong!!
3、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;
}
C语言的函数查找机制
问题:为什么C++支持函数重载,而C语言不支持函数重载?
解释:请查看下面的代码分析过程
Stack.h文件
#pragma once #include<stdlib.h> struct Stack { int* a; int size; int capacity; //... }; void StackInit(struct Stack* ps, int n = 4); void StackPush(struct Stack* ps, int x);
Stack.cpp文件
#include "Stack.h" void StackInit(struct Stack* ps, int n) { ps->a = (int*)malloc(sizeof(int) * n); } void StackPush(struct Stack* ps, int x) {}
Test.cpp文件
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include"Stack.h" using namespace std; int main() { struct Stack st1; // 1、确定要插入100个数据 StackInit(&st1, 100); // call StackInit(?) // 2、只插入10个数据 struct Stack st2; StackInit(&st2, 10); // call StackInit(?) // 3、不知道要插入多少个 struct Stack st3; StackInit(&st3); return 0; }
预处理阶段:Stack.h会在两个.cpp文件中展开,并形成两个新文件Stack.i和Test.i
编译阶段:确保Stack.i和Test.i无语法和逻辑错误后,会将它们生成由汇编指令组成的文件Stack.s和Test.s,其中StaclInit(&st1,100)对应的汇编指令是“call StaclInit(0A01357h)
这是因为函数是一系列功能代码的集合,所以从底层的角度来讲函数就是这些功能代码转变成的汇编指令的集合,函数地址就是集合中第一句汇编指令的地址(有点像数组),继续执行反汇编指令就会进入到函数真正所在的地址0A01770h处
结论1:从底层上讲函数的地址就是函数中第一条汇编指令的地址
结论2:函数声明不能得到函数的地址(因为内部无指令),函数定义可以得到函数的地址(因为有内部指令)(所以Stack.s文件有StaclInit函数的地址,而Test.s文件没有)
汇编阶段:汇编器将Stack.s和Test.s转换为两个由机器码组成的Stack.o和Test.o文件
链接阶段:尝试连接两个.o文件,但因为Test.o文件只有StackInit函数的声明,所以链接器就会依据StackInit的函数名去字符表(在编译阶段所有的变量、函数、以及其它标志符号的信息都会被统计进字符表中)中寻找它定义的地址(字符表会根据函数名告诉链接器该函数的地址)(如果当前项目中的所有文件都没有StackInit函数的定义,就会提示链接错误 ,这就是函数只声明不定义产生的常见链接错误)
结论3:对编译器而言函数声明相当于承诺,函数定义相当于兑现承诺
最终解释:如果存在多个重名函数,而C语言链接器在字符表中只是简单的依据函数名去找函数地址时,就无法判断函数地址谁是谁的
C++的函数名修饰规则
基本概念:也称为符号修饰,是编译器用来处理函数重载和其他C++语言特性的机制。由于C++支持函数重载和模板等特性,不同的函数可能会使用相同的名字,但有不同的参数类型或数量。为了在链接过程中区分这些不同的函数,编译器对函数名进行修饰,生成一个唯一的符号
实现方法:
- 函数名:原始函数名被保留,作为符号的一部分
- 参数类型:每个参数的类型都会编码到符号中
- 命名空间或类名:如果函数位于某个命名空间或类中,命名空间或类名也会被编码到符号中
- 返回类型:对于非成员函数,返回类型通常不包含在符号中(因为函数重载不依赖函数的返回类型),但对于某些特定情况(如模板函数,这里不做解释了,了解即可),返回类型可能会包含在修饰符号中
#include <iostream>
namespace MyNamespace
{
class MyClass
{
public:
void foo(int);
void foo(double);
};
}
int main()
{
MyNamespace::MyClass obj;
obj.foo(5);
obj.foo(3.14);
return 0;
}
编译后,使用GCC中的 nm
工具(通常用于查看目标文件中的符号表)可以看到生成的修饰符号:
$ nm main.o
0000000000000000 T _ZN11MyNamespace7MyClass3fooEi
0000000000000000 T _ZN11MyNamespace7MyClass3fooEd
-
_ZN11MyNamespace7MyClass3fooEi
:_Z
: 名字修饰的前缀,表示函数名修饰开始N11MyNamespace
: 表示命名空间MyNamespace
(N
后的数字 11 表示MyNamespace
有 11 个字符)7MyClass
: 表示类MyClass
(7
表示类名有 7 个字符)3foo
: 表示函数foo
(3
表示函数名有 3 个字符)i
: 表示参数类型是int
-
_ZN11MyNamespace7MyClass3fooEd
:- 这一部分与上面类似,只是
d
表示参数类型是double
- 这一部分与上面类似,只是
~over~