一个分数类可以转换为小数类型。
class Fraction {
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) {}
operator double() const { // 重载类型转换运算符 double()
return (double)(m_numerator * 1.0 / m_denominator);
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
};
可以用于强制类型转换,也有可能隐式转换:
Fraction f(3,5);
double d = f + 4; // 隐式转换,调用 Fraction::operator double() 函数将f转换为 double 类型变量
对于f+4,编译器先寻找+号的重载函数,没有找到则进行隐式转换;若定义了重载运算符+的函数,就不会再发生隐式转换。
class Fraction {
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) {}
explicit operator double() const { // 重载类型转换运算符 double()
return (double) (m_numerator * 1.0 / m_denominator);
}
double operator+(double d) const { // 重载运算符 +
return (double) (m_numerator * 1.0 / m_denominator) + d;
}
private:
int m_numerator;
int m_denominator;
};
Fraction f(3, 5);
double d = f + 4; // 直接调用 Fraction::operator+(double),不发生类型转换
类似地,通过构造函数还能将其他类型的数转换为分数类型。
class Fraction {
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) {}
Fraction operator+(const Fraction &f) const { // 重载运算符 +
return Fraction(m_numerator + f.m_numerator, m_denominator + f.m_denominator);
}
private:
int m_numerator;
int m_denominator;
}
Fraction f1(3, 5);
Fraction f2 = f1 + 4; // 调用 Fraction 类构造函数将 4 转换为 Fraction 类型变量
如果不想隐式转换,可以通过关键字explicit来避免。一般用于构造函数之前。
class Fraction {
public:
explicit Fraction(int num, int den = 1) // 避免隐式调用构造函数进行类型转换
: m_numerator(num), m_denominator(den) {}
explicit operator double() const { // 避免隐式调用成员函数进行类型转换
return (double) (m_numerator * 1.0 / m_denominator);
}
private:
int m_numerator;
int m_denominator;
};
Fraction f1(3, 5);
Fraction f2 = f1 + 4; // 编译不通过: error: no match for operator+...
double d = f1 + 4; // 编译不通过: error: no match for operator+...
类似于指针的对象,实现方式是重载*和->运算符.。
template<class T>
class shared_ptr {
public:
T& operator*() const { // 重载 * 运算符
return *px;
}
T *operator->() const { // 重载 -> 运算符
return px;
}
//...
private:
T *px;
// ...
};
int *px = new Foo;
shared_ptr<int> sp(px);
func(*sp); // 语句1: 被解释为 func(*px)
sp -> method(); // 语句2: 被解释为 px -> method()
对于语句1,形式上解释得通,重载运算符*使得func(*sp)被编译器解释为func(*px)。
对于语句2,形式上有瑕疵,重载运算符->使得sp ->被编译器解释为px,这样运算符->就被消耗掉了,只能理解为->运算符不会被消耗掉.
标准库中的迭代器_List_iterator也是一个指针类,代码如下:
template<class _Tp, class Ref, class Ptr>
struct _List_iterator {
_List_iterator& operator++() { ... }
_List_iterator operator++(int) { ... }
_List_iterator& operator--(){ ... }
_List_iterator operator--(int) { ... }
bool operator==(const _Self &__x) { ... }
bool operator!=(const _Self &__x) { ... }
Ref operator*() { ... }
Ptr operator->() { ... }
};
_List_iterator 除了重载*和->运算符之外,还重载了原生指针的其他运算符.
实现方式是重载()运算符.举例如下:
template<class T>
struct identity {
const T &
operator()(const T &x) const { return x; }
};
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; }
};
类模板实例化时需要指定具体类型:
template<typename T>
class complex {
public:
complex(T r = 0, T i = 0)
: re(r), im(i)
{}
complex &operator+=(const complex &);
T real() const { return re; }
T imag() const { return im; }
private:
T re, im;
}
// 类模板实例化时需要指定具体类型
complex<double> c1(2.5, 1.5);
complex<int> c2(2, 6);
函数模板在调用时编译器会进行参数推导(argument deduction),因此不需要指定具体类型:
template<class T>
inline const T &min(const T &a, const T &b) {
return b < a ? b : a;
}
// 函数模板实例化时不需要指定具体类型
min(3, 2);
min(complex(2, 3), complex(1, 5));
成员模板用于指定成员函数的参数类型:
template<class T1, class T2>
struct pair {
typedef T1 first_type;
typedef T1 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) {}
}
pair<Derived1, Derived2> p1; // 使用子类构建对象
pair<Base1, Base2> p2(p1); // 将子类对象应用到需要父类的参数上
模板特化用来针对某些特定参数类型执行操作,因为其可能有更好更快的算法。
template<class Key>
struct hash {
// ...
};
template<>
struct hash<char> {
size_t operator()(char x) const { return x; }
};
template<>
struct hash<int> {
size_t operator()(char x) const { return x; }
};
template<>
struct hash<long> {
size_t operator()(char x) const { return x; }
};
上述代码实现针对char、int和long这三个数据类型使用指定代码创建对象,其它数据类型使用默认的泛化操作创建对象.
template<typename T, typename Alloc>
class vector{
// ...
};
template<typename Alloc>
class vector<bool, Alloc>{ // 指定了第一个参数类型
// ...
};
template<typename T>
class C{
// 声明1...
};
template<typename T>
class C<T*>{ // 指定了参数类型为指针类型
// 声明2...
};
模板模板参数是指模板的参数还是模板的情况
template<typename T, template<typename U> class Container>
class XCls {
private:
Container<T> c;
public:
// ...
};
在上面例子里, XCls 的第二个模板参数 template class Container 仍然是个模板,因此可以在类声明内使用Container c语句对模板Container进行特化,使用方式如下:
XCls<string, list> mylst1; // mylst1的成员变量c是一个list
上面语句构造的mylst1变量的成员变量c是一个特化的类list.仅从模板模板参数的语法来说,上面语句是正确的,但是实际上不能编译通过,因为list模板有2个模板参数,第二个模板参数通常会被省略,但在类声明体内不能省略其他模板参数,因此可以使用using语法达到目的:
template<typename T>
using LST = list<T, allocator<T>>
XCls<string, LST> mylst2; // mylst2的成员变量c是一个list
这样就能够编译通过了。
声明引用(reference)时候必须赋初值,指定其代表某个变量,且之后不能再改变改引用的指向.对引用调用=运算符同时改变引用和其指向变量的值,不改变引用的指向.
int x = 0;
int *p = &x;
int &r = x; // r代表x,现在r,x都是0
int x2 = 5;
r = x2; // r不能重新代表其他变量,现在r,x都是5
int &r2 = r; // 现在r2,r,x都是5(r2和r都代表x)
虽然在实现上,几乎所有的编译器里引用的底层实现形式都是指针,但C++制造了以下两个假象,确保对于使用者来说引用和其指向的变相本身是一致的:
typedef struct Stag { int a, b, c, d; } S;
int main(int argc, char **argv) {
double x = 0;
double *p = &x; // p指向x,p的值是x的地址
double &r = x; // r代表x,现在r,x都是0
cout << sizeof(x) << endl; // 8
cout << sizeof(p) << endl; // 4, 指针大小为4字节
cout << sizeof(r) << endl; // 8, 假象: r的大小和x相同,屏蔽了r底层的指针
cout << p << endl; // 0065FDFC, x的地址
cout << *p << endl; // 0
cout << x << endl; // 0
cout << r << endl; // 0
cout << &x << endl; // 0065FDFC
cout << &r << endl; // 0065FDFC, 假象: r的地址就是x的地址,屏蔽了r底层的指针
S s;
S &rs = s;
cout << sizeof(s) << endl; // 16
cout << sizeof(rs) << endl; // 16
cout << &s << endl; // 0065FDE8
cout << &rs << endl; // 0065FDE8
return 0;
}
在编写程序时,很少将变量类型声明为引用,引用一般用于声明参数类型(parameter type)和返回值类型(return type).
// 参数类型声明为引用,不影响函数体内使用变量的方式
void func1(Cls obj) { opj.xxx(); } // 值传递参数
void func2(Cls *Pobj) { pobj->XXX(); } // 指针传递参数,函数体内使用变量的方式需要修改
void func3(Cls &obj) { obj.xxx(); } // 引用传递参数,函数体内使用变量的方式与值传递相同
// 参数类型声明为引用,不影响参数传递的方式
Cls obj;
func1(obj); // 值传递参数
func2(&obj); // 指针传递参数,传递参数时需要对参数作出修改
func3(obj); // 引用传递参数,传递参数时不需对参数做出修改
值得注意的是,因为引用传递参数和值传递参数的用法相同,所以两个函数的函数签名(signature)相同,不能同时存在.
有意思的是,指示常量成员函数的const也是函数签名的一部分,因此const和non-const的同名成员函数可以在同一类内共存.