《C++ Primer》第14章 重载运算与类型转换(一)

这篇具有很好参考价值的文章主要介绍了《C++ Primer》第14章 重载运算与类型转换(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

参考资料:

  • 《C++ Primer》第5版
  • 《C++ Primer 习题集》第5版

14.1 基本概念(P490)

重载的运算符是具有特殊名字的函数,其名字有 operator 和要定义的运算符组合而成。和其他函数一样,重载运算符也具有返回类型、参数列表、函数体。

重载运算符函数的参数数量和该运算符的运算对象数量一样多,对于二元运算符来说,左侧运算对象传递给第一个参数,右侧运算对象传递给第二个参数。除了重载调用运算符 operator() 外,其他重载运算符不能有默认实参。

如果一个重载运算符函数是成员函数,则它的第一个运算对象隐式绑定到 this 指针上。

对于一个重载运算符函数来说,它至少含有一个类类型的参数

《C++ Primer》第14章 重载运算与类型转换(一),《C++ Primer》,c++,开发语言

有四个符号( +, -, *, &既是一元运算符也是二元运算符,具体定义哪种运算符由参数数量决定。

重载运算符不改变优先级结合律

直接调用一个重载运算符

我们可以像调用普通函数一样直接调用运算符函数:

data1 + data2;    // 间接调用
operator+(data1, data2);    // 直接调用
data1.operator+=(data2);    // 直接调用(成员函数)

某些运算符不应该被重载

前面提到过,某些运算符规定了求值顺序(如逻辑与、逻辑或、逗号),由于使用重载运算符本质上是一次函数调用,所以这些求值顺序将无法应用到重载运算符上。另外,逻辑与和逻辑或的短路属性在重载运算符中也不能实现。此外,C++ 语言已经定义了逗号取地址运算符作用于类类型时的含义,所以一般情况下我们也不应该重载它们。

总结:通常情况下,不应该重载逗号、取地址、逻辑与、逻辑或运算符。

使用与内置类型一致的含义

如果某些类操作在逻辑上与运算符相关,则它们适合被定义成重载运算符:

  • 如果类执行 IO 操作,则定义移位运算符使其与内置类型的 IO 保持一致。
  • 如果某个类的操作检查相等性,则定义 operator==operator!=
  • 如果类包含一个单序比较操作,则定义 operator< 和其他比较操作。
  • 重载运算符的返回类型应与内置版本的返回类型兼容:逻辑运算符和关系运算符应返回 bool ;算术运算符返回一个类类型的值;赋值和复合赋值运算符返回左侧运算对象的引用。

选择作为成员或非成员

当我们定义重载运算符时,需要确定将其声明为类的成员函数还是普通函数。下面的准则有助于我们做出选择:

  • 赋值、下标、调用、成员访问箭头运算符必须是成员
  • 复合赋值运算符一般是成员
  • 改变对象状态与给定类型密切相关的运算符,如递增、递减、解引用,通常应该是成员
  • 具有对称性的运算符通常应该是非成员函数

关于最后一点,这里着重解释一下。当我们把运算符定义成成员函数时,它的左侧运算对象必须是运算符所属类的一个对象。而如 + 这种具有对称性的运算符,常常会遇到下面这种情况:

string s = "hello";
string t = s + "!";    // 正确
t = "!" + s;

如果 string 的重载 + 是成员函数,那么最后一条语句就是错误的,我们显然不希望这种情况发生。

14.2 输入和输出运算符(P494)

14.2.1 重载输出运算符<<(P494)

通常情况下,输出运算符的第一个形参是一个非常量 ostream 对象的引用,第二个形参是一个常量的引用,返回值为 ostream 形参(引用类型)。

Sales_data的输出运算符

ostream &operator<<(ostream &os, const Sales_data &item){
    os << item.isbn() << " " << item.units_sold << " "
       << item.revenue << " " << item.avg_price();
    return os;
}

输出运算符应尽量减少格式化操作

内置类型的输出运算符不太考虑格式化操作,尤其不会打印换行符

输入输出运算符必须是非成员函数

IO 运算符一般被声明成类的友元。

14.2.2 重载输入运算符>>

Sales_data的输入运算符

istream &operator>>(istream &is, Sales_data &item){
    double price;
    is >> item.bookNo >> item.units_sold >> price;
    if(is)    // 检测输入是否成功
        item.revenue = item.units_sold * price;
    else    // 输入失败,对象被赋予默认状态
        item = Sale_data();
    return is;
}

输入运算符必须处理输入失败的情况

输入时的错误

当读取操作发生错误时,输入运算符应该负责从错误中恢复。

14.3 算术和关系运算符(P497)

通常情况下,我们把算术运算符关系运算符定义成非成员函数,以允许左侧或右侧运算对象进行转换。

算术运算符通常会计算它的两个运算对象并得到一个新值,这个新值常常位于一个局部变量之内,最后返回该局部变量的副本。如果一个类定义了算术运算符,一般也会定义对应的复合赋值运算符,此时最有效的方式是用复合赋值来定义算术运算符

Sales_data
operator+(const Sales_data &lhs, const Sales_data &rhs) {
	Sales_data sum = lhs;
	sum += rhs;
	return sum;
}

14.3.1 相等运算符(P497)

bool operator==(const Sales_data &lhs, const Sales_data &rhs) {
	return lhs.isbn() = rhs.isbn() &&
		lhs.units_sold == rhs.units_sold
		lhs.revenue == rhs.revenue;
}
bool operator!=(const Sales_data &lhs, const Sales_data &rhs) {
	return !(lsh == rhs);
}

14.3.2 关系运算符(P498)

前面提到过,关联容器和一些算法需要用到小于运算符,所以定义 operator< 会比较有用。此时需要注意,如果类同时也含有 == 运算符,应保证:如果两个对象 == ,则不应有一个对象 < 令一个对象成立;如果两个对象 != ,则必有一个对象 < 另外一个对象。

14.4 赋值运算符(P499)

标准库 vector 支持用花括号内的元素列表赋值:

vector<string> v;
v = {"a", "an", "the"};

同样地,我们也为 StrVec 添加这种赋值方法:

class StrVec{
public:
    StrVec &operator=(initializer_list<string>);
};

StrVec &StrVec::operator=(initializer_list<string> il) {
	auto data = alloc_n_copy(il.begin(), il.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}

复合赋值运算符

复合赋值运算符不必须是类的成员,但一般还是将其设计成成员函数:

Sales_data &Sales_data::operator+=(const Sales_data &rhs) {
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;
	return *this;
}

14.5 下标运算符(P501)

下标运算符必须是成员函数。为了与下标的原始定义兼容,下标运算符通常以所访问元素的引用作为返回值。同时,我们最好同时定义下标运算符的常量版本非常量版本

class StrVec{
public:
    string &operator[](size_t n)
        { return elements[n]; }
    const string &operator[](size_t n) const
        { return elements[n];}
}

14.6 递增和递减运算符(P502)

C++ 并不要求递增和递减运算符必须是类的成员,但因为它们改变所操作对象的状态,所以建议将其设定为成员函数/

定义前置递增/递减运算符

class StrBlobPtr {
public:
	StrBlobPtr &operator++();
	StrBlobPtr &operator--();
};

StrBlobPtr &StrBlobPtr::operator++() {
	check(curr, "increment past end of StrBlobPtr");
	++curr;
	return *this;
}
StrBlobPtr &StrBlobPtr::operator--() {
	// curr为无符号类型,如果curr为0,--后将得到一个很大的正数
	--curr;
	check(curr, "increment past end of StrBlobPtr");
	return *this;
}

区分前置和后置运算符

前置和后置版本使用的是同一个符号,并且运算对象的数量和类型也相同。为了区分前置版本和后置版本,后置版本接受一个额外的、不被使用的 int 类型形参,当我们使用后置运算符时,编译器为这个形参提供值为 0 的实参:

class StrBlobPtr {
public:
	StrBlobPtr &operator++(int);
	StrBlobPtr &operator--(int);
};

// 无需为int形参命名
StrBlobPtr &StrBlobPtr::operator++(int) {
	StrBlobPtr ret = *this;    // 记录当前的值
	++*this;
	return ret;
}
StrBlobPtr &StrBlobPtr::operator--(int) {
	StrBlobPtr ret = *this;    // 记录当前的值
	--*this;
	return ret;
}

显式地调用后置运算符

StrBlobPtr p(a1);
p.operator++(0);    // 显式调用后置版本
p.operator++();    // 显式调用前置版本

14.7 成员访问运算符(P504)

迭代器类和智能指针类常常用到解引用运算符和箭头运算符:

class StrBlobPtr {
public:
	string &operator*() const {
		auto p = check(curr, "dereference past end");
		return (*p)[curr];
	}
	string *operator->() const {
		return &(this->operator*());
	}
    // ...
};
StrBlob a1 = {"hi", "bye", "now"};
StrBlobPtr p(a1);
*p = "okay";

对箭头运算符返回值的限定

对于形如 point->mem 的表达式来说,point 必须是指向类对象的指针或者是一个重载了 operator->类对象,根据 point 类型的不同,point->mem 分别等价于:

(*point).mem;
point.operator()->mem;

point->mem 的执行过程:文章来源地址https://www.toymoban.com/news/detail-808583.html

  1. 如果 point 是指针,则我们应用内置箭头运算符,表达式等价于 (*point).mem
  2. 如果 point 是定义了 operator-> 的类的一个对象,则我们使用 point.operator->() 的结果来获取 mem 。如果结果是一个指针,则执行第 1 步;否则重复调用当前步骤。

到了这里,关于《C++ Primer》第14章 重载运算与类型转换(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 | 再谈构造函数:初始化列表,隐式类型转换,缺省值)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 取地址及const取地址操作符重载 再谈构造函数 初始化列表 隐式类型转换 explicit 成员变量缺省值 结语 本篇主要内容:类的六个默认成员函数中的 取地址 及 const取地址重载 , 构造函数 初始化列表 , 隐式类型转换

    2024年04月26日
    浏览(36)
  • 【C++】详解运算符重载,赋值运算符重载,++运算符重载

    目录 前言 运算符重载 概念 目的 写法 调用 注意事项 详解注意事项 运算符重载成全局性的弊端 类中隐含的this指针 赋值运算符重载 赋值运算符重载格式 注意点 明晰赋值运算符重载函数的调用 连续赋值 传引用与传值返回 默认赋值运算符重载 前置++和后置++重载 先梳理一下

    2024年04月25日
    浏览(64)
  • C++ Primer Plus笔记: 2023.07.14

    第五章编程题: (1) 第一种解法: 第二种解法: 第三种解法: (2) (3) 第一种解法: 第二种解法: 第三种解法: (4) 第一种解法: 第二种解法: (5) (6) (7)

    2024年02月16日
    浏览(25)
  • 【C++】运算符重载案例 - 字符串类 ⑤ ( 重载 大于 > 运算符 | 重载 小于 < 运算符 | 重载 右移 >> 运算符 - 使用全局函数重载 | 代码示例 )

    使用 成员函数 实现 等于判断 == 运算符重载 : 首先 , 写出函数名 , 函数名规则为 \\\" operate \\\" 后面跟上要重载的运算符 , 要对 String a , b 对象对比操作 , 使用 大于 运算符 , 使用时用法为 a b ; 函数名是 operate ; 然后 , 根据操作数 写出函数参数 , 参数一般都是 对象的引用 ; 要对

    2024年02月07日
    浏览(38)
  • C++——运算符重载

    运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。 运算符重载的目的是让语法更加简洁 运算符重载不能改变本来寓意,不能改变基础类型寓意 运算符重载的本质是另一种函数调用(是编译器去调用) 这个函数统一的名字叫opera

    2024年02月16日
    浏览(40)
  • 【C++】运算符重载

    目录 1. 基本概念 1.1 直接调用一个重载的运算符函数 1.2 某些运算符不应该被重载 1.3 使用与内置类型一致的含义 1.4 赋值和复合赋值运算符 1.5 选择作为成员或者非成员 2. 输入和输出运算符 2.1 输出运算符重载 2.2 输入运算符重载 3. 算术和关系运算符 3.1 算数运算符重载 3.2

    2024年02月11日
    浏览(37)
  • C++:重载运算符

    1.重载不能改变运算符运算的对象个数 2.重载不能改变运算符的优先级别 3.重载不能改变运算符的结合性 4.重载运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应该有一个是类对象,或类对象的引用 5.重载运算符的功能要类似于该运算符作用于标准类型数据

    2024年02月10日
    浏览(37)
  • C++运算符号重载详解

    接下来我们用来举例的代码 目录 1)=   重载 2)    重载 3)  =    重载 4)   重载 5)=   重载 6)==   符号 7)+  重载 8)+=   重载 9)   流输出重载 10)    输入流重载 总结: 1)=   重载 很多人认为 在不是初始化的时候利用 =  号是拷贝构造函数实现的,其实不然,

    2024年02月05日
    浏览(43)
  • C++ 面向对象(3)——重载运算符和重载函数

    C++ 允许在同一作用域中的某个 函数 和 运算符 指定多个定义,分别称为 函数重载 和 运算符重载 。 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。 当您调用一个 重载函数 或 重载运算符 时

    2024年02月10日
    浏览(39)
  • C++,运算符重载——关系运算符练习

    一、关系运算符重载 = = == !=  二、知识点整理  

    2024年02月11日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包