C++ Primer 笔记——重载运算
1.对于二元运算符来说,左侧运算对象传递给第一个参数,而右侧运算对象传递给第二个参数。除了重载的函数调用运算符operator()之外,其他重载元素运算符不能含有默认实参。
class test
{
void operator() (int i = ) {}; // 正确, 但是operator()只能是成员函数
}; int operator+ (test t, int j = ); // 错误,不可以有默认实参
2.当一个重载的运算符是成员函数时,this绑定到左侧运算对象。成员运算符函数的(显示)参数数量比运算对象的数量少一个。
class test
{
public:
int operator+ (int i) { return i + ; };
}; test t;
int i = t + ;
3.对一个运算符函数来说,它或是类的成员,或者至少含有一个类类型或枚举类型的参数,所以我们无法改变内置类型的运算符的含义。
class test
{
}; int operator- (int i, test t); // 正确
int operator+ (int i, int j); // 错误,没有类类型或枚举的参数
4.以下是可重载和不可重载运算符的列表:
- 有四个符号(+,-,*,&)既是一元运算符,也是二元运算符,从参数的数量我们可以推断到底定义的是哪种运算符。
- 对于一个重载的运算符来说,其优先级和结合律与内置运算符保持一致。
- 赋值(=),下标([ ]),调用(( ))和成员访问箭头(->)运算符必须是成员
5.重载运算符函数的调用方法:
class test
{
public:
int operator+=(int i) { return + i; }
}; int operator- (int i, test t)
{
return i - ;
} test t;
// 以下两种调用是等价的
int i = - t;
i = operator-(, t); // 以下两种调用是等价的
i = t += ;
i = t.operator+=();
6.与iostream标准库兼容的输入输出运算符必须是普通的非成员函数,而不能是类的成员函数。否则,它的左侧运算对象将是我们的类的一个对象。
7.operator<<一般要返回它的ostream形参,operator>>一般要返回它的istream形参,输入运算符必须处理输入可能失败的情况。
8.定义递增和递减运算符的类应该同时定义前置版本和后置版本。为了与内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。后置运算符应该返回对象的原值,返回的形式是一个值而非引用。为了区分前置和后置运算符,后置版本接受一个额外的(不被使用)int型形参,当我们使用后置运算符的时候,编译器为这个形参提供一个值为0的实参,尽管从语法上来说后置函数可以使用这个额外的形参,但是实际过程中我们通常不会这么做。
class test
{
public:
test(int i) :m_count(i) {} test& operator++() // 前置运算符
{
++m_count;
return *this;
} test operator--(int) // 后置运算符
{
test tmp = *this;
--m_count;
return tmp;
} int m_count;
}; test t();
test t1 = ++t; // t1.m_count = 2
test t2 = t--; // t2.m_count = 2 // 显示调用
test t3 = t.operator++(); // t3.m_count = 2
test t4 = t.operator--(); // t4.m_count = 2
9.我们能令operator*完成任何我们指定的操作,比如返回一个固定的值等等,但是operator->获取成员的事实则永远不变,我们可以改变箭头是从哪个对象中获取成员,例如如果箭头的对象是string类型那么箭头获取的成员一定有size(),这个是无法改变的。重载的箭头运算必须返回类的指针或者自定义了箭头运算符的某个类的对象。
class test
{
public:
test(std::string name) :m_name(name) {} std::string* operator->()
{
return &(this->m_name);
} std::string m_name;
}; test t("test");
std::size_t i = t->size(); // i=4
10.函数调用运算符必须是成员函数,一个类可以定义多个不同版本的调用运算符,互相之间应该在参数或类型上有所区别。如果类定义了调用运算符,则该类的对象称作函数对象。
class test
{
public:
int operator()(int i)
{
return i > ? i : -i;
} }; test abs;
int i = abs(-); // i=1
11.标准库定义了一组表示算术运算符,关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符。这些类都被定义成模板的形式。
std::plus<int> test;
int i = test(, ); // i=30
12.我们可以将标准库函数对象运用在算法中,而且标准库规定其函数对象对于指针同样适用。
std::vector<std::string> vec = { "","","" };
std::sort(vec.begin(), vec.end(), std::greater<std::string>()); // 将默认的升序排列改为了降序排列 std::vector<std::string*> vecp = { new std::string(""), new std::string(""), new std::string("") };
// 错误,以下比较指针的大小是未定义的
std::sort(vecp.begin(), vecp.end(), [](std::string *a, std::string *b) { return a < b; }); // 正确,标准库规定指针的less是定义良好的
std::sort(vecp.begin(), vecp.end(), std::less<std::string*>());
13.我们可以使用一个名为function的新标准库类型来定义函数类型。function也是一个模板,function类型重载了调用运算符,该运算符接受它自己的实参然后将其传递给存好的可调用对象。
int add(int a, int b)
{
return a + b;
} auto mod = [](int a, int b) {return a % b; }; struct divide
{
int operator() (int a, int b) { return a / b; }
}; int main()
{
std::map<std::string, int(*)(int, int)> mapFunc =
{
{ "+", add }, // 正确,add是一个函数指针
{ "/", divide() }, // 错误,不是一个函数指针
{ "*", mod} // 错误,lambda是个类类型
}; std::function<int(int, int)> f1 = add; // 函数指针
std::function<int(int, int)> f2 = divide(); // 函数对象类的对象
std::function<int(int, int)> f3 = mod; // lambda int ret;
ret = f3(, ); // ret=1 std::map<std::string, std::function<int(int, int)>> mapFuncs =
{
{"+", add}, // 函数指针
{"-", std::minus<int>()}, // 标准库函数对象
{"/", divide()}, // 影狐定义的函数对象
{"*", [](int a, int b) { return a * b; }}, // 未命名的lambda
{"%",mod} // 命名了的lambda对象
}; ret = mapFuncs["+"](, ); // ret = 3 return ;
}
14.我们不能(直接)将重载函数的名字存入function类型的对象中,但是可以通过存储函数指针的方法。
int add(int a, int b)
{
return a + b;
} double add(double a, double b)
{
return a + b;
} std::map<std::string, std::function<int(int, int)>> mapFunc =
{
{ "+", add }, // 错误,分不清哪个add
}; int(*fp)(int, int) = add;
std::map<std::string, std::function<int(int, int)>> mapFuncs =
{
{ "+", fp }, // 正确,是int型的add
};
15.类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型,一般形式如下:
operator type() const;
- 其中type表示某种类型,类型转换运算符可以面向任意类型(void除外)进行定义,只要该类型能作为函数的返回类型。因此我们不允许转换成数组或函数类型,但允许转换成指针或引用类型。
- 类型转换运算符既没有显示的返回类型,也没有形参,而且必须定义成类的成员函数。通常不应该改变转换对象的内容,因此一般被定义成const成员。
- 因为类型转换运算符是隐式执行的,所以无法给这些函数传递实参,尽管类型转换函数不负责指定返回类型,但实际上每个类型转换函数都i会返回一个对应类型的值。
- 尽管编译器一次只能执行一个用户定义的类型转换,但是隐式的用户定义类型转换可以置于一个标准类型转换之后或之前。
class test
{
public:
test(std::size_t size) : m_size(size) {}
operator std::size_t() const { return m_size; } private:
std::size_t m_size;
}; test t = ; // 首先将3隐式的转换成test,然后调用赋值运算符
std::size_t i = t + ; // 首先将t隐式的转换为size_t,然后执行加法
double d = t + 3.14; // t先被转换成了size_t,后又被转换成double
test t1 = 3.14; // double被转换成了size_t
16.为了防止不必要的隐式自动类型转换,C++11引入了显示的类型转换运算符。
class test
{
public:
test(std::size_t size) : m_size(size) {}
explicit operator std::size_t() const { return m_size; } private:
std::size_t m_size;
}; test t();
std::size_t s = t + ; // 错误,运算符是显示的
std::size_t s1 = static_cast<std::size_t>(t) + ; // 正确
该规定存在一个例外,即如果表达式被用作条件,则编译器会将显示的类型转换自动应用于它:
- if , while及do语句的条件部分
- for语句头的条件表达式
- 逻辑与或非运算符的运算对象
- 条件运算符(? :)的条件表达式
17.如果类中包含一个或多个类型转换,则必须确保在类类型和目标类型之间只存在唯一一种转换方式。否则会产生二义性,只能通过显示调用转换运算符或转换构造函数来解决,强制类型转换也无法解决二义性问题。
class testex;
class test
{
public:
test() {}
test(const testex& t);
}; class testex
{
public:
operator test() const;
}; test func(const test&);
testex tex;
test t1 = func(tex); // 二义性错误,我们想把tex转成test类型,但是有两种方法
test t2 = func(tex.operator test()); // 正确
test t3 = func(test(tex)); // 正确
18.当我们使用两个用户定义的类型转换时,如果转换函数之前或者之后存在标准类型转换,则标准类型转换将决定最佳匹配到底是哪个。
class test
{
public:
test(int i = ) {}
test(double d) {} operator int() const {}
operator double() const {}
}; void func(long double);
test t;
func(t); // 二义性错误,不知道调用哪个operator long l;
test t1(l); // 理论上也会产生二义性,此时标准类型转换将决定最佳匹配是哪个
19.如果我们对同一个类既提供了转换目标是算术类型的类型转换,也提供了重载的运算符,则将会遇到重载运算符与内置运算符的二义性问题。
class test
{
friend test operator+(const test& t1, const test& t2);
public:
test(int i = ):m_count(i) {}
operator int() const { return m_count; } int m_count;
}; test operator+(const test& t1, const test& t2)
{
return test(t1.m_count + t2.m_count);
} test t1, t2;
test t3 = t1 + t2; // 正确
int i = t3 + ; // 二义性错误
C++ Primer 笔记——重载运算的更多相关文章
- C++ Primer笔记
C++ Primer笔记 ch2 变量和基本类型 声明 extern int i; extern int i = 3.14;//定义 左值引用(绑定零一变量初始值,别名) 不能定义引用的引用:引用必须 ...
- C++ Primer : 第十四章 : 重载运算与类型转换之重载运算符
重载前须知 重载运算符是特殊的函数,它们的名字由operator和其后要重载的运算符号共同组成. 因为重载运算符时函数, 因此它包含返回值.参数列表和函数体. 对于重载运算符是成员函数时, 它的第一个 ...
- 高放的c++学习笔记之重载运算与类型转换
▲基本概念 (1)重载运算符是具有特殊名字的函数,它们的名字又operator和其后要定义的运算符号共同构成.. (2)对于一个运算符号来说它或者是类的成员,或者至少含有一个类类型的参数. (3)我们 ...
- C++ Primer笔记10_运算符重载_赋值运算符_进入/输出操作符
1.颂值运营商 首先来福值运算符引入后面要说的运算符重载.上一节说了构造函数.拷贝构造函数:一个类要想进行更好的控制.须要定义自己的构造函数.拷贝构造函数.析构函数.当然,还有赋值运算符.常说的三大函 ...
- C++ Primer 5th 第14章 重载运算与类型转换
当运算符作用域类类型的对象时,可以通过运算符重载来重新定义该运算符的含义.重载运算符的意义在于我们和用户能够更简洁的书写和更方便的使用代码. 基本概念 重载的运算符是具有特殊名字的函数:函数名由关键词 ...
- 【c++ Prime 学习笔记】第14章 重载运算与类型转换
14.1 基本概念 重载的运算符是特殊的函数:名字由关键字operator后接要定义的算符共同组成,也有返回类型.参数列表.函数体. 重载运算符函数的参数量与该算符作用的运算对象数量一样多 除重载调用 ...
- C++ Primer笔记13_运算符重载_总结
总结: 1.不能重载的运算符: . 和 .* 和 ?: 和 :: 和 sizeof 和 typeid 2.重载运算符有两种基本选择: 类的成员函数或者友元函数, 建议规则例如以下: 运算符 建议使用 ...
- C++ Primer笔记12_运算符重载_递增递减运算符_成员訪问运算符
1.递增递减运算符 C++语言并不要求递增递减运算符必须是类的成员.可是由于他们改变的正好是所操作对象的状态.所以建议设定为成员函数. 对于递增与递减运算符来说,有前置与后置两个版本号,因此.我们应该 ...
- C++primer笔记之顺序容器
最近又重新拾起C++primer,发现每一次看都会有不同的体验,但每一次看后因为不常用,忘记得很快,所以记笔记是很关键的一环,咋一看是浪费时间,实际上是节省了很多时间.下面就把这一节的内容做一个简单的 ...
随机推荐
- Spring boot+Thymeleaf+easyui集成:js创建组件页面报错
开发工具:Ideal 使用场景:Demo 前提: 环境:Spring boot +Thymeleaf+easyui 引入thymeleaf模板引擎 <html lang=" ...
- 集合-HashMap
该文章的实践内容来自how2java网站,集合的学习篇章 1.概念: HashMap是以哈希表作为底层数据结构,以一组键值对作为存储单元的Map接口的实现类. 其主要特点是,容器内的元素不以添加顺序排 ...
- 【转载】双调排序Bitonic Sort,适合并行计算的排序算法
双调排序是data-independent的排序, 即比较顺序与数据无关的排序方法, 特别适合做并行计算,例如用GPU.fpga来计算. 1.双调序列 在了解双调排序算法之前,我们先来看看什么是双调序 ...
- FLASK-----基本知识(一)
中文文档(http://docs.jinkan.org/docs/flask/) 英文文档(http://flask.pocoo.org/docs/0.11/) FLASK介绍 Flask是一个基于P ...
- understand 在windows 以及 unbuntu 下的安装
1.win7 64位下安装 1)下载Understand.4.0.908.x64.rar. 2)解压之,直接运行里面的Understand-4.0.908-Windows-64bit.exe. 3)选 ...
- [sklearn] 实现随即梯度下降(SGD)&分类器评价参数查看
直接贴代码吧: 1 # -*- coding:UTF-8 -*- 2 from sklearn import datasets 3 from sklearn.cross_validation impo ...
- CSDN沙龙记录
Panel python踩过的坑 曹正: 原因:语言的理解不精准. 语言特性坑:函数的参数不可变类型的定义类似list[],惰性处理简而言之延后执行, 胡阳: gevent的问题,django连接池的 ...
- Linux中设置别名
作者:邓聪聪 查看别名: alias设置别名: 临时设置: alias show='ls -al' 永久生效: 修改 家目录/.bashrc [root@localhost ~]# cat .bash ...
- 题解-PKUWC2018 随机算法
Problem loj2540 题意简述:给定\(n\)个点的无向图,给定求最大独立集的近似算法:随机排列\(1\cdots n\),按照该排列顺序贪心构造最大独立集(即对每个点能加入独立集就加),求 ...
- struts2框架之输入校验(参考第二天学习笔记)
输入校验: 1. 分类 客户端校验:javascript,它是用户体验而已,可以绕开. 服务器端校验 * 代码校验 1). 要求Action必须继承ActionSupport 2). 重写Action ...