专业的编程技术博客社区

网站首页 > 博客文章 正文

C++重载函数和重载运算符

baijin 2025-01-15 10:41:30 博客文章 11 ℃ 0 评论

重载概念

C++可以在同一个作用域中对某个函数和运算符进行多个定义,这些分别成为函数重载和运算符重载。通俗地讲,就是同一个名称可以完成不同的功能,而编译器是通过参数类型进行匹配的。

在C++中,存在编译时的多态性与运行时的多态性。源程序->编译->链接->可执行文件,该过程中将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编(binding)。在编译过程中进行联编被称为静态联编(static binding);而在程序运行时才完成的联编被称为动态联编 (dynamic binding)

静态联编(static binding):指系统在编译时就决定实现某一动作,要求在编译时就知道调用函数的全部信息。静态联编支持的多态性成为编译时多态性(静态多态性),通过函数重载(包括运算符重载)和模板实现。

动态联编(dynamic binding):指系统在运行时动态实现某一动作。采用这种联编方式,一直要到程序运行时才能确定调用哪个函数。 动态联编支持的多态性成为运行时的多态性(动态多态性),通过虚函数实现。其优点是提供了更好的灵活性、问题抽象性和程序易维护性。

函数重载(Overroad)

2.1 函数重载概念

函数重载是指同一作用域内,可以定义一组具有相同函数名称不同参数列表的函数,这些函数被称为重载函数;虽然函数名称相同,但和不同参数搭配时执行的内容就不同。函数重载的判断标准为:1) 参数个数不同;2) 参数类型不同;3) 参数顺序不同。函数重载至少满足其中一个条件,但注意的是不能将函数返回值作为函数重载的判断标准。

When two or more different declarations are specified for a single name in the same scope, that name is said to overloaded. By extension, two declarations in the same scope that declare the same name but with different types are called overloaded declarations. Only function declarations can be overloaded; object and type declarations cannot be overloaded. ——摘自《ANSI C++ Standard. P290》

重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。举个例子:

#include <iostream>
using namespace std;
void Max(int a, int b)
{
	cout << "Max 1" << endl;
}
void Max(double a, double b)
{
	cout << "Max 2" << endl;
}
void Max(double a, double b, double c)
{
	cout << "Max 3" << endl;
}
int main()
{
	Max(3, 4);  //调用 void Max(int, int)
	Max(2.4, 6.0);  //调用 void Max(doubleA double)
	Max(1.2, 3.4, 5);  //调用 void Max(double, double, double)
	Max(1, 2, 3);  //调用 void Max(double, double, double)
	//Max(3, 1.5);  //出错:二义性
	return 0;
}

可以看出,编译器根据调用 Max 函数的语句所给的实参的个数和类型,可以找到完全匹配的函数。而最后一个Max(3, 1.5)编译会出错,因为两个实参一个是整型,一个是实数型。如果将整型自动转换成实数型,那么看来应该调用 void Max(double, double) 这个函数;可是如果将实数型去尾自动转换为整型,那么调用 void Max(int, int) 似乎也说得过去。

2.2 编译器调用重载函数的准则

  • 将所有同名函数作为候选者;
  • 尝试寻找可行的候选函数;
  • 精确匹配实参;
  • 通过默认参数能够匹配实参;
  • 通过默认类型转换匹配实参;
  • 匹配失败;
  • 最终寻找到的可行候选函数不唯一,则出现二义性,编译失败;
  • 无法匹配所有候选者,函数未定义,编译失败。

2.3 函数重载遇上函数默认参数

举个例子:

#include <iostream>
using namespace std;
int func(int a, int b, int c = 0)
{
	return a * b* c;
}
int func(int a, int b)
{
	return a + b;
}
int func(int a)
{
	return a;
}
int main()
{
	int c = 0;
	c = func(1, 2); // 存在二义性,调用失败,编译不能通过 
	printf("c = %d\n", c);
	printf("Press enter to continue ...");
	return 0;
}

其中func(1, 2)存在二义性,可以看成缺省函数,可以调用int func(int a, int b)函数,也可以调用int func(int a, int b, int c = 0)函数。

2.4 函数重载和函数指针结合

当使用重载函数名对函数指针进行赋值时,根据重载规则挑选与函数指针参数列表一致的候选者,严格匹配候选者的函数类型与函数指针的函数类型。举个例子:

#include <iostream>
using namespace std;
typedef int(*PFUNC)(int a); // int(int a)
int func(int x)
{
	return x;
}
int func(int a, int b)
{
	return a + b;
}
int func(const char* s)
{
	return strlen(s);
}
int main()
{
	int c = 0;
	PFUNC p = func;
	c = p(1);//调用int func(int x)
	printf("c = %d\n", c);
	printf("Press enter to continue ...");
	return 0;
}

2.5 引入函数重载的原因

  • 在C语言中没有重载机制,需要对具有不同参数类型或不同参数个数,但具有同一种功能的函数定义多个名称,对编程来说比较麻烦。
  • 类的构造函数跟类名相同。如果没有函数重载机制,要想实例化不同的对象,那是相当的麻烦!
  • 操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用,如+可用于连接字符串等!

2.6 C++是如何做到函数重载的

int max(int a,int b) 映射为_Z3maxii、double max(double a,double b) 映射为_Z3maxdd。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议(Overload Resolution)。不同的编译器有不同的重命名方式,同时函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。

2.7 编译器解析重载函数调用过程

编译器实现调用重载函数解析机制的时候,肯定是首先找出同名的一些候选函数,然后从候选函数中找出最符合的,如果找不到就报错。下面介绍一种重载函数解析的方法:编译器在对重载函数调用进行处理时,由语法分析、C++文法、符号表、抽象语法树交互处理,交互图大致如下:

这个四个解析步骤所如下:

  • 由匹配文法中的函数调用,获取函数名;
  • 获得函数各参数表达式类型;
  • 语法分析器查找重载函数,符号表内部经过重载解析返回最佳的函数;
  • 语法分析器创建抽象语法树,将符号表中存储的最佳函数绑定到抽象语法树上。

运算符重载

3.1 运算符重载概念

与函数重载类似,运算符也可以重载,如图所示是运算符重载的例子:

重载的运算符是带有特殊名称的函数,函数名是由关键operator和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。例如:

Box operator+(const Box&);

声明加法运算符用于把两个Box对象相加,返回最终的Box对象。大多数的重载运算符可被定义为普通的非成员函数或者被定义为类成员函数。如果定义上面的函数为类的非成员函数,那么需要为每次操作传递两个参数,例如:

Box operator+(const Box&, const Box&);

举个例子, 对象作为参数进行传递,对象的属性使用 this 运算符进行访问:

#include <iostream>
using namespace std;
class Box
{
public:
	double get_volume(void)
	{
		return length * width * height;
	}
	void set_param(double len, double wid, double hei)
	{
		length = len;
		width = wid;
		height = hei;
	}
	// 重载 + 运算符,用于把两个 Box 对象相加
	Box operator+(const Box& b)
	{
		Box box;
		box.length = this->length + b.length;
		box.width = this->width + b.width;
		box.height = this->height + b.height;
		return box;
	}
private:
	double length;      // 长度
	double width;		// 宽度
	double height;      // 高度
};
// 程序的主函数
int main()
{
	Box Box1;                // 声明 Box1,类型为 Box
	Box Box2;                // 声明 Box2,类型为 Box
	Box Box3;                // 声明 Box3,类型为 Box
	double volume = 0.0;     // 把体积存储在该变量中
	Box1.set_param(6.0, 7.0, 8.0);
	Box2.set_param(12.0, 13.0, 14.0);
	// Box1 的体积
	volume = Box1.get_volume();
	cout << "Volume of Box1 : " << volume << endl;
	// Box2 的体积
	volume = Box2.get_volume();
	cout << "Volume of Box2 : " << volume << endl;
	// 把两个对象相加,得到 Box3
	Box3 = Box1 + Box2;
	// Box3 的体积
	volume = Box3.get_volume();
	cout << "Volume of Box3 : " << volume << endl;
	return 0;
}

3.2 可重载运算符/不可重载运算符

3.3 运算符重载准则

  • 只能对已有运算符进行重载,不允许用户自己定义新的运算符。
  • 运算符重载是针对新类型数据的实际需要,不建议改变原运算符的含义。(例如将+运算符重载为进行减法运算)。
  • 运算符重载不能改变运算符的操作对象(即操作数)的个数。
  • 运算符重载不能改变运算符原有的优先级。
  • 运算符重载不能改变运算符原有的结合性。
  • 运算符重载函数的参数至少有一个是类对象(或其引用)。目的是防止用户修改用于标准类型数据的运算符性质。
  • 运算符重载函数可以是普通函数、类成员函数、类的友元函数。
  • 赋值运算符“="可以不必用户进行重载。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表