|
面向对象高级编程(下)
转换函数
将其他类型转换为类:

对于下面的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
重载示例:

 |
|