目录
一、传引用有没有拷贝
二、引用和指针的区别
三、构造/析构函数中可不可以调用虚函数
四、怎样区分继承和组合
五、多态的实现原理、虚表虚指针
六、用过哪些设计模式
6.1状态模式
6.2享元模式
6.3单例模式
6.4工厂模式
6.5观察者
七、观察者模式中有哪些接口
八、生产消费机制的实现
九、生产者速度高于消费者速度怎么办
十、单例的懒汉式、饿汉式有什么区别
面试参加了不少,问答也积累了足够多,基础的、进阶的、学院派的、实践派的、奇葩的,都见识到了,有些也是让人哭笑不得。
后来养成了面完即整理的习惯,不直接照搬网上科班式严谨的论述,因为大部分人实际面试时做不到完整又流畅的表达,我就结合自己的理解和面试的临场发挥,对主要的或有特点的问答做下记录,并分享出来希望能帮到有需要的小伙伴。
为了避免单篇篇幅过长,我会分拆成几个,每篇的问答个数尽量控制在10个左右并控制篇幅,主题集中在C++、Qt,并持续更新,有错误的地方欢迎指正,别喷就好。
一、传引用有没有拷贝
没有。
上来就放一个让我心慌的问题,印象中引用就是为了解决拷贝问题的,而且上网查了之后确实也没有见到有讨论传引用的拷贝问题的,但又感觉直接回答没有好像虚虚的,也许我才疏学浅,还有别的内涵在里面吗?
二、引用和指针的区别
答案先TODO一下。
先说说这种面试官的心理,问这种看似基础实则刁钻的问题,居心何在呢,凸显自己或者PUA候选人?只能说有点儿太八股了。
三、构造/析构函数中可不可以调用虚函数
可以调用,语法上没问题,但起不到虚函数运行时多态调用的作用,不建议使用,容易造成理解上的混淆。
构造过程是先基类后子类,析构过程是先子类后基类,都是从基类指针指向子类实体对象来理解。
在构造函数中调用虚函数,因为构造函数执行完后对象才算构造完,才会生成虚函数表和虚指针,因此在构造过程中调用虚函数无法触发多态机制,只能按普通函数调用,调用的是类本地的函数。
在析构函数中调用虚函数,由于子类先于基类析构,因此在基类的析构函数中已无法多态调用至子类的虚函数,所以也只能按普通函数调用,调用的是类本地的函数。
四、怎样区分继承和组合
通常来讲,继承体现的是is-a的关系,即子类是基类的一种,如苹果树是一种树,那么苹果树继承自树;组合体现的是has-a的关系,即被组合对象是组合对象中的一部分,如树根、树叶都是树的一部分,那么树根、树叶和树是组合关系。
继承和组合都是代码复用的方式,在继承中基类的内部细节对子类可见,破坏了基类的封装性,且子类基类间耦合度高;组合关系中对象之间的内部细节互相不可见,封装性好,耦合度低,但整体对象中包含很多局部对象。
除非两个类之间是is-a的关系,否则尽量使用组合。
五、多态的实现原理、虚表虚指针
这是经典问题,也是C++程序员的立身之本,请务必掌握。
封装、继承、多态是OOP的三大基本特征。
多态以虚函数表和虚指针为基础实现。
在有虚函数的继承关系中,每个对象拥有一个虚指针,指向其虚函数表,在用基类指针或引用进行函数调用时,在虚函数表中找到实际对象中的函数地址。
六、用过哪些设计模式
6.1状态模式
项目中有报警功能,分为一级报警、二级报警、三级报警、无报警,这几种报警状态之间互有跳转,非常适合套用状态模式;而且先前的编码是冗长的if-else,这就更加适合状态模式的设计初衷了,于是对报警模块进行了重构。
重构完成后模块化程度、代码整洁行、可读性都大大提高,而且使用方便,印象中仅对外暴露一个调用接口即可,推广起来非常方便。
不过也有不足,就是状态模式的通病:类的数量很多。
6.2享元模式
开发的软件主要是面向大量硬件设备的监控,比如某种设备有100个,常规的做法就是创建100个界面实例,不过我的应用场景有一点儿特殊的是,这么多设备同时最多只看一个,也就是说不需要那么多设备界面同时显示着,这就有了升级改造的下手之地。
研究了一下决定采用享元模式进行改造,即仅创建一个该种设备的监控界面实例,利用后台数据对象中本就存在的唯一性标记,比如编号、IP地址等,在界面切换时根据标记进行数据的加载与切换显示,在用户无感的情况下即大大减少了界面实例的数量,降低了内存消耗。
此次重构也被领导作为样板推广。
6.3单例模式
我们当时的架构设计中,业务执行单元有多个,往上增加了一层业务管理类以方便集中管理,这个业务管理类做成了单例,记得是用的懒汉式。
软件初始化、业务调度、配置文件读写、设备监控、报警监控,等等,多个口子都涉及到顶层业务管理,而这种管理显然只能有一个出口,不能政出多门,所以设计成单例。
单例的重点:构造函数定义成私有的、instance和GetInstance()定义成static的、注意多线程竞争问题、懒汉饿汉的区别。
6.4工厂模式
工厂模式是结合着上面说的享元模式用的,当时的设备情况比较复杂,不是单纯的几类毫不搭边的设备,其中有那么几种,从软件角度来看是大同小异的,所以就抽象了一层基类、派生出几个子类,在享元模式生成界面实体的过程中,对这几种设备加了一层工厂模式,简化了外部调用时的复杂度,代码逻辑整体更有层次感。
6.5观察者
稍微上点儿规模的软件一般都会用到观察者模式或者更复杂的生产消费模式,不过说实在的,在我们的项目里这块代码不是我实际写的,但我看过很多遍,调bug的时候打断点也一行一行走过很多遍,可以说是门清。
我们的应用首先进入生产消费模式,从网络上接收数据,搜帧、解帧后发出通知,相当于生产,之后消费线程取走数据做进一步解析;
然后是观察者模式,消费线程解析完后,分发给界面显示、数据存储、业务计算等模块实现各自的需求。
七、观察者模式中有哪些接口
观察者分为发布者Publisher和订阅者Subscriber,对它们要设计抽象基类以对统一接口做出约束,并方便注册时对象的多态传递。
发布者中包含注册接口register()用来注册订阅者、注销接口detach()用来注销订阅者、通知接口notify()用来通知订阅者更新消息;
订阅者中包含更新接口update(),在发布者的通知接口中被调用,以更新消息。
八、生产消费机制的实现
类似观察者模式,但生产者消费者之间解除直接耦合,通过数据缓冲区及通知机制进行数据通信,生产者往数据队列加入数据,消费者从数据队列中取出数据做处理。
数据缓冲区为空时消费者线程cv处于wait状态并检测空状态标记,生产者线程可以往数据缓冲区中加入数据,数据加入完成后生产者通知消费者到缓冲区取数据;若缓冲区满,生产者wait并检测满状态标记以暂停加入,待消费者线程消费使得缓冲区不满并notify之后继续加入数据。
设置数据缓冲区的意义:解耦观察者模式、解决并发问题、解决生产消费步调不一致的问题。
九、生产者速度高于消费者速度怎么办
这个问题事后回想让人想骂街了,不知道是我没表达清楚还是面试官糊涂,问题八实际已经回答了这个问题,他可能理解成我在讲观察者了,把我整的很懵,绞尽脑汁我也不知道发生了什么,于是颤颤巍巍地答了个“限制生产的速度或者数量”,结果被人家鄙视一顿。
事后就上网查吧,说的都是设置缓冲区啊,所以我已经答过了啊怎么还要问,总之这家自以为是的公司让人感觉很奇葩,后面还有奇葩的问题。
十、单例的懒汉式、饿汉式有什么区别
所谓懒,即用到时才动作,即在用到该单例对象的地方才创建单例对象,写法一般即是在GetInstance()方法中创建,可能会引发线程同步问题,但也能节约程序初始化的耗时;
所谓饿,即上来就要吃就要创建,在main()函数执行前即执行new操作,后续GetInstance()方法直接返回该单例对象即可,避免了线程间竞争创建的问题,但增加初始化耗时。
这里增加一个对单例static的讲解,主要是为了通过类名访问:
(102条消息) 单例模式中instance为什么一定要是static的_naerna的博客-CSDN博客
十一、单例与全局变量
全局变量虽然也能提供单例模式实现的全局访问功能,但它保证不了类实例的唯一性;
而且全局变量无论被用到与否都会被创建,单例的懒汉式就没有这个问题,尤其在资源是敏感点时这个问题更加明显。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)