1. 封装、继承、多态
封装:将所需的数据成员,以及对数据的操作方法(成员函数),绑定在一起成为类(类型),定义该类型的对象时,成员被自动隐藏在对象内部。通过封装可以限定对数据的直接访问,限定类的成员函数访问和操作,类的对象、友元类/友元函数访问类的成员。
继承:可以极大的提高代码的复用率,便于提高代码的可读性、可维护性。子类继承父类的属性、方法,减少重复代码,处了父类的构造函数、析构函数外都可以继承,但是由于继承方式和父类的成员属性,访问权限由情况而定,子类可以隐藏父类的同名函数,作为自己的更新函数功能
多态:是在继承与虚函数的继承上实现的,虚函数是父类定义初始化成员时加入关键词virtual ,而子类继承父类的虚函数,并且进行覆盖(区别于无虚函数的重载),他允许函数参数列表内含有父类的指针、引用时,由最终函数调用的需要传入子类对象,实现不同的操作结果。
2. 类里面static是在什么时候初始化,如果一个函数里面有一个static变量,此时static什么时候初始化?
答:
- 全局静态变量在编译时初始化(主函数被调用前初始化仅一次)
- 局部的static变量仅在函数中有效,第一次进入函数初始化,之后沿用上一次的值
- 局部的静态变量的生命周期在主函数结束时才结束,因为他有全局静态数据区
3. 如果出现了内存泄漏的问题,有什么方法可以避免?
内存溢出:OOM(out of Memory);比如申请了一个int,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。最终的结果就是导致OOM。
内存泄漏的四种常见情况:
指针重新赋值
char * p = (char *)malloc(10);
char * np = (char *)malloc(10);
p=np;这时候,指针变量 p 被 np 指针重新赋值,其结果是 p 以前所指向的内存位置变成了孤立的内存。它无法释放,因为没有指向该位置的引用,从而导致 10 字节的内存泄漏。
类似的情况,连续重复new的情况也是类似:
int *p = new int; p = new int...;//错误
错误的内存释放
假设有一个指针变量 p,它指向一个 10 字节的内存位置。该内存位置的第三个字节又指向某个动态分配的 10 字节的内存位置。
如果程序需要执行如下赋值语句时:
free(p);
很显然,如果通过调用 free 来释放指针 p,则 np 指针也会因此而变得无效。np 以前所指向的内存位置也无法释放,因为已经没有指向该位置的指针。换句话说,np 所指向的内存位置变为孤立的,从而导致内存泄漏。
正确处理:因此,每当释放结构化的元素,而该元素又包含指向动态分配的内存位置的指针时,应首先遍历子内存位置(如本示例中的 np),并从那里开始释放,然后再遍历回父节点,如下面的代码所示:
free(p->np); free(p);
返回值的不正确处理
有时候,某些函数会返回对动态分配的内存的引用,如下面的示例代码所示:
char *f() { return (char *)malloc(10); }
void f1(){ f(); }函数 f1 中对 f 函数的调用并未处理该内存位置的返回地址,其结果将导致 f 函数所分配的 10 个字节的块丢失,并导致内存泄漏。
在内存分配后忘记使用 free 进行释放
如何避免内存泄漏:
- 已经以动态申请的地址初始化的指针,在该指针地址未释放前,不要被其他指针赋值
- malloc 分配的 于free()对应,New 分配与delete对应
- 当指针写值时,确保写入的字节数不会超过分配的字节数
- 释放结构化元素时,先遍历释放子内存的堆内存,再释放父节点内存
- 正确处理动态分配的内存引用函数的返回值
4. 如果让你设计一个监测内存泄漏的方法,你有什么思路?
设计一个监测内存泄漏的方法,我会考虑以下几个关键步骤和思路:
- 工具选择:首先,我需要选择一个合适的内存分析工具。对于C++程序,可以考虑使用如Valgrind、AddressSanitizer(ASan)或Visual Studio的内存分析工具等。这些工具可以帮助我检测内存泄漏和其他内存相关的问题。
- 代码插桩:在某些情况下,可能需要在代码中插入特定的监控代码(即插桩),以便更精确地跟踪内存分配和释放。这可以通过使用特定的库或框架来实现,这些库或框架提供了用于内存管理的钩子函数。
- 内存快照:在程序运行的不同阶段,我可以捕获内存的快照。通过比较这些快照,我可以找出哪些内存块在不再需要时没有被释放。这可以通过比较不同时间点的内存映射或堆状态来实现。
- 引用计数:对于使用引用计数的内存管理系统,我可以检查引用计数是否在所有引用都被删除后归零。如果引用计数不为零,那么可能存在内存泄漏。
- 垃圾回收:如果语言或系统支持垃圾回收,我可以检查垃圾回收器是否能够正确回收不再使用的内存。如果不能,那么可能存在内存泄漏。
- 日志和报告:我需要设计一种方式来记录检测到的内存泄漏,并生成详细的报告。报告应包含泄漏的内存大小、位置(如文件名和行号)以及可能的原因。
- 自动化测试:将内存泄漏检测集成到自动化测试流程中,以确保每次代码更改后都会进行内存泄漏检查。这有助于及早发现并修复内存泄漏问题。
- 教育和培训:最后,但同样重要的是,对开发团队进行内存管理和内存泄漏相关的教育和培训。了解如何避免常见的内存泄漏问题,以及如何使用工具来检测和修复这些问题,对于减少内存泄漏至关重要。
5. 如何监测到内存申请?(挂钩子)
6. new \malloc申请失败的话有什么区别?
malloc返回空,new 返回异常
7. 虚继承与虚函数
虚函数:
- 虚函数是使用virtual关键字声明的函数,它是动态多态实现的基础。当一个类中的成员函数被声明为虚函数时,任何派生类都可以覆盖(重写)这个函数,并且通过基类指针或引用来调用该函数时,将执行派生类中的版本,而不是基类中的版本。这就是多态性的体现。
- 虚函数主要用于解决基类指针或引用指向派生类对象时,调用基类的成员函数而实际执行派生类成员函数的问题。这允许我们在不改变基类代码的情况下,通过派生类来扩展或修改基类的行为。
- 虚函数后+ “ =0 ”表示未纯虚函数,无需定义无实际意义,仅仅作为基类描述派生类的接口,而含有纯虚函数的基类为抽象类,抽象类无法定义对象,其唯一的作用为派生派生类,而派生类实现继承的纯虚函数改为虚函数后,抽象类作为接口实现多态性
虚继承:
虚继承则是为了解决多重继承中的数据冗余和二义性问题。在菱形继承体系中,如果没有使用虚继承,子类会多次继承同一个基类,导致基类数据在子类中存在多份拷贝,这不仅浪费空间,还可能导致访问时的歧义。
使用虚继承后,被虚继承的基类称为虚基类,虚基类的子类除了拥有虚基类的成员变量之外,还额外拥有一个指针,称为虚基类表指针。通过这个指针和偏移量,子类可以访问虚基类的成员,避免了数据冗余和二义性。