跳到主要内容

C++ 一元运算符重载

在C++中,运算符重载是一种强大的特性,它允许我们为自定义类型定义运算符的行为。一元运算符是只需要一个操作数的运算符,比如递增(++)、递减(--)、负号(-)、逻辑非(!)等。通过重载这些运算符,我们可以使自定义类型的使用方式更加直观和自然。

什么是一元运算符?

一元运算符是只对一个操作数进行操作的运算符。C++中常见的一元运算符包括:

  • 前置递增(++a)和后置递增(a++)
  • 前置递减(--a)和后置递减(a--)
  • 负号(-a)
  • 逻辑非(!a)
  • 按位取反(~a)
  • 地址运算符(&a)
  • 解引用运算符(*a)

一元运算符重载的基本语法

一元运算符重载可以通过成员函数或友元函数来实现:

成员函数形式

cpp
class ClassName {
public:
// 前置递增运算符
ReturnType operator++();

// 后置递增运算符(注意int参数)
ReturnType operator++(int);

// 负号运算符
ReturnType operator-() const;

// 逻辑非运算符
ReturnType operator!() const;

// ...其他一元运算符
};

友元函数形式

cpp
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)先增加值,然后返回增加后的引用。

cpp
// 成员函数形式
Type& operator++() {
// 先执行递增操作
// ...

// 返回自身引用
return *this;
}

后置递增/递减运算符

后置运算符(如a++)先返回当前值的副本,然后再增加值。为了区分前置和后置版本,后置版本的函数参数列表中有一个额外的int参数(编译器会自动传入0)。

cpp
// 成员函数形式
Type operator++(int) {
// 保存当前状态
Type old = *this;

// 执行递增操作
// ...

// 返回递增前的副本
return old;
}
提示

为了性能考虑,前置递增/递减通常比后置版本更高效,因为后置版本需要创建一个临时对象。

实例:自定义整数类的一元运算符重载

让我们创建一个简单的整数包装类,并为它重载几种一元运算符:

cpp
#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

负号运算符重载

负号运算符通常应返回一个新对象,该对象表示原对象的相反值:

cpp
// 成员函数实现
Integer operator-() const {
return Integer(-value); // 返回一个新对象
}

// 友元函数实现
friend Integer operator-(const Integer& obj) {
return Integer(-obj.value);
}

逻辑非运算符重载

逻辑非运算符通常返回一个布尔值,表示对象是否为"假":

cpp
// 成员函数实现
bool operator!() const {
return (value == 0); // 如果值为0返回true
}

// 友元函数实现
friend bool operator!(const Integer& obj) {
return (obj.value == 0);
}

一元运算符重载的实际应用

智能指针

智能指针是一元运算符重载的典型应用,比如解引用运算符(*)和箭头运算符(->):

cpp
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; }
};

使用示例:

cpp
struct Person {
void sayHello() { std::cout << "Hello!" << std::endl; }
};

int main() {
SmartPtr<Person> person(new Person());

// 使用解引用运算符
(*person).sayHello();

// 使用箭头运算符
person->sayHello();

return 0;
}

迭代器

迭代器通常重载一元运算符来提供类似于指针的功能:

cpp
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() { /* 实现 */ }
};

一元运算符重载的注意事项

  1. 保持语义一致性:重载运算符时,应确保其行为与内置类型的预期行为一致。例如,前置++应立即修改对象并返回引用。

  2. 避免副作用:像-!这样的运算符不应修改对象本身,应返回一个新对象。

  3. 返回类型选择

    • 前置递增/递减通常返回引用(Type&
    • 后置递增/递减通常返回值(Type
    • 一元负号、取反等通常返回新对象
  4. 不要重载&运算符:除非有特殊理由,一般不建议重载取地址运算符,因为这会使对象行为变得不可预测。

  5. 考虑const正确性:不修改对象状态的运算符(如-!)应声明为const成员函数。

练习

  1. 创建一个表示角度的类,重载前置和后置递增/递减运算符,使其在0-359度范围内循环。

  2. 为复数类重载负号运算符,使其返回共轭复数。

  3. 实现一个日期类,重载前置和后置递增/递减运算符,使其能够实现日期的加减操作。

总结

一元运算符重载是C++中的重要特性,它让我们能够为自定义类型提供直观、自然的操作方式。通过本文我们学习了:

  • 一元运算符重载的基本语法和形式
  • 前置与后置递增/递减运算符的区别及实现
  • 负号、逻辑非等其他一元运算符的重载
  • 一元运算符重载的实际应用场景
  • 运算符重载的最佳实践和注意事项

掌握一元运算符重载,将使您的C++类更加强大和易用,符合C++的习惯用法,也为您理解标准库中的复杂类型提供了基础。

进一步阅读

  • C++运算符优先级和结合性
  • 二元运算符重载
  • 转换运算符重载
  • 重载函数调用运算符和用户定义字面量