本文预览:
- 转换函数 与 non-explict-one-argument Ctor
- pointer-like class
- function-like class
- Reference经验谈
前言
本篇会提到一些琐碎的东西,虽然有一些在我们编写代码的过程中不经常用到,但是都是一些帮助你理解C++底层次的东西。网上有人开玩笑说,一个java程序员学了三年,师傅对他说,你已经精通java了,可以下山了;一个C++程序员,学了十年,师傅对他说,你可以下山了,但是不要说你精通C++。这当然只是个笑话,但是从侧面反映出一个深刻的道理,那就是C++坑大水深,入坑需谨慎~。既然你已经入坑了,不想就此自费武功,那就好好修炼内功吧。
转换函数 与 non-explict-one-argument Ctor
转换函数是什么鬼?类型转换嘛,顾名思义。转换函数记住一句话就好了,有转出去和转进来之分。
举个例子:设计一个分数类 x/y
class Fraction {//这个分数类还是非常简单的嘛
public:
Fraction(int x, int y = 1):_x(x),_y(y){}
inline int x() const {return _x;}
inline int y() const {return _y;}
operator double() const
{
return (double)(_x ) / (double)(_y);
}
Fraction operator+(const Fraction& f)
{
return Fraction(...);
}
private:
int _x; //分子
int _y; //分母
};
分数类设计好了,使用的时候出现问题了:
Fraction f(3,5);
double d = 4 + f;
一个整数加一个分数,能编译通过吗?我们是不是还需要重载一个+操作符来来实现运算呢?现在好像是不用了,编译器能通过编译,计算结果也是正确的。你也许已经注意到了:
operator double() const
{
return (double)(_x ) / (double)(_y);
}
这就是转换函数的语法,没有返回值类型,函数名称就已经说明了你需要转换出去的类型。C++编译器会这么在遇到 4+f的时候首先会去寻找operator+(int),都没有int可以稳定转成double, operator+(double)肯定是有的,double+double类型能通过吗?找找看Fraction能转成double类型吗?operator double(),定义了转换到double类型的函数,可以转换。
现在我们把转换函数去掉,Fraction不能转成double了:
Fraction f(3,5);
double d = f + 4;
我们也没有operator+(const double) ,但是编译能过吗?我们重载了一个operator+(const Fraction&),这个必须是要有的,没有重载+肯定是编译不过的,也就是说,我们这次的+,肯定是用到了operator+(const Fraction),那么好了,如果能编译通过,肯定是int转成了Fraction类型了。但是,我们不可能去修改C++语言内核,在int里也加一个转换函数吧。为题出在哪里了?它怎么自动转的,这就用到了non-explict-one-argument Ctor, 自动调用了Fraction的构造函数,把int转换成了Fraction。
注意:
Fraction(int x, int y = 1):_x(x),_y(y){}
// explict Fraction(int x, int y = 1):_x(x),_y(y){}
这种构造函数叫做non-explict-one-argument Ctor,前面没有explict修饰,并且只有一个实参,第二个参数是有默认值的,如果没有默认值就不能叫one-argument。如果没有加explict编译器是可以自行调用,把其他类型转进来的。加了explict,编译就不通过了。
pointer-like class
pointer-like class:把class设计出来当做指针用。在智能指针和迭代器都是这么设计的。
- 智能指针的设计
智能指针被C++设计用来管内存,写法相当固定,必须要重载的和->,解引用这个应该都是没有什么困惑,但是问题在->这个问题就出来了,sp->method()调用返回的是T啊,在第一次已经使用了->,返回的T*还怎么调用method(),C++语法定义了->有继续作用的特性。这个问题就解决了。我们第一次虽然使用了->,但是由于C++语法规定,->能继续作用,因此,智能指针这么设计是没有问题的。
- 迭代器的设计
迭代器在STL里大部分都被设计成了class,迭代器的使用方法也很像是指针,我们以list的迭代器来看,是如何重载指针的解引用和调用操作符的。
在list中*操作符将获取元素对象,也就是list_no
![Uploading 屏幕快照 2017-03-08 16.00.50_078916.png . . .]
de<T>的data部分;->操作符获得data的指针。这样,我们就能使用迭代器,直接获取list元素对象和调用相应的方法了。
function-like class 所谓仿函数
function-like class: 将类设计出来,当做函数使用,又叫仿函数。其实质就是在里面重载了()操作符
STL里面的算法less实现:
template <class T>
struct less
{
bool operator()(const T& x, const T& y) const {return x < y; }
}
这是算法吗?分明就是一个class,这是一个class吗?这不是仿函数吗?自己好像很少用到仿函数吧,C++11里面出现了lamda表达式之后对仿函数简直就是秒杀有没有。但是它确实在STL里面有大量的应用。
bool ret = less<int>()(2,1); //第一个()表示是一个临时对象,第二个()才真正调用了operator()
cout<<ret<<endl;
float d = plus<float>()(1.2,2.1);
cout<<d<<endl;
以上是使用示例。
Reference经验谈
如果从内存角度看一个变量,那么变量大概可以分为三类:
- value : 分配一块内存空间放值
- pointer : 分配一块内存空间放地址
- reference : 分配一块地址空间,放一个对象,这个对象代表了他所引用的对象
int x = 0;
int& r = x;
cout<<x<<endl;
cout<<&x<<endl;
cout<<sizeof(x)<<endl;
cout<<r<<endl;
cout<<&r<<endl;
cout<<sizeof(r)<<endl;
你所看到的r和x所有的都是一样的,于是我们称reference是object的别名。但是它的内部实现是指针,但是它屏蔽了指针的概念和用法,在用法上和它所引用的对象是一样的。
关于引用需要知道:
- 一个引用变量在创建的时候要有初始值;
- 通常不用做变量声明,而作为参数传递;
- java里面的所有变量都是reference;
- 修改引用,也会修改它所代表的对象;
- 实现是指针,逻辑上我们理解为代表或者别名。