C++学习:六个月从基础到就业——面向对象编程:重载运算符(下)
本文是我C++学习之旅系列的第十三篇技术文章,是面向对象编程中运算符重载主题的下篇。本篇文章将继续深入探讨高级运算符重载技术、特殊运算符、常见应用场景和最佳实践。查看完整系列目录了解更多内容。
引言
在上一篇文章中,我们介绍了C++运算符重载的基本概念、语法和常见运算符(如算术、赋值、关系和流运算符等)的重载方法。本篇文章将继续探讨更多高级主题,包括类型转换运算符、内存管理运算符的重载、自定义字面量以及在实际项目中的应用案例。
运算符重载是C++中一个强大而灵活的特性,当合理使用时,可以显著提高代码的可读性和表达能力。然而,过度或不当的使用也可能导致代码难以理解和维护。通过这篇文章,我们将学习如何有效且恰当地利用这一特性,创建直观、安全且高效的代码。
类型转换运算符
类型转换运算符允许我们定义如何将自定义类型转换为其他类型。这些运算符不指定返回类型,因为返回类型已由运算符名称决定。
基本语法
class MyClass {
public:
// 类型转换运算符语法
operator TargetType() const;
};
示例:分数类到浮点数的转换
class Fraction {
private:
int numerator;
int denominator;
public:
Fraction(int num = 0, int denom = 1) : numerator(num), denominator(denom) {
if (denominator == 0) throw std::invalid_argument("Denominator cannot be zero");
normalize();
}
// 分数转换为double
operator double() const {
return static_cast<double>(numerator) / denominator;
}
// 显示分数
void display() const {
std::cout << numerator << "/" << denominator;
}
private:
// 规范化分数(约分)
void normalize() {
if (denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
int gcd = findGCD(std::abs(numerator), denominator);
numerator /= gcd;
denominator /= gcd;
}
// 求最大公约数
int findGCD(int a, int b) const {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
};
int main() {
Fraction f(3, 4); // 3/4
double d = f; // 隐式转换:调用operator double()
std::cout << "Fraction: ";
f.display();
std::cout << " as double: " << d << std::endl;
// 在表达式中使用
double result = f * 2.0; // f被转换为double,然后乘以2.0
std::cout << "f * 2.0 = " << result << std::endl;
return 0;
}
显式转换运算符
C++11引入了explicit
关键字用于转换运算符,以防止隐式类型转换可能导致的意外行为:
class Fraction {
// ...前面的代码...
// 显式转换为double,防止意外的隐式转换
explicit operator double() const {
return static_cast<double>(numerator) / denominator;
}
};
int main() {
Fraction f(3, 4);
// double d = f; // 错误:不允许隐式转换
double d = static_cast<double>(f); // 正确:显式转换
// if (f) { ... } // 错误:不允许boolean上下文的隐式转换
if (static_cast<double>(f)) { /* ... */ } // 正确
return 0;
}
布尔转换运算符
转换为bool
类型的运算符特别常见,用于条件判断中:
class SmartPointer {
private:
int* ptr;
public:
explicit SmartPointer(int* p = nullptr) : ptr(p) {}
~SmartPointer() { delete ptr; }
// 布尔转换运算符
explicit operator bool() const {
return ptr != nullptr;
}
int& operator*() const {
if (!ptr) throw std::runtime_error("Null pointer dereference");
return *ptr;
}
};
int main() {
SmartPointer p1(new int(42));
SmartPointer p2;
if (p1) { // 使用bool转换运算符
std::cout << "p1 is valid, value: " << *p1 << std::endl;
}
if (!p2) { // 使用bool转换运算符
std::cout << "p2 is null" << std::endl;
}
return 0;
}
内存管理运算符重载
new和delete运算符
C++允许重载new
和delete
运算符,以实现自定义的内存分配和释放策略:
class MemoryTracker {
private:
static size_t totalAllocated;
static size_t totalFreed;
public:
// 重载全局new
void* operator new(size_t size) {
totalAllocated += size;
std::cout << "Allocating " << size << " bytes, total: "
<< totalAllocated << std::endl;
return ::operator new(size); // 调用全局的new
}
// 重载全局delete
void operator delete(void* ptr) noexcept {
::operator delete(ptr); // 调用全局的delete
std::cout << "Memory freed" << std::endl;
}
// 重载数组new
void* operator new[](size_t size) {
totalAllocated += size;
std::cout << "Allocating array of " << size << " bytes, total: "
<< totalAllocated << std::endl;
return ::operator new[](size);
}
// 重载数组delete
void operator delete[](void* ptr) noexcept {
::operator delete[](ptr);
std::cout << "Array memory freed" << std::endl;
}
// 显示内存使用统计
static void showStats() {
std::cout << "Total allocated: " << totalAllocated << " bytes" << std::endl;
std::cout << "Total freed: " << totalFreed << " bytes" << std::endl;
}
};
// 初始化静态成员
size_t MemoryTracker::totalAllocated = 0;
size_t MemoryTracker::totalFreed = 0;
int main() {
MemoryTracker* obj = new MemoryTracker();
delete obj;
MemoryTracker* arr = new MemoryTracker[5];
delete[] arr;
MemoryTracker::showStats();
return 0;
}
带placement参数的new运算符
Placement new是一种特殊形式的new
运算符,允许在预先分配的内存中构造对象:
class PlacementExample {
private:
int data;
public:
PlacementExample(int d) : data(d) {
std::cout << "Constructor called with data = " << data << std::endl;
}
~PlacementExample() {
std::cout << "Destructor called for data = " << data << std::endl;
}
// 显示数据
void display() const {
std::cout << "Data: " << data << std::endl;
}
// 自定义placement new
void* operator new(size_t size, char* buffer, const char* name) {
std::cout << "Placement new called with name: " << name << std::endl;
return buffer;
}
// 匹配的placement delete(仅当构造函数抛出异常时调用)
void operator delete(void* ptr, char* buffer, const char* name) {
std::cout << "Placement delete called with name: " << name << std::endl;
}
};
int main() {
// 预分配内存
char buffer[sizeof(PlacementExample)];
// 使用placement new在buffer中构造对象
PlacementExample* obj = new(buffer, "MyObject") PlacementExample(42);
obj->display();
// 显式调用析构函数(不要使用delete,因为内存不是通过new分配的)
obj->~PlacementExample();
return 0;
}
高级运算符重载技术
自定义字面量(C++11)
C++11引入了用户定义字面量,允许为自定义类型创建特殊的字面量语法:
#include <iostream>
#include <string>
#include <complex>
// 距离字面量
class Distance {
private:
double meters;
public:
explicit Distance(double m) : meters(m) {}
double getMeters() const { return meters; }
double getKilometers() const { return meters / 1000.0; }
double getMiles() const { return meters / 1609.344; }
};
// 用户自定义字面量
Distance operator"" _km(long double km) {
return Distance(static_cast<double>(km * 1000.0));
}
Distance operator"" _m(long double m) {
return Distance(static_cast<double>(m));
}
Distance operator"" _mi(long double miles) {
return Distance(static_cast<double>(miles * 1609.344));
}
// 字符串字面量
std::string operator"" _s(const char* str, size_t len) {
return std::string(str, len);
}
// 复数字面量(使用标准库复数类)
std::complex<double> operator"" _i(long double val) {
return std::complex<double>(0.0, static_cast<double>(val));
}
int main() {
auto dist1 = 5.0_km;
auto dist2 = 100.0_m;
auto dist3 = 3.5_mi;
std::cout << "Distance in kilometers: " << dist1.getKilometers() << " km" << std::endl;
std::cout << "Distance in meters: " << dist2.getMeters() << " m" << std::endl;
std::cout << "Distance in miles: " << dist3.getMiles() << " mi" << std::endl;
std::cout << "Distance in meters: " << dist3.getMeters() << " m" << std::endl;
auto name = "John Doe"_s;
std::cout << "Name: " << name << ", length: " << name.length() << std::endl;
auto c = 2.0 + 3.0_i;
std::cout << "Complex number: " << c << std::endl;
std::cout << "Real part: " << c.real() << ", Imaginary part: " << c.imag() << std::endl;
return 0;
}
重载逗号运算符
逗号运算符可以重载,但在实际应用中很少使用,因为它可能导致代码难以理解:
class Sequence {
private:
std::vector<int> data;
public:
// 重载逗号运算符以创建序列
Sequence& operator,(int value) {
data.push_back(value);
return *this;
}
// 显示序列内容
void display() const {
std::cout << "Sequence: ";
for (int val : data) {
std::cout << val << " ";
}
std::cout << std::endl;
}
};
int main() {
Sequence seq;
// 使用逗号运算符构建序列
seq, 1, 2, 3, 4, 5;
seq.display(); // 输出: Sequence: 1 2 3 4 5
return 0;
}
虽然这是可行的,但这种用法不直观且可能令人困惑,通常应避免重载逗号运算符。
智能引用(代理类)模式
有时我们需要在访问某个元素时执行额外的逻辑。这可以通过返回一个"代理"对象而不是直接引用来实现:
class BoundsCheckedArray {
private:
int* data;
size_t size;
// 代理类,用于[]运算符的返回值
class Reference {
private:
BoundsCheckedArray& array;
size_t index;
public:
Reference(BoundsCheckedArray& a, size_t idx) : array(a), index(idx) {}
// 转换为int,允许读取值
operator int() const {
std::cout << "Reading element at index " << index << std::endl;
return array.data[index];
}
// 赋值运算符,允许修改值
Reference& operator=(int value) {
std::cout << "Writing value " << value << " to index " << index << std::endl;
array.data[index] = value;
return *this;
}
};
public:
BoundsCheckedArray(size_t s) : size(s) {
data = new int[size]();
}
~BoundsCheckedArray() {
delete[] data;
}
// 下标运算符返回Reference对象而非int&
Reference operator[](size_t index) {
if (index >= size) {
throw std::out_of_range("Index out of bounds");
}
return Reference(*this, index);
}
size_t getSize() const { return size; }
};
int main() {
BoundsCheckedArray arr(5);
// 写入值
arr[0] = 10;
arr[1] = 20;
// 读取值
int val = arr[0];
std::cout << "Value at index 0: " << val << std::endl;
// 读取并修改
arr[2] = arr[1] * 2;
std::cout << "Value at index 2: " << arr[2] << std::endl;
try {
arr[10] = 100; // 会抛出异常
} catch (const std::exception& e) {
std::cout << "Exception: " << e.what() << std::endl;
}
return 0;
}
这种模式在STL的std::vector<bool>
中使用,因为它并不真正存储布尔值,而是以位形式压缩存储。
自定义索引
下标运算符不限于使用整数索引,我们可以使用任何类型作为索引:
class Dictionary {
private:
std::map<std::string, std::string> entries;
public:
// 使用字符串作为索引
std::string& operator[](const std::string& key) {
return entries[key];
}
// const版本
const std::string& operator[](const std::string& key) const {
auto it = entries.find(key);
if (it == entries.end()) {
static const std::string empty_string;
return empty_string;
}
return it->second;
}
// 检查键是否存在
bool contains(const std::string& key) const {
return entries.find(key) != entries.end();
}
// 获取条目数量
size_t size() const {
return entries.size();
}
};
int main() {
Dictionary dict;
dict["apple"] = "A fruit with red or green skin and crisp flesh";
dict["banana"] = "A long curved fruit with a yellow skin";
dict["cherry"] = "A small, round stone fruit that is typically bright or dark red";
std::cout << "Apple: " << dict["apple"] << std::endl;
std::cout << "Banana: " << dict["banana"] << std::endl;
std::cout << "Unknown: " << dict["unknown"] << std::endl; // 返回空字符串
std::cout << "Number of entries: " << dict.size() << std::endl;
return 0;
}
多维下标运算符
我们可以链式调用下标运算符来实现多维索引:
class Matrix {
private:
std::vector<std::vector<double>> data;
size_t rows, cols;
// 代理类,用于实现第二维索引
class RowProxy {
private:
std::vector<double>& row;
public:
RowProxy(std::vector<double>& r) : row(r) {}
// 返回特定元素的引用
double& operator[](size_t col) {
return row[col];
}
// const版本
const double& operator[](size_t col) const {
return row[col];
}
};
public:
Matrix(size_t r, size_t c) : rows(r), cols(c) {
data.resize(rows);
for (auto& row : data) {
row.resize(cols, 0.0);
}
}
// 返回代理对象,用于实现第二维索引
RowProxy operator[](size_t row) {
return RowProxy(data[row]);
}
// const版本
const RowProxy operator[](size_t row) const {
return RowProxy(const_cast<std::vector<double>&>(data[row]));
}
// 获取尺寸
size_t getRows() const { return rows; }
size_t getCols() const { return cols; }
};
int main() {
Matrix m(3, 3);
// 初始化矩阵
for (size_t i = 0; i < m.getRows(); ++i) {
for (size_t j = 0; j < m.getCols(); ++j) {
m[i][j] = i * m.getCols() + j + 1;
}
}
// 显示矩阵
for (size_t i = 0; i < m.getRows(); ++i) {
for (size_t j = 0; j < m.getCols(); ++j) {
std::cout << m[i][j] << "\t";
}
std::cout << std::endl;
}
return 0;
}
实际应用案例
大整数类
以下是一个更完整的大整数类实现,展示了多种运算符重载的应用:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <stdexcept>
class BigInteger {
private:
std::vector<int> digits; // 每个元素存储一位数字,低位在前
bool negative; // 符号标志
// 移除前导零
void removeLeadingZeros() {
while (digits.size() > 1 && digits.back() == 0) {
digits.pop_back();
}
// 特殊情况:零应该是正数
if (digits.size() == 1 && digits[0] == 0) {
negative = false;
}
}
// 比较绝对值
int compareAbs(const BigInteger& other) const {
if (digits.size() != other.digits.size()) {
return digits.size() > other.digits.size() ? 1 : -1;
}
for (int i = digits.size() - 1; i >= 0; --i) {
if (digits[i] != other.digits[i]) {
return digits[i] > other.digits[i] ? 1 : -1;
}
}
return 0; // 相等
}
// 加法实现(不考虑符号)
BigInteger addAbs(const BigInteger& other) const {
BigInteger result;
result.digits.clear();
result.negative = false;
int carry = 0;
size_t i = 0, j = 0;
while (i < digits.size() || j < other.digits.size() || carry) {
int sum = carry;
if (i < digits.size()) sum += digits[i++];
if (j < other.digits.size()) sum += other.digits[j++];
result.digits.push_back(sum % 10);
carry = sum / 10;
}
return result;
}
// 减法实现(不考虑符号,假设this >= other)
BigInteger subAbs(const BigInteger& other) const {
BigInteger result;
result.digits.clear();
result.negative = false;
int borrow = 0;
size_t i = 0, j = 0;
while (i < digits.size()) {
int diff = digits[i++] - borrow;
if (j < other.digits.size()) diff -= other.digits[j++];
if (diff < 0) {
diff += 10;
borrow = 1;
} else {
borrow = 0;
}
result.digits.push_back(diff);
}
result.removeLeadingZeros();
return result;
}
// 乘法实现(不考虑符号)
BigInteger mulAbs(const BigInteger& other) const {
// 特殊情况处理:如果任一数为0,则结果为0
if ((digits.size() == 1 && digits[0] == 0) ||
(other.digits.size() == 1 && other.digits[0] == 0)) {
return BigInteger("0");
}
// 初始化结果为0,长度为两数之和
std::vector<int> result(digits.size() + other.digits.size(), 0);
// 执行乘法
for (size_t i = 0; i < digits.size(); ++i) {
int carry = 0;
for (size_t j = 0; j < other.digits.size() || carry; ++j) {
int current = result[i + j] + digits[i] * (j < other.digits.size() ? other.digits[j] : 0) + carry;
result[i + j] = current % 10;
carry = current / 10;
}
}
// 构造结果
BigInteger product;
product.digits = result;
product.removeLeadingZeros();
return product;
}
public:
// 构造函数
BigInteger() : negative(false) {
digits.push_back(0); // 默认值为0
}
BigInteger(long long num) : negative(num < 0) {
num = std::abs(num);
if (num == 0) {
digits.push_back(0);
} else {
while (num > 0) {
digits.push_back(num % 10);
num /= 10;
}
}
}
BigInteger(const std::string& str) : negative(false) {
if (str.empty()) {
digits.push_back(0);
return;
}
size_t start = 0;
if (str[0] == '-') {
negative = true;
start = 1;
} else if (str[0] == '+') {
start = 1;
}
// 逆序添加数字(低位在前)
for (int i = str.length() - 1; i >= static_cast<int>(start); --i) {
if (!std::isdigit(str[i])) {
throw std::invalid_argument("Invalid character in string");
}
digits.push_back(str[i] - '0');
}
removeLeadingZeros();
}
// 一元加号运算符
BigInteger operator+() const {
return *this;
}
// 一元减号运算符
BigInteger operator-() const {
BigInteger result = *this;
// 零的符号始终为正
if (!(result.digits.size() == 1 && result.digits[0] == 0)) {
result.negative = !result.negative;
}
return result;
}
// 加法运算符
BigInteger operator+(const BigInteger& other) const {
// 如果符号相同
if (negative == other.negative) {
BigInteger result = addAbs(other);
result.negative = negative;
return result;
}
// 符号不同:|a| >= |b|时,结果是a的符号;否则是b的符号
int cmp = compareAbs(other);
if (cmp == 0) {
return BigInteger(); // 结果为0
} else if (cmp > 0) {
BigInteger result = subAbs(other);
result.negative = negative;
return result;
} else {
BigInteger result = other.subAbs(*this);
result.negative = other.negative;
return result;
}
}
// 减法运算符
BigInteger operator-(const BigInteger& other) const {
return *this + (-other);
}
// 乘法运算符
BigInteger operator*(const BigInteger& other) const {
BigInteger result = mulAbs(other);
// 异号得负,同号得正
result.negative = (negative != other.negative) && !(result.digits.size() == 1 && result.digits[0] == 0);
return result;
}
// 复合赋值运算符
BigInteger& operator+=(const BigInteger& other) {
*this = *this + other;
return *this;
}
BigInteger& operator-=(const BigInteger& other) {
*this = *this - other;
return *this;
}
BigInteger& operator*=(const BigInteger& other) {
*this = *this * other;
return *this;
}
// 关系运算符
bool operator==(const BigInteger& other) const {
return negative == other.negative && digits == other.digits;
}
bool operator!=(const BigInteger& other) const {
return !(*this == other);
}
bool operator<(const BigInteger& other) const {
// 符号不同
if (negative != other.negative) {
return negative;
}
// 符号相同,比较绝对值
int cmp = compareAbs(other);
return negative ? cmp > 0 : cmp < 0;
}
bool operator>(const BigInteger& other) const {
return other < *this;
}
bool operator<=(const BigInteger& other) const {
return !(other < *this);
}
bool operator>=(const BigInteger& other) const {
return !(*this < other);
}
// 输入输出运算符
friend std::ostream& operator<<(std::ostream& os, const BigInteger& num);
friend std::istream& operator>>(std::istream& is, BigInteger& num);
// 转换为字符串
std::string toString() const {
if (digits.size() == 1 && digits[0] == 0) {
return "0";
}
std::string result;
if (negative) {
result += "-";
}
for (int i = digits.size() - 1; i >= 0; --i) {
result += static_cast<char>(digits[i] + '0');
}
return result;
}
};
// 输出运算符
std::ostream& operator<<(std::ostream& os, const BigInteger& num) {
os << num.toString();
return os;
}
// 输入运算符
std::istream& operator>>(std::istream& is, BigInteger& num) {
std::string input;
is >> input;
try {
num = BigInteger(input);
} catch (const std::invalid_argument& e) {
is.setstate(std::ios_base::failbit);
}
return is;
}
int main() {
BigInteger a("123456789012345678901234567890");
BigInteger b("987654321098765432109876543210");
BigInteger c = -a;
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
std::cout << "c = " << c << std::endl;
std::cout << "a + b = " << (a + b) << std::endl;
std::cout << "a - b = " << (a - b) << std::endl;
std::cout << "a * b = " << (a * b) << std::endl;
std::cout << "a < b? " << std::boolalpha << (a < b) << std::endl;
std::cout << "a > b? " << (a > b) << std::endl;
std::cout << "a == b? " << (a == b) << std::endl;
std::cout << "a != b? " << (a != b) << std::endl;
BigInteger d("0");
std::cout << "-d = " << -d << std::endl; // 应该仍然是0
std::cout << "请输入一个大整数: ";
BigInteger input;
if (std::cin >> input) {
std::cout << "您输入的数字是: " << input << std::endl;
std::cout << "input + a = " << (input + a) << std::endl;
} else {
std::cout << "输入格式错误!" << std::endl;
}
return 0;
}
日期类
以下是一个日期类的实现,展示了各种运算符的重载:
#include <iostream>
#include <iomanip>
#include <string>
#include <stdexcept>
class Date {
private:
int year;
int month;
int day;
// 检查日期是否有效
bool isValid() const {
if (year < 1 || month < 1 || month > 12 || day < 1) {
return false;
}
const int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int maxDay = daysInMonth[month];
// 二月闰年判断
if (month == 2 && isLeapYear()) {
maxDay = 29;
}
return day <= maxDay;
}
// 判断闰年
bool isLeapYear() const {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
// 计算从公元元年1月1日开始的总天数
int toDays() const {
int days = (year - 1) * 365 + (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400;
const int daysBeforeMonth[] = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
days += daysBeforeMonth[month];
// 如果当前年是闰年且已经过了2月
if (month > 2 && isLeapYear()) {
days++;
}
days += day;
return days;
}
// 从总天数设置日期
void fromDays(int days) {
// 估算年份
year = days / 365;
int leapDays = year / 4 - year / 100 + year / 400;
while (days <= leapDays + (year - 1) * 365 + (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400) {
year--;
leapDays = year / 4 - year / 100 + year / 400;
}
while (days > leapDays + year * 365 + year / 4 - year / 100 + year / 400) {
year++;
leapDays = year / 4 - year / 100 + year / 400;
}
// 计算日期
int daysInYear = days - ((year - 1) * 365 + (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400);
const int daysBeforeMonth[] = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
bool isLeap = isLeapYear();
month = 1;
while (month < 12 && daysInYear > daysBeforeMonth[month + 1] + (isLeap && month >= 2 ? 1 : 0)) {
month++;
}
day = daysInYear - daysBeforeMonth[month] - (isLeap && month > 2 ? 1 : 0);
}
public:
// 构造函数
Date(int y, int m, int d) : year(y), month(m), day(d) {
if (!isValid()) {
throw std::invalid_argument("Invalid date");
}
}
// 获取年、月、日
int getYear() const { return year; }
int getMonth() const { return month; }
int getDay() const { return day; }
// 加减天数
Date operator+(int days) const {
int totalDays = toDays() + days;
Date result(1, 1, 1); // 临时日期
result.fromDays(totalDays);
return result;
}
Date operator-(int days) const {
return *this + (-days);
}
// 日期差值
int operator-(const Date& other) const {
return toDays() - other.toDays();
}
// 复合赋值
Date& operator+=(int days) {
*this = *this + days;
return *this;
}
Date& operator-=(int days) {
*this = *this - days;
return *this;
}
// 递增递减
Date& operator++() { // 前缀
*this += 1;
return *this;
}
Date operator++(int) { // 后缀
Date temp = *this;
*this += 1;
return temp;
}
Date& operator--() { // 前缀
*this -= 1;
return *this;
}
Date operator--(int) { // 后缀
Date temp = *this;
*this -= 1;
return temp;
}
// 比较运算符
bool operator==(const Date& other) const {
return year == other.year && month == other.month && day == other.day;
}
bool operator!=(const Date& other) const {
return !(*this == other);
}
bool operator<(const Date& other) const {
if (year != other.year) return year < other.year;
if (month != other.month) return month < other.month;
return day < other.day;
}
bool operator>(const Date& other) const {
return other < *this;
}
bool operator<=(const Date& other) const {
return !(*this > other);
}
bool operator>=(const Date& other) const {
return !(*this < other);
}
// 输入输出
friend std::ostream& operator<<(std::ostream& os, const Date& date);
friend std::istream& operator>>(std::istream& is, Date& date);
};
// 输出运算符
std::ostream& operator<<(std::ostream& os, const Date& date) {
os << std::setw(4) << std::setfill('0') << date.year << '-'
<< std::setw(2) << std::setfill('0') << date.month << '-'
<< std::setw(2) << std::setfill('0') << date.day;
return os;
}
// 输入运算符
std::istream& operator>>(std::istream& is, Date& date) {
char dash1, dash2;
int y, m, d;
is >> y >> dash1 >> m >> dash2 >> d;
if (is && dash1 == '-' && dash2 == '-') {
try {
date = Date(y, m, d);
} catch (const std::invalid_argument&) {
is.setstate(std::ios_base::failbit);
}
} else {
is.setstate(std::ios_base::failbit);
}
return is;
}
int main() {
try {
Date today(2023, 10, 28);
Date tomorrow = today + 1;
Date yesterday = today - 1;
std::cout << "Today: " << today << std::endl;
std::cout << "Tomorrow: " << tomorrow << std::endl;
std::cout << "Yesterday: " << yesterday << std::endl;
std::cout << "Days between tomorrow and yesterday: " << (tomorrow - yesterday) << std::endl;
Date nextWeek = today;
nextWeek += 7;
std::cout << "Next week: " << nextWeek << std::endl;
// 递增测试
Date d = today;
std::cout << "d++ = " << d++ << std::endl;
std::cout << "After d++: " << d << std::endl;
std::cout << "++d = " << ++d << std::endl;
// 比较测试
std::cout << "today == tomorrow? " << std::boolalpha << (today == tomorrow) << std::endl;
std::cout << "today < tomorrow? " << (today < tomorrow) << std::endl;
std::cout << "today > yesterday? " << (today > yesterday) << std::endl;
std::cout << "请输入日期 (YYYY-MM-DD): ";
Date input(2000, 1, 1);
if (std::cin >> input) {
std::cout << "您输入的日期是: " << input << std::endl;
std::cout << "距离今天的天数: " << (input - today) << std::endl;
} else {
std::cout << "输入格式错误!" << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
最佳实践与陷阱
运算符重载的最佳实践
保持语义直观:重载运算符应该与其原始语义保持一致。例如,
+
应该表示加法操作,而不是其他无关的功能。成对实现相关运算符:如果实现了
==
,也应实现!=
;如果实现了<
,考虑实现所有比较运算符。避免意外的类型转换:使用
explicit
关键字防止意外的隐式类型转换。保证一致的返回类型:运算符的返回类型应与其语义匹配。例如,赋值运算符应返回对象的引用,比较运算符应返回布尔值。
考虑链式操作:复合赋值运算符应该返回
*this
的引用,以支持链式操作。基于复合赋值运算符实现二元运算符:这样可以减少代码重复。
关注效率:特别是对于大型对象,应该避免不必要的对象复制。
使用友元函数适当地:尤其是对于需要转换左操作数的情况。
常见陷阱
- 修改了运算符的预期行为:
// 错误示例
class BadIdea {
public:
bool operator+(const BadIdea&) const {
return true; // 加法运算符返回布尔值?
}
void operator-(const BadIdea&) const {
std::cout << "Subtraction" << std::endl; // 减法运算符无返回值?
}
};
- 不正确处理自赋值:
class Resource {
public:
Resource& operator=(const Resource& other) {
// 错误:未检查自赋值
delete[] data; // 如果this == &other,这会删除正在尝试复制的数据
data = new int[other.size];
std::copy(other.data, other.data + other.size, data);
return *this;
}
private:
int* data;
size_t size;
};
- 破坏不变式:
class Fraction {
public:
// 错误:不保证分数格式的有效性
Fraction& operator+=(const Fraction& other) {
numerator = numerator * other.denominator + other.numerator * denominator;
denominator = denominator * other.denominator;
// 缺少对分数的规范化(约分)
return *this;
}
private:
int numerator;
int denominator;
};
- 忽略边界情况:
class SafeDivision {
public:
// 错误:没有处理除以零的情况
double operator/(double divisor) const {
// 应该检查divisor是否为零
return value / divisor;
}
private:
double value;
};
- 重载逻辑运算符:
class LogicalOverload {
public:
// 错误:重载&&和||会失去短路评估特性
bool operator&&(const LogicalOverload& other) const {
return value && other.value;
}
bool operator||(const LogicalOverload& other) const {
return value || other.value;
}
private:
bool value;
};
- 过度重载:
// 错误:为类重载过多不必要的运算符
class OverloadEverything {
public:
OverloadEverything operator+(const OverloadEverything&) const;
OverloadEverything operator-(const OverloadEverything&) const;
OverloadEverything operator*(const OverloadEverything&) const;
OverloadEverything operator/(const OverloadEverything&) const;
OverloadEverything operator%(const OverloadEverything&) const;
OverloadEverything operator^(const OverloadEverything&) const;
OverloadEverything operator&(const OverloadEverything&) const;
OverloadEverything operator|(const OverloadEverything&) const;
// ... 更多不必要的运算符
};
总结
运算符重载是C++面向对象编程中一个强大而灵活的特性,它允许我们为自定义类型定义标准运算符的行为,创建直观且易于使用的接口。通过合理地使用运算符重载,我们可以使得代码更加自然、可读,并且保持与内置类型一致的使用方式。
本文介绍了多种高级运算符重载技术和应用场景,包括类型转换运算符、内存管理运算符、自定义字面量、代理类模式等。我们还通过两个完整的实际应用案例——大整数类和日期类,展示了如何在实践中应用这些技术。
然而,运算符重载也需要谨慎使用。遵循最佳实践并避免常见陷阱,是确保代码质量和可维护性的关键。特别是,我们应该始终保持运算符的语义直观性,避免违反用户的预期,并确保运算符的行为与其原始含义一致。
随着C++标准的发展,运算符重载的应用也在不断扩展。从C++11引入的移动语义和自定义字面量,到C++20引入的三路比较运算符(<=>
),这些都为我们提供了更多表达力和灵活性。
在下一篇文章中,我们将探讨C++面向对象编程的另一个重要主题:虚函数与抽象类,这是多态性的核心机制。
这是我C++学习之旅系列的第十三篇技术文章。查看完整系列目录了解更多内容。