C++学习:六个月从基础到就业——面向对象编程:重载运算符(下)
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();}// 分数转换为doubleoperator 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/4double d = f; // 隐式转换:调用operator double()std::cout << "Fraction: ";f.display();std::cout << " as double: " << d << std::endl;// 在表达式中使用double result = f * 2.0; // f被转换为double,然后乘以2.0std::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:// 重载全局newvoid* operator new(size_t size) {totalAllocated += size;std::cout << "Allocating " << size << " bytes, total: " << totalAllocated << std::endl;return ::operator new(size); // 调用全局的new}// 重载全局deletevoid operator delete(void* ptr) noexcept {::operator delete(ptr); // 调用全局的deletestd::cout << "Memory freed" << std::endl;}// 重载数组newvoid* operator new[](size_t size) {totalAllocated += size;std::cout << "Allocating array of " << size << " bytes, total: " << totalAllocated << std::endl;return ::operator new[](size);}// 重载数组deletevoid 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 newvoid* 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 5return 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,则结果为0if ((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; // 应该仍然是0std::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++学习之旅系列的第十三篇技术文章。查看完整系列目录了解更多内容。
相关文章:
C++学习:六个月从基础到就业——面向对象编程:重载运算符(下)
C学习:六个月从基础到就业——面向对象编程:重载运算符(下) 本文是我C学习之旅系列的第十三篇技术文章,是面向对象编程中运算符重载主题的下篇。本篇文章将继续深入探讨高级运算符重载技术、特殊运算符、常见应用场景和…...
电压模式控制学习
电压模式控制 在开关电源中,大的可分为三大控制模式,分别是电压模式控制,电流模式控制,迟滞模式控制。今天简要介绍下电压模式控制的优缺点。 原理 架构图如下 如图所示,电压模式控制可以分为三部分:误…...
vue3 Ts axios 封装
vue3 Ts axios 封装 axios的封装 import axios, { AxiosError, AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosRequestConfig, AxiosHeaders } from axios import qs from qs import { config } from ./config import { ElMessage } from element-plus// …...
GPT,Bert类模型对比
以下是对 BERT-base、RoBERTa-base、DeBERTa-base 和 DistilBERT-base 四个模型在参数量、训练数据、GPU 内存占用、性能表现以及优缺点方面的对比: 模型参数量与训练数据 模型参数量训练数据量BERT-base110MBookCorpus(8亿词) 英文维基百科…...
3.Rust + Axum 提取器模式深度剖析
摘要 深入解读 Rust Axum 提取器模式,涵盖内置提取器及自定义实现。 一、引言 在 Rust 的 Web 开发领域,Axum 作为一款轻量级且高效的 Web 框架,为开发者提供了强大的功能。其中,提取器(Extractor)模式…...
Dify vs n8n vs RAGFlow:2025年AI应用与自动化工作流平台的终极对决
我将为大家整理一份关于 Dify、n8n 和 Ragflow 的最新研究分析,涵盖以下六个方面:功能对比、应用场景、架构设计、集成能力、和使用门槛。我会尽可能引用其官方文档、GitHub 仓库以及社区讨论等权威信息来源。 我整理好后会第一时间通知你查看。 1.Dify、n8n 和 RAGFlow 最新…...
ffmpeg无损转格式的命令行
将ffmpeg.exe拖入命令行窗口 c:\users\zhangsan>D:\ffmpeg-2025-03-11\bin\ffmpeg.exe -i happy.mp4 -c:v copy -c:a copy 格式转换后.mkv -c:v copy 仅做拷贝视频,不重新编码 -c:a copy 仅做拷贝音频 ,不重新编码...
Flutter 常用命令
1、创建项目 flutter create <项目名称> 示例: flutter create my_app 1.1 参数说明 --org:设置包名(默认 com.example) flutter create --org com.yourcompany my_app -a/-i:指定语言(Kotlin…...
【零基础】基于DeepSeek-R1与Qwen2.5Max的行业洞察自动化平台
自动生成行业报告,通过调用两个不同的大模型(DeepSeek 和 Qwen),完成从行业趋势分析到结构化报告生成的全过程。 完整代码:https://mp.weixin.qq.com/s/6pHi_aIDBcJKw1U61n1uUg 🧠 1. 整体目的与功能 该脚本实现了一个名为 ReportGenerator 的类,用于: 调用 DeepSe…...
UE5 关卡序列
文章目录 介绍创建一个关卡序列编辑动画添加一个物体编辑动画时间轴显示秒而不是帧时间轴跳转到一个确定的时间时间轴的显示范围更改关键帧的动画插值方式操作多个关键帧 播放动画 介绍 类似于Unity的Animation动画,可以用来录制场景中物体的动画 创建一个关卡序列…...
1.凸包、极点、极边基础概念
目录 1.凸包 2.调色问题 3.极性(Extrem) 4.凸组合(Convex Combination) 5.问题转化(Strategy)编辑 6.In-Triangle test 7.To-Left-test 8.极边(Extream Edges) 1.凸包 凸包就是上面蓝色皮筋围出来的范围 这些钉子可以转换到坐标轴中࿰…...
MahApps.Metro:专为 WPF 应用程序设计的 UI 框架
推荐一个WPF 应用程序设计的 UI 框架,方便我们快速构建美观、流畅的应用程序。 01 项目简介 MahApps.Metro 是一个开源的 UI 框架,它可以让开发者快速构建现代化、美观的 WPF 应用程序。 提供了一套完整的 UI 组件和主题,支持流畅的动画效…...
【LangChain4j快速入门】5分钟用Java玩转GPT-4o-mini,Spring Boot整合实战!| 附源码
【LangChain4j快速入门】5分钟用Java玩转GPT-4o-mini,Spring Boot整合实战! 前言:当Java遇上大模型 在AI浪潮席卷全球的今天,Java开发者如何快速拥抱大语言模型?LangChain4j作为专为Java打造的AI开发框架,…...
乐言科技:云原生加速电商行业赋能,云消息队列助力降本 37%
深耕 AI SaaS,助力数万电商客户数智化转型 上海乐言科技股份有限公司(以下简称“乐言科技”,官网:https://www.leyantech.com/)自 2016 年成立以来,专注于利用自然语言处理和深度学习等核心 AI 技术&#…...
vscode构建简单编译和调试环境
一、设置环境变量 将bin目录路径(如D:\DevTools\mingw64\bin)加入系统环境变量PATH34 二、VS Code插件配置 核心插件安装 C/C(微软官方扩展,提供语法高亮、智能提示)Code Runner࿰…...
STM32控制DRV8825驱动42BYGH34步进电机
最近想玩一下人工智能,然后买了个步进电机想玩一下,刚到了一脸懵逼,发现驱动器20多块,有点超预算,然后整了个驱动板,方便自己画线路板,经过各种搜索,终于转起来了,记录一…...
系统清理专家,一键释放磁盘空间!
打工人们你们好!这里是摸鱼 特供版~ 今天给大家带来一款超实用的系统清理工具——Glary Disk Cleaner,帮助你快速清理系统中的垃圾文件,释放磁盘空间,提升系统运行速度! 推荐指数:★★★★★ 软件简介 G…...
识别法院PDF文件特定字段并插入数据库【正则表达式+本地化部署】
pdf解析法院协助单特定字段,开源项目,结合若依项目进行开发,不连互联网,本地开发部署,前端使用vue3技术,后端用若依分离版spring botot技术,实现将pdf法院协助执行通知书中的特定字段如:时间、文…...
探索智能体开发新范式:Cangjie Magic深度解析与实践指南
引言:智能体开发的新纪元 2025年3月,仓颉社区开源了基于仓颉编程语言原生构建的LLM Agent开发平台——Cangjie Magic,为智能体开发领域带来了革命性的变革。作为一名长期关注AI技术发展的开发者,我有幸在第一时间体验了这一创新平…...
计算机网络 - UDP协议
通过一些问题来讨论 UDP 协议 什么是 UDP?举几个应用了 UDP 协议的例子UDP 与 TCP 有啥区别?(PS:介绍三四个就可以了,不用说太多)具体 UDP 是不可靠的,那你觉得如何实现一个可靠的 UDP &#x…...
阿里云ECS访问不了
使用xshell连接阿里云ECS,下载nginx,然后启动 sudo systemctl start nginx 查看状态是 sudo systemctl status nginx 输入公网ip访问实例访问不到,出现 可以查看阿里云实例中的安全组,是否对外开放了80端口和443端口 添加入方向…...
Starrocks添删改查数据(二)
先安装好Starrocks,参考:Starrocks入门(二)_backend node not found. check if any backend node -CSDN博客 1、建立库 建库成功。 2、建立表 参考:表概览 | StarRocks 执行如下SQL: CREATE TABLE user_…...
RT-Thread学习笔记(一)
RT-Thread学习笔记 AIotMMUCPU架构RT-Thread版本工程创建时钟配置FinSH内核RT-Thread内核启动流程 RT-Thread是一个组件完整丰富、高度可伸缩、简易开发、超低功耗、高安全性的物联网操作系统 全称Real Time Thread AIot AIot: Artificial Intelligence of Things…...
【源码】30个Python小游戏
下载链接:https://github.com/pyGuru123/Python-Games 本站下载链接:【免费】源码30个Python小游戏资源-CSDN文库 包含:飞机大战、愤怒的墙、圆弧冲刺、行星游戏、弹跳的球、汽车避障、洞穴物语、愤怒的小鸟、丛林探险、扫雷、俄罗斯方块、…...
【Web前端技术】第二节—HTML标签(上)
hello!好久不见—— 做出一个属于自己的网站! 云边有个稻草人-个人主页 Web前端技术—本篇文章所属专栏 目录 一、HTML 语法规范 1.1 基本语法概述 1.2 标签关系 二、HTML 基本结构标签 2.1 第一个 HTML 网页 2.2 基本结构标签总结 三、网页开发…...
Android开发协调布局滑动悬停
Android开发协调布局滑动悬停 直接给个xml,防止下次忘了怎么写。 <?xml version"1.0" encoding"utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android"http://schemas.android.com/apk/res/android…...
R语言简介与下载安装
1.R语言简介与下载安装 R语言其诞生于新西兰奥克兰大学,由Ross Ihaka 和Robert Gentleman开发,属于商业软件S语言的替代品;R语言是一款开源的编程类工具,专门用于数据清洗、整理、统计分析、可视化以及数据挖掘等方面,…...
CGAL边折叠edge_collapse的问题
使用edge_collapse对一个模型简化,之后回收垃圾,collect_garbage 处理之前的顶点和三角形数量: number_of_vertices: 955730 number_of_faces: 1903410 num_vertices: 955730 num_faces: 1903410 处理之后的顶点和三角形数量:…...
2025 全球分布式云大会演讲实录 | 沈建发:智启边缘,畅想未来:边缘计算新场景落地与 Al 趋势新畅想
4 月 9 日,2025 全球分布式云大会暨 AI 基础设施大会在深圳成功举办,火山引擎边缘云产品解决方案高级总监沈建发出席并以《智启边缘,畅想未来:边缘计算新场景落地与 Al 趋势新畅想》为主题,分享了边缘计算在 AI 技术趋…...
【ELF2学习板】OpenCL程序测试
目录 引言 OpenCL简介 主要特点 编程模型 应用场景 测试程序 代码说明 构建编译环境 头文件 库文件 程序编译 测试结果 结语 引言 ELF2开发板采用的是RK3588处理器,它是瑞芯微推出的一款高性能 SoC。RK3588 集成了 ARM Mali-G610 MP4 GPU,…...
EtherCAT转ProfiNet边缘计算网关配置优化:汽车制造场景下PLC与机器人协同作业案例
1.行业背景与需求分析 智能汽车焊装车间是汽车制造的核心工艺环节,某德国豪华品牌在其上海MEB工厂新建的焊装车间中,采用西门子S7-1500PLC作为ProfiNet主站,负责整线协调与质量追溯;同时部署KUKAKR1500Titan机器人(Eth…...
AI 推理与训练优化的核心理论体系建构及关键技术分析框架
AI 推理与训练优化的核心理论体系建构及关键技术分析框架 一、推理加速的动态系统理论建模与算法设计 (一)基于MDP的动态计算图理论 生物启发的决策框架:模拟灵长类视觉系统的注意力分配机制,构建马尔可夫决策过程(M…...
Jupyter 中 Markdown 邂逅 LaTeX:一场知识的绮梦
引言: 在日常编程和数据分析工作中,我们经常需要记录和分享信息。传统的注释方式功能有限,而富文本编辑器又过于臃肿。Markdown作为一种轻量级标记语言,完美解决了这个问题。 为什么选择Markdown? Markdown具有两大优势…...
AI 对话高效输入指令攻略(二):关于豆包的指令
免责声明:该文章的所有样例只是测试,没有唆使大家利用AI抄袭作业!更没有宣传豆包。 前言 没有听不懂话的AI,只有不会调教AI的人。(自己瞎说的)当你把AI当人看之后,你就会发现,他是多…...
Apache Atlas构建安装(Linux)
一、环境准备 maven 3.6.3python 2.7nodejs 12java 1.8 注意环境一定要对,不然一堆问题 1. python2.7 安装 参考文章:https://blog.csdn.net/weixin_42081389/article/details/101276251 打开链接:https://www.python.org/downloads/sourc…...
【JAVA】在idea新加artifact时,点击Build-Build Artifacts时,新加的artifact不能选中
首先保证添加artifact无问题,比如依赖都正确、无重复命令的情况等 办法 一 File > Invalidate Caches / Restart。 重启IDEA后,重新检查Artifact是否可选 办法 二 打开 Project Structure(CtrlShiftAltS)。 进入 Artifacts 选…...
【杂谈】-自动驾驶变革:货运革新与机器人出租车崛起
自动驾驶变革:货运革新与机器人出租车崛起 文章目录 自动驾驶变革:货运革新与机器人出租车崛起一、市场主导力量二、机器人出租车的崛起三、货运运输的变革四、商业视角下的分析五、责任归属问题 汽车行业,凭借其在道路状况、车辆性能及整体环…...
吊顶上的灯线怎么预留?是提前到位还是后期随意拉拽?
业主问家里的灯线怎么预留? 问业主灯线指的是主灯还是射灯? 业主说他家里边要做边吊,边吊上边要放一些射灯。 在射灯上方对应的留灯线就可以,不用特别的精确,把线头放的稍微长一点即可。 这位业主又说为什么要这样预留…...
网易游戏 x Apache Doris:湖仓一体架构演进之路
导读:网易游戏引入 Apache Doris 升级架构,先是替换 Elasticsearch、Hbase、Clickhouse 构建了实时数仓,而后基于 Apache Doris 和 Iceberg 构建了湖仓融合架构,实现架构的大幅简化及统一。目前,网易游戏 Apache Doris…...
大模型会不会取代人类工作
大模型是否会取代人类工作? 随着人工智能技术的迅猛发展,尤其是大型语言模型(如GPT-4、BERT等)的出现,人们开始担忧这些先进的技术是否会在未来取代人类的工作。这种担忧并非空穴来风,历史上每一次技术革命…...
深入理解 Linux 权限管理:从 Shell 到文件权限
🌼🌼 在 Linux 系统中,权限是保障系统安全与稳定的核心之一。每个操作都可能涉及权限的管理和控制,特别是当你开始以不同用户的身份进行操作时。本文将通过生动的比喻与详细的技术解析,带你一起深入理解 Linux 权限系统…...
.net core 项目快速接入Coze智能体-开箱即用-全局说明
目录 一、Coze智能体的核心价值 二、开箱即用-效果如下 三 流程与交互设计 为什么要分析意图,而不是全部交由AI处理。 四 接入前的准备工作 五:代码实现----字节Coze 签署 JWT和获取Token .net core 项目快速接入Coze智能体-开箱即用 .net core快…...
React 开放封闭原则详解,构建可扩展的应用
开放封闭原则 React 采用了一些面向对象编程的原则和概念,其中之一就是开放封闭原则(Open-Closed Principle,OCP),它是面向对象编程的一个基本原则。本文将详细解释开放封闭原则的概念和在 React 中的应用,…...
【并行分布计算】Hadoop完全分布搭建
Hadoop完全分布搭建 1.为了使机器都处于同一个局域网中,先要修改机器的ip地址分配方式为固定ip,并为其固定分配一个ip地址。 [rootlocalhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens33 问题:修改ip地址后,网络无法正常…...
Three.js + React 实战系列 : 从零搭建 3D 个人主页
可能你对tailiwindcss毫不了解,别紧张,记住我们只是在学习,学习的是作者的思想和技巧,并不是某一行代码。 在之前的几篇文章中,我们已经熟悉了 Three.js 的基本用法,并通过 react-three-fiber 快速构建了一…...
四月十六日华为发布会
智能家居及穿戴类 华为智能门锁 2 系列发布:2025 年 4 月 16 日,华为智能门锁 2 系列正式发布。该系列引入 AI 掌静脉识别技术和 AI 3D 人脸识别 3.0,具备高安全性;采用超清智能大猫眼,可视范围达 161;通过…...
23种设计模式-创建型模式之工厂方法模式(Java版本)
Java 工厂方法模式(Factory Method Pattern)详解 🌟 什么是工厂方法模式? 工厂方法模式是一种创建型设计模式,定义一个创建对象的接口,让子类决定实例化哪一个类。 它让类的实例化推迟到子类进行…...
基于FreeBSD的Unix系统搭建Nginx+PHP5运行环境
服务器操作系统版本:FreeBSD-10.1-i386 Nginx安装配置 1.以ports形式安装 #进入nginx目录 cd /usr/ports/www/nginx #执行编译安装 make install 2.编辑配置文件 #进入etc目录 cd /etc #编辑配置文件 ee rc.conf #进入编辑模式 i #参照如下设置 nginx_enable”YES…...
医院处方外流对接外部药房系统(合规python代码版)
系统概述 本系统旨在帮助医院实现与外部零售药店的安全、合规对接,满足2025年医保局和卫健委关于处方流转的最新规定。系统采用Python开发,基于RESTful API实现医院HIS系统与外部药房之间的处方信息传输、医保支付验证和处方状态跟踪等功能。 系统架构 #mermaid-svg-zKU5Wj…...
第十七届“华中杯”B 题校园共享单车的调度与维护问题分析
问题1:估算共享单车总量及不同停车点位在不同时间点的数量分布 首先,我们需要对附件1的数据进行汇总,以估算出校园内的共享单车总量。由于数据是按不同时间和停车点位统计的,我们可以通过对所有时间和点位的单车数量进行求和&…...