C++的缺省参数、函数重载以及C语言的函数查找机制、C++的函数名修饰规则(重要)

目录

缺省参数

缺省参数的分类

全缺省参数

半缺省参数

函数重载

C语言的函数查找机制 

C++的函数名修饰规则


缺省参数

基本概念:在调用函数时,如果没有指定实参则采用该形参的缺省值(默认值),否则使用指定的实参

#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)
注意事项:
1、半缺省参数必须从右往左依次来给出,不能间隔着给,否则出错
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: 表示命名空间 MyNamespaceN 后的数字 11 表示 MyNamespace 有 11 个字符)
    • 7MyClass: 表示类 MyClass7 表示类名有 7 个字符)
    • 3foo: 表示函数 foo3 表示函数名有 3 个字符)
    • i: 表示参数类型是 int
  • _ZN11MyNamespace7MyClass3fooEd

    • 这一部分与上面类似,只是 d 表示参数类型是 double

~over~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值