1. 转换函数(conversion function)
作用:将类转换为其他类型
class Fraction
{
public:
...
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()
编译器的步骤:
首先,寻找operator+的重载,找 int + Fraction的函数,没有找到,编译器尝试失败。
然后再找能否将Fraction 转换为double函数,找到了转换函数,就可以编译成功了。
2. explicit-one-argument-ctor
class Fraction
{
public:
explicit Fraction(int num, int den = 1):m_numerator(num),m_denominator(den)
{}
private:
int m_numerator;
int m_denominator;
}
Fraction f(3, 5);
Fraction d2 = 4 + f; // error
如果没有explicit,4可能会被转换为fraction,与上面 f 可能转换为double,导致二义性。
关键字explicit一般用在构造函数。
3. pointer-like classes(智能指针和迭代器)
智能指针
template<class T>
class shared_ptr
{
public:
T& operator*() const // *重载
{
return *px;
}
T& operator->() const // ->重载
{
return px;
}
...
private:
T* px;
long* pn;
...
}
智能指针类一定包括*操作符和->操作符重载。
->有特殊的行为,得到的东西继续要->作用上去
sp->method();
sp->,操作符重载,得到px,然后->继续作用下去,得到:
px->method();
而对于其他的操作符,则会消耗掉。例如:
*sp;
//通过*重载,转换为
*px;
迭代器
例如 list的迭代器:
reference operator*() const
{
return (*node).data;
}
pointer operator->() const
{
return &(operator*());
}
4. function-like classes(仿函数)
()操作符, 被称为function call操作符,函数调用操作符,所以类能够接受()操作符,就可以称为 function-like,这样的对象被称为函数对象
class test
{
public:
void operator() (const string& str) const
{...}
...
//调用
test func;
func("hello!");
func()("hello!"); // error
和其他的操作符不同,调用()操作符时,需要省略()
5. member template(成员模板)
模板类内,有成员函数是模板函数,被称为成员模板。
标准库大量的类中,一般是构造函数为成员模板,能够让构造函数更有弹性。
template <typename _Tp>
class shared_ptr:public __shared_ptr<_Tp>
{
...
template<template _Tp1>
explicit shared_ptr(_Tp1* __p)
:__shared_ptr<_Tp>(__p) {}
}
// 调用
Base* ptr = new Derived1; // up-cast
6. 模板特化
设计模板后,对某些函数进行局部特征化,编译器会优先考虑特化程序。
// 泛化
template <class key>
struct hash{};
//特化
template<>
struct hash<char>
{ ... }
template<>
struct hash<int>
{ ... }
//调用
cout << hash<int>()(1000);
7. 模板偏特化
个数偏特化
模板有多个参数,只特化其中一部分参数,顺序是从左向右。
template<typename T, typename Alloc=....>
class Vector
{ ... };
// 偏特化
template<typename Alloc= ....>
class vector <bool, Alloc>
{ ... };
范围偏特化
进行范围特化,例如特化为指针。
// 程序1
template<typename T> //泛化
class C
{ ... };
// 程序2
template<typename U> // 范围偏特化
class C<U*>
{ ... };
//调用
C<string> obj1; // 应用程序1 泛化版本
C<string*> obj2; // 应用程序2 范围偏特化版本
8. C++11的三个主题
数量不定的模板参数
把参数分为一个和一个模板参数包(pack),
// 步骤1
void print() // 0个参数
{}
// 步骤2
template<typename T, typename... Types>
void print(const T& firstArg, const Types&... args)
{
sizeof...(args); //判断模板参数包是几个
cout << firstArg << endl;
print(args...);
}
// 调用
print(7.5, "hello", bitset<16>(377), 42);
auto
编译器自动给你推出类型。有时候类型比较复杂,可以使用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);
ranged-base for
for循环遍历
for(decl : coll){...}
//例如
vector<double> vec;
...
for(auto elem: vec){ ... } //传值
for(auto& elem: vec){ ... } //传引用
如上图所示,如果传值,for循环会将容器里的元素逐个复制出来,只是只读,不能修改。
如果传引用,则会修改容器里的元素。
9. reference
reference概念
reference是引用(别名),其底层是指针,也被称为弱化的指针。其更接近const指针
int &rodents = rats;
// 伪装表示如下,const变量必须初始化
int* const pr = &rats;
//引用 rodent 等价于 *pr
reference必须初始化,一个reference只能指向一个对象。
object和reference的大小相同,地址也相同(这些都是假象,实际上底层代表指针)。
reference用途
通常用在参数类型和返回类型。
double imag(const double& im) { ... }
int imag(const double im) { ... } //二义性
// 其中imag(const double im)被称为函数签名
由于接口一致,因此会被视为same signature,上述会出现二义性。如果是成员函数可以加 const解决
例如:
double imag(const double& im) { ... }
int imag(const double im) const { ... } //ok
10. 对象模型(Object Model)关于vptr 、vtbl
继承会把数据和函数的调用权继承下来
有虚函数就会有vptr指针,指向虚表vtbl
virtual table 放的都是函数指针,指向虚函数
C的时代,会直接通过call跳到函数固定地址,然后return,被称为静态绑定。
而通过指针调用虚函数被称为动态绑定,动态绑定的逻辑意义是通过指针找到vptr, 再找到vtbl,然后再找到指向的虚函数。这正是面向对象的关键所在。
//动态绑定路线的代码形式是:
(*(p->vptr)[n])(p);
//或者是
(*p->vptr[n])(p);
符合以下三点,将会动态绑定,也被称为多态:
- 通过指针
- 向上转型(继承)
- 调用函数