文章目录
- 一、基本概念
- 可以被重载
- 某些运算符不应被重载
- 尽量明智使用运算符重载
- 赋值和复合赋值运算符
- 选择作为成员或者非成员
- 输入和输出运算符
- 输入运算符尽量减少格式化操作
- 输入输出运算符必须是非成员函数
- 重载输入运算符>>
- 输入时的错误
- 标示错误
- 算数和关系运算符
- 相等运算符
- 关系运算符
- 赋值运算符
- 下标运算符
- 递增和递减运算符
- 定义前置递增、递减运算符
- 区分前置和后置运算符
- 成员访问运算符->
- 14.8 函数调用运算符
- 含有状态的函数对象类
- lambda是函数对象
- 标准库定义的函数对象
- 可调用对象与function
- 不同类别可能具有相同都调用形式
- 标准库function
- 重载函数与function
- 14.9 重载、类型转换与运算符
- 类型转换运算符
- 类类型转换成内置类型
- 类类型转换为自定义类型
- 转换为指针类型示例
- 类型转换运算符可能产生意外结果
- 显式类型转换运算符
- 转为bool
- 避免有二义性的类型转换
- 第一种情况
- 第二种情况
- 重载函数与转换构造函数
- 重载函数与用户定义的类型转换
- 函数匹配与重载运算符
一、基本概念
可以被重载
某些运算符不应被重载
通常:不应该重载逗号、取地址、逻辑与/或运算符
尽量明智使用运算符重载
赋值和复合赋值运算符
选择作为成员或者非成员
输入和输出运算符
ostream &operator<<(ostream &os.const Sales_dataaa &item)
{
os<<item.isbn()<<""<<item.units_sold<<""<<item.revenue<<""<<item.avg_price();
return os;
}
输入运算符尽量减少格式化操作
通常输出运算符应该主要负责打印对象内容而非控制格式,输出运算符不应该打印换行符
输入输出运算符必须是非成员函数
重载输入运算符>>
istream &operator>>(istream &is,sales_data &item)
{
double price;//不需要初始化,因为我们将先读入数据到Price,之后使用
is>>item.bookNo>>item.units_sold>>price;
if(is)
item.revenue=item.units_sold*price;
else
item.revenue=item.units_sold*price;
return is;
}
输入时的错误
当读取操作发生错误时,输入运算符应该负责从错误中恢复。
标示错误
算数和关系运算符
如果类同时定义了算术运算符和相关的复合赋值运算符,则通常情况下应该使用复合赋值来实现算术运算符。
相等运算符
如果某个类在逻辑上有相等性都含义,则该类应该定义operator==.这样做可以使得用户更容易使用标准库算法来处理这个类
关系运算符
赋值运算符
下标运算符
标示容器的类通常可以通过元素在容器中的位置访问元素,这些类一般会定义下标运算符Operator[]
下标运算符必须是成员函数
class StrVec
{
public:
std::string& operator[]{std::size_t n}
{return element[n];}
const std::string& operator[](std::size_t n) const
{return elements[n];}
private:
std::string *elements;
};
递增和递减运算符
定义前置递增、递减运算符
Class StrBlobPtr
{
Public:
//递增和递减运算符
StrBlobPtr& operator++(); //前置运算符
StrBlobPtr& operator--(); //前置运算符
}
为了与内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。
区分前置和后置运算符
成员访问运算符->
在迭代器和智能指针常常用到引用运算符和箭头运算符
class StrBlobPtr
{
public:
std::string& operator*() const
{
autop p=check(curr,"dereference past end");
return (*p)[curr];
}
std::string* operator->()const
{
return & this->operator*();
}
}
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <stdexcept>
// 前向声明StrBlobPtr类,以便在StrBlob中使用
class StrBlobPtr;
// StrBlob类:管理动态字符串数组的容器
class StrBlob {
friend class StrBlobPtr; // 允许StrBlobPtr访问私有成员
public:
using size_type = std::vector<std::string>::size_type;
// 默认构造函数:初始化空的vector
StrBlob() : data(std::make_shared<std::vector<std::string>>()) {}
// 带初始化列表的构造函数:用初始值列表初始化vector
StrBlob(std::initializer_list<std::string> il)
: data(std::make_shared<std::vector<std::string>>(il)) {}
// 返回容器大小
size_type size() const { return data->size(); }
// 判断容器是否为空
bool empty() const { return data->empty(); }
// 向容器尾部添加元素
void push_back(const std::string &t) { data->push_back(t); }
// 删除容器尾部元素(需检查容器非空)
void pop_back();
// 获取容器首元素的引用(非常量版本)
std::string& front();
// 获取容器首元素的引用(常量版本)
const std::string& front() const;
// 获取容器尾元素的引用(非常量版本)
std::string& back();
// 获取容器尾元素的引用(常量版本)
const std::string& back() const;
// 返回指向容器首元素的StrBlobPtr
StrBlobPtr begin();
// 返回指向容器尾后位置的StrBlobPtr
StrBlobPtr end();
private:
// 共享指针管理vector,允许多个StrBlob共享同一数据
std::shared_ptr<std::vector<std::string>> data;
// 检查索引是否合法,不合法则抛出异常
void check(size_type i, const std::string &msg) const;
};
// 检查索引合法性,若不合法则抛出out_of_range异常
void StrBlob::check(size_type i, const std::string &msg) const {
if (i >= data->size())
throw std::out_of_range(msg);
}
// 返回首元素引用(非常量版本)
std::string& StrBlob::front() {
check(0, "front on empty StrBlob"); // 检查容器非空
return data->front();
}
// 返回首元素引用(常量版本)
const std::string& StrBlob::front() const {
check(0, "front on empty StrBlob"); // 检查容器非空
return data->front();
}
// 返回尾元素引用(非常量版本)
std::string& StrBlob::back() {
check(0, "back on empty StrBlob"); // 检查容器非空
return data->back();
}
// 返回尾元素引用(常量版本)
const std::string& StrBlob::back() const {
check(0, "back on empty StrBlob"); // 检查容器非空
return data->back();
}
// 删除尾元素
void StrBlob::pop_back() {
check(0, "pop_back on empty StrBlob"); // 检查容器非空
data->pop_back();
}
// StrBlobPtr类:StrBlob的智能指针/迭代器
class StrBlobPtr {
public:
// 默认构造函数:初始化为未绑定状态
StrBlobPtr() : curr(0) {}
// 构造函数:绑定到指定StrBlob的指定位置
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
// 解引用操作符:返回当前位置的字符串引用
std::string& operator*() const {
auto p = check(curr, "dereference past end"); // 检查索引合法性
return (*p)[curr]; // 返回vector中curr位置的字符串
}
// 箭头操作符:返回当前位置的字符串指针
std::string* operator->() const {
return & this->operator*(); // 调用解引用操作符并返回地址
}
// 前置递增操作符:移动到下一个位置
StrBlobPtr& operator++();
// 前置递减操作符:移动到前一个位置
StrBlobPtr& operator--();
// 不等比较操作符:比较两个指针是否指向不同位置
bool operator!=(const StrBlobPtr& rhs) const {
return curr != rhs.curr;
}
// 赋值运算符:复制另一个StrBlobPtr的状态
StrBlobPtr& operator=(const StrBlobPtr& rhs) {
if (this != &rhs) { // 避免自我赋值
wptr = rhs.wptr; // 复制弱引用(不增加引用计数)
curr = rhs.curr; // 复制当前位置
}
return *this; // 返回自身引用,支持链式赋值
}
// assign函数:将指针重新绑定到指定StrBlob的指定位置
StrBlobPtr& assign(StrBlob& sb, size_t pos = 0) {
wptr = sb.data; // 绑定到新StrBlob的data
curr = pos; // 设置新位置
return *this; // 返回自身引用,支持链式调用
}
// assign函数:复制另一个StrBlobPtr的状态
StrBlobPtr& assign(const StrBlobPtr& rhs) {
if (this != &rhs) { // 避免自我赋值
wptr = rhs.wptr; // 复制弱引用
curr = rhs.curr; // 复制当前位置
}
return *this; // 返回自身引用
}
private:
// 弱引用:指向StrBlob的底层vector,不控制其生命周期
std::weak_ptr<std::vector<std::string>> wptr;
// 当前位置索引
size_t curr;
// 检查底层vector是否存在且索引是否合法
std::shared_ptr<std::vector<std::string>>
check(size_t, const std::string&) const;
};
// 检查底层vector是否存在且索引是否合法,返回shared_ptr
std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(size_t i, const std::string &msg) const {
auto ret = wptr.lock(); // 尝试获取shared_ptr(检查vector是否存在)
if (!ret)
throw std::runtime_error("unbound StrBlobPtr"); // vector已销毁
if (i >= ret->size())
throw std::out_of_range(msg); // 索引越界
return ret; // 返回shared_ptr,确保在使用期间vector不会被销毁
}
// 前置递增操作符实现
StrBlobPtr& StrBlobPtr::operator++() {
check(curr, "increment past end of StrBlobPtr"); // 检查当前位置合法
++curr; // 移动到下一个位置
return *this; // 返回自身引用
}
// 前置递减操作符实现
StrBlobPtr& StrBlobPtr::operator--() {
--curr; // 移动到前一个位置
check(curr, "decrement past begin of StrBlobPtr"); // 检查新位置合法
return *this; // 返回自身引用
}
// 返回指向StrBlob首元素的StrBlobPtr
StrBlobPtr StrBlob::begin() {
return StrBlobPtr(*this);
}
// 返回指向StrBlob尾后位置的StrBlobPtr
StrBlobPtr StrBlob::end() {
return StrBlobPtr(*this, data->size());
}
// 示例使用
int main() {
// 创建StrBlob并初始化
StrBlob sb = {"hello", "world", "!"};
// 使用StrBlobPtr遍历StrBlob
for (auto p = sb.begin(); p != sb.end(); ++p) {
// 使用解引用操作符获取字符串
std::cout << *p << std::endl;
// 使用箭头操作符调用字符串的成员函数
std::cout << p->size() << std::endl;
}
// 使用assign函数重新定位指针
StrBlobPtr p(sb);
p.assign(sb, 1); // 指向第二个元素("world")
std::cout << *p << std::endl; // 输出: world
return 0;
}
14.8 函数调用运算符
如果类兴义了调用运算符,则该类的对象称作函数对象。因为可以调用这种对象,所以我们说这些对象的“行为像函数一样”
含有状态的函数对象类
#include <iostream>
#include <vector>
#include <algorithm> // for std::for_each
class printstring {
private:
std::ostream& os;
char delim;
public:
printstring(std::ostream& os, char delim = '\n')
: os(os), delim(delim) {}
void operator()(const std::string& s) const {
os << s << delim;
}
};
int main() {
std::vector<std::string> vs = {"hello", "world", "!"};
// 使用for_each和printstring函数对象
std::for_each(vs.begin(), vs.end(), printstring(std::cerr, '\n'));
// 等价于以下循环
for (const auto& s : vs) {
std::cerr << s << '\n';
}
return 0;
}
lambda是函数对象
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
class ShorterString {
public:
bool operator()(const std::string &s1, const std::string &s2) const {
return s1.size() < s2.size();
}
};
int main() {
std::vector<std::string> strings = {"apple", "banana", "pear", "kiwi"};
auto min_str = std::min_element(strings.begin(), strings.end(), ShorterString());
if (min_str != strings.end()) {
std::cout << "The shortest string is: " << *min_str << std::endl;
}
return 0;
}
标准库定义的函数对象
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
int main() {
std::vector<std::string> svec = {"apple", "banana", "cherry", "date"};
// 使用greater<string>()作为比较函数对象进行排序
std::sort(svec.begin(), svec.end(), std::greater<std::string>());
for (const auto& str : svec) {
std::cout << str << " ";
}
std::cout << std::endl;
return 0;
}
可调用对象与function
不同类别可能具有相同都调用形式
#include <iostream>
#include <map>
#include <string>
#include <functional>
// 定义加法函数
int add(int a, int b) {
return a + b;
}
// 定义减法函数
int subtract(int a, int b) {
return a - b;
}
// 定义乘法函数
int multiply(int a, int b) {
return a * b;
}
// 定义除法函数
int divide(int a, int b) {
if (b == 0) {
std::cerr << "除数不能为0" << std::endl;
return 0;
}
return a / b;
}
int main() {
// 使用std::map来实现函数表
// 键是表示运算符的std::string对象,值是对应的函数对象(这里用std::function包装函数指针)
std::map<std::string, std::function<int(int, int)>> functionTable;
functionTable["+"] = add;
functionTable["-"] = subtract;
functionTable["*"] = multiply;
functionTable["/"] = divide;
int num1 = 10;
int num2 = 2;
std::string op = "/";
// 从函数表中查找对应运算符的函数
auto it = functionTable.find(op);
if (it != functionTable.end()) {
// 调用找到的函数进行计算
int result = it->second(num1, num2);
std::cout << num1 << " " << op << " " << num2 << " = " << result << std::endl;
} else {
std::cerr << "不支持的运算符" << std::endl;
}
return 0;
}
标准库function
function定义在functional头文件中。
重载函数与function
#include <iostream>
#include <map>
#include <string>
#include <functional>
// 自定义类Sales_data(简单示例,假设只有一个数据成员price)
class Sales_data {
public:
double price;
Sales_data(double p) : price(p) {}
};
// 定义用于计算两个整数相加的函数
int add(int i, int j) {
return i + j;
}
// 定义用于计算两个Sales_data对象相加的函数(这里简单将price相加)
Sales_data add(const Sales_data& s1, const Sales_data& s2) {
return Sales_data(s1.price + s2.price);
}
int main() {
// 定义一个map,键为string类型表示操作符,值为接受两个int参数并返回int结果的函数对象
std::map<std::string, std::function<int(int, int)>> binops;
// 以下插入操作会出错,因为存在函数重载,编译器不知道该用哪个add
// binops.insert({" + ", add});
// 修正方法一:使用lambda表达式明确指定插入的函数逻辑
binops.insert({" + ", [](int a, int b) { return add(a, b); }});
// 修正方法二:定义一个新的函数,明确调用int版本的add函数
int newAdd(int a, int b) {
return add(a, b);
}
binops.insert({" + ", newAdd});
// 测试插入后的函数调用
int result = binops["+"](3, 4);
std::cout << "计算结果: " << result << std::endl;
return 0;
}
14.9 重载、类型转换与运算符
类型转换运算符
- 特殊成员函数:类型转换运算符是类里面特殊的成员函数,和普通成员函数不同,它没有显式写出来的返回类型(但实际有转换后的类型 ),也没有参数。
- 转换功能:作用是把类类型的值转换为其他类型,这个其他类型可以是内置类型(像 int、double 等 ),也可以是自定义类型(比如其他类 ),但不能是 void ,也不能转换为数组或者函数类型,不过能转成指针(包括数组指针、函数指针 )或者引用类型。
- const 限定:一般定义成 const 成员函数,因为转换过程通常不应该改变被转换对象本身的内容。
类类型转换成内置类型
#include <iostream>
#include <string>
class MyInt {
private:
int value;
public:
MyInt(int v) : value(v) {}
// 类型转换运算符,将MyInt类型转换为int类型
operator int() const {
return value;
}
};
int main() {
MyInt num(5);
int result = num + 3; // 这里会自动调用operator int()将num转换为int类型
std::cout << "结果: " << result << std::endl;
return 0;
}
类类型转换为自定义类型
#include <iostream>
#include <string>
class SmallInt {
private:
int smallValue;
public:
SmallInt(int v) : smallValue(v) {}
operator int() const {
return smallValue;
}
};
class BigInt {
private:
int bigValue;
public:
BigInt(int v) : bigValue(v) {}
// 类型转换运算符,将BigInt类型转换为SmallInt类型
operator SmallInt() const {
// 这里简单处理,假设取bigValue的个位数作为SmallInt的值
return SmallInt(bigValue % 10);
}
};
int main() {
BigInt big(25);
SmallInt small = big; // 这里会自动调用operator SmallInt()将big转换为SmallInt类型
std::cout << "转换后的SmallInt值: " << static_cast<int>(small) << std::endl;
return 0;
}
转换为指针类型示例
#include <iostream>
class MyClass {
private:
int data;
public:
MyClass(int d) : data(d) {}
// 类型转换运算符,将MyClass类型转换为int*类型(这里只是示例,实际意义需根据场景确定)
operator int*() const {
return &data;
}
};
int main() {
MyClass obj(10);
int* ptr = obj; // 这里会自动调用operator int*()将obj转换为int*类型
std::cout << "指针指向的值: " << *ptr << std::endl;
return 0;
}
类型转换运算符可能产生意外结果
显式类型转换运算符
转为bool
避免有二义性的类型转换
第一种情况
假设我们有两个类 A 和 B ,以下代码展示了两个类提供相同类型转换导致的问题:
#include <iostream>
class B; // 前向声明
class A {
public:
// 转换构造函数,接受B类对象来构造A类对象
A(const B& b) {
std::cout << "A的转换构造函数被调用" << std::endl;
}
};
class B {
public:
// 类型转换运算符,将B类对象转换为A类对象
operator A() const {
std::cout << "B的类型转换运算符被调用" << std::endl;
return A(*this);
}
};
void func(A a) {
std::cout << "函数func接受A类对象" << std::endl;
}
int main() {
B b;
// 这里会产生二义性
// 编译器不知道是该调用A的转换构造函数,还是B的类型转换运算符
/// 调用时必须显式转换
//func(static_cast<A>(b)); // 明确指定使用B的转换运算符
func(b);
return 0;
}
第二种情况
以一个自定义类 MyNumber 为例,假设它有多种与算术类型相关的转换规则,这可能会引发问题:
#include <iostream>
class MyNumber {
private:
int value;
public:
MyNumber(int v) : value(v) {}
// 类型转换运算符,转换为int类型
operator int() const {
std::cout << "转换为int类型" << std::endl;
return value;
}
// 只允许显式转换
// explicit operator int() const { return value; }
// explicit operator double() const { return value; }
// 类型转换运算符,转换为double类型
operator double() const {
std::cout << "转换为double类型" << std::endl;
return static_cast<double>(value);
}
};
void func(int i) {
std::cout << "函数func接受int类型参数: " << i << std::endl;
}
void func(double d) {
std::cout << "函数func接受double类型参数: " << d << std::endl;
}
int main() {
MyNumber num(5);
// 这里会产生二义性
// 编译器不知道是该将num转换为int还是double
func(num);
return 0;
}
重载函数与转换构造函数
重载函数与用户定义的类型转换
显式转换
函数匹配与重载运算符
#include <iostream>
// 定义一个类
class MyClass {
private:
int value;
public:
MyClass(int v) : value(v) {}
// 重载 + 运算符,作为成员函数
MyClass operator+(const MyClass& other) const {
return MyClass(value + other.value);
}
};
// 重载 + 运算符,作为非成员函数
MyClass operator+(const MyClass& a, const MyClass& b) {
return MyClass(a.value + b.value);
}
int main() {
MyClass a(1);
MyClass b(2);
// 表达式 a + b ,这里编译器会考虑成员函数版本和非成员函数版本的 operator+
MyClass result = a + b;
std::cout << "Result: " << result.value << std::endl;
return 0;
}