智能指针

智能指针

1.什么是智能指针?

​ 智能指针是对裸指针的封装,初衷是让程序员无需手动释放内存,避免内存泄漏。常见的智能指针有auto_ptr(被淘汰)

,unique_ptr,shared_ptr,weak_ptr.。

2.几种智能指针的优缺点

​ 1.weak_ptr:weak_ptr本身不具备资源管理的能力,它存在的目的是解决shared_ptr产生的循环引用问题。weak_ptr指向的对象的引用计数不会增加。

​ 2.unique_ptr:unique_ptr最大的特点是,它对资源是独占的,它不可以复制。它通过在析构函数中释放资源来管理对象的生命周期。可以防止多个指针指向同一个对象,它比shared_ptr更加高效。

​ 3.shared_ptr:shared_ptr最大的特点是可以共享资源。当一个资源需要在多个对象间共享时,就需要shared_ptr了。他通过引用计数来管理资源。当引用计数为0时,资源被释放。shared_ptr可能会导致循环引用问题。

3.shared_ptr自动管理内存的原理

​ shared_ptr内部维护了一个计数器,来跟踪有多少shared_ptr指向同一个资源,当计数器为0的时候,调用delete或自定义的delete来释放资源。

​ 引用计数器何时增加:1.新建一个shared_ptr并指向一个资源; 2.复制构造函数创建一个shared_ptr; 3.用复制运算符(=)给把一个shared_ptr给另一个shared_ptr时

​ 引用计数器何时减少:1.shared_ptr被销毁时,如离开局部作用域或析构函数析构类对象时;2.shared_ptr不再指向一个资源时,如调用reset函数或让它指向另一个资源时

4.循环引用的发生和解决

​ 两个或多个对象相互引用,或复杂数据结构,如图、双向链表等,存在多个引用路径的情况下,可能会存在循环引用问题,导致资源无法释放。这是就需要用weak_ptr来解除循环引用,因为weak_ptr指向的对象的引用计数不会增加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <memory>
class myclass
{
public:
std::shared_ptr<myclass> other;
myclass(){}
~myclass(){}
};
int main(int argc, char** argv)
{
std::shared_ptr<myclass> ptr1(new myclass());
std::shared_ptr<myclass> ptr2(new myclass());
//这里在myclass内部,ptr1和ptr2相互引用,导致ptr1和ptr2的引用计数均为2,资源无法释放。
ptr1->other = ptr2;
ptr2->other = ptr1;
return 0;
}

面向对象设计原则

面向对象设计原则

1.何为设计原则

​ 设计原则是写面向对象代码时的指导思想。我们在设计软件时,需求往往是复杂而多变的,为了应对这一点,我们有两大法宝:分解和抽象。分解即把复杂问题分解成若干简单问题,抽象即提取事物的核心特征。面向对象设计原则的核心任务就是抵御变化,将变化点进行封装来减少对代码的修改,来方便人们进行功能的拓展。

2.单一责任原则

​ 单一责任原则是设计原则的核心,它认为:每个类应该只有一个职责。因此也只有一个修改该类的原因。如此一来,我们可以最大程度减少类方法的膨胀,减小修改类内容的可能性。

​ 如:一个日志类应该拥有添加日志记录的功能,而不应该有将日志持久化到磁盘的功能。应该专门设计一个类去执行持久化操作。

3.开放封闭原则

​ 开放封闭原则实质上是对单一责任原则和抽象的运用。它认为:软件实体对拓展开放,对修改封闭。我们可以将某个类的功能进行拆分,将其分为多个部分,提取其核心抽象部分作为抽象基类,当新业务到来,直接继承抽象基类,在新的业务类中实现具体细节即可,此为拓展。

​ 如:要实现一个过滤器,(如通过颜色、大小过滤对象)。可以将其分为两个部分(单一责任原则):过滤器(输入并返回某些对象的过程),规范(用于规定如何过滤数据元素)。如此,当我们需要一个具体的过滤器的时候,就可以继承过滤器抽象基类,再将规范作为参数传递给类方法,我们变可以得到各种过滤器了。

4.里氏替换原则

​ 里氏替换反映了继承必须基于抽象。它认为:如果某个接口以基类Parent类型的对象为参数,那么它应该同等的接受子类对象为参数,并且程序不会产生任何异常。简单来说,子类可以替换它们的基类。

5.接口隔离原则

​ 接口隔离原则也是对单一责任原则的运用。它认为:应该将复杂的接口分离为多个单独的接口,以避免强制实现者必须实现他们实际上并不需要的接口。

​ 如:需要一个抽象打印机基类,有打印、扫描、传真三个接口,但是有时使用者只需要打印功能,他还得把三个接口全实现一遍。如果将打印机基类拆分成打印、扫描、传真三个接口类,再由类组合或继承的方式生成打印机基类,那么就可以根据想要的功能,去实现对应的接口,不需要实现内些不需要的接口。

​ 实际上还是体现了类的单一责任,尽可能缩减类的功能,减少耦合,增加灵活性。

6.依赖倒置(倒转)原则

​ 依赖倒置原则体现了抽象和封装变化思想。它认为:

​ 高层模块(稳定)不应该依赖低层模块,它们都该依赖于抽象(稳定)

​ 抽象(稳定)不应该依赖于实现细节(变化),实现细节(变化)应该依赖于抽象(稳定)

​ 如:有一个打印日志的模块,它应该依赖于日志抽象类(将日志抽象类的引用或指针作为类成员),而不应该依赖于某个具体的日志类。

​ 使用建议:

​ (1)每个类尽量都有接口或抽象类,或者接口和抽象类两者都具备。

​ (2)变量的表面类型尽量是接口或抽象类。

​ (3)任何类都不应该从具体类派生。

​ (4)尽量不要重写基类的方法。如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要重写。

​ (5)结合里氏替换原则使用。

C++基础一

                c++基础一

1.多态的实现原理

c++中的多态分为静态多态和动态多态。
静态多态是编译时的多态,通过重载函数和模板实现。
动态多态是运行时的多态,通过虚函数和继承实现。


动态多态实现原理:为每个包含虚函数的类建立一个虚函数表。
虚函数表的项存储各个虚函数在内存中的入口地址。
在该类的每个对象中设置一个指向虚函数表的指针。
在调用虚函数时,先利用虚指针找到虚函数表,确认调用的虚函数的入口地址在表中的位置,获取入口地址后完成调用

静态多态的实现原理:
    函数重载:编译器用过函数调用的时候传递的参数类型和数量来确定合适的函数版本。
         在编译阶段,编译器检查函数调用的上下文,通过实际参数确定调用重载版本
    模板:模板允许我们编写通用的代码。编译器根据模板实例化的类型生成对应的代码

2.菱形继承问题
菱形继承问题是指在多重继承时,两个或多个派生类继承自同一个基类,然后又有另一个类继承这些派生类。
这种情况下,最终生成的派生类将包含多个来自同一个基类的子对象副本,导致二义性和资源浪费。
为了解决这个问题:可以使用虚继承,让最终生成的派生类只包含一个基类的子对象副本
菱形继承下的虚函数:若子类中有虚函数,而所继承的父类中没有虚函数,则子类就有自己的虚表指针。当父类中有虚函数,则子类的虚表放在 第一个继承的父类中。

3.析构函数为什么设置成虚函数
一般来说,如果这个类是一个带有多态性质的类,那么它的析构函数就应该是虚函数,否则就不应该是虚函数。
原因是,如果这个类是有虚函数,那它很可能会被继承。当通过父类指针指向子类对象时,在父类对象销毁时,
如果它的析构函数不是虚函数,那么只有父类的析构函数会被调用,子类析构函数不会调用。造成内存的局部释放,
导致资源泄露

4.static和const对象的用法’
const