目录
一、运算符重载介绍
运算符重载是C++的一个特性,允许程序员将运算符的定义扩展到对象上,目的是提高代码的直观性和可读性。简单的运算符表达式通常比函数调用更简洁易懂。系统为基本数据类型提供了预定义的运算符,如整数加法。对于字符串拼接,通常使用函数如strcat
,但也可以通过重载“+”运算符来实现,例如szStrA = szStrA + szStrB
。
在C++中,除了条件运算符“?:”、指针分量运算符“->”、分量运算符“.”、范围解析运算符“::”和取大小运算符“sizeof”外,其他运算符都可以被重载。重载时必须保持原运算符的优先级、结合性、操作数个数和语法结构不变。例如,重载的“*”运算符优先级应高于“+”,且必须是双目运算。
运算符重载通过定义函数实现,本质上是函数的重载。编译器根据参数类型和个数选择合适的重载运算符。重载应符合使用习惯,易于理解。例如,在字符串类中,重载“+”运算符比“*”更直观。不能创造新的运算符,如不能定义“**”表示幂运算。
在C++中,运算符重载函数通常采用成员函数或友元函数的形式。
二、成员函数形式运算符重载
它是一种特殊的成员函数,其语法形式为:
<函数类型><类名>::operator<符号>(参数表){
//相对于该类而定义的操作(运算符重载函数体)
}
int ClassA::operator+(int nNumA, int nNumB){
//...
}
在C++编程中,成员函数形式的运算符重载是一种重要的技术手段。参数表清晰地列出了运算符所需的操作数。对于单目运算,参数表为空,此时调用该函数的对象即为唯一的操作数。而在双目运算中,参数表包含一个参数,调用该函数的对象作为第一操作数,参数表中的参数作为第二操作数。
运算符函数体对重载的运算符赋予了新的含义,这种解释仅适用于重载该运算符的类。在X类对象的上下文中,运算符的含义由函数体定义;一旦超出这个范围,运算符将恢复其系统预定义的含义。
这种重载机制不仅增强了运算符的灵活性,也使得代码更加直观和易于理解,从而提升了编程的效率和质量。
在C++中,成员函数形式的运算符重载允许类的对象使用特定的运算符,就像它们是基本数据类型一样。下面是一个简单的例子,展示了如何使用成员函数形式来重载加法运算符(+):
#include <iostream>
#include <string>
class MyString {
private:
std::string str;
public:
// 构造函数
MyString(const std::string& s = "") : str(s) {}
// 成员函数形式的运算符重载
MyString operator+(const MyString& other) {
return MyString(str + other.str);
}
// 输出字符串
void display() const {
std::cout << str << std::endl;
}
};
int main() {
MyString str1("Hello, ");
MyString str2("World!");
// 使用重载的加法运算符
MyString result = str1 + str2;
// 输出结果
result.display();
return 0;
}
在这个例子中,我们定义了一个名为MyString
的类,它包含一个std::string
类型的成员变量str
。我们重载了加法运算符(+),使得两个MyString
对象可以相加,结果是一个新的MyString
对象,其内容是两个字符串的连接。
在main
函数中,我们创建了两个MyString
对象str1
和str2
,并使用重载的加法运算符将它们相加,得到一个新的MyString
对象result
。最后,我们调用result
的display
方法来输出结果字符串。
这个例子展示了如何使用成员函数形式来重载运算符,使得类的对象可以像基本数据类型一样进行运算。
三、友元函数形式运算符重载
在C++中,友元函数形式的运算符重载允许非成员函数访问类的私有和保护成员。这种形式的运算符重载通常用于双目运算符,其中一个操作数是类的对象,另一个操作数是另一个类的对象或基本数据类型。下面是一个使用友元函数形式重载加法运算符(+)的例子:
#include <iostream>
#include <string>
class MyString {
private:
std::string str;
public:
// 构造函数
MyString(const std::string& s = "") : str(s) {}
// 声明友元函数
friend MyString operator+(const MyString& lhs, const MyString& rhs);
// 输出字符串
void display() const {
std::cout << str << std::endl;
}
};
// 友元函数形式的运算符重载
MyString operator+(const MyString& lhs, const MyString& rhs) {
return MyString(lhs.str + rhs.str);
}
int main() {
MyString str1("Hello, ");
MyString str2("World!");
// 使用重载的加法运算符
MyString result = str1 + str2;
// 输出结果
result.display();
return 0;
}
在这个例子中,我们定义了一个名为MyString
的类,它包含一个std::string
类型的成员变量str
。我们声明了一个友元函数operator+
,它接受两个MyString
对象作为参数,并返回一个新的MyString
对象,其内容是两个字符串的连接。
在main
函数中,我们创建了两个MyString
对象str1
和str2
,并使用重载的加法运算符将它们相加,得到一个新的MyString
对象result
。最后,我们调用result
的display
方法来输出结果字符串。
这个例子展示了如何使用友元函数形式来重载运算符,使得类的对象可以与其他对象或基本数据类型进行运算。
四、成员/友元函数形式的运算符重载总结
五、特殊符号的重载
在C++中,大多数运算符都可以被重载,这意味着你可以为自定义类型(如类和结构体)定义这些运算符的行为。然而,有一些运算符是不允许被重载的。下面列出了可以被重载和不可以被重载的运算符:
可以被重载的运算符:
-
算术运算符:
+
(加)、-
(减)、*
(乘)、/
(除)、%
(取模) -
关系运算符:
==
(等于)、!=
(不等于)、<
(小于)、>
(大于)、<=
(小于等于)、>=
(大于等于) -
逻辑运算符:
&&
(逻辑与)、||
(逻辑或)、!
(逻辑非)(注意:逻辑非只能通过成员函数重载) -
位运算符:
&
(按位与)、|
(按位或)、^
(按位异或)、~
(按位取反)、<<
(左移)、>>
(右移) -
赋值运算符:
=
(赋值)、+=
、-=
、*=
、/=
、%=
、&=
、|=
、^=
、<<=
、>>=
-
自增自减运算符:
++
(自增)、--
(自减) -
内存访问运算符:
new
、new[]
、delete
、delete[]
-
其他运算符:
()
(函数调用)、[]
(下标)、->
(成员访问)、,
(逗号)、->*
(成员指针访问)
不可以被重载的运算符:
-
成员访问运算符:
.
(成员访问) -
指针访问运算符:
.*
(成员指针访问) -
条件运算符:
?:
(条件) -
域解析运算符:
::
(域解析) -
大小运算符:
sizeof
(大小)
需要注意的是,即使运算符可以被重载,也应该遵循一些规则和准则,以保持代码的可读性和可维护性。例如,重载的运算符应该保持原有的操作数个数、优先级和结合性,以及应该符合常规的使用习惯。此外,重载运算符时应该避免创建不直观或容易引起混淆的行为。
重载"="例子:
#include<iostream>
using namespace std;
class CCounter {
public:
CCounter(int nNum) {
m_nValue = nNum;
}
CCounter(CCounter& obj) { m_nValue = obj.m_nValue; }
CCounter& operator=(CCounter obj) {
m_nValue = obj.m_nValue;
return *this;
}
int operator*() {
return m_nValue;
}
private:
int m_nValue;
};
int main() {
CCounter objA(5), objB(15);
objA = objB;
cout << *objA << endl;
return 0;
}
重载"[ ]"例子:
#include<iostream>
using namespace std;
class CCounter{
public:
CCounter() {
for (int i = 0; i < 15; i++) {
m_nValue[i] = i;
}
}
int operator[](int nCount) {
return m_nValue[nCount];
}
private:
int m_nValue[15];
};
int main() {
CCounter objA;
for (int i = 0; i < 15; i++) {
cout << objA[i] << ' ';
}
return 0;
}
重载 ">>" "<<"例子:
#include<iostream>
using namespace std;
class CCounter{
public:
CCounter() {
memset(m_szStr, 0, sizeof(m_szStr));
}
friend ostream& operator<<(ostream& output, CCounter& obj) {
output << obj.m_szStr;
return output;
}
friend istream& operator>>(istream& input, CCounter& obj) {
input.getline(obj.m_szStr, 15);
return input;
}
private:
char m_szStr[15];
};
int main() {
CCounter obj;
cin >> obj;
cout << obj;
return 0;
}
六、总结
运算符重载是C++的一项强大特性,它使得程序代码更加直观,便于理解和操作对象。然而,在重载运算符时,必须遵守一些基本原则:不能改变运算符的优先级和结合性,不能改变操作数的数量,也不能创造新的运算符。如果在类中没有显式定义拷贝构造函数和赋值运算符,编译器会提供默认的版本,但这些默认实现仅适用于简单的类对象。
运算符重载函数可以定义为成员函数或友元函数。调用这些函数可以是显式的,也可以是隐式的。在定义前增量和后增量运算符时,形参int
仅作为区分两者的标志,并无其他实际作用。拷贝构造函数通过已存在的对象创建一个同类型的新对象,而赋值运算符则将一个对象的成员变量复制到另一个已存在的同类对象的相应成员变量中。