C++ 小白 学习记录14

第十四章 重载运算符和类型转换

14.1 基本概念

一元运算符有一个参数.

二元运算符: 左侧运算对象传递给第一个参数, 右侧为第二个参数.

除了重载的函数调用运算符operator() 之外 都不能含有默认实参.

如果运算符函数是成员函数, 则第一个运算对象隐式的为this指针.

当运算符作用于内置类型的运算对象时, 不能改变该运算符的含义: 即:不能重定义int类型的+ 运算符

只能重载已有的运算符, 不能发明 创造.

可以被重载的运算符:

 不能被重载的运算符:

 

如果类含有算术运算符或者位运算符, 则最好也提供对应的复合赋值运算符, 因为可能会使用+=运算符.

是否将运算符定义为成员/非成员函数的判断方法:

  • 赋值=, 下标[], 调用 (), 成员访问箭头->  必须是成员函数
  • 符合运算符一般是成员, 但不是必须
  • 改变对象状态的运算符或者与给定类型密切相关的, 如++, --, 解引用* 通常为成员函数
  • 对称性的运算符可能转换任意一端的运算对象 如 算术, 相等性, 关系和位运算 通常是非成员函数, 即:可以任意交换两者运算位置的不能是成员函数, 成员函数默认第一个参数是this

14.2 输入和输出运算符

输出运算符应该打印换行符, 通常只负责打印内容, 不输出格式.

输出函数必须是非成员函数.

输入运算符必须处理输入可能失败的情况, 输出运算符不需要.

输入运算符需要处理输入错误的情况.

14.3 算数和关系运算符

14.3.1 相等运算符

如果定义了==, 也应该定义!=, 反之依然,  基本上是基于使用喜欢来的.

14.3.2 关系运算符

< > 的定义要符合实际需求, 需要通盘考虑类中个成员变量的实际情况.

14.4 赋值运算符

赋值运算符必须是成员函数, 返回一个左值.

注意 类似于 vector中的 ={} 的赋值情况

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;
}

14.5 下标运算符

下标运算符必须是成员函数, 通常有两种版本的返回值: 普通引用和常量引用

14.6 递增和递减运算符

作为成员函数

前置版本的定义:  注意递增和递减定义中检查指针的顺序.

StrBlobPtr& operator++();
StrBlobPtr& operator--();

StrBlobPtr& StrBlobPtr::operator++() {
    check(xxxx); // 检查curr是否已经指向了尾后位置, 否则将抛出异常
    ++curr;
    return *this;
}
StrBlobPtr& StrBlobPtr::operator--() {
    --curr; // 如果curr是0, 继续递减将产生一个无效下标
    check(xxxx);
    return *this;
}

后置版本的定义. 后置版本需要提供一个无用的int形参, 用于区分前置版本. 返回值是一个值而非引用.

// 此处提供的形参int 没有任何意义, 仅仅用于区别该版本函数是后置版本
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;
}

前置,后置版本区别

  • 前置返回引用, 后置返回值
  • 前置需要验证有效性, 后置不需要
  • 前置没有参数, 后置有一个无用形参用于区分前置还是后置

14.7 成员访问运算符

解引用 和 箭头 函数 都是成员函数.

string& operator*() const;
string* operator->() const;

箭头函数 等价于  (*p).mem 和 p.operator()->mem;

重载的箭头运算符 必须 返回类的指针 或者 自定义了箭头运算符的某个类的对象.

14.8 函数调用运算符

struct absInt {
	int operator()(int val) const { return val < 0 ? -val : val; }
};
int i = -32;
absInt absObj;
int ui = absObj(i);

感觉像 对象 增加了一个默认函数.

必须是成员函数, 可以定义多个, 根据参数区别.

14.8.1 lambda 是函数对象

14.8.2 保准库定义的函数对象

一些运算符的类方法表示<functional>:

例如:

plus<int> intAdd;
int sum = intAdd(10,20); // sum = 30

 其中一个用途:

sort默认使用<比较, 然后排序,  可以使用greater<T>() 更改sort 的默认排序比较运算符为>, 从而更改了sort排序的方法.

14.8.3 可调用对象与function

使用map 存储具有相同调用形式的函数. 所谓具有相同调用形式如:

int add(int, int)

int mod(int, int)

int divide(int, int)

等,  无论函数内部是如何执行的, 但是其共同特点就是 定义的时候 具有相同参数类型和个数, 具有相同的返回值类型. 这类函数可以使用统一的调用方式 int x(int, int). 这些函数可以放进一个map 中.

map<string, int(*)<int,int>> binmap;
binmap.insert({"+", add});  // add 是指向add函数的指针

但是 如何能够 把一个lambda也放进去呢? 由此出现了function类型, 可以理解为function 重新包装函数.

function<int(int, int)> fAdd = add;
function<int<int, int>> fDivide = divide();
function<int<int, int>> fLambda = [](int i, int j) {return i*j;};

此时 更改map的定义为 

map<string, function<int(int, int)>> exeMaps;
exeMaps.insert({"+", add});
exeMaps.insert({"*", fLambda});

// 调用
exeMaps["+"](10, 10);
exeMaps["*"](2, 33);

即: 只要具有相同调用方式的函数, 包括lambda表达式, 都可以添加进map. 在调用是则可充分 展示此种方法的便利性.

重载的函数如何使用此种方法呢?  重载时 函数名 相同

int add(int a, int b) {return a+b;}
ClassA add(const ClassA&, const Class&);

map<string, function<int<int, int)>> exeMaps;
exeMaps.insert({"*", add});  //此处 无法确定 add 究竟指向的是哪一个

可以使用函数指针:

int (*fAdd)(int, int) = add; // 因定义是指明了函数的参数类型和返回值, 
// 由此可以确定add是指的第一个函数

exeMaps.insert({"+", fAdd});

也可以包装一层lambda 以确定使用哪个 add:

exeMaps.insert({"+", [](int a, int b){return add(a, b);}});

14.9 重载 类型转换与运算符

14.9.1 类型转换运算符

负责将一个类类型的值转换成其他类型. 格式 

operator type() const;
  • 必须是类的成员函数
  • 不能声明返回类型 没有必要, 目标类 已经说明了返回值的类型
  • 形参列表必须为空  因为类型转换符是隐式执行的, 所以无法传递参数
  • 通常应该是const

一个小例子:

class SamllInt {
public:
	SamllInt(int i = 0) : val(i) {
		if (i < 0 || i> 255)
			throw std::out_of_range("Invalid value");
	}
	operator int() const { return val; }  // 此处就是类型转换运算符
private:
	std::size_t val;
};

// 使用

SmallInt s =3;
s+3; // 此处发生隐式转换,  SamllInt  -> int, 调用了int()

隐式类型转换运算符 需要谨慎, 否则 运算结果 将与预期结果相去万里.

class SmallInt{
public:
	SmallInt(int i = 0) : val(i) {
		if (i < 0 || i> 255)
			throw std::out_of_range("Invalid value");
	}
	explicit operator int() const { return val; }  // 此处就是类型转换运算符
private:
	std::size_t val;
};

// 调用
SmallInt s = 3;
s+3;  // 抛出类型不匹配的错误, 不会隐式调用int() 进行类型转换
static_cast<int>(s) + 3; // 显示调用int() 执行类型转换

显式调用被自动调用的地方: 被用作条件时:

  • if while do的条件部分
  • for 条件表达式中
  • 逻辑非运算符!, 逻辑或运算符||, 逻辑与运算符&&
  • 条件运算符 ? :

向bool的类型转换通常用在条件部分, 因此operator bool 一般定义成 explicit.

14.9.2 避免有二义性的类型转换

两种情况可能会产生二义性转换:

  • A的构造函数可以把B转换为A, B中又定义转换为A的转换构造函数.
  • 定义多个转换规则, 而设计的类型本身可以通过其他类型转换联系在一起.

对于算术类型 需要尤其小心, 尽量不要构建两个或以上  源/目标 是 算术类型 的转换.

除了显式的向bool类型的转换之外, 应当尽量避免定义类型转换函数, 并尽可能地限制那些非显式构造函数.

// 14.51

转换匹配顺序:

  1. 精确匹配
  2. 常量版本
  3. 类型提升
  4. 算术转换或指针转换   14.51 使用的算术转换
  5. 类类型转换
void calc(string a) { cout << "int a" << endl; };
void calc(LongDouble l) { cout << "LongDouble l" << endl; }

int main(int argc, char** argv) {
	//14.51
	double dval = 1.0;
	calc(dval); // 将使用类类型转换, 结果为LongDouble l
}
// 如果 void calc(int a)   calc(dval)  // 将使用算术类型转换  结果为int a
// 如果 void calc(double d) { cout << "double d" << endl; } 结果 姜维 double d

14.9.3 函数匹配与重载运算符

表达式中运算符的后院函数集既包括成员函数, 也应该包括非成员函数.

class SmallInt {
	friend SmallInt operator+(const SmallInt&, const SmallInt&);
public:
	SmallInt(int i = 0) : val(i) {
		if (i < 0 || i> 255)
			throw std::out_of_range("Invalid value");
	}
	explicit operator int() const { return val; }  // 此处就是类型转换运算符
private:
	std::size_t val;
};

int main(int argc, char** argv) {
	SmallInt s1, s2;
	SmallInt s3 = s1 + s2; // 此处可以, 调用SmallInt的+
	int a = s3 + 0; // 此处具有二义性, 既可以 把0 转成SmallInt, 也可以把S3 转成int
}

如果一个类既定义了转换目标是算术类型的类型转换, 也提供了重载的运算符, 则 将会遇到重载运算符与内置运算符的二义性问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

yutao1131

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

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

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

打赏作者

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

抵扣说明:

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

余额充值