| 
 | 
 
面向对象高级编程(下) 
 
转换函数 
 
将其他类型转换为类: 
 
 
  
对于下面的4,我们可以调用绿色部分代码将其转换为Fraction类 
将类转换为其他类型: 
 
 
  
转换函数书写的方式类似操作符重载,只不过这里重载的是类型。 
调用这个函数的时候我们可以隐式的调用这个函数,而也正因为如此可能会发生如下的错误: 
 
  
在这种情况下,如下两种操作都是合理的: 
 
- 将f转为double类型(黄色部分)
 
 - 将4转换为Fraction类型(绿色部分)
 
而如此将会产生歧义从而报错,而为了避免这种情况引申出了关键字:explicit 
explicit关键字: 
 
  
  
 
在c++中,explicit只能用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。 
通过explicit对构造函数的声明,使4不会隐式的调用构造函数返回成Fraction类型 
智能指针 
 
 
  
 
一种像指针的类,在内部实现的对 * 和 -> 的重载 
 
- 对于 * 的重载,我们返回的是reference
 
 - 对于 -> 的重载,我们返回的是pointer
 
但有一点需要注意,当调用 -> 时,这个操作符不会被消耗,而是继续作用下去。 
上图中先调用 sp-> 返回的应该是px。照理说接下来应该是px method(),而实际上 -> 并没有消失而是继续作用在px身上 
迭代器 
 
  
  
 
由红色框和黄色部分可知,这也是个智能指针,但与其他不同的是他还需要重载++,--符号。 
接下来分析一下黄色部分的重载: 
 
 
  
 
  
 
对于 * 的重载,我们返回的是(*node).data,也就是说返回的是一个数据类型为T的对象 
对于 -> 的重载,我们需要返回一个数据类型为T的对象的指针,所以需要在 (*node).data 的基础上& 
仿函数 
 
 
  
 
行为像函数的一个类,实现了对()的重载 
使用示例: 
class test { 
public: 
    test(int x, int y) : a(x), b(y) {} 
    int operator ()(int x){return a+b;} 
    int a; 
    int b; 
}; 
int main() { 
    test x(1,4); 
    cout<<x(0)<<endl; 
    cout<<test(3,8)(7); 
} 
模板 
类模板 
类模板简单示例: 
template<typename T> 
class test{ 
public: 
    test(T x,T y):a(x),b(y){} 
    T sum(){return a+b;} 
private: 
    T a,b; 
}; 
 
int main(){ 
    test<double> k(1.54,1.003); 
    cout<<k.sum(); 
} 
函数模板 
 
 
  
 
与类模板相比,函数模板显然更为智能。在调用函数时我们不必指明type,编译器会为我们自行推到type 
成员模板 
 
 
  
 
在模板的内部又声明了一个模板 
示例1 
 
 
  
 
通过成员模板去描述继承的关系 
示例2 
 
 
  
 
智能指针相关的 
模板特化 
全特化 
 
 
  
namespace case1 { 
template <class T> 
class test {}; 
template <> 
class test<int> { 
public: 
    string operator()() { return &#34;这是int\n&#34;; } 
    int a; 
}; 
template <> 
class test<double> { 
public: 
    string operator()() { return &#34;这是double\n&#34;; } 
    int a; 
}; 
}  // namespace case1 
int main() { 
    case1::test<int> k; 
    cout << k(); 
    cout << case1::test<double>()(); 
} 
偏特化 
个数的偏特化 
 
 
  
#include <iostream> 
namespace case2 { 
 
template <typename T1, typename T2> 
class test { 
public: 
    string operator()(){return &#34;这是没有特化的\n&#34;;} 
}; 
template<typename T> 
class test<int,T>{ 
public: 
    string operator()(){return &#34;这是偏特化---int\n&#34;;} 
}; 
 
}  // namespace case2 
 
int main() { 
    cout<<case2::test<double,double>()(); 
    cout<<case2::test<int,double>()(); 
    cout<<case2::test<int,float>()(); 
} 
/*结果: 
这是没有特化的 
这是偏特化---int 
这是偏特化---int 
*/ 
范围的偏 
 
 
  
namespace case3 { 
template <typename T> 
class test { 
public: 
    string operator()() { return &#34;这是没有特化的\n&#34;; } 
}; 
template <typename T> 
class test<T*> { 
public: 
    string operator()() { return &#34;这是范围偏特化的---指针\n&#34;; } 
}; 
}  // namespace case3 
int main() { 
    cout<<case3::test<string>()(); 
    cout<<case3::test<string*>()(); 
} 
/*结果: 
这是没有特化的 
这是范围偏特化的---指针 
*/ 
模板模板参数 
 
 
  
template <typename T, template <typename U> class Container> 
class XCls { 
private: 
    Container<T> c; 
}; 
 
template <typename T> 
class test { 
private: 
    T t; 
}; 
 
//因为list容器实质上是有第二个参数的,所以我们用如下方法定义: 
template <typename T> 
using Lst = std::list<T, std::allocator<T>>; 
 
int main(void) { 
    XCls<std::string, test> mylst1;  //用定义的一个模板类传入 
    XCls<std::string, Lst> mylst2;   //传入容器 
 return 0; 
} 
另一种情况(注意,这个不是模板模板参数) 
 
 
  
 
数量不定的模板参数 
 
 
  
 
书写不定模板参数时需要注意 ... 要一直都写!!! 
void Print(){} 
template <typename T, typename... Types> 
void Print(const T& firstarg, const Types&... args) { 
    cout<<firstarg<<endl; 
 //以下两种方式都可:  
 //Print(args...);//重载参数列表为空的时候 
 if(sizeof...(args))Print(args...);//对剩余args的个数进行判定 
} 
int main() { 
    Print(8,21,2.423,&#34;ASdfa&#34;,&#39;D&#39;); 
} 
reference细析 
 
 
  
 
引用与指针的不同: 
 
- 在大小方面,引用的大小是和对象保持一致的,这和指针只保存地址不同
 
 - 对于使用后更改方面:
 
 
- 指针初始化时指向了一个对象,日后也可以指向其他对象
 
 - 当引用初始化时指向了一个对象,此时引用相当于该对象的别名,换句话说此时引用和对象形同一体,也就无法更改引用指向其他对象
 
 
  
 
  
  
 
因为上面已经提到引用绑定对象后相当于和对象形同一体,所以在调用方法的时候和对象的操作是相同,和指针的操作是不同的。而也正是因为行同一体这点,当我们想对函数进行重载时也应该注意 & 对象 和 对象 实际上是等价的,也就是上面图片说的签名是相同的。 
虚指针和虚函数 
内存状态 
下图阐述了虚函数在内存中的存在状态 
 
 
  
 
当类里面有虚指针时,在类的内存空间中就会多个指针指向虚函数表 
动态绑定 
 
 
  
 
  
 
再谈const 
 
 
  
 
const也是函数签名的一部分,也就是说可以重载有const的和没有const的两个版本的函数。 
当上述两个函数都同时存在时,const对象只会调用const版本的,反之亦然。 
当const对象调用非const的函数时会出错。 
再谈new,delete 
作用域 
 
 
  
 
 
- 如果使用某个类的成员函数来重载这些运算符,则意味着这些运算符仅针对该特定类才被重载。
 
 - 如 果重载是在类外完成的(即它不是类的成员函数),则只要您使用这些运算符(在类内或类外),都将调用重载的“ new”和“ delete”。 这是全局超载。
 
示例 
 
  
  
 
new: 
 
 
  
 
- new不带虚函数的类时,其内存空间除了存有数据外还有一块记录大小的内存块。
 
 - new带虚函数的类时,其内存空间除了存有数据外还有一个指针。
 
delete: 
 
  
  
 
调用delete时发现都没有进入自己写的重载delete中去 
重载的要求: 
 
 
  
 
对于new而言: 
 
- 我们可以重载多个版本的new
 
 - new的参数第一个必须是size_t
 
 - 但是在调用时这个size_t不用指明
 
对于delete而言L: 
 - 我们同样可以重载多个版本的delete
 
 - 我们重载的delete不会被delete调用,只有当new抛出异常时才会调用我们所重载的delete
 
  重载示例: 
 
 
  
 
  |   
 
 
 
 |