目录
- 函数重载
- 运算符重载
- C++运算符重载范围对照表
- 注意事项
- 运算符重载语法
- 全局运算符重载
- 类内运算符重载
- 下面以一个一元运算符为例,介绍特性1:
- 下面介绍特性3:(必须类内重载的运算符)
函数重载
函数重载是指同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。
函数重载的要点:
- 同一作用域的同名函数。
也就是说,如果两个同名的函数不在同一个作用域那就不算重载,具体调用在哪个域就使用哪个函数。 - 形式参数,也就是传入的参数。个数、类型或者顺序必须不同,编译器会自动根据传入的参数判断使用的是哪个函数。
下面举例体会:
#include<iostream>
using namespace std;namespace myspace{void myfunction(int){cout<<"Hello from myspace"<<endl;}void myfunction(double){cout<<"Hello from myspace double"<<endl;}
}void myfunction(float)
{cout<<"Hello from global namespace"<<endl;
} int main()
{myspace::myfunction(10); //输出Hello from myspace,匹配到myspace::myfunction(int)myspace::myfunction(3.14); ///输出Hello from myspace double,匹配到myspace::myfunction(double)myfunction(1.23); //输出Hello from global namespace,匹配到myfunction(float)return 0;
}
运行后输出的结果如下:
两个在同一个命名空间的函数myfunction,根据其传入值的类型来决定实际调用哪个。并且当不同命名空间的myfunction调用时,总是选择最近的一个(全局)。符合预期的结果。
函数重载的好处显而易见,
可以极大程度地避免冗余的函数命名
,例如可以add_int和add_double之类的函数合并为一个同名的add,优化代码复用与维护。
运算符重载
运算符重载最经典的一个例子就是iostream中的cin与cout重载的<<与>>
。原本>> or <<
只是用来作移位操作的,这里被重载为输入与输出的功能。
运算符重载(Operator Overloading)是面向对象编程中的核心特性,允许对已有运算符赋予新的功能以适应自定义数据类型。其本质是通过定义特定函数,改变运算符在处理类或结构体时的行为逻辑,使得对象操作更贴近内置类型的使用方式。简单来说就是对已有的运算符进行函数重载,赋予新的功能可以重载的运算符有下面:
C++运算符重载范围对照表
运算符类型 | 可重载运算符 | 不可重载运算符 |
---|---|---|
算术运算符 | + - * / % ++ -- | |
关系运算符 | == != < > <= >= | |
逻辑运算符 | && || ! | |
位运算符 | & | ^ ~ << >> | |
赋值运算符 | = += -= *= /= %= &= |= ^= <<= >>= | |
其他运算符 | [] () -> , new delete new[] delete[] | :: .* . ?: sizeof typeid |
特殊说明 | 流运算符<< >> 必须全局重载 | 成员访问符. 等禁止重载 |
注意事项
- 不能改变运算符优先级和结合性
- 至少一个操作数为自定义类型
- 保持运算符原有语义
运算符重载语法
运算符重载有着严格的语法规定:
返回类型 operator运算符符号(参数列表) { // 实现运算符逻辑 }
并且重载之后遵循原运算符的计算顺序,例如My_class operator+(My_class c1,My_class c2)
调用时应该为My_class num3 = num1 + num2
。
全局运算符重载
百分之九十以上的运算符重载都是在类内进行的,即使是在类外的全局运算符重载函数,其输入参数也必须包含一个类对象。
例如 int operator+( int a, int b)
这样是不允许的。因为C++标准明确规定:运算符重载至少有一个参数必须是类类型、枚举类型或对它们的引用,不能重载基本类型(如int)的运算符。
改成下面这样可以编译通过:(string是一个类)
#include<iostream>
#include<string>
using namespace std;void operator+(string a, int b)
{cout << "hello" << endl;
}int main()
{string s = "h";int i = 10;s + i; //操作符重载return 0;
}
输出为:
当然了,全局运算符重载的作用可不是像上面这样用来搞怪的。前面说到了,运算符重载的输入参数必须有一个是类类型,枚举类型或对它们的引用,对于类内运算符重载来说,类内运算符重载默认第一个参数(左参数)是类本身指针(this)
因此其可以忽略传入第一个参数的声明。
对于全局运算符重载来说全局运算符重载默认第一个参数(左参数)可以是任意类类型,枚举类型或对它们的引用
,并且需显式声明所有操作数参数(也就是说全局重载时,operate不能省略第一个传入的参数。)流运算符的重载必须是全局运算符重载
。
下面给正经示例:
// 全局重载+,混合内置类型和类类型
Vector operator*(const Vector& v, double scalar) {return Vector(v.x * scalar, v.y * scalar);
}
// 调用方式
Vector v1 = v * 2.5; // 合法
友元函数与流运算
#include <iostream>
using namespace std;class Vector {
private:double x, y;
public:Vector(double x=0, double y=0) : x(x), y(y) {}// 声明友元全局运算符friend Vector operator+(const Vector& v1, const Vector& v2);friend ostream& operator<<(ostream& os, const Vector& v);
};// 实现友元运算符+
Vector operator+(const Vector& v1, const Vector& v2) {return Vector(v1.x + v2.x, v1.y + v2.y);
}// 实现友元流输出运算符
ostream& operator<<(ostream& os, const Vector& v) {return os << "(" << v.x << ", " << v.y << ")";
}int main() {Vector a(1, 2), b(3, 4);cout << "a + b = " << a + b << endl;return 0;
}
上面的全局运算符重载定义了两个友元函数:
- + 号的重载。
- 流运算符 <<的重载。
定义成友元函数是因为两个全局运算符重载函数不属于类的成员函数,但它们都要访问类的私有成员。因此需要在类内声明它们为类的友元函数。
类内运算符重载
类内运算符重载有这么几个核心特性:
- 隐式调用this指针。类内重载的运算符函数默认以this作为左操作数(左操作数也必须是当前对象),因此二元运算符只需显式声明一个参数(右操作数)。例如operator+(this,b) == operator+(b),this被省略。
理论上一元运算符,如++,--之类的类内重载都不需要显式声明形参,但有时候为了区分运算的顺序,例如a++和++a,需要传入伪参数。
- operator作为成员函数,可直接访问类的私有成员,无需友元声明。
- 必须为类内重载的运算符:=(赋值)、[](下标)、()(函数调用)、->(成员访问)。
下面以一个一元运算符为例,介绍特性1:
#include<iostream>
using namespace std;class Counter {
public:int count;Counter(int n=0) : count(n) {}// 前缀++(返回引用,避免拷贝)Counter& operator++() {++count;return *this;}// 后缀++(int参数区分)Counter operator++(int) {Counter temp = *this;++count;return temp;}
};int main()
{Counter c;cout << c.count << endl; // 0cout << ++c.count << endl; // 1cout << c.count++ << endl; // 1cout << c.count << endl; // 2return 0;
}
下面介绍特性3:(必须类内重载的运算符)
- 赋值运算符=
用于对象间的深拷贝,需处理自赋值问题。
class MyClass {
public:MyClass& operator=(const MyClass& rhs);
};
正常来说,如果没有自定义赋值运算符编译器会默认进行浅拷贝,如下:
class BadClass {int* data;
public:BadClass() : data(new int[100]) {}~BadClass() { delete[] data; }
};
BadClass b1, b2;
b2 = b1; // 危险:两个对象指向同一内存
什么是浅拷贝?
在C++中,当类包含动态分配的内存时,直接使用默认的赋值运算符会导致浅拷贝问题,前拷贝对于基本类型(如int、double)直接复制值,类类型成员调用其自身的赋值运算符,指针类型就仅复制地址(不复制指向的内容)
上面的代码进行浅拷贝后,对于b2.data的更改会同步到b1.data(浅拷贝使得二者地址相同)。更好的办法是下面这样:
class BadClass {int* data;
public:// ... 原有构造函数和析构函数 ...// 自定义赋值运算符BadClass& operator=(const BadClass& other) {if (this != &other) { // 防止自赋值delete[] data; // 释放原有内存data = new int[100]; //指向新地址std::copy(other.data, other.data + 100, data); // 值深拷贝}return *this;}
};
- 函数调用运算符()仿函数
仿函数就是让类的对象能像函数一样被调用。语法如下:
典型例子:
class Adder {int value;
public:Adder(int v) : value(v) {}int operator()(int x) { return x + value; }
};Adder add5(5);
cout << add5(10); // 输出15
对于int operator()(int x) { return x + value; }
,类内运算符重载函数可以访问类内的成员变量,因此value无需传入。
3. 下标运算符[]
提供类似数组的访问方式,通常返回引用以支持修改。
class Vector {
public:int& operator[](int index);
};
- 成员访问运算符->
常用于智能指针或迭代器实现。
class SmartPtr {
public:T* operator->() { return ptr; }
};