C++教程正在更新中,具体请查看教程目录
C++重载运算符和重载函数
C++的运算符重载在维基百科中的定义如下:
在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。这里,运算符(比如+,=或==)被当作多态函数,它们的行为随着其参数类型的不同而不同。运算符并不一定总是符号。
运算符重载通常只是一种语法糖。它可以简单地通过函数调用来模拟:
a + b * c
在一个支持运算符重载的语言里,上面的写法要比下面的写法有效而简练:
add(a, multiply(b, c))
(假设运算符* 的优先级高于运算符 +)当一种语言允许运算符在某种情况下被隐式调用的时候,运算符重载将不只提供写法上的方便。例如,Ruby中的to_s运算符就是如此,它返回一个对象的字符串表示。
C++中的函数重载在维基百科中的定义如下:
函数重载(英语:function overloading),是Ada、C++、C#、D和Java等编程语言中具有的一项特性,这项特性允许创建数项名称相同但输入输出类型或个数不同的子程序,它可以简单地称为一个单独功能可以执行多项任务的能力。
C++允许在同一个作用域中的某个函数或者运算符指定多个定义,这样的方式分别被称为函数重载和运算符重载。
重载声明是指的一个与之前已经在这个作用域内声明过的函数或者方法具有相同的名称的声明,但是他们的参数和定义并不完全相同。
当调用一个重载函数和重载运算发的时候,编译器会通过把使用的参数类型的定义中的参数类型进行比较,最终选定最合适的定义,选择最合适的重载函数或者重载运算符的过程,这样的过程成员为重载决策。
C++中的函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的参数(参数个数,参数类型,参数顺序)必须不同,同时不能仅仅通过返回类型的不同来重载函数。
接下来我们看一段函数重载的代码:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class printData {
public:
void print(int number) {
cout << number << "\n";
}
void print(double number) {
cout << number << "\n";
}
void print(string s) {
cout << s << "\n";
}
};
int main() {
printData test;
test.print(1);
test.print(1.1);
test.print("abcde");
return 0;
}
通过上面的代码,即使函数名相同,单数传入的参数类型不同,那么程序调用的函数也不相同,程序的执行结果也不相同,这就是C++中的函数名重载。
C++中的运算符重载
在C++中额可以重载或重定义大多数的内置的运算符,这样我们就可以使用自定义类型的运算符。
重载运算符的函数通常是带有特殊函数名的函数,函数是由关键字operator和后面的运算符符合组成的,和普通函数相同,重载的运算符有一个返回类型和参数列表。
形式如下:
Box operator + (const Box&);
重载加法运算是用于将两个Box类型的对象进行相加并最终返回Box类型的对象,大多数的重载运算符可以被定义为普通的非成员函数或者类内的成员函数,如果定义的函数是类的非成员函数,那么我们需要在每次执行函数的时候向函数内传递两个参数,形式如下所示:
Box operator + (const Box&, const Box&);
下面的代码通过使用成员函数进行了运算符重载,此时,对象作为参数进行传递,代码如下所示:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Box {
private:
int length, breadth, height;
public:
int get_volume() {
return length * breadth * height;
}
void set_length(int l) {
length = l;
}
void set_breadth(int b) {
breadth = b;
}
void set_height(int h) {
height = h;
}
Box operator + (Box& add_box) {
Box box;
box.length = add_box.length + length, box.breadth = add_box.breadth + breadth, box.height = add_box.height + height;
return box;
}
};
int main() {
Box box1, box2, box3;
box1.set_length(1), box1.set_breadth(2), box1.set_height(3);
box2.set_length(4), box2.set_breadth(5), box2.set_height(6);
box3 = box1 + box2;
cout << "The volume of box1 is: " << box1.get_volume() << "\n";
cout << "The volume of box2 is: " << box2.get_volume() << "\n";
cout << "The volume of box3 is: " << box3.get_volume() << "\n";
return 0;
}
可重载运算符/不可重载运算符
下面列出C++中可重载和不可重载的运算符。
可重载运算符:
运算符类型 | 运算符名称 |
---|---|
算数运算符 | + - * / % |
关系运算符 | == != < > <= >= |
逻辑运算符 | || && ! |
单目运算符 | + - * & |
自增运算符 | ++ – |
位运算符 | | & ~ ^ << >> |
赋值运算符 | = += -= *= /= %= &= |= ^= <<= >>= |
空间申请和释放 | new delete new[] delete[] |
其他 | () -> , [] |
不可重载运算符:
运算符名称 | 运算符符号 |
---|---|
成员访问运算符 | . |
成员指针访问运算符 | .* ->* |
域运算符 | :: |
长度运算符 | sizeof |
条件运算符 | ?: |
预处理符号 | # |
下面逐个对可重载的运算符进行演示。
一元运算符重载
一元运算符指的是只对一个操作数进行操作,下面是一元运算符的实例。
递增运算符(++),递减运算符(–)
负号(-)
逻辑非运算符(!)
通常一元运算符出现在操作的对象的左边,下面演示重载负号(-)一元运算符:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Point {
private:
int x, y;
public:
Point(int x_x = 0, int y_y = 0) {
x = x_x, y = y_y;
}
Point operator - () {
x = -x, y = -y;
return *this;
}
void Show_Point() {
cout << "(" << x << "," << y << ")\n";
}
};
int main() {
int x, y;
cin >> x >> y;
Point p(x, y);
-p;
p.Show_Point();
return 0;
}
上面设计了一段求点关于原点对称的点的代码,重载了-运算符来直接对对象进行操作。
二元运算符重载
二元运算符主要需要两个参数,我们通常使用的加(+),减(-),乘(*),除(/)都是二元运算符。接下来我们尝试重载 + 运算符:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Box {
private:
int length, breadth, height;
public:
int get_volume() {
return length * breadth * height;
}
void set_length(int l) {
length = l;
}
void set_breadth(int b) {
breadth = b;
}
void set_height(int h) {
height = h;
}
Box operator + (Box& add_box) {
Box box;
box.length = add_box.length + length, box.breadth = add_box.breadth + breadth, box.height = add_box.height + height;
return box;
}
};
int main() {
Box box1, box2, box3;
box1.set_length(1), box1.set_breadth(2), box1.set_height(3);
box2.set_length(4), box2.set_breadth(5), box2.set_height(6);
box3 = box1 + box2;
cout << "The volume of box1 is: " << box1.get_volume() << "\n";
cout << "The volume of box2 is: " << box2.get_volume() << "\n";
cout << "The volume of box3 is: " << box3.get_volume() << "\n";
return 0;
}
接下来我们尝试将上面的代码进行改写,将运算符重载的函数以非成员函数的方式进行重载运算符:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Box {
private:
int length, breadth, height;
public:
int get_volume() {
return length * breadth * height;
}
void set_length(int l) {
length = l;
}
void set_breadth(int b) {
breadth = b;
}
void set_height(int h) {
height = h;
}
friend Box operator + (Box& add_box1, Box& add_box2);
};
Box operator + (Box& add_box1, Box &add_box2) {
Box box;
box.length = add_box1.length + add_box2.length, box.breadth = add_box1.breadth + add_box2.breadth, box.height = add_box1.height + add_box2.height;
return box;
}
int main() {
Box box1, box2, box3;
box1.set_length(1), box1.set_breadth(2), box1.set_height(3);
box2.set_length(4), box2.set_breadth(5), box2.set_height(6);
box3 = box1 + box2;
cout << "The volume of box1 is: " << box1.get_volume() << "\n";
cout << "The volume of box2 is: " << box2.get_volume() << "\n";
cout << "The volume of box3 is: " << box3.get_volume() << "\n";
return 0;
}
将重载函数作为非成员函数的时候,是将两个对象进行相加,并且由于函数是全局函数,那么需要传入的参数个数为2,同时当重载预算符的函数是全局函数的时候,需要在类中将这个函数声明为友元函数。
关系运算符重载
C++支持很多种关系运算符,这些关系运算符用来比较C++中的数据类型,我们同时也可以重载任何一个关系运算符,接下来我们尝试重载>,<,==关系运算符,从而实现一个成绩排名的小程序:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Student {
private:
string name, ID;
int score1, score2;
public:
Student(string n, string id, int number1, int number2) {
name = n, ID = id, score1 = number1, score2 = number2;
}
Student() {
}
bool operator > (Student& s) {
if (score1 + score2 > s.score1 + s.score2) {
return true;
}
else {
return false;
}
}
bool operator < (Student& s) {
if (score1 + score2 < s.score1 + s.score2) {
return true;
}
else {
return false;
}
}
bool operator == (Student& s) {
if (score1 + score2 == s.score1 + s.score2) {
return true;
}
else {
return false;
}
}
};
int main() {
string name, ID;
int score1, score2;
cin >> name >> ID >> score1 >> score2;
Student Student1(name, ID, score1, score2);
cin >> name >> ID >> score1 >> score2;
Student Student2(name, ID, score1, score2);
if (Student1 > Student2) {
cout << "Student1 is better than Student2";
}
else if (Student1 == Student2) {
cout << "Student1 is equal to Student2";
}
else if (Student1 < Student2) {
cout << "Student1 is worse than Student2";
}
return 0;
}
输入输出运算符重载
C++能够使用流提取运算符>>和流插入运算符<<来进行输入和输出内置的数据类型,我们也可以重载流提取和插入运算符来操作对象或者我们自定义的数据类型。
通常我们会将输入术后出的函数声明为类的友元函数,这样我们在使用的函数就不需要创建对象二直接调用函数。
下面我们将之前的对学生成绩的代码进行修改,添加了重载流提取运算符和流插入运算符的部分:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Student {
private:
string name, ID;
int score1, score2;
public:
Student(string n, string id, int number1, int number2) {
name = n, ID = id, score1 = number1, score2 = number2;
}
Student() {
}
bool operator > (Student& s) {
if (score1 + score2 > s.score1 + s.score2) {
return true;
}
else {
return false;
}
}
bool operator < (Student& s) {
if (score1 + score2 < s.score1 + s.score2) {
return true;
}
else {
return false;
}
}
bool operator == (Student& s) {
if (score1 + score2 == s.score1 + s.score2) {
return true;
}
else {
return false;
}
}
friend istream &operator >> (istream& input, Student &student) {
input >> student.name >> student.ID >> student.score1 >> student.score2;
return input;
}
friend ostream &operator << (ostream& output, Student &student) {
output << "Student:" << student.name << " ID:" << student.ID << " total score:" << student.score1 + student.score2 << "\n";
return output;
}
};
int main() {
string name, ID;
int score1, score2;
cin >> name >> ID >> score1 >> score2;
Student Student1(name, ID, score1, score2);
cin >> name >> ID >> score1 >> score2;
Student Student2(name, ID, score1, score2);
if (Student1 > Student2) {
cout << "Student1 is better than Student2\n";
}
else if (Student1 == Student2) {
cout << "Student1 is equal to Student2\n";
}
else if (Student1 < Student2) {
cout << "Student1 is worse than Student2\n";
}
cout << Student1 << Student2;
return 0;
}
上述代码我们将流插入和提取运算符进行重载,可以直接对对象中的成员变量进行输入和输出。
通常我们输入输出习惯上使用cin>>和cout<<,这样重载的时候需要使用友元函数来重载运算符,如果使用成员函数来重载运算符的时候将会出现d1<<cout这样不自然的代码。
++和–运算符重载
++和–运算符是C++中两个重要的一元运算符,下面的示例中以时间变化为例演示重载++运算符的前缀和后缀的两种用法:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Time {
private:
int hour, minute, second;
public:
Time() {
hour = 0, minute = 0, second = 0;
}
Time(int h, int m, int s) {
hour = h, minute = m, second = s;
}
friend ostream& operator << (ostream& output, Time T) {
output << "The time is " << T.hour << ":" << T.minute << ":" << T.second << "\n";
return output;
}
Time operator ++() {
++second;
if (second >= 60) {
++minute;
minute -= 60;
}
if (minute >= 60) {
++hour;
minute -= 60;
}
if (hour >= 24) {
hour = 0;
}
return *this;
}
Time operator ++(int) {
++second;
if (second >= 60) {
++minute;
minute -= 60;
}
if (minute >= 60) {
++hour;
minute -= 60;
}
if (hour >= 24) {
hour = 0;
}
return *this;
}
};
int main() {
int hour, minute, second;
cin >> hour >> minute >> second;
Time T(hour, minute, second);
++T;
cout << T;
T++;
cout << T;
return 0;
}
递增和递减通常是改变对象的状态,所以对递增和递减运算符的重载通常为成员函数。
重载递增递减一定要和指针的递增和递减进行区分,疑问这里的重载操作的是对象而不是指针(由于指针是内置类型,因此指针的递增和递减是无法被重载的),所以通常情况下的递增和递减操作的对象是对象内部的成员变量。
递增和递减运算符分为前置和后置两种情况,因为符号相同,因此给后置的版本增加一个int类型的形参,这个形参为0,在函数体中式用不到的,引入这个形参只是为了区分富豪的前置和后置。
赋值运算符重载
和其他运算符相同,C++中允许我们重载赋值运算符,用来创建宇哥对象,比如拷贝构造函数。下面我们演示对赋值运算符的重载:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Time {
private:
int hour, minute, second;
public:
Time() {
hour = 0, minute = 0, second = 0;
}
Time(int h, int m, int s) {
hour = h, minute = m, second = s;
}
friend ostream& operator << (ostream& output, Time T) {
output << "The time is " << T.hour << ":" << T.minute << ":" << T.second << "\n";
return output;
}
Time operator = (Time& T) {
hour = T.hour, minute = T.minute, second = T.second;
return *this;
}
};
int main() {
int hour, minute, second;
cin >> hour >> minute >> second;
Time T1(hour, minute, second);
Time T2;
T2 = T1;
cout << T1 << T2;
return 0;
}
上述代码通过将时间进行复制的操作演示了对赋值运算符的重载。
浅拷贝和隐式转换:通常情况我们使用隐式转换函数的时候十分谨慎,因为当我们不需要使用转换蛤属的时候,这个函数仍可能被调用执行,这些不正确的程序可能会出现一些意想不到的事情,而我们很难对此做出判断,同时浅拷贝存在指针悬空的问题,所以我们通常禁止隐式转换,在构造函数前面添加explicit关键字,通常情况下隐式转换的声明的意义不大,在实际情况中通常使用浅拷贝,不会调用隐式转换。
函数调用运算符重载
蛤属调用运算符可以被重载与类的对象,当我们重载()的时候,我们并不是调用了一种新的调用函数的方式,相反的这是创建了一个可以传递任意数目参数的运算符函数,下面我们通过求总秒数的例子来演示重载函数调用运算符:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Time {
private:
int hour, minute, second, total_second;
public:
Time() {
hour = 0, minute = 0, second = 0, total_second = 0;
}
Time(int h, int m, int s) {
hour = h, minute = m, second = s, total_second = 3600 * 24 * hour + 3600 * minute + second;
}
void show_total_second() {
cout << total_second;
}
Time operator () (int h, int m, int s) {
Time T;
T.total_second = 24 * 60 * 60 * h + 60 * 60 * m + s;
return T;
}
Time operator = (Time& T) {
this->total_second = T.total_second;
return *this;
}
};
int main() {
int hour, minute, second;
cin >> hour >> minute >> second;
Time T1;
Time T2 = T1(hour, minute, second);
T2.show_total_second();
return 0;
}
下标运算符[]重载
下标操作符[]通常用来访问数组元素,重载这个运算符可以增强操作数组的功能,下面的代码通过重载下标运算符来更方便得访问类中数组指定的元素:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Array {
private:
int number[10], posion;
public:
int &operator [] (int i) {
return number[i];
}
Array() {
posion = 0;
}
void set_value(int num) {
number[posion] = num, ++ posion;
}
};
int main() {
Array number;
for (R int i = 0; i < 10; ++i) {
int n;
cin >> n;
number.set_value(n);
}
for (R int i = 0; i < 10; ++i) {
cout << number[i] << " ";
}
return 0;
}
类成员访问运算符( -> )可以被重载,但它较为麻烦。它被定义用于为一个类赋予"指针"行为。运算符 -> 必须是一个成员函数。如果使用了 -> 运算符,返回类型必须是指针或者是类的对象。
运算符 -> 通常与指针引用运算符 * 结合使用,用于实现"智能指针"的功能。这些指针是行为与正常指针相似的对象,唯一不同的是,当通过指针访问对象时,它们会执行其他的任务。比如,当指针销毁时,或者当指针指向另一个对象时,会自动删除对象。
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
// 假设一个实际的类
class Obj {
static int i, j;
public:
void f() const { cout << i++ << endl; }
void g() const { cout << j++ << endl; }
};
// 静态成员定义
int Obj::i = 10;
int Obj::j = 12;
// 为上面的类实现一个容器
class ObjContainer {
vector<Obj*> a;
public:
void add(Obj* obj)
{
a.push_back(obj); // 调用向量的标准方法
}
friend class SmartPointer;
};
// 实现智能指针,用于访问类 Obj 的成员
class SmartPointer {
ObjContainer oc;
int index;
public:
SmartPointer(ObjContainer& objc)
{
oc = objc;
index = 0;
}
// 返回值表示列表结束
bool operator++() // 前缀版本
{
if(index >= oc.a.size() - 1) return false;
if(oc.a[++index] == 0) return false;
return true;
}
bool operator++(int) // 后缀版本
{
return operator++();
}
// 重载运算符 ->
Obj* operator->() const
{
if(!oc.a[index])
{
cout << "Zero value";
return (Obj*)0;
}
return oc.a[index];
}
};
int main() {
const int sz = 10;
Obj o[sz];
ObjContainer oc;
for(int i = 0; i < sz; i++)
{
oc.add(&o[i]);
}
SmartPointer sp(oc); // 创建一个迭代器
do {
sp->f(); // 智能指针调用
sp->g();
} while(sp++);
return 0;
}
一些注意的要点
运算符重载不可以改变语法结构;
运算符重载不可以改变操作数的个数;
运算符重载不可以改变运算的优先级;
运算符重载不可以改变结合性。
类重载、覆盖、重定义之间的区别:
重载指的是函数具有的不同的参数列表,而函数名相同的函数。重载要求参数列表必须不同,比如参数的类型不同、参数的个数不同、参数的顺序不同。如果仅仅是函数的返回值不同是没办法重载的,因为重载要求参数列表必须不同。(发生在同一个类里)
覆盖是存在类中,子类重写从基类继承过来的函数。被重写的函数不能是static的。必须是virtual的。但是函数名、返回值、参数列表都必须和基类相同(发生在基类和子类)
重定义也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。(发生在基类和子类)
this 指针的作用
this 指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向正在被该成员函数操作的那个对象。当对一个对象调用成员函数时,编译器先将对象的地址赋给 this 指针,然后调用成员函数,每次成员函数存取数据成员时由隐含使用 this 指针。