讨论主题:泛型编程和面向对象编程。主要说明C++中模板的应用。
类型转型
- 转换函数,operator typename()
- 转换构造函数,one-argument constructor
- explicit关键字
类的两种形式
- pointer-like classes,智能指针
- function-like classes, 仿函数
模板
- 类模板
- 函数模板
- 成员模板
- 模板特化
- 模板偏特化
- 模板模板参数
C++ 11
- auto
- 可变模板参数
- range-base for
conversion function转换函数
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) {}
operator double() const {
return (double)(m_numerator / m_denominator);
}
private:
int m_numerator;
int m_denominator;
};
{
Fraction f(3, 5);
double d = 4 + f; //调用operator double()将f转换为3/5。
}
通过operator typename()可以将一个类转换为typename类型(转出)。
non-explicit-one-argument constructor转换构造函数
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) {}
Fraction operator + (const Fraction& f){
return Fraction(…);
}
private:
int m_numerator;
int m_denominator;
};
{
Fraction f(3, 5);
Fraction d2 = f + 4; //调用f.operator+()
}
Fraction构造函数有两个parameter,但有一个默认参数,可以仅需一个argument。同时,在构造函数前未申明为explicit,所以执行Fraction d2 = f + 4时,4将调用该构造函数,转型为Fraction,然后调用operator+()。
此时,讨论的转换构造函数,将单个argument转型为类对象(转入)。
转换函数 vs. 转换构造函数
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) {}
operator double() const {
return (double)(m_numerator / m_denominator);
}
Fraction operator + (const Fraction& f){
return Fraction(…);
}
private:
int m_numerator;
int m_denominator;
};
{
Fraction f(3, 5);
Fraction d2 = f + 4; //error,ambiguity
}
执行d2 = f + 4时,由于4可以调用转换构造函数转型为Fraction,这样符合operator+()调用形式。同样,f可以调用double()转型为double,计算结果在调用Fraction构造函数转型为Fraction。两者并不优劣,编译器无法选择。
explicit-one-argument constructor
class Fraction
{
public:
explicit Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) {}
operator double() const {
return (double)(m_numerator / m_denominator);
}
Fraction operator + (const Fraction& f){
return Fraction(…);
}
private:
int m_numerator;
int m_denominator;
};
{
Fraction f(3, 5);
Fraction d2 = f + 4; //error,conversion from ‘double’ to ‘Fraction’ requested
}
加上explicit关键字,不允许编译器自动调用转换构造函数。这样,4不能再转为Fraction。进而,不满足operator+()的调用形式。
explicit关键字常用于one-argument的构造函数前,作用是防止构造函数得隐式转换。
当类构造函数参数大于等于2时,编译器是不会隐式转换。此时,explicit无效。
pointer-like classes,智能指针
template <class T>
class shared_ptr
{
public:
T& operator * () const
{ return *px; }
T* operator -> () const
{ return px; }
phared_ptr(T* p) : px(p) { }
private:
T* px;
long* pn;
};
以智能指针为例,说明像指针的类的用法。shared_ptr重载操作符*和->,在使用该类时,可以像一个指针一样。
struct Foo{
…
void method(void) { … }
};
shared_ptr<Foo> sp(new Foo); //实例模板,并把new的对象托管给shared_ptr
Foo f(*sp); //重载了operator * ( )
sp->method(); //重载了 operator -> ()
pointer-like classes,迭代器
template <class T>
strcut __list_node{
void* prev;
void* next;
T data;
};
template <class T, class Ref, class Ptr>
struct __list_iterator{
typedef __list_iterator<T, Ref, Ptr> self;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T*> link_type;
link_type node;
reference operator * (const self& x) const { return (*node).data; }
pointer operator -> (const self& x) const { return &(operator*()); }
bool operator == (const self& x) const { return node == x.node; }
bool operator != (const self& x) const { return node != x.node; }
self& operator ++ () { node = (link_type)((*node).next); return *this; }
self operator ++ (int) { self tmp = *this; ++*this; return tmp; }
self& operator -- () { node = (link_type)((*node).prev); return *this; }
self operator -- (int) { self tmp = *this; --*this; return tmp; }
};
遍历容器的迭代器有类似指针的用法。它也是一个类,不过重载了操作符*和->。
当然,迭代器相比较于shared_ptr,还有==、++、–等的需求。
operator++()重载前缀++,而operator++(int)重载后缀++。
在本例中,后缀++调用了前缀++而实现。
function-like classes,仿函数
类有类似函数的行为。这是因为仿函数重载了操作符()。
template <class T>
struct identity {
const T&
operator() (const T& x) const { return x; }
};
{
identity<bool> ibool() ();
}
前一个括号调用默认的无参构造函数,后者括号调用operator(),看起来类似函数。
template <class T1, class T2>
struct pair{
T1 first;
T2 second;
pair() : first( T1() ), second( T2() ) { } //无参构造函数,仍使用T()生成临时对象去赋值
pair(const T1& a, const T2& b) : first(a), second(b) { }
};
template <class pair>
struct select1st{
const typename pair::first_type&
operator() (const pair& x) const
{ return x.first; }
};
template <class pair>
struct select2nd{
const typename pair::second_type&
operator() (const pair& x) const
{ return x.second; }
};
select1st和select2nd两个类,均重载了(),分别返回pair中first和second的值。
class template,类模板
为了增加类的复用性和类型定义的弹性。在写类时,内部成员的类型可以在定义对象时指定。
template < typename T>
class complex{
private:
T re, im;
public:
complex(T r=0, T i=0)
: re(r), im(i)
{}
};
{
complex<double> c1(1.2, 2.5);
complex<int> c2(1,4);
}
function template,函数模板
当函数功能相同,但参数类型不同时,C风格就需要写很多类似的函数,显得累赘。C++中引入函数模板,这样不仅实用于各种类型,函数名还一致。
在调用函数模板时,自动根据传入形参类型推导出函数模板参数类型。
template <class T>
inline
const T& min(const T& a, const T& b){
return b < a ? b : a;
}
在该例中,如果T非内置类型,而是自定义的类,则需要重载<运算符。
member template,成员模板
在类成员中,使用函数模板。进一步提高类的弹性。
template <class T1, class T2>
struct pair{
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) { }
};
{
class Base1{};
class Derived1:public Base1{};
class Base2{};
class Derived2:public Base2{};
pair<Derived1, Derived2> p;
pair<Base1, Base2> p2(p); //用p去初始化p2
}
在此例pair中,使用成员模板,使得构造函数不仅可以接受T1和T2类型,还可以是任何可以正常赋值给T1和T2类型的变量。
对象p是pair<Derived1,Derived2>类型,而p2是pair<Base1, Base2>类型,但也可以将派生类赋值给父类。
{
Base1 *ptr = new Derived1; //up-cast
shared_ptr<Base1> sptr(new Derived1);
}
基类指针既可以指向基类对象也可以指向父类对象。
specialization,模板特化
template <class Key>
struct hash{ };
template <>
struct hash<char>{
size_t operator () (char x) const { return x; }
};
template <>
struct hash<int>{
size_t operator () (int x) const { return x; }
};
template <>
struct hash<long>{
size_t operator () (long x) const { return x; }
};
模板特化,是将模板hash中某些类型单独提出来,单独处理。在使用类模板时,如果定义类型已经特化,则优先使用特化模板。
cout << hash<long> () (1000); //优先使用特化模板
partial specialization,模板偏特化
template <typename T, typename Alloc=…>
class vector{
…
};
template < typedef Alloc=…>
class vector<bool, Alloc> { //将T绑定为bool,但是Alloc仍未确定
…
};
以上时模板参数个数的偏特化。
template <typename T>
class C{
…
};
template <typename T>
class C<T*> {
…
};
上诉是模板参数的范围偏特化,将任意类型指定为指针类型。
template template parameter,模板模板参数
template <typename T,
template <typename T>
class Container
>
class XCls{
private:
Container<T> c;
public:
…
};
{
template <typename T>
using Lst = list<T, allocator<T> >;
XCls<string, Lst> mylst;
}
该例子的目的是定义对象mylst中成员变量c是类型为list。
template <typename T,
template <typename T>
class SmartPtr
>
class XCls {
private:
SmartPtr<T> sp;
public:
XCls() : sp(new T) { }
};
{
XCls<string, shared_ptr> p1;
XCls<long, auto_ptr> p2;
}
shared_ptr构造函数仅需一个argument,这样p1中sp的类型为shared_ptr,p2中sp的类型为auto_ptr。
variadic templates,可变模板参数
void print()
{
}
template <typename T, typename… Types>
void print(const T& firstArg, const Types&… args)
{
cout << firstArg << endl;
print(args…);
}
print(7.5, “hello”, bitset<16>(377), 42);
每一次调用print,将参数分为两个部分,firstArg和args,前者为一个对象,后者是一包。递归调用print,最后args…为空,则调用无参print函数(空函数,但必不可少)。
要查看args一包中参数的个数,可以调用sizeof…(args)。
auto
可进行对象类型自动推导。
{
list<string> c;
list<string>::iterator ite;
ite = find(c.begin(), c.end(), target);
}
{
list<string> c;
auto ite = find(c.begin(), c.end(), target);
}
但是不能单独定义一个auto对象,编译器无法进行推导。
auto ite; //error
ranged-base for
vector<double> vec;
for( auto elem : vec ){
cout << elem << endl; //pass by value
}
for( auto & elem : vec ) {
elem *= 3; //pass by reference
}
对容器遍历有一定的便利性。
reference
int x = 0;
int* p = &x;
int & r = x; //r代表x,现在r和x都是0
int x2 = 5;
r = x2; //r不能重新代表其他对象,此处是赋值
int& r2 = r; //现在r,r2都代表x
reference仍然是使用指针实现,在32位机实质上都是4bytes。但是编译器制造了一个假象。使得,sizeof® == sizeof(x)和&x == &r。即,引用类型大小等于被引用类型大小,且两者占用同一片地址。
reference通常不用于申明变量,二十修饰参数类型和返回值类型。
{
void func1(Cls* pobj) { pobj->xxx(); }
void func2(Cls obj) { obj.xxx(); } //被调用端方法相同,很好
void func3(Cls& obj) { obj.xxx(); } //被调用端方法相同,很好
Cls obj;
func1(&obj); //接口不同,困难
func2(obj); //调用端接口相同,很好
func3(obj); //调用端接口相同,很好
}
函数签名,包括函数名和参数列(返回值不算)。
double imag(const double& im) {}
double imag(const double im) {}
引用不属于签名,所以上诉的两个的签名一致,即如果两个函数存在,属于重复定义。
但是const关键字属于签名。
double imag(const double im) const{}
double imag(const double im) {}
所以,两者不算重复定义。