当前位置: 首页 > news >正文

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

文章目录

  • 一、基本概念
    • 可以被重载
    • 某些运算符不应被重载
    • 尽量明智使用运算符重载
    • 赋值和复合赋值运算符
    • 选择作为成员或者非成员
  • 输入和输出运算符
    • 输入运算符尽量减少格式化操作
      • 输入输出运算符必须是非成员函数
    • 重载输入运算符>>
    • 输入时的错误
    • 标示错误
  • 算数和关系运算符
    • 相等运算符
    • 关系运算符
    • 赋值运算符
    • 下标运算符
    • 递增和递减运算符
      • 定义前置递增、递减运算符
      • 区分前置和后置运算符
    • 成员访问运算符->
    • 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;elseitem.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;// 默认构造函数:初始化空的vectorStrBlob() : data(std::make_shared<std::vector<std::string>>()) {}// 带初始化列表的构造函数:用初始值列表初始化vectorStrBlob(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;// 返回指向容器首元素的StrBlobPtrStrBlobPtr begin();// 返回指向容器尾后位置的StrBlobPtrStrBlobPtr 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的datacurr = 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遍历StrBlobfor (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;  // 输出: worldreturn 0;
}

在这里插入图片描述

14.8 函数调用运算符

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果类兴义了调用运算符,则该类的对象称作函数对象。因为可以调用这种对象,所以我们说这些对象的“行为像函数一样”

含有状态的函数对象类

#include <iostream>
#include <vector>
#include <algorithm>  // for std::for_eachclass 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还是doublefunc(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;
}

相关文章:

  • 图像匹配导航定位技术 第 8 章
  • lammps原子组速度的计算方法
  • CMake笔记(简易教程)
  • 【hadoop】Hbase java api 案例
  • 【Java ee初阶】初始网络
  • 接口-DAO模式
  • AI Agent开发之门:微软官方课程全面解析
  • SpringBoot整合PDF导出功能
  • 机器人领域和心理学领域 恐怖谷 是什么
  • WSL部署CosyVoice
  • 零基础入门Hadoop:IntelliJ IDEA远程连接服务器中Hadoop运行WordCount
  • Redis协议与异步方式
  • 编写大模型Prompt提示词方法
  • 问题 | 当前计算机视觉迫切解决的问题
  • LangChain框架-PromptTemplate 详解
  • 2025.5.8总结(中期审视)
  • 关于MySQL 数据库故障排查指南
  • uniapp|获取当前用户定位、与系统设定位置计算相隔米数、实现打卡签到(可自定义设定位置、位置有效范围米数)
  • #define ccw (0)和#define ccw 0什么区别
  • javaer快速从idea转战vscode
  • 马上评丨行人转身相撞案:走路该保持“安全距离”吗
  • 山寨“小米”智能马桶、花洒销售额过亿,被判赔3500万元
  • 扶桑谈|素称清廉的石破茂被曝受贿,日本政坛或掀起倒阁浪潮
  • 戴维·珀杜宣誓就任美国驻华大使
  • 上海:5月8日起5年以上首套个人住房公积金贷款利率下调至2.6%
  • 马上评|持续对标国际一流,才有22项“全球最优”