[GeekBand] C++ 高级编程技术 (1)
0赞一、类型转换
class Fraction
{
public:
explicit Fraction(int num,int den=1)
:m_numerator(num),m_denominator(den)
{cout<<m_numerator<<' '<<m_denominator<<endl;}
......
operatordouble()const{
return(double)m_numerator/m_denominator;
}
......
private:
int m_numerator;//
int m_denominator;//
};
转出去——利用转换函数(Conversion Function)
从一个类中转出去的方法是对int(),doube()等"操作符"进行重载。
值得注意的一点是,所有的转换函数都不需要声明返回参数类型,也不需要输入参数。可以通过(double) obj_of_Fraction 进行调用。
除了显式使用转换函数以外,转换函数也会进行隐式调用,例如:
Fraction f(3,5);
double d=4+f;
在这种情况下,首先,编译器会检查是否存在+运算符的合适的函数重载。如果没有合适的函数重载被定义,则会先将f隐式转换为double,再进行相加。
转进来——合理使用explicit关键字避免二义性
构造函数可以实现"转进来"的目的。Fraction(4)可以创造一个分数,其分母为1,分子为4。
同样地,"转进来"的构造函数也可以被隐式调用。
例如对于语句 Fraction d2 = f + 4,第一步,编译器仍然会检查是否存在+运算符的合适的重载Fraction::operator +(double);如果没有合适的函数重载被定义,则会将4转换为分数,然后再进行相加。
不过,这种隐式转换有时反而是不利的,可能会导致二义性。考虑下面这种情况,如果在类的定义中存在下面的函数:
Fractionoperator+(const Fraction&f){
cout<<"operator+(): "<<f.m_numerator<<' '<<f.m_denominator<<endl;
//... plus
returnf;
}
那么,对于d2 = f + 4;有以下两种路径,产生了二义性从而不能通过。
- f转double->double 相加->结果转回Fraction->赋值
- 4转Fraction->Fraction相加->赋值
为了消除这一二义性,在构造函数前面加了explicit关键字。Explicit关键字的意义是,这个函数只可以被显示调用,而不能被任何形式地隐式调用。 (由于这种声明,Fraction a = 1也不能被调用了。)
如果给拷贝构造函数加explicit关键字,则Fraction B(A) 可以使用,Fraction B = A也不能使用了。
二、pointer-like class
1) 智能指针
template<class T>
class shared_ptr{
public:
T&operator*()const{return*px;}
//*运算符作为成员函数默认的重载方式为操作符在前,注意返回值为引用、
T*operator->()const{returnpx;}
//注意返回值为指针类型
shared_ptr(T*p):px(p){}
//以上三个函数是几乎所有智能指针都需要的。
private:
T*px;
......
}
这里需要特别说明一下对于->操作符重载的调用,考虑如下代码:
shared_ptr<Foo>sp(newFoo);
Foo f(*sp);
sp->method();
将会自动转化为px->method();这里涉及到了'->'的特别行为。'->'运算符的一个特点是,它不会再运算过程中被消耗。'sp->'的运算结果是px,又由于'->'没有被消掉,因此其转换为px->method()。
2)迭代器——一种特别的智能指针
其和智能指针基本一致,只是还需要对++和—运算符进行重载。
T&operator*()const{return(*node).data;}
T*operator->()const{return&(operator*();)}
需要注意的一点是,这里显式调用了operator*(),这种形式是非常方便的。
三、Function-like class(仿函数)
即重载()运算符(又名函数调用操作符,其重载时的默认调用位置就在中间),在标准库中被广泛使用。
template<class pair>
struct select1st:public uniary_func<.......>{
const typename pair::first_type&
operator()(const pair&x)const{
returnx.first;
}
}
这样就能够像函数一样使用这个类,select1st(pair(1,2));
四、泛型编程初步
泛型编程和面向对象编程是C++的两个重要方面。
类模板和函数模板的作用
通常来讲,类模板可以被用来做容器、迭代器等;而函数模板则是被用来做泛型算法。
只需要使用template<class/typename T>进行声明。
类模板在使用时需要指明参数类型,但函数模板可以自动进行实参推导。
类的成员模板
template<class T1,class T2>
struct pair{
typedefT1 first_type;
typedefT2 second_type;
T1 first;
T2 second;
pair():first(T1()),second(T2()){}
pair(const T1&a,const T2&b):first(a),second(b){}
template<class U1,class U2>
pair(const pair<U1,U2>&p):first(p.first),second(p.second){}
};
注意其中的成员函数——拷贝构造函数。通过这种成员模板方式,可以实现拷贝构造。
模板的特化
模板是一种泛化操作,而特化则是一种逆向的操作,其语法是:
template<>
struct hash<int>{
size_toperator()(int x)const{returnx;}
}
通过这种形式,可以给int参数类型单独制定方法,同样的语法也可以使用与函数模板。
cout<<hash<int>()(1000);
其中的hash<int>()是声明了一个无参临时对象。
模板偏特化
4.1个数上的偏特化
与全特化类似,这种偏特化也是固定参数进行特化。
泛化声明:
template<class T1,class T2>
class vector
{......}
偏特化声明:
template<class T2>
class vector<bool,T2>
{......}
注意偏特化声明中的T2虽然仍写为T2,但与全泛化的T2并没有关系。
4.2范围上的偏特化
这种偏特化是指对于 const * p类型、*p类型、p类型的限制,偏特化就是按照范围划分不同的方法,例如如下的代码:
template<class T>
class C{......}
template<class T>
class C<T*>{......}
当仅有第一种定义时,会自动调用第一种类型的模板;然而当有上两种定义时,对于指针类型的模板参数,可以使用特别的方法定义,这种分类是十分必要的。
模板模板参数
template<typename T,
template<typename T>
class Container
>
class XCls
{
private:
Container<T>c;
public:
XCls()
{
for(long i=0;i<100;++i)
c.insert(c.end(),T());
}
};
注意第二个模板参数template<typename T>class SmartPtr ,其本身又是一个模板。
使用方法:
template<class T>
usingLst=list<T,allocator<T>>;
XCls<string,Lst>mylist;
注意:下面的用法不是模板模板参数。
template<class T,class Sequence=deque<T>>
class stack{
protected:
Sequence C;
}
其中第二个参数制定了stack要依靠什么基本容器类型进行重载,其具有默认值deque。
使用方式为: stack
数量不定的模板参数(Since C++ 11,使用包)
void print(){}
template<typename T,typename...Types>
void print(const T&firstArg,const Types&...args){
cout<<firstArg<<endl;
print(args...);
}
…是C++11中的新类型,…代表包(package),也就是以容器存储的数量不定的参数。对于这种数量不定的模板参数,始终采取将参数分为一个和一包的办法,这个包可以作为模板参数,也可以作为函数参数。在print()中,其采用了递归调用方法。也可以将第二参数其作为容器使用。
五、C++的两个常用语法糖
auto类型
list<string>C;
auto ite=find(C.begin(),C.end(),target);
其中的auto类型会自动被推导成 list
::iterator 新的for语法
for(variable:container){
......
}
变量会在容器中遍历,直到变量结尾。
e.g.
for(auto i:{1,2,3,4}){
......
}
这种语法搭配引用可以修改容器中的值。
for(auto&elem:vector_obj){
elem*=3;
}