首页 / 设计模式 / 面经 - 1 - C++/设计模式
面经 - 1 - C++/设计模式
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了面经 - 1 - C++/设计模式,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含15508字,纯文字阅读大概需要23分钟。
内容图文
![面经 - 1 - C++/设计模式](/upload/InfoBanner/zyjiaocheng/611/429953c3437f422e9a6fc31136b377c5.jpg)
c++四种强制类型转换:
https://www.cnblogs.com/Allen-rg/p/6999360.html
3. BOW算法
2. 设计模式 | 单例模式 | 工厂模式 | 设计模式:多线程中单例模式怎样上锁?工厂模式解决什么问题?
单例模式:https://light-city.club/sc/design_pattern/singleton/singleton/
懒汉在多线程时不安全,饿汉安全
多线程加锁->双重检查锁DCLP->双重检查锁+自动回收DCL
- 先分配内存->2.在内存中构造对象->3.指针指向这块被分配的内存(1顺序确定,23顺序不定)->出现Bug,dclp需要在123的顺序执行时发挥所用所以要确保2在3之前执行->c++11就将123合成为一个原子操作,新的C++11规定了新的内存模型,保证了执行上述3个步骤的时候不会发生线程切换,相当这个初始化过程是“原子性”的的操作,DCL又可以正确使用了
C++11之前解决方法是barrier指令。要使其正确执行的话,就得在步骤2、3直接加上一道memory barrier。强迫CPU执行的时候按照1、2、3的步骤来运行。
单线程下,正确。
C++11及以后的版本(如C++14)的多线程下,正确。
C++11之前的多线程下,不一定正确。
原因在于在C++11之前的标准中并没有规定local static变量的内存模型。于是乎它就是不是线程安全的了。但是在C++11却是线程安全的,这是因为新的C++标准规定了当一个线程正在初始化一个变量的时候,其他线程必须得等到该初始化完成以后才能访问它。
单利模式应该用场景:
核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
工厂模式使用场景:
1.工厂模式的目的是为了实现解耦,将对象的创建和使用分开,即应用程序将对象的创建和初始化职责交给工厂对象。若一个对象A想要调用对象B时,如果直接通过new关键字来创建一个B实例,然后调用B实例,这样做的不好处是,当需求变更,要将B实例换成C实例时,则需要修改所有new了该实例的方法。
2.降低代码重复。如果对象B的创建过程比较复杂,并且很多地方都用到了,那么很可能出现很多重复的代码,通过统一将创建对象B的代码放到工厂里面统一管理,可以减少代码的重复率,同时也方便维护。相比于构造函数来说,复杂的初始化,会使得构造函数非常的复杂。
由于创建过程都由工厂统一的管理,有利于当业务发生变化之后的修改
3.工厂模式将创建和使用分离,使用者不需要知道具体的创建过程,只需要使用即可。
4.类本身有很多子类,并且经常性发生变化。
(创建对象需要大量重复的代码。
创建对象需要访问某些信息,而这些信息不应该包含在复合类中。
创建对象的生命周期必须集中管理,以保证在整个程序中具有一致的行为。
)
简单工厂模式:一个工厂,多个产品。产品需要有一个虚基类。通过传入参数,生成具体产品对象,并利用基类指针指向此对象。通过工厂获取此虚基类指针,通过运行时多肽,调用子类实现。
工厂方法模式:工厂类和车子类是两个虚基类,工厂类指针新建一个宝马工厂, 车子类的指针 = 工厂类的指针调用具体宝马工厂的方法()
抽象工厂模式:新增高配车虚基类以及高配奔驰车继承工厂方法模式中的奔驰车,奔驰工厂系列类不变。
https://www.cnblogs.com/huiz/p/8232783.html
- 虚函数怎么实现的,怎么用的 | 说下虚函数,什么情况下使用基类指针指向派生类的这种写法 | 构造函数能是虚函数吗?| 多态是什么?(虚函数的继承)
C++primer原话:OOP的核心思想是多态性,多态性的含义是“多种形式”,我们把具有继承关系的多个类型称之为堕胎类型,因为我们能使用这些类型的“多种形式”而无需在意他们的差异。引用或指针的静态类型与动态类型不同这一事实正是C++语言支持多态性的根本所在。
虚函数和纯虚函数都能做到这一点,区别是,子类如果不提供虚函数的实现,那就会自动调用基类的缺省方案。而子类如果不提供纯虚函数的实现,则编译将会失败。基类提供的纯虚函数实现版本,无法通过指向子类对象的基类类型指针或引用来调用,因此不能作为子类相应虚函数的备选方案。纯虚函数是虚函数后面跟个“=0”,纯虚函数在继承类中必须要实现。
多态:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数
派生类对象的地址可以赋给指向基类对象的指针变量(简称基类指针),即基类指针也可以指向派生类对象。为什么有这一规定呢?因为它可以实现多态性【1】,即向不同的对象发送同一个消息,不同的对象在接受时会产生不同的行为。
为什么非要用基类指针去指派生类呢?看下面例子,当一组指针要指向不同的类的时候就把这组指针统一定义为基类指针:
int main()
{
Shape* array[2]; //定义基类指针数组
Square Sq(2.0);
Circle Cir(1.0);
array[0] = &Sq;
array[1] =&Cir;
for (int i = 0; i < 2; i++) /
{
cout << array[i]->area() << endl;
}
return 0;
}
构造函数可以是虚函数吗?
- 先补充虚函数与虚函数表的区别:虚函数的地址存放于虚函数表之中。运行期多态就是通过虚函数和虚函数表实现的。类的对象内部会有指向类内部的虚表地址的指针。通过这个指针调用虚函数。虚函数的调用会被编译器转换为对虚函数表的访问。
- 为什么构造函数不能是虚函数:从存储空间角度,虚函数对应一个vtable(虚函数表),可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。
- 析构函数可以是虚函数:基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。 C++不把虚析构函数直接作为默认值的原因是虚函数表的开销以及和C语言的类型的兼容性。如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。因为它会为类增加一个虚函数表,使得对象的体积翻倍,还有可能降低其可移植性。
- 浅拷贝和深拷贝
浅拷贝(位拷贝)就是缺省拷贝构造函数(无论是没参数还是使用默认缺省参数),只构造一次,使用相同内存空间,但是析构两次会造成程序崩溃。
深拷贝就是显示拷贝构造函数,各自拥有不同的内存。
https://blog.csdn.net/weixin_41143631/article/details/81486817
https://blog.csdn.net/caoshangpa/article/details/79226270
- 说一下vector和list和deque的使用场景?
https://www.cnblogs.com/inception6-lxc/p/9244194.html
vector VS list VS deque
使用过cmake吗?那makefile呢,总知道吧- 熟悉那些脚本语言?脚本语言有什么好处
python、js、php
脚本语言不需要编译,可以直接由解释器来负责解释。
脚本语言除了不适合用来做操作系统和关键的引擎,其它的都能做!
- 智能指针,循环引用 || 智能指针(unique_ptr与share_ptr区别、weak_ptr作用、share_ptr复制一个对象时,引用参数怎么设置,是一个吗?)
https://blog.csdn.net/zhwenx3/article/details/82789537
防止内存泄漏->引入智能指针->智能指针被循环利用也会造成内存泄漏->share_ptr换成weak_ptr减少引用计数,
weak_ptr是一种不控制所指向对象生存期的智能指针,指向shared_ptr管理的对象,但是不影响shared_ptr的引用计数。它像shared_ptr的助手,一旦最后一个shared_ptr被销毁,对象就被释放,weak_ptr不影响这个过程。
unique_ptr VS share_ptr VS weak_ptr
https://blog.csdn.net/zhuziyu1157817544/article/details/64927834?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
- weak_ptr指向shared_ptr管理的对象,但是不影响shared_ptr的引用计数。它像shared_ptr的助手,一旦最后一个shared_ptr被销毁,对象就被释放,weak_ptr不影响这个过程。
- 与shared_ptr不同,某一时刻,只能有一个unique_ptr指向一个给定的对象。因此,当unique_ptr被销毁,它所指的对象也会被销毁。
- 迭代器你都了解那些,怎么实现的,模板类有什么作用,String类的实现
- 正向迭代器、反向迭代器
- 常量正向迭代器、常量反向迭代器(指针指向的数据是常量)
实现见连接:http://c.biancheng.net/view/338.html
类模板强调的是模板
c++模板类给程序开发带来了非常大的方便,我总结了c++模板类的好处如下
- 可用来创建动态增加或减少的数据结构
- 它与某种特定类型无关,因此代码可重复使用
- 它在编译时检查数据类型而不是运行时检查数据类型,保证了类型的安全
- 它是平台无关的,具有很好的移植性
template<typename T>
class A
{};
这个A就是一个模板
模板类强调的是类
class A<int> 这个A<int> 就是模板类,类模板的实例
实例:
#include<iostream>
using namespace std;
template<typename T> class A; //前置声明
template <typename T> ostream & operator<<(ostream &os, const A<T> &a);
template <typename T> istream & operator>>(istream &is, A<T> & a);
template<typename T>
class A
{
private:
T c;
public:
A(){}
friend istream& operator>> <T>(istream& is, A<T> &a);
// 这里要加一个<T>
friend ostream& operator<< <T>(ostream& os, const A<T> &a);
//注意用空格将 <<与<T>隔开
};
template<typename T>
istream & operator>> (istream& is, A<T>& a)
{
is >> a.c;
return is;
}
template<typename T>
ostream & operator<< (ostream& os, const A<T>& a)
{
os << "a: " << a.c << endl;
return os;
}
int main()
{
A<int> a;
cin >> a;
cout << a;
}
string类的实现:https://www.cnblogs.com/zhizhan/p/4876093.html
- sizeof一个类的大小,字节对齐方面
https://blog.csdn.net/starstar1992/article/details/61619422
摘自:http://www.myexception.cn/program/1666528.html
//编译器为每个有虚函数的类都建立一个虚函数表(其大小不计算在类中),并为这个类安插一个指向虚函数表的指针,即每个有虚函数的类其大小至少为一个指针的大小4
class A
{
public:
int a;
void Function();
};
class B
{
public:
int a;
virtual void Function();
};
class C : public B
{
public:
char b;
};
class D : public B
{
public:
virtual void Function2();
};
class E
{
public:
static void Function();
};
class staticE
{
static int intVar;
static void fun(){}
};
void test1()
{
cout<<"sizeof(A)="<<sizeof(A)<<endl;//4 (内含一个int,普通函数不占大小)
cout<<"sizeof(B)="<<sizeof(B)<<endl;//8 (一个int ,一个虚函数表指针)
cout<<"sizeof(C)="<<sizeof(C)<<endl;//12 (一个int ,一个虚函数表指针,一个char ,再加上数据对齐)
cout<<"sizeof(D)="<<sizeof(D)<<endl;//8 (一个int ,一个虚函数表指针,多个虚函数是放在一个表里的,所以虚函数表指针只要一个就行了)
cout<<"sizeof(E)="<<sizeof(E)<<endl;//1 (static 函数不占大小,空类大小为1)
cout<<"sizeof(staticE)="<<sizeof(staticE)<<endl;//1 静态数据成员不计入类内
- STL常用容器、map\set区别、vector变慢原因及解决(用reserve函数预留空间)
常用容器:vector,map,set,string,list,queue
map\set区别:
map和set都是C++的关联容器,其底层实现都是红黑树(RB-Tree)。由于 map 和set所开放的各种操作接口,RB-tree 也都提供了,所以几乎所有的 map 和set的操作行为,都只是转调 RB-tree 的操作行为。
map和set区别在于:
(1)map中的元素是key-value(关键字—值)对:关键字起到索引的作用,值则表示与索引相关联的数据;Set与之相对就是关键字的简单集合,set中每个元素只包含一个关键字。
(2)set的迭代器是const的,不允许修改元素的值;map允许修改value,但不允许修改key。其原因是因为map和set是根据关键字排序来保证其有序性的,如果允许修改key的话,那么首先需要删除该键,然后调节平衡,再插入修改后的键值,调节平衡,如此一来,严重破坏了map和set的结构,导致iterator失效,不知道应该指向改变前的位置,还是指向改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值;而map的迭代器则不允许修改key值,允许修改value值。
(3)map支持下标操作,set不支持下标操作。map可以用key做下标,map的下标运算符[]将关键码作为下标去执行查找,如果关键码不存在,则插入一个具有该关键码和mapped_type类型默认值的元素至map中,因此下标运算符[ ]在map应用中需要慎用,const_map不能用,只希望确定某一个关键值是否存在而不希望插入元素时也不应该使用,mapped_type类型没有默认值也不应该使用。如果find能解决需要,尽可能用find。
vector变慢原因及解决:
vector是STL中最常用的容器,比起用户自定义的数组,具有内存分配对用户透明,可动态增长等特点。 vector什么操作导致效率低?毫无疑问,那就是当vector 预留空间不足时 常用操作push_back()函数在每次插入元素时会检测预留空间是否够用
push_back()时预留空间不够用:要重新分配内存,并且拷贝当前已有的所有元素到新的内存区域。如果已有元素很多,这个操作将变的非常昂贵。
ector的内存管理策略是:一旦空间不足,则增长一倍,对于大数据量,这也许是一块不容小觑的资源 所以应该尽量避免vector重新分配内存。
怎么避免vector自动重新分配内存?
可以预先估计元素的个数,用reserve函数进行预留空间的分配
这个函数会分配一块指定大小的空间,但不进行任何初始化,所以分配出来的空间不包含元素,也就不能访问。然后用同样的方式使用push_back函数,此时只要不超过之前reserve的空间,vector不会进行内存重新分配,只是简单的依次往后摆放。
所以当你用push_back时,请慎重考虑是否需要reserve。
- 结构体与类的区别?
结构体和类的唯一区别就是: 结构体和类具有不同的默认访问控制属性。
- 类中,对于未指定访问控制属性的成员,其访问控制属性为私有类型(private)
- 结构体中,对于未指定任何访问控制属性的成员,其访问控制属性为公有类型(public)
C++中,不使用结构体丝毫不会影响程序的表达能力。C++之所以要引入结构体,是为了保持和C程序的兼容性。
但有时仍会在C++中使用结构体,是因为,可以使用结构体将不同类型数据组成整体,方便于保存数据。(若用类来保存,因类中成员默认为私有,还要为每个数据成员特定函数来读取和改写各个属性,比较麻烦。)
- i++与++i的区别,哪个好,原因?
http://c.biancheng.net/view/338.html
- 字符型指针和浮点型指针的大小?
(一样大)指针跟硬件有关32位的电脑就是4字节 所有指针类型都一样大小的
- 指针和引用的区别
https://www.cnblogs.com/zhxmdefj/p/11185124.html
- c++内存管理
- c++和java的区别
- C语言中除了 malloc还有哪些获取内存的方式(不是指 API的不同,而是获取内存方式的不同)
开始的时候我回答realloc, calloc, new, allocator,他说这些都是底层封装malloc,他问的是那种底层实现就不一样的。然后提示了我一下mmap,我说就是那种文件映射,共享内存,他说共享内存算是一种。
内存分区
内存分配
volatile?
- 循环展开知道吗?weak_ptr了解吗(我讲了解决shared_ptr的循环引用问题,面试官就没再问了,应该算押到考点…
- move语义?(讲了移动构造函数的原理)
- template了解吗
实现一下 memcpy() [要点:地址重叠]
对void*你了解什么?
int a[10000000]会有什么问题?
如果想让一个函数在main函数之前执行,该怎么做?
介绍常见数据结构
设计模式
软件工程常用开发模型
软件开发流程
空悬指针与野指针
怎么避免空悬指针
NULL与nullptr差别
Utf-8几个字节,汉字呢
单例模式,线程安全
重载,多态
虚函数表
auto
左值右值
move,forward
new 和 malloc 区别
A: 讲了一下new expression是如何调用operator new、构造函数 和 处理异常的。详细见cppreference - new表达式。malloc的内存只是调用glibc库函数申请一块可用的内存,并没有调用构造函数。而且也不能保证正确的alignment。
C++内存管理:std::shared_ptr, std::unique_ptr, std::weak_ptr
除了std::weak_ptr,还有哪些解决循环引用的办法?
A: 可以用解决死锁循环等待的办法,允许循环引用偶尔发生。循环引用较多时,构造系统的资源图,在图中找到环,并破坏这些环。面试管回答说实践中的确有这种办法。
来实现一个unique_ptr吧,不需要自定义的deleter,简单的就可以
C++从源文件到可执行文件的过程
A: C++标准规定的9个翻译阶段 和 预处理 编译 汇编 链接的对应,详见个人博客 - C++从源文件到可执行文件系列
- Q: 上面这段程序,sizeof(b)为多少
A: 考虑vptr和对齐,关于x64上的虚函数和虚基类相关的ABI,可以参考Itanium C++ ABI - Chapter 2: Virtual Table Layout
B自己的data member:8bytes对齐后为16bytes
A自己的data member:8bytes对齐后为16bytes
vptr为8bytes,A为B的primary base class,则A和B共享一个vptr
因此总共为16 + 16 + 8 = 40bytes
- Q: 上面这段程序存在哪些问题?
A:当多态地使用derived class B时,如果A的析构函数未声明未虚函数,可能会导致内存泄漏
derived class B中的函数void f(){}和基类中的virtual void f()有相同的函数签名,虽然此时会override基类中的virtual void f(),但为了避免函数签名写错而导致错误的声明一个新函数,还是要标上override
使用callback的网络库使业务逻辑割裂,使用coroutine使业务逻辑更清晰
io_uring大大减少系统调用的次数,socket的读写都在内核中完成
structured concurrency,减少了使用std::shared_ptr的次数,使用C++的RAII即可维护对象的生命周期
File I/O 和 socket 可以在同一框架下使用。
- Q: 除了使用coroutine的异步编程,还知道哪些异步编程的模式呢
A: 鬼扯了一下C++23的executor模型和Facebook的libunifex
- 基类和派生类中构造函数和析构函数的顺序
- 构造函数可以是虚函数吗(不可以)
- 析构函数可以是虚函数吗?(可以),必须是吗?
- 动态链接库和静态链接库了解吗,说一下他们的的区别
- 说一下函数重载,重载函数和原函数在程序里面编译的时候一样吗?
- c++里面的多态是怎么实现的
- c++程序运行过程中的编译和链接知道吗?说一下
- 虚函数的实现
- 1map的实现
- 写程序解数独,主要考思路
- 概率,A,B两个赌徒胜的概率都是0.5,A赢2局及以上就可以获胜,B赢3局以上就可以获胜,求各自的胜率
- 链表深复制,节点有next和rand指针
lambda表达式和智能指针的理解,
- 接着是问智能指针是否是线程安全的;
-
- 先问的C++的相关知识:智能指针和lambda表达式
- auto关键字的用法(主要考察了能否只声明不初始化);
- 然后问到python装饰器,我说python用的少面试官也就没再问;
有用C++写过服务端就问了IO多路复用和epoll适合的场景;
最后是阐述对C++中继承的理解。
内容总结
以上是互联网集市为您收集整理的面经 - 1 - C++/设计模式全部内容,希望文章能够帮你解决面经 - 1 - C++/设计模式所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。