C++ Primer (第五版)-第十四章重载运算与类型转换

一、基本概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以被重载

在这里插入图片描述

某些运算符不应被重载

在这里插入图片描述

通常:不应该重载逗号、取地址、逻辑与/或运算符

尽量明智使用运算符重载

在这里插入图片描述

赋值和复合赋值运算符

在这里插入图片描述

选择作为成员或者非成员

在这里插入图片描述
在这里插入图片描述

输入和输出运算符

在这里插入图片描述

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是函数对象

在这里插入图片描述
ppppppppp

#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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓纪同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值