img
对象复用的了解,零拷贝的了解
对象复用
指的是设计模式,对象可以采用不同的设计模式达到复用的目的,最常见的就是继承和组合模式了。
零拷贝:
零拷贝主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。
零拷贝技术常见linux中,例如用户空间到内核空间的拷贝,这个是没有必要的,我们可以采用零拷贝技术,这个技术就是通过mmap,直接将内核空间的数据通过映射的方法映射到用户空间上,即物理上共用这段数据。
默认构造函数、一般构造函数、拷贝构造函数
成员初始化列表的概念,为什么用成员初始化列表会快一些?
类型一 :内置数据类型,复合类型(指针,引用)
类型二 : 用户定义类型(类类型)
对于类型一,在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
对于类型二,结果上相同,但是性能上存在很大的差别
因为类类型的数据成员对象在进入函数体是已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,这是调用一个构造函数,
在进入函数体之后,进行的是 对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)
简单的来说:
对于用户定义类型:
所以对于用户定义类型,使用列表初始化可以减少一次默认构造函数调用过程
自己封装宏函数,进行打印出错位置的文件,行号,函数
通过gcc -DDEBUG_EN 打开调试信息输出
#ifdefine DEBUG_EN
#define DEBUG(fmt, args...) \
do { \
printf("DEBUG:%s-%d-%s "fmt, __FILE__, __LINE__, __FUNCTION__, ##args);\
}while(0)
#define ERROR(fmt, args...) \
do { \
printf("ERROR:%s-%d-%s "fmt, __FILE__, __LINE__, __FUNCTION__, ##args);\
}while(0)
#else
#define DEBUG(fmt, args) do{}while(0)
#define ERROR(fmt, args) do{}while(0)
#endif
当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump.
MAP 文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,是整个程序工程信息的静态文本,通常由linker生成。
通过运行程序,打断点、单步、查看变量的值等方式在运行时定位bug。
file <文件名> | 加载被调试的可执行程序文件 |
---|---|
b <行号>/<函数名称> | 在第几行或者某个函数第一行代码前设置断点 |
r | 运行 |
s | 单步执行一行代码 |
n | 执行一行代码,执行函数调用(如果有) |
c | 继续运行程序至下一个断点或者结束 |
p<变量名称> | 查看变量值 |
q | 退出 |
引用是否能实现动态绑定,为什么引用可以实现
因为对象的类型是确定的,在编译期就确定了,指针或引用是在运行期根据他们绑定的具体对象确定。
在什么情况下系统会调用拷贝构造函数:(三种情况)
(1)用类的一个对象去初始化另一个对象时
(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用
(3)当函数的返回值是类的对象或引用时
左值和右值
左值:可以对表达式取地址,有名字的的值就是左值,一般指表达式结束后依然存在的持久对象
右值:不能对表达式取地址,没有名字的值,就是右值,一般指表达式结束后不再存在的临时对象
右值引用可以实现转移语义和完美转发的新特性
在类的内部,不受限定符号的约束,可以随意访问,在类的外部,只能访问类中public的成员
struct 和class 都可以定义类,struct连的成员权限都是public的
底层实现:红黑树
key 和 value , key 是不可以重复的,所有元素的值都会自动排序,key不允许重复
key 和 value , key是可以重复的,所有元素的值都会自动排序,key不允许重复
连续存储的容器,内存分配在堆上面,动态数组
底层实现:数组
两倍容量增长:vector一次性分配好内存, 在增加新元素的时候,如果没有超过当前的容量,那么直接添加,然后调整迭代器,如果超过了当前的容量, 则vector会重新配置原数组的内存的2倍空间,将原空间元素内存拷贝到新空间,释放掉原空间,且此时迭代器会失效
性能:
插入末尾:空间不够,则需要申请内存,和释放原空间,对数据进行拷贝
空间够,则直接插入,速度很快
插入中间:空间不够,则需要申请内存,和释放原空间,对数据进行拷贝
空间够,内存拷贝
动态链表,内存分配在堆上,每增加一个数据,则会开辟一个数据的空间,删除一个数据,则会释放掉一个数据的空间
底层实现:双向链表
集合,所有元素都会根据元素的值进行排序,且不允许重复
底层实现:红黑树(一种平衡二叉树)
适用场景:有序不重复集合
迭代器是类模版,表现的像指针。封装了指针的一些行为,重载了指针的++/--/->/*
等操作符号,相当于一种智能指针。可以根据不同的数据结构,来实现 ++ 和 -- 操作
terator模式
是运用于一种聚合对象的模式,把不同集合内的访问逻辑抽象出来,使得不暴露对象的内部结构而达到遍历集合的效果ostream_iterator
的扩展迭代器时如何删除元素的?
vector<int> val = { 1,2,3,4,5,6 };
vector<int>::iterator iter;
for (iter = val.begin(); iter != val.end(); )
{
if (3 == *iter)
iter = val.erase(iter); //返回下一个有效的迭代器,无需+1
else
++iter;
}
set<int> valset = { 1,2,3,4,5,6 };
set<int>::iterator iter;
for (iter = valset.begin(); iter != valset.end(); )
{
if (3 == *iter)
valset.erase(iter++);
else
++iter;
}
resize 是改变容器内含有元素的数量,它会创建元素,且会将值默认为0,如果resize后需要追加数据,则是在尾部追加
reverse 是改变容器的最大容量,它不会创建元素
预处理阶段:将源代码文件中头文件,宏定义进行分析和替换,生成预编译文件
编译阶段:将预编译文件转换成特定的汇编代码,生成汇编文件
汇编阶段:将编译阶段的汇编文件转换成机器码,生成可重定位目标文件
链接阶段:将多个目标文件及所需的库链接成最终的可执行文件
"" :
<>:
向内存申请一块连续可用的空间,并返回指向这块空间的指针
向内存申请一块连续可用的空间,并返回指向这块空间的指针
void* calloc(size_t num, size_t size);
向内存申请一块可用的空间,并返回指向这块空间的指针
void* realloc(void* ptr, size_t size);
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
当是情况1
的时候,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。
当是情况2
的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
用来释放动态开辟的内存
void free(void* ptr);
朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力
好了,本次就到这里,**下一次 后端纯干货面试题整理 I I **,
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是小魔童哪吒,欢迎点赞关注收藏,下次见~