C++ 一元运算符重载
在C++中,运算符重载是一种强大的特性,它允许我们为自定义类型定义运算符的行为。一元运算符是只需要一个操作数的运算符,比如递增(++
)、递减(--
)、负号(-
)、逻辑非(!
)等。通过重载这些运算符,我们可以使自定义类型的使用方式更加直观和自然。
什么是一元运算符?
一元运算符是只对一个操作数进行操作的运算符。C++中常见的一元运算符包括:
- 前置递增(
++a
)和后置递增(a++
) - 前置递减(
--a
)和后置递减(a--
) - 负号(
-a
) - 逻辑非(
!a
) - 按位取反(
~a
) - 地址运算符(
&a
) - 解引用运算符(
*a
)
一元运算符重载的基本语法
一元运算符重载可以通过成员函数或友元函数来实现:
成员函数形式
class ClassName {
public:
// 前置递增运算符
ReturnType operator++();
// 后置递增运算符(注意int参数)
ReturnType operator++(int);
// 负号运算符
ReturnType operator-() const;
// 逻辑非运算符
ReturnType operator!() const;
// ...其他一元运算符
};
友元函数形式
class ClassName {
friend ReturnType operator++(ClassName& obj);
friend ReturnType operator++(ClassName& obj, int);
friend ReturnType operator-(const ClassName& obj);
friend ReturnType operator!(const ClassName& obj);
// ...其他一元运算符
};
// 在类外部定义
ReturnType operator++(ClassName& obj) { /*...*/ }
ReturnType operator++(ClassName& obj, int) { /*...*/ }
// ...
前置和后置递增/递减运算符
前置和后置递增/递减运算符的区别是非常重要的,它们在重载时也有不同的写法。
前置递增/递减运算符
前置运算符(如++a
)先增加值,然后返回增加后的引用。
// 成员函数形式
Type& operator++() {
// 先执行递增操作
// ...
// 返回自身引用
return *this;
}
后置递增/递减运算符
后置运算符(如a++
)先返回当前值的副本,然后再增加值。为了区分前置和后置版本,后置版本的函数参数列表中有一个额外的int
参数(编译器会自动传入0)。
// 成员函数形式
Type operator++(int) {
// 保存当前状态
Type old = *this;
// 执行递增操作
// ...
// 返回递增前的副本
return old;
}
为了性能考虑,前置递增/递减通常比后置版本更高效,因为后置版本需要创建一个临时对象。
实例:自定义整数类的一元运算符重载
让我们创建一个简单的整数包装类,并为它重载几种一元运算符:
#include <iostream>
class Integer {
private:
int value;
public:
Integer(int val = 0) : value(val) {}
// 获取值
int getValue() const { return value; }
// 前置递增
Integer& operator++() {
++value;
return *this;
}
// 后置递增
Integer operator++(int) {
Integer old = *this;
++value;
return old;
}
// 前置递减
Integer& operator--() {
--value;
return *this;
}
// 后置递减
Integer operator--(int) {
Integer old = *this;
--value;
return old;
}
// 负号运算符
Integer operator-() const {
return Integer(-value);
}
// 逻辑非运算符
bool operator!() const {
return (value == 0);
}
// 输出运算符(非一元运算符,为了方便输出)
friend std::ostream& operator<<(std::ostream& os, const Integer& obj) {
os << obj.value;
return os;
}
};
int main() {
Integer a(5);
std::cout << "原始值: " << a << std::endl;
// 前置递增
++a;
std::cout << "++a 后: " << a << std::endl;
// 后置递增
Integer b = a++;
std::cout << "a++ 后, a = " << a << ", b = " << b << std::endl;
// 负号运算符
Integer c = -a;
std::cout << "-a = " << c << std::endl;
// 逻辑非运算符
Integer d(0);
std::cout << "!a = " << !a << ", !d = " << !d << std::endl;
return 0;
}
输出结果:
原始值: 5
++a 后: 6
a++ 后, a = 7, b = 6
-a = -7
!a = 0, !d = 1
负号运算符重载
负号运算符通常应返回一个新对象,该对象表示原对象的相反值:
// 成员函数实现
Integer operator-() const {
return Integer(-value); // 返回一个新对象
}
// 友元函数实现
friend Integer operator-(const Integer& obj) {
return Integer(-obj.value);
}
逻辑非运算符重载
逻辑非运算符通常返回一个布尔值,表示对象是否为"假":
// 成员函数实现
bool operator!() const {
return (value == 0); // 如果值为0返回true
}
// 友元函数实现
friend bool operator!(const Integer& obj) {
return (obj.value == 0);
}
一元运算符重载的实际应用
智能指针
智能指针是一元运算符重载的典型应用,比如解引用运算符(*
)和箭头运算符(->
):
template <typename T>
class SmartPtr {
private:
T* ptr;
public:
SmartPtr(T* p = nullptr) : ptr(p) {}
~SmartPtr() { delete ptr; }
// 解引用运算符
T& operator*() const { return *ptr; }
// 箭头运算符
T* operator->() const { return ptr; }
};
使用示例:
struct Person {
void sayHello() { std::cout << "Hello!" << std::endl; }
};
int main() {
SmartPtr<Person> person(new Person());
// 使用解引用运算符
(*person).sayHello();
// 使用箭头运算符
person->sayHello();
return 0;
}
迭代器
迭代器通常重载一元运算符来提供类似于指针的功能:
template <typename T>
class MyContainer {
private:
// 容器实现...
public:
class Iterator {
private:
T* ptr;
public:
Iterator(T* p) : ptr(p) {}
// 前置递增
Iterator& operator++() {
++ptr;
return *this;
}
// 解引用
T& operator*() const {
return *ptr;
}
// 相等比较(非一元运算符,但迭代器需要用到)
bool operator==(const Iterator& other) const {
return ptr == other.ptr;
}
bool operator!=(const Iterator& other) const {
return ptr != other.ptr;
}
};
Iterator begin() { /* 实现 */ }
Iterator end() { /* 实现 */ }
};
一元运算符重载的注意事项
-
保持语义一致性:重载运算符时,应确保其行为与内置类型的预期行为一致。例如,前置
++
应立即修改对象并返回引用。 -
避免副作用:像
-
、!
这样的运算符不应修改对象本身,应返回一个新对象。 -
返回类型选择:
- 前置递增/递减通常返回引用(
Type&
) - 后置递增/递减通常返回值(
Type
) - 一元负号、取反等通常返回新对象
- 前置递增/递减通常返回引用(
-
不要重载
&
运算符:除非有特殊理由,一般不建议重载取地址运算符,因为这会使对象行为变得不可预测。 -
考虑const正确性:不修改对象状态的运算符(如
-
、!
)应声明为const
成员函数。
练习
-
创建一个表示角度的类,重载前置和后置递增/递减运算符,使其在0-359度范围内循环。
-
为复数类重载负号运算符,使其返回共轭复数。
-
实现一个日期类,重载前置和后置递增/递减运算符,使其能够实现日期的加减操作。
总结
一元运算符重载是C++中的重要特性,它让我们能够为自定义类型提供直观、自然的操作方式。通过本文我们学习了:
- 一元运算符重载的基本语法和形式
- 前置与后置递增/递减运算符的区别及实现
- 负号、逻辑非等其他一元运算符的重载
- 一元运算符重载的实际应用场景
- 运算符重载的最佳实践和注意事项
掌握一元运算符重载,将使您的C++类更加强大和易用,符合C++的习惯用法,也为您理解标准库中的复杂类型提供了基础。
进一步阅读
- C++运算符优先级和结合性
- 二元运算符重载
- 转换运算符重载
- 重载函数调用运算符和用户定义字面量