-
什么是面向对象(OOP)
- 面向对象是一种对现实世界理解和抽象的方法、思想,通过将需求转化为对象进行问题处理的一种思想。
-
三大特性?
-
继承
-
继承的意义:继承主要实现代码重用,节省开发时间。
-
C++的三种继承 :public继承方式、protect继承方式、private继承方式(默认)
-
虚继承、多重继承和直接继承的区别
-
交叉继承(虚继承)出现的目的:解决继承代码的内存空间冗余问题
-
什么是虚继承?它与一般的继承有什么不同?它有什么用?
-
虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的,为了节省内存空间。
-
举例:类D继承自类B和类C, 而类B和类C都继承自类A.在类D中会两次出现A。为了节省内存空间,可以将B、C对A 的继承定义为虚拟继承,而A就成了虚拟基类。
-
-
虚继承和普通继承/一般继承的区别
-
在普通继承中,如果继承的子类本身有虚函数,就会在从父类继承过来的虚表上进行扩展;而在虚继承中,如果子类本身有虚函数,编译器就会为其单独生成一个虚表指针(vptr)和虚函数表,如果子类本身没有新增虚函数,那么vptr就不会存在,也不会有对应的虚函数表。
-
普通继承中是先按父类的方式构造对象,然后在此基础上进行扩展和覆盖;虚继承中父类对象的虚表是单独保存的,通过新增的虚基类指针和虚基类表,来标明各个父类对象内存空间的偏移值。
-
注意:虚函数继承和虚继承是完全不同的两个概念
-
-
-
封装
-
封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。
-
封装的意义:保护或者防止代码被无意破坏。
-
-
多态
-
C++多态的实现(用代码实现多态):https://www.cnblogs.com/OFSHK/p/14523298.html
-
多态怎么实现(底层实现)、意义/好处、运行时多态的原理
- 虚函数:原理、如何实现的(常规的虚函数底层实现+具体调用过程+如何实现的多态)、目的、用法。之前写的:https://www.cnblogs.com/OFSHK/p/14536996.html
-
虚函数表:虚函数表是如何组织的(数据结构是什么)、运行时怎么查虚函数表
-
虚函数和纯虚函数的区别:https://www.cnblogs.com/OFSHK/p/14536996.html
-
virtual关键字作用:vitural的函数将父类函数变为虚函数。(加入了虚函数之后,调用不同指针对象指定函数的时候都是去自动调用当前对象类中的具体函数形式,而不是像一般函数的调用一样,只是去调用父类的函数)
-
C没有继承能实现多态吗,实现一下(用结构体来实现)
-
实现多态的方法有哪些?
-
总的来说:重载、重写/覆盖、模板、虚函数
-
静态多态(在程序编译时系统就决定调用哪个函数):重载、模板
-
动态多态(在程序运行过程中动态确定调用哪个函数):重写/覆盖、虚函数
-
-
构造函数可以声明为虚函数吗?不可以,构造函数不能声明为虚函数,但是析构函数可以
-
-
C#和C++的区别
-
C++的GC机制:https://www.cnblogs.com/QG-whz/p/5079638.html?utm_medium=referral
-
讲两个熟悉的GC回收器+GC过程(引用计数算法、Mark&Sweep算法 即标记&清除算法)
-
如何判断对象可回收, 哪些内容可以作为GCroot。
-
-
STL
-
是什么?STL由容器、算法、迭代器(融合前两者)。
- 容器:存放数据的地方,如array。容器分为两类:序列式容器和关联式容器。序列式容器:元素不一定有序,但可以被排序。如:vector、list、deque、stack、queue、heap、priority_queue、slist;关联式容器:内部是平衡二叉树。所谓关联,指每个元素都有一个键值和一个实值,元素按照一定的规则存放。如:RB-tree、set、map、multiset、multimap、hashtable、hash_set、hash_map、hash_multiset、hash_multimap。
-
用过哪些stl、容器各自优缺点
-
map、list、deque、queue、set、unordered_map, array,、vector、arraylist?
-
底层存储=存储结构=数据存储方式:list封装了链表、map和set封装了二叉树,查找用的二分、vector封装了数组
-
如何选择?https://www.cnblogs.com/huxiaoyun90/archive/2013/09/22/3332473.html
-
-
stack、queue、vector、list、map、unordered_map实现增删改查的时间复杂度分别是多少
-
unordered_map和map区别
-
vector的resize和reserve
-
vector的初始的扩容方式代价太大,初始扩容效率低, 需要频繁增长,不仅操作效率比较低,而且频繁的向操作系统申请内存容易造成过多的内存碎片,所以需要合理使用resize()和reserve()方法提高效率减少内存碎片的。关于内存碎片的原因和处理,见:https://www.zhihu.com/question/51836333
-
resize和reserve详情见下面的链接。
-
-
vector的扩容机制(扩容方式)以及优化方法
-
扩容机制:GCC是2倍扩容,VS13是1.5倍扩容。(原因可以从内存碎片、伙伴系统、内存的浪费角度出发)
-
优化方法:1reserve提前分配足够的空间以避免不必要的重新分配和复制周期;2在填充或拷贝到 vector 时,用赋值不要用insert或push_back;3复制拷贝数据的时候:效率从高到低分别是:swap > copy > assign > 直接赋值 > push_back赋值。(原因是xxx)。(2和3是同一种方法)更细的解释见:https://blog.csdn.net/jaycsu/article/details/598727?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242
-
参考:https://www.cnblogs.com/-citywall123/p/12846941.html、https://www.cnblogs.com/simonote/p/9265374.html
-
其他:1vector内部的数据是存放在连续的存储空间;2每删除容器中数据的时候,缓冲区大小并不会改变,仅仅只是清除了其中的数据,只有在析构函数调用的时候vector才会自动释放缓冲区。
-
-
为什么map和set的插入删除效率比用其他序列容器高?
- 因为对于关联容器来说,不需要做内存拷贝和内存移动。map和set容器内所有元素都是以节点的方式来存储,其节点结构和链表差 不多,指向父节点和子节点。
-
map和HashMap有什么区别?
- 相对map来说,HashMap具有更高的查询速度。
-
-
你编写程序的时候如何进行调试?如何快速定位到一个异常?
-
i++是原子的吗?为什么不是原子的?会出现什么情况?
-
内存
-
C语言申请内存过程
-
C/C++的内存分配 = 变量分配内存的方式有哪些 = 内存管理 = 你对内存的了解
-
从堆上分配(动态内存分配):由程序员申请(malloc或new)和释放(free或delete)。若程序员不释放,程序结束时可能由OS回收。优点:灵活。缺点:容易造成内存泄漏。
-
在栈上创建:由编译器自动分配释放。比如:函数内局部变量的存储单元(所以在主函数内部申请内存过大会造成爆栈)。优点:效率很高。缺点:分配的内存容量有限。
-
从静态区(=全局区=静态区)分配:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。存放如全局变量、static变量、静态变量。
-
常量区:存放常量,不允许更改,程序结束后由OS释放
-
自由区/程序代码区:存放函数体的二进制代码,由OS管理。该区是只读、共享的
-
-
什么是内存泄漏?
-
用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。
-
内存泄露的情况
-
1申请内存但并未释放(比如给指针分配了空间但是忘了释放)
-
2同一块空间释放两次导致崩溃
-
3程序的误操作将堆破坏
-
4申请的空间不足以赋值,释放导致崩溃
-
5释放时传入的地址和分配时的地址不一样时,会导致崩溃。
-
-
-
面对内存泄漏和指针越界,你有哪些方法来避免和减少这类错误?
- 1使用的时候要记得指针的长度;2malloc的时候得确定在那里free;3对指针赋值的时候应该注意被赋值指针需要不需要释放;4动态分配内存的指针最好不要再次赋值;5尽量避免在堆上分配内存
-
C++有什么机制防止内存泄漏?
-
智能指针:boost::shared_ptr、unique_ptr、weak_ptr( + 原理)
-
GC垃圾回收机制
-
从QT中学到的方法,将堆中分配的内存组成森林,然后删除根结点的时候级联删除所有子结点。这样就简化了内存管理:从管理所有内存到只要管理根结点。
-
RALL
-
-
堆栈区别:https://blog.csdn.net/jianxinss/article/details/6905021
-
堆溢出和栈溢出
-
产生堆栈溢出的原因:函数调用层次太深、动态申请空间后未释放
-
栈溢出:栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,每调用一次,函数的参数、局部变量等信息就压一次栈。在不断的压栈过程中,造成栈容量超过1m而导致溢出;局部静态变量体积太大。
- 解决办法:手动加栈
#pragma comment(linker, “/STACK:1024000000,1024000000”)
- 解决办法:手动加栈
-
堆溢出:堆溢出的产生是由于过多的函数调用,导致调用堆栈无法容纳这些调用的返回地址,一般在递归中产生。堆溢出很可能由无限递归产生,但也可能仅仅是过多的堆栈层级。
-
-
动态内存访问错误原因
- 数组访问越界、指针非法访问
-
虚拟地址、虚拟内存和实际内存的联系
-
虚拟地址空间就是应用程序自己分配的内存大小,映射到物理地址空间,这样物理细节对于应用程序是透明的,并可以通过硬盘增加内存。
-
虚拟地址空间与物理地址空间映射
-
-
虚拟内存和物理内存的区别https://blog.csdn.net/lvyibin890/article/details/82217193
-
作用不同。虚拟内存是使得应用程序认为拥有连续的可用的内存,当计算机随机存储器不足时,操作系统用虚拟存储器进行补偿,缓解内存紧张。物理内存是在计算机运行时为操作系统和各种程序提供临时储存。
-
特点不同。虚拟内存是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。物理内存会对系统的虚拟内存限制有影响,虚拟内存的大小=物理内存容量+所有页面文件的最大容量。
-
主体不同。虚拟内存是计算机系统内存管理的一种技术,是Windows为作为内存使用的一部分硬盘空间。物理内存指通过物理内存条而获得的内存空间。
-
-
内存空间中堆空间、栈空间分别是什么,栈和堆的用法、区别、哪个空间更大、哪个访问更快。https://blog.csdn.net/baidu_37964071/article/details/81428139 、https://blog.csdn.net/zrh_CSDN/article/details/80959053?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.control&dist_request_id=1328697.1288.16166708270476377&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.control
-
栈快。栈是机器系统提供的数据结构,而堆栈是C/C++函数库提供的。
-
堆空间更大,因为向着内存地址增大的方向消耗空间。
-
-
堆和栈的区别?堆和栈的生命周期?
-
空间分配区别:
-
栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
-
堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
-
-
缓存方式区别:
-
栈:使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;
-
堆:是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
-
-
数据结构区别:
-
栈(数据结构):一种先进后出的数据结构
-
堆(数据结构):堆可以被看成是一棵树,如:堆排
-
-
-
-
struct和class的区别?struct 的成员默认公有、而类的成员默认私有
-
浅拷贝、深拷贝
-
如果一个类拥有资源,当这个类的对象发生复制过程的时候,如果资源重新分配了就是深拷贝;反之没有重新分配资源,就是浅拷贝。
-
浅拷贝就比如像引用类型,而深拷贝就比如值类型。
-
浅拷贝:源对象与拷贝对象共用一份实体。只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
-
深拷贝:源对象与拷贝对象互相独立 。当数据成员中有指针时,必须要用深拷贝。深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
-
-
new和malloc区别
-
malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。(因为是在堆上动态分配内存,所以之后释放的时候需要用户去手动释放,如果不释放会造成内存泄漏(申请内存但未释放))
-
返回的指针:new可以认为是malloc加构造函数的执行。new返回的就是一个所创建的对象的指针,是直接带类型信息的;malloc返回的都是void指针
-
安全性:new的安全性要高一些,因为他返回的就是一个所创建的对象的指针。malloc返回的是void*,还要进行强制类型转换,显然这是一个危险的漏洞。
-
开辟内存失败:new开辟内存失败是抛出bad_alloc类型的异常(因此代码上要捕获该类型的异常才能正确的判断堆内存是否分配成功);malloc内存开辟失败返回的是nullptr指针。
-
底层:new的底层也是通过malloc来开辟内存的
-
重载:我们可以对new/delete重载,使内存分配按照我们的意愿进行,这样更具有灵活性,malloc则不行。
-
new比malloc多一项功能,就是开辟完内存,还可以进行初始化操作
-
delete比free多一项功能就是在释放内存之前,还可以析构指针指向的对象
-
-
重载/overload、重写/覆盖override、重定义/隐藏overwrite 的区别
-
重载:参数列表(参数的类型、个数、顺序)不同、返回值类型不同、函数名字相同、virtual关键字可有可无、作用域相同、返回值是不影响函数签名的、C++语言支持函数重载而C不支持函数重载。
-
重写:方法体不同、派生类覆盖基类的虚函数,实现接口的重用,返回值类型必须相同、不同范围(基类和派生类)、函数名字相同、参数相同、基类中必须有virtual关键字(必须是虚函数)
-
重定义:派生类屏蔽了其同名的基类函数,返回值类型可以不同、不同范围(基类和派生类)、函数名字相同、参数不同或者参数相同且无virtual关键字
-
-
重载的原理?
- C++利用name mangling(倾轧)技术,来改变函数名,区分参数不同的同名函数。(Name Mangling 是一种在编译过程中,将函数、变量的名称重新改编的机制。在 C++重载、namespace等操作符下,函数可以有同样的名字,编译器为了区分各个不同地方的函数,将各个函数通过编译器内定的算法,将函数改成唯一的名称。)
-
C++ new一个对象的内部过程/对象的初始化过程/对象的创建过程/类对象的创建过程
-
初始化一个对象时调用顺序:new -> operator new -> malloc -> 调用构造函数(placement new)
-
初始化多个对象时调用顺序:new -> operator new[] -> operator new -> malloc -> 调用构造函数
-
new 类名
-
分配内存空间。(开辟空间,分配内存地址)malloc/new
-
分配在栈区域的对象。栈区域的大小由编译器的设置决定,栈空间是有限的,在栈区域内同时分配超过空间大小的对象会导致栈区域溢出,由于栈区域的分配是在编译阶段完成的,所以在栈区域溢出的时候会抛出编译阶段的异常。(对于全局对象静态对象+分配在栈区域内的对象的内存分配是在编译阶段就完成了)
-
分配在堆区域的对象。堆内存空间的分配是在运行是进行的(在运行是动态进行的),由于堆空间也是有限的,在栈区域内试图同时分配大量的对象会导致分配失败,通常情况会抛出运行时异常或者返回一个没有意义的值(通常是0)。
-
没有内存的话,抛异常。内存一般有编译器分配,不太会出现内存不够用的情况
-
-
初始化。
- 对类对象的初始化,实际上是对类对象内的所有数据成员进行初始化。C++已经为我们提供了对类对象进行初始化的能力,我们可以通过实现构造函数的初始化列表来实现。
-
赋值。
- 类对象的赋值实际上是对类对象内的所有数据成员进行赋值。
-
执行构造函数
-
调用对象的方法
-
new和delete对象过程的参考博客:https://blog.csdn.net/xxpresent/article/details/53024555?utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.control、https://blog.csdn.net/wx_assa/article/details/107842163
-
-
C++ delete一个对象的内部过程
-
释放一个对象时调用顺序:delete -> 调用析构函数 -> operator delete -> free
-
释放多个对象时调用顺序:delete[] -> 调用析构函数 -> operator delete[] -> operator delete -> free
-
-
关键字volatile?
-
volatile 关键字告诉编译器该关键字修饰的变量是随时可能发生变化的,每次使用它的时候必须从内存中取出它的值,因而编译器生成的汇编代码会重新从它的地址处读取数据放在左值中。
-
如果该变量是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说volatile 可以保证对特殊地址的稳定访问。
-
-
关键字static可以修饰什么?
-
静态局部变量:作用域只在函数内部,但是在全局静态存储区分配内存,也就是说生存周期随着程序运行结束而结束。会在第一次被执行的时候初始化,以后的函数调用不再初始化。
-
全局静态变量/static修饰的函数:隐藏。该变量/函数不能被其他文件引用。
-
静态数据成员:只会被初始化一次,与实例无关,并且只能在类外初始化。存储在全局静态区。
-
静态成员函数:用于修饰类的成员函数。只能访问静态数据成员或静态成员函数。
-
-
const
-
const关键字用法,const的理解
-
初始化:在定义时必须进行初始化
-
const 函数只能调用 const 函数。非 const 函数可以调用 const 函数。如果 const 构成函数重载,const 对象只能调用 const 函数,非 const 对象优先调用非 const 函数。
-
const修饰词修饰范围:1变量与对象(使其成为一般常量与对象常量)、2函数的参数与返回值、3成员函数的函数体(指类的成员函数)
-
修饰常量:声明的时候要进行初始化
-
修饰类的成员变量:表示成员常量,不能被修改。
-
修饰指针
-
const int *p
:p指针const int的指针,不能用赋值语句对p赋值,但是可以对p赋值。 -
int* const p=&j
:p是指向int的const指针。p是const数据,所以其本身不可改变,而*p可以被赋值。 -
const int * const p=&i
:p是一个const指针,其指向const数据i。p、*p都不能再被赋值。必须在在初始化p指针时对其初始化。
-
-
修饰函数
-
普通函数
-
放在返回值前修饰返回值表示返回值必须保持其常量性,不能被更改;
-
放在参数前修饰参数表示该参数必须保持其常量性,不能在函数体内被修改;
-
-
类成员函数
- 放在类成员函数尾部,表示其在函数内并不修改对象的属性,只是读取等操作非更易型操作
-
-
修饰类对象
-
当类的对象被声明为const类型后,它只能调用用const修改的成员函数。
-
const修饰函数承诺在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数。
-
-
-
怎样解决const对象可以通过指针修改
-
函数后面放const什么作用:表示这是一个类中的常函数,他表示这个函数不会对类本身的成员进行任何修改。
-
-
C++ 四种强制/显示类型转换(四大转化):static_cast、const_cast、dynamic_cast、reinterpret_cast
-
static_cast :把一个表达式转换为某种类型,但没有运行时类型检查来保证转换的安全性。本质上是传统c语言强制转换的替代品。
-
const_cast:去除const常量属性,使其可以修改,编译器不会再阻止我们对该对象进行写操作 ; volatile属性的转换
-
dynamic_cast:用于将基类指针或引用安全的转换成派生类的指针或引用(运行时类型识别/检查)。
-
reinterpret_cast:通常为了将一种数据类型转换成另一种数据类型(比如指针转int)
-
-
定义和声明的区别
-
声明是告诉编译器变量的类型和名字,不会为变量分配空间。声明一般是对于函数
-
定义需要分配空间,同一个变量可以被声明多次,但是只能被定义一次。定义一般是对于变量
-
-
C++的动态绑定:我们在使用基类的引用(指针)调用虚函数时,就会发生动态绑定。所谓动态绑定,就是在运行时,虚函数会根据绑定对象的实际类型,选择调用函数的版本。
-
静态链接库和动态链接库
-
windows下静态库为
.lib
, 动态库为.dll
-
程序编译的四个步骤:预编译-编译-汇编-链接
-
不同点:静态库和动态库就是在链接阶段行为不同。静态库会在链接阶段将汇编生成的目标文件 .o 与引用的库一起链接打包到可执行文件中。静态库其实就是一系列目标文件的集合。
-
静态链接库特点:1静态库对函数的链接在编译时期完成;2程序在运行时与函数库再无关系;3浪费资源空间(因为所有相关的目标文件都会被链接到一个可执行文件中)
-
使用动态库的原因:1静态库很耗费内存空间;2如果库源码发生变动/更新那么静态库不得不重新生成。
-
动态库特点:1延迟加载一些库函数,既用到才加载;2动态库可以同时被多个程序共享,节省内存。
-
-
placement new
-
默认生成的构造函数
-
C++对象模型是是什么
-
语言中直接支持面向对象程序设计的部分:如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等
-
对于各种支持的底层实现机制
-
-
指针大小
-
无论指针指向哪种数据类型,指针类型的大小是固定的。在32位系统中为4字节,在64位系统中为8字节
-
char *p = new char[10],求sizeof(p)
-
-
指针的释放时机
-
栈上变量,自动释放
-
delete
-
-
C++中指针指向的内容被释放后指针指向哪里?
-
1指针的值不变,还指向你申请来的那块空间,但delete后那块空间就不再归你现有的程序所有,虽然那个指针还指向那块内存空间,但并不具有对那块空间的使用权和访问权
-
2如果没有delete,那么就不会被释放。要一直到进程退出的时候,os清理现场才会把new出来的内存释放掉。依赖于进程退出去释放内存是很危险的时候,要么自己去释放每一个new出来的对象。要么尽量使用c++的智能指针,要么尽量避免使用new/delete。
-
-
C++智能指针
-
unique_ptr什么时候会析构:该类的析构函数为虚函数?!
-
C++版本的改动,shared_pointer的实现方式
-
怎么判断机子是32位或者64位
-
直接使用sizeof判断指针大小, 32位机指针就4个字节,64位机指针是8个字节
void*number = 0; printf("%d ", sizeof(&number));
-
使用宏定义__WORDSIZE判断
printf("size:%d ", __WORDSIZE);
-
查看宏定义,32位机有宏i386, 64位机有宏x86_64
#ifdef __x86_64__ printf("64bits machine "); #elif __i386__ printf("32 bits machine "); #endif
-
-
C++编写过多线程/进程
-
析构函数不加virtual关键字?
- 可以加。构造函数不能声明为虚函数,但是析构函数可以
-
断点原理
-
静态内部类和非静态内部类的区别?(扯到了安卓中内存泄漏的问题,如何去解决非静态内部类引起的泄漏问题,说到了用弱引用)
-
事件委托?
-
类的static变量在什么时候初始化?函数的static变量在什么时候初始化?
- 类的静态成员变量在类实例化之前就已经存在了,并且分配了内存。函数的static变量在执行此函数时进行初始化。
-
三种参数引用方式:传值调用、引用调用 、常数引用调用
-
模版的声明和实现应该在.h还是.cpp
- 模版的声明和定义都要放在.h头文件中
-
同步方法和代码块的区别、原理
-
学过编译原理吗 说下C++转成exe过程/GCC编译C语言(详情见:<见:https://www.cnblogs.com/OFSHK/p/14355678.html>)
-
预处理:把C/C++源代码(.c/.cpp/.cc/.hh)处理生成一个
.i
文件。预处理一般做预编译指令:头文件引入、宏的展开、指令的处理 -
汇编:生成一个
.s
文件 -
编译:生成一个
.obj
/o
生成目标文件 -
链接/生成目标文件:目的:把多个目标文件,包括动态库、静态库,链接到一起,生成最终的可执行文件。(windows
.exe
、linux.elf
文件)
-
-
有没有用过lambda表达式。语法+内联特性。
-
什么是内联函数?如何使用?适用情况?优缺点?
-
定义在类声明之中的成员函数将自动地成为内联函数。
-
使用:在函数返回类型前加上inline关键字。
-
为什么用内联函数:C为了解决一些频繁调用的小函数大量消耗栈空间或是叫栈内存的问题,引入inline表示内联函数。(栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。)
-
适用情况:1一个函数被重复调用、2函数只有几行,且不包含for,while,switch、递归等循环语句。应放在头文件中定义,这一点不同于其他函数。
- 为什么不能带循环语句? 不是内联函数中不能有循环语句,而是当内联函数中出现了复杂的逻辑控制语句后,编译器会不再认为它是一个内联函数 = 当内联函数中实现过于复杂时,编译器会将它作为一个普通函数处理。这是由内联函数的特殊性所决定的,由于内联是调用处展开的方式,所以编译器认为只有足够简单的函数才可以具有该特性,复杂函数编译器会放弃内联特性。
-
优点:
-
减少因为函数调用引起开销,主要是参数压栈、栈帧开辟与回收,以及寄存器保存与恢复等。
-
内联后编译器在处理调用内联函数的函数(如上例中的foo()函数)时,因为可供分析的代码更多,因此它能做的优化更深入彻底。
-
inline定义的内联函数,函数代码被放入符号表中,在使用时进行替换(像宏一样展开),效率很高
-
类的内联函数也是函数。编绎器在调用一个内联函数,首先会检查参数问题,保证调用正确,像对待真正函数一样,消除了隐患及局限性。
-
-
缺点:
-
内联函数以复制为代价,活动产函数开销
-
如果函数的代码较长,使用内联将消耗过多内存
-
如果函数体内有循环,那么执行函数代码时间比调用开销大
-
-
-
内联和宏定义在使用上的区别
-
内联函数在编译时展开,而宏在预编译时展开
-
在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。
-
内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能。
-
宏不是函数是单纯的代码替换,而inline是函数
-
-
内联函数和普通调用比的优缺点
-
内联函数优点:内联函数在编译的时候不进行函数调用,编译器将内联函数的代码粘贴在调用处(形式上调用),可以提高效率。
-
缺点:
-
1在内联函数内不允许用循环语句和开关语句
-
2如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数(自己调用自己的函数)是不能被用来做内联函数的
-
3内联函数只适合于只有1~5行的小函数。对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现
-
4内联函数的定义必须出现在内联函数第一次被调用之前。
-
-
-
对模板类的了解。
-
模板的特性/技术核心,就是编译期的动态机制,这种机制使程序在运行期具有更大的效率优势。
-
泛型编程,主要利用的技术就是模板。
-
模板就是建立通用的模具,大大提高复用性。
-
模板的特点:模板不可以直接使用,它只是一个框架;模板的通用并不是万能的。
-
C++提供两种模板机制:函数模板(swap)和类模板。
-
C++提高代码的可重用性主要有两方面:继承、模板
-
-
介绍用过的模板类
- 比如:数组类、链表类、Stack类、Queue类、vector、unordered_map这些都是。
-
热部署,当在运行时修改了这个类 会发生什么, 出现什么问题?
-
结合设计模式说说继承(主要工厂模式)
-
函数调用中参数传递有传值、传指针和传参,它们有什么区别
-
函数调用参数传递时底层是怎么做的
-
C++中,引用和指针的区别
-
引用:必须被初始化,值不能为NULL,初始化以后不能被改变,只能是一级
-
指针:不用初始化,值可以为空,初始化以后可以改变所指的对象,可以有多级,可以作为参数进行传递
-
C++中尽量用引用。个人觉得所有的引用都可以用指针,但指针比引用容易出错
-
引用当然更直观更直接,做参数时,如果在函数内不刻意要用指针的那些副作用(如越界访问,动态定向什么的)
-
指针:是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元。可以有const指针,但是没有const引用
-
引用:跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已
-
"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小
-
指针和引用的自增(++)运算意义不一样
-
-
讲一讲函数调用的过程;为什么参数要从右到左压栈;为什么要有不同的函数调用约定
-
static关键字的使用方式?
-
局部静态变量(C)
-
外部静态变量/函数(C)
-
静态数据成员/成员函数(C++)
-
-
解释C++中静态函数和静态变量?
-
类静态数据成员在编译时创建并初始化:在该类的任何对象建立之前就存在,不属于任何对象,而非静态类成员变量则是属于对象所有的。类静态数据成员只有一个拷贝,为所有此类的对象所共享。
-
类静态成员函数属于整个类,不属于某个对象,由该类所有对象共享。
-
static 成员变量实现了同类对象间信息共享。
-
static 成员类外存储,求类大小,并不包含在内。
-
static 成员是命名空间属于类的全局变量,存储在 data 区的rw段。
-
static 成员只能类外初始化。
-
可以通过类名访问(无对象生成时亦可),也可以通过对象访问
-
-
赋值运算符和拷贝构造函数的区别?
-
相同:都是将一个对象copy 到另一个中去
-
不同:拷贝构造函数涉及到要新建立一个对象
-
-
数组和指针的区别
-
数组要么在全局数据区被创建,要么在栈上被创建;指针可以随时指向任意类型的内存块。
-
用运算符sizeof 可以计算出数组的容量(字节数)。sizeof指针得到的是一个指针变量的字节数,而不是p所指的内存容量。
-
C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
-
-
C++向函数传递参数有哪几种方式?/引用有哪三种传递,有什么区别?
- 三种:值传递、引用传递、指针传递
-
C++11新特性
-
移动语义(move)和完美转发(forward)
-
右值引用的作用
- 主要用来解决C++遗留的移动语义问题。在C++11之前,要将一个对象移动到另一个对象所做的操作是先复制一份到新对象,然后将原对象删除。但是高效的做法就是将原对象的资源直接转移到新对象,但是C++只提供了拷贝构造函数,并不能完成这项任务。C++语言设计者们注意到右值的资源是可以被安全转移的,所以右值引用就作为被用来表达移动语义,同时如果确定可以安全转移的左值可以用
std::move()
强制转换为右值引用。std::move()
是C++11新增的用于将参数强制转换为右值引用。
- 主要用来解决C++遗留的移动语义问题。在C++11之前,要将一个对象移动到另一个对象所做的操作是先复制一份到新对象,然后将原对象删除。但是高效的做法就是将原对象的资源直接转移到新对象,但是C++只提供了拷贝构造函数,并不能完成这项任务。C++语言设计者们注意到右值的资源是可以被安全转移的,所以右值引用就作为被用来表达移动语义,同时如果确定可以安全转移的左值可以用
-
讲一下右值引用的移动语义
std::move()
。移动语义,其实就是实现类的移动构造函数。移动语义在C++11中的应用主要是在STL中,例如std::vector
std::map
等容器的减少拷贝,还有unique_ptr
function
等不能被拷贝的类。其他的见上个回答
-
右值引用的应用场景
-
移动拷贝构造
-
通用引用
-
-
右值引用为什么会提高效率
- 因为减少拷贝了
-
左值和右值区别
-
左值:放在等号左边。有名字+可以取地址。
-
右值:放在等号右边。无名字+不能取地址。
例子: int a = b + c; a是左值,有变量名+可以取地址。表达式b+c的返回值是右值,无名字且不能取地址,因为&(b+c)不能通过编译。
-
-
左值引用和右值引用
type &name = Nicole; // 左值引用 type &&name = Nicole; // 右值引用
-
lambda表达式
-
完美转发:就是指函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
-
-
空指针和悬垂指针的区别
-
空指针是指被赋值为NULL的指针;delete指向动态分配对象的指针将会产生悬垂指针
-
空指针可以被多次delete,而悬垂指针再次删除时程序会变得非常不稳定
-
使用空指针和悬垂指针都是非法的,而且有可能造成程序崩溃,如果指针是空指针,尽管同样是崩溃,但和悬垂指针相比是一种可预料的崩溃
-
-
内联函数和宏定义的区别
+ 宏定义在预处理的时候进行简单的字符串替换,而内联函数在编译时在每个调用内联函数的地方将函数展开,这样不用使内联函数占用栈空间,提高效率。
+ 宏定义没有类型检查,但是内联函数还是具有函数的性质,有参数以及返回值。
-
讲一下内存池
- 内存池是一种内存分配方式。通常我们习惯直接使用new、malloc申请内存,这样做的缺点在于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。内存池则是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。
-
在C++程序中调用被C编译器编译后的函数,为什么要加extern"C"?
-
typdef、define、const区别
-
define是预处理命令,执行简单的替换,不做正确性的检查
-
typedef是在编译时处理的,它是在自己的作用域内给已经存在的类型一个别名
-