C++的类是抽象数据类型(Abstract Data Type,ADT)的最佳实践,实现了数据和操作数据的函数的封装,接口实现和使用的分离。类的分类可以让我们对C++的类有一个更深刻的认识。
1 Object Based (基于对象) 与Object Oriented (面向对象)
Object Based : 面对的是单一class 的设计。
Object Oriented : 面对的是多重classes 的设计,classes 和classes 之间的关系,如对象的继承、组合等。
2 Class without pointer member(s) 与Class with pointer member(s)
在C和C++中,对于数据的初始化和堆内存的释放对于程序的安全性来说至关重要,在C++的类中,通过构造函数和析构函数可以自动化完成这一过程。基于析构函数是否需要释放堆内存,类可以分为包含指针成员的类和不含指针成员的类,对于包含指针成员的类,通常会有堆内存的分配,由这个指针成员指向一块堆内存,所以在析构函数中对其释放就显得很重要,如string类。当然,不是说没有指针成员或不需要释放堆内存就不需要析构函数了,在特定场合,如一个类的对象计数,当析构时,也可以对一个计数的数据进行“--”操作。
即使程序员没有忘记释放堆内存,但因为潜在的一些异常会可能会改变程序既定的流程,从而可能使free()或delete语句没有机会执行。
2.1 Class without pointer member(s):complex类
![0ecaf8ef21f9b1295fbd20ecf805a5bd.png](https://i-blog.csdnimg.cn/blog_migrate/87c8b56520391cc597c4c875dd1b7b9a.jpeg)
上述complex类的数据成员并没有指针成员,所以也没有堆内存申请的动作,由此,如果其析构函数~complex()并不做特别的操作,使用其默认的析构函数即可。
2.2 Class with pointer member(s):string类
![cd3ade9f57dd0ce9cf067963e9cdda7c.png](https://i-blog.csdnimg.cn/blog_migrate/8d3123fafd1b51134cf719037adfea51.jpeg)
上述string类,其数据成员只有一根指针,用于指向申请的堆内存,堆内存使用完后需要析构,所以其析构函数就显得很重要了,需要重新定义:
![7f01a083ad8bee6d801605de79892ce6.png](https://i-blog.csdnimg.cn/blog_migrate/ef2fdcfeff9fd88c9c31d2df2f9f7df6.jpeg)
同时,为避免浅拷贝而引起的潜在问题,拷贝构造与拷贝赋值操作符都需要重新定义:
![525e976bb54476c59f03bdc0e8c948e0.png](https://i-blog.csdnimg.cn/blog_migrate/9132ce7423c36b23d78eec88de7f1fe3.jpeg)
![99fb738bbb7af330cab0fb5aedb6fca8.png](https://i-blog.csdnimg.cn/blog_migrate/4f4eae7d603ff2b770ef9a5604f14332.jpeg)
3 pointer-like classes:智能指针
C++不像Java,没有内存回收机制,堆内存需要手动释放始终不是一个最佳的选择。利用类的析构函数自动调用这一特性,似乎可以做一些事情。对,就是封装一个需要指向堆内存的指针,由析构函数来自动完成内存释放这一动作。智能指针的初衷即来自于此,当然,智能指针还可以定义一些更智能化的操作,如share_ptr的对象计数、unique_ptr的所有权转移。
下面这张图很好地说明了智能指针的内部结构和使用方法:
![532ffee39c302c7be737a45f0b46b697.png](https://i-blog.csdnimg.cn/blog_migrate/3ab423036ca5ae064171b561e6040dae.jpeg)
智能指针在语法上有三个很关键的地方,第一个是保存的外部指针,对应于上图的T* px,这个指针将代替传入指针进行相关传入指针的操作;第二个是重载“*”运算符,解引用,返回一个指针所指向的对象;第三个是重载“->”运算符,返回一个指针,对应于上图就是px。
4 pointer-like classes:迭代器
在C++中,泛型编程的最佳实践就是STL库了。C++的类实现了数据结构和操作数据结构的函数的封装,相对于数据与操作的分离,这是一进步,但当两者的结合度不是很高时,或者想让作为算法的函数能够独立于特定的数据结构而有更高的通用性时,这就是泛型编程的思想。STL用类模板封装数据结构为容器,用函数模板封装算法库,通过封装容器类对象的指针的迭代器来联结算法库与容器类,实现算法对容器一定的独立性,从而实现其通用性。
迭代器也是一种智能指针,这里也存在上面提到的智能指针的三个要素,分别对应于下图的红色字体和黄色标注部分:
![1d2282c5097d2c2e3c1ccda8fc816344.png](https://i-blog.csdnimg.cn/blog_migrate/fee3c7e0d6febb49aef3a1a255b26b16.jpeg)
下图是迭代器重载的“*”和“->”重载符:
![5003f28c5c42d8ddf027081263537b42.png](https://i-blog.csdnimg.cn/blog_migrate/33cb48d07b74209e6b475d20b1110a1b.jpeg)
创建一个list迭代器对象,list::iterator ite; iterator是一个内部类,是list的内部类,这里的list用于保存Foo对象,也就是list模板定义里的class T,operator*()返回的是一个(*node).data对象,node是__link_type类型,然而__link_type又是__list_node*类型,这里的T是Foo,所以node是__list_node*类型,所以(*node).data得到的是Foo类型的一个对象,而&(operator*())最终得到的是该Foo对象的一个地址,即返回Foo* 类型的一个指针。
5 function-like classes:functor 仿函数
我们排序时,升序还是降序排列,设计的升序或降序排序算法只有一行代码的不同,如果写成的一个函数既可以做升序也可以做降序排序呢?用一个函数指针做参数即可。
函数的运算符是“()”,而类对"()"的重载即可以模仿函数的行为,称为仿函数或函数对象。仿函数是对函数指针的一种加强,仿函数相对于函数指针,既可以操作对象状态,还可以由编译器做一个内联调用,从而具有更佳的性能。
![6a891d99b99406b57c899bf5711e3f53.png](https://i-blog.csdnimg.cn/blog_migrate/37e2341e1fa3b660812bef6361fc0019.jpeg)
重上图可以看到,每个仿函数都是某个类重载“()”运算符,然后变成了“仿函数”,实质还是一个类的函数,但看起来具有函数的属性。每个仿函数其实在背后都集成了一个奇怪的类,如下图所示,这个类不用程序员手动显式声明。
![53114e538309f78c0f0b9878f0303b9c.png](https://i-blog.csdnimg.cn/blog_migrate/37860af3820cc38a2c7268e087350be1.jpeg)
标准库中的仿函数也同样继承了一个奇怪的类:
![1f65fcd332e7dd1fa2cff4b584e7f838.png](https://i-blog.csdnimg.cn/blog_migrate/6dce13898224abcf21282bde2c91af7e.jpeg)
这个类的内容如下图所示,只是声明了一些东西,里面没有实际的变量或者函数。
![fc9fedfee78b3d54c43f209bdf641940.png](https://i-blog.csdnimg.cn/blog_migrate/552efbd56f1818728e4c17c805fae4cf.jpeg)
ref
C++面对对象高级编程[侯捷]
https://www.bilibili.com/video/BV1S7411p7g2
-End-