diff --git a/docs/cpp.md b/docs/cpp.md index 2b0fa08..32ef458 100644 --- a/docs/cpp.md +++ b/docs/cpp.md @@ -1215,3 +1215,1129 @@ char * a = STR(object); #=> char * a = "object"; - [C++ Infographics & Cheat Sheets](https://hackingcpp.com/cpp/cheat_sheets.html) _(hackingcpp.com)_ - [C++ reference](https://zh.cppreference.com/w/) _(cppreference.com)_ - [C++ Language Tutorials](http://www.cplusplus.com/doc/tutorial/) _(cplusplus.com)_ + +# 数据结构与开发技巧 +## map和set +```cpp +#include +#include // 注意map的key会自动排序, 所以在遇到排序问题时参考 +#include +#include +#include +using namespace std; +// map中 所有元素都是pair +// pair中 第一个元素为key(键值) 用于索引 第二个元素value(实值) +// 所有元素都会根据键值自动排序 +// 本质: +// map /mulmap底层都是二叉树 +// 优点: +// 可根据key值快速找到value值 + +// map不允许容器中出现相同的值 +// mulmap中允许出现重复的值2 +// map大小和交换: +// .size() //返回容器中元素的数目 +// .empty() //判断容器是否为空 +// .swap(st) //交换两个容器 +// 插入和删除: +// insert(elem) //容器中插入元素 inseert(pair ( , )); +// clear() //清除所有元素 +// erase(pos) //删除pos迭代器所指的元素 返回下一个迭 代器位置 +// erase(key) 删除键值为key的元素 + +void map_test(){ + // https://blog.csdn.net/tcx1992/article/details/80928790 + // https://blog.csdn.net/sevenjoin/article/details/81937695 + typedef map myMap; // 这其实就是将map里面的数据格式给固定下来而已, map = myMap + myMap test; + //插入 + test.insert(pair(3, "a")); + test.insert(pair(4, "b")); + test.insert(pair(5, "c")); + test.insert(pair(8, "d")); + test.insert(pair(50, "e")); + + //遍历(二叉搜索树的中序遍历,按照key值递增顺序) + cout << "遍历" << endl; + + // for(auto i : test){ // 将temp里面的每个值, 放到i中, 这个i是新建的 + // for(auto &i : test){ // 将temp里面的每个值, 软连接到i, 修改i就是在修改temp中的值 + for(const auto &i : test){ // 将temp里面的每个值, 软连接到i, 禁用修改, 防止在遍历过程中出现改值 + cout << i.second << endl; + cout << endl; + auto iter = test.rbegin();//最大的N个数 + for (int i = 0; i < 3; i++) + cout << iter++->second << endl; + //查找 + cout << "查找" << endl; + // 使用find,返回的是被查找元素的位置,没有则返回map.end()。 + auto it = test.find(50); //查找key=50的数据是, find(key)返回的是pair格式, 也就是(50, e), 所以it->second= + if (it != test.end()) + cout << it->second << endl; + // 使用count,返回的是被查找元素的个数。如果有,返回1;否则,返回0 + cout << test.count(3) << endl; + //删除 + cout << "删除" << endl; + if (test.erase(3)) + cout << "delete success" << endl; + for (auto &i : test) + cout << i.second << endl; +} + +void map_test2(){ + map myMap; // 创建 + myMap.insert(pair(3, "a")); // 插入 + myMap.insert(pair(5, "b")); + myMap.insert(pair(50, "d")); + for (auto &i : myMap) cout <::reverse_iterator iter = myMap.rbegin(); + if (iter != myMap.rend()) cout<<"最后一个值是"<first << "-" << iter->second <first << "-" << myMap.end()->second <second << endl; + + // 查找find + auto it = myMap.find(50); //查找key=50的数据是, find(key)返回的是pair格式, 也就是(50, e), 所以it->second= + if (it != myMap.end()) + cout <first << "-"<second << endl; + + // 判断存在, + cout << "3有" << myMap.count(3) << endl; +} + +int main() +{ + // map_test2(); + unordered_map map1{{1, "hel"}, {2, "ahskg"}, {3, "world"}}; + cout< +#include +#include +#include +#include // 为了使用stringsteam +using namespace std; +// 题目描述: 给定字符串S,T, 求S中包含T所有字符的最短连续子字符串的长度, 时间复杂度不能超过O(n) +// 输入样例: +// Input: S = "ADOBECODEBANC", T = "ABC" +// Output: "BANC" + +string String_test(){ + string str="hello world"; + + //直接打印字符串 + cout<<"str="< + str3 << n1; + string str4 = str3.str(); + cout<<"将int类型转化为string类型: "<> str5; + cout< +#include +#include +#include +using namespace std; + +// 指针和引用 +void test1(int*a, int b, int &c){ + cout<<"函数参数输入是: *a="<<*a<<" b="< +#include +#include +#include +#include // C++引入了ostringstream、istringstream、stringstream这三个类 +/* +istringstream类用于执行C++风格的串流的输入操作。 string str="i am a boy"; 该类可以搞出来一个队列分解这四个单词 +ostringstream类用于执行C风格的串流的输出操作。 +strstream类同时可以支持C风格的串流的输入输出操作。 +*/ + +using namespace std; + +int main(int argc, char const *argv[]) +{ + // 这里打印出来的东西主要是看s的类型, + // 如果s是int, 就打印出12,3,4,5,67 + // 如果s是char, 就打印出所有字符 + // 如果s是string, 就直接将整行打印出来 + string a = "12+3-4+5*67"; + istringstream s1(a); + int i; + // 12 3 -4 5 + while(s1 >> i){ + cout<> c){ + cout<> s){ + cout< counts; + // hello//world,//my//name//is//zjq// + while(s4 >> str1){ + counts[str1]++; + }cout< // getline + +string str; +getline(cin, str); //可以将带空格的字符放入到str中 +cout<name` 和 ` (*p).name`注意: 降低程序运行效率, shared_ptr析构函数不能太复杂, 特别慢, 当他析构的时候, 整个线程会阻塞, + +weak_ptr 打破循环引用, 只做观察指针, 看一下对象对象存不存在 + +auto_ptr已经不用了 + +[智能指针是线程安全的么?](http://www.cppblog.com/Solstice/archive/2013/01/28/197597.html) 显然智能指针控制写不是,因为智能指针操作不是原子性, 当赋值语句执行时, 其实智能指针拷贝对象同时还得对对象的计数进行+1操作, 这两步就会被其他线程钻空子了 + + + + + +C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11 支持,并且第一个已经被 11 弃用。 + +1. 作用是管理一个指针; 申请空间在函数结束时忘记释放, 造成内存泄漏, 而使用智能指针, 一旦智能指针超出类的作用域, 类会自动调用析构函数, 释放资源, 所以智能指针的作用原理在函数结束后, 自动释放内存空间; +2. auto_ptr p1 (new string ("I reigned lonely as a cloud.”)); + auto_ptr p2; + p2 = p1; //auto_ptr 不会报错. 此时p2掠夺了p1所有权, 使用p1的时候, 内存崩溃 +3. unique_ptr p3 (new string ("auto")); + unique_ptr p4; + p4 = p3;// 报错, 非法, 避免内存崩溃 +4. shared_ptr共享拥有, 多个智能指针可以指向同一个对象, 该对象和其相关资源会在最后一个引用被销毁后释放 +5. weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象, 作为管理指针; 为了解决循环引用导致的内存泄漏, 构造函数不会改变引用计数, 不会对对象内存进行管理, 像是一个普通指针, 但会检测所管理的对象是否被释放, 从而避免内存泄漏; + ** + +*** + +## #define和const区别 + +| const | +| ---------------------------- | +| 有类型有名字, 放到静态存储 | +| 编译时确定, 只有一个拷贝 | +| 可以用指针去指向该变量的地址 | +| 不能定义函数 | + +## 重载overload,覆盖(重写)override,隐藏(重定义)overwrite,这三者之间的区别 + +1. overload,将语义相近的几个函数用同一个名字表示,但是参数列表(参数的类型,个数,顺序不同)不同,这就是==函数重载==,返回值类型可以不同 + 特征:相同范围(同一个类中)、函数名字相同、参数不同、virtual关键字可有可无 +2. override,派生类覆盖基类的虚函数,实现接口的重用,==返回值类型必须相同== + 特征:==不同范围(基类和派生类)、函数名字相同、参数相同、基类中必须有virtual关键字==(必须是虚函数) +3. overwrite,派生类屏蔽了其同名的基类函数,返回值类型可以不同 + 特征:不同范围(基类和派生类)、函数名字相同、参数不同或者参数相同且无virtual关键字 + + + +## 多态 + +### [动态多态和静态多态](https://www.cnblogs.com/lizhenghn/p/3667681.html) + +多态的实现分为==静态多态和动态多态== + +1. 静态多态: 主要是 ==重载== ,在编译的时候就已经确定; +2. 静态多态设计思想: 对于相关的对象类型,直接实现它们各自的定义,不需要共有基类,甚至可以没有任何关系。只需要各个具体类的实现中要求相同的接口声明,这里的接口称之为隐式接口。客户端把操作这些对象的函数定义为模板,当需要操作什么类型的对象时,直接对模板指定该类型实参即可(或通过实参演绎获得)。 +3. 动态多态: 用虚函数机制实现的,在运行期间动态绑定 +4. 动态多态设计思想: 对于相关的对象类型,确定它们之间的一个共同功能集,然后在基类中,把这些共同的功能声明为多个公共的虚函数接口。各个子类重写这些虚函数,以完成具体的功能。客户端的代码(操作函数)通过指向基类的引用或指针来操作这些对象,对虚函数的调用会自动绑定到实际提供的子类对象上去。 + +如动物音乐大赛, 乌鸦和狗和猫报名, 但是这三个对象都指向动物类(这是一个基类), 使用动物指针对乌鸦, 狗, 猫进行方法调用, 就是多态 + +### 动态多台和静态多态的比较 + +| | 静态多态 | 动态多态 | +| ------ | ------------------------------------------------------------ | ---------------------------------------------- | +| 优点 | 编译期完成, 效率高, 编译器可优化 | 运行期动态绑定, | +| | 强适配性和松耦合性, 通过偏特化,全特化处理特殊类型 | 实现与接口分离, 可以复用 | +| | 静态多态通过模板编程为C++带来了泛型设计概念, 如STL库 | 处理同一继承体系下异质对象集合的强大威力 | +| 缺点 | 用模板实现静态多态, 模板不足, 调试困难,编译耗时, 代码膨胀, 编译器支持的兼容性, | 运行期绑定, 运行开销大 | +| | 不能处理异质对象集合 | 编译器无法对虚函数进行优化 | +| | | 笨重的类继承体系, 对接口的修改影响整个类的层次 | +| 不同点 | 本质不同, 静态多态,编译阶段, 模板实现, 动态多态,运行阶段, 继承虚函数实现 | | +| | 动态多态接口是显式, 静态是隐式, | | + + +https://www.cnblogs.com/zkfopen/p/11061414.html + +### 虚函数表 + +```cpp +class B { + virtual int f1 (void); // 0 + virtual void f2 (int); // 1 + virtual int f3 (int); // 2 +}; + +// 虚函数表 +vptr -> [B::f1, B::f2, B::f3] + 0 1 2 +``` + +首先对于包含虚函数的类, 编译器会为每个包含虚函数的类生成一张虚函数表,即存放每个虚函数地址的函数指针的数组,简称虚表(vtbl),每个虚函数对应一个虚函数表中的下标。 + +除了为包含虚函数的类生成虚函数表以外,编译器还会为该类增加一个隐式成员变量,通常在该类实例化对象的起始位置,用于存放虚函数表的首地址, +该变量被称为虚函数表指针,简称虚指针(vptr)。例如: + +```cpp +B* pb = new B; / +pb->f3 (12); +// 被编译为 +pb->vptr[2] (pb, 12); // B::f3 参数pb是this指针, 他首先找到虚函数表, 调用对应的f3函数 + +// 注意:虚表是一个类一张,而不是一个对象一张,同一个类的多个对象,通过各自的虚指针,共享同一张虚表。 +vptr-> | vptr1 | vptr2 | vptr3 | +``` + +## 多态的工作原理(底层实现机制) + +```cpp +// 继承自B的子类 +class D : public B { + int f1 (void); + int f3 (int); + virtual void f4 (void); +}; + +// 虚函数表 +// 子类覆盖了基类的f1和f3,继承了基类的f2,增加了自己的f4,编译器同样会为子类生成一张专属于它的虚表。 +// 如下所示, 当基类指向子类时, vptr->vptr(子类)->D::f3, 这是因为他根据动态绑定原则, 先不直接加载基类自身函数, 编译器在运行时, 根据基类指向的子类的vptr函数进行加载指令, 这就实现了多态 +vptr(子类)-> D::f1, B::f2, D::f3, D::f4 + 0 1 2 3 +``` + +```cpp +// 指向子类虚表的虚指针就存放在子类对象的基类子对象中。例如: +B* pb = new D; // 父类指向子类, 调用子类的方法 +pb->f3 (12); +// 被编译为 +pb->vptr(子类)[2] (pb, 12); // D::f3 pb是基类指针, 他首先找基类的虚函数表vptr, +``` + + +```cpp +// 示例 +class A{ +public: + A():m_ch('A'){} + virtual void foo() { + cout << m_ch << "::foo()" << endl ; + } + virtual void bar(){ + cout << m_ch << "::bar()" << endl ; + } +private: + char m_ch ; +} ; +class B:public A{ +public: + B():m_ch('B'){} + void foo(){ + cout << "B::foo()" <(i); //相当于创建一个static_cast类型的匿名对象赋值给d2 +float *p4 = static_cast(&i); // err +int* p2 = reinterpret_cast(i); // 任意类型转换 +int *p = const_cast(&i); +``` + +*** + +## [const指针](https://blog.csdn.net/xixihaha331/article/details/51280263) + +1. 常量指针和指针常量 + +```cpp +int a = 20; +int b=40; +//------------------------------------------------ +const int *p; // 常量指针值不变, 对象可变, 这就是为何 for(const auto &a : arr){} +p=&a; +// (*p)++; // 这里会报错, 因为不能修改指向的值 +p=&b; // 这里不会报错, 因为可以指向别的对象 +printf("a=%d, b=%d, *p=%d\n", a, b, *p); //a=20, b=40, *p=40 + +//------------------------------------------------ +int* const p1 = &a; // 指针常量, 对象不可变, 值可变 +// p1=&b; // 会报错, 因为指针是常量, 对象不能变 +(*p1)++; // 这里不会报错, 因为可以改值, 但是不可以改对象 +printf("a=%d, b=%d, *p1=%d\n", a, b, *p1); //a=21, b=40, *p1=40 + +//------------------------------------------------ +const int* const p2 = &b; // 常量指针常量值, +// p2=&a; // 会报错, 因为指针是常量, 对象不能变 +// (*p2)++; // 会报错, 因为值是常量, 值不能变 +printf("a=%d, b=%d, *p2=%d\n", a, b, *p2); //a=21, b=40, *p2=40 +``` + +2. 常量参数 `void func(char *dest_str, const char *src_str)` +3. 修饰函数返回值 `const char *get_string(void)` 注意只能是指针传递, 如果是值传递就没用了 +4. 修饰成员函数 `int get_count(void) const;` 不可以修改对象的成员变量 + + + +## [new/delete 与 malloc/free 的区别是什么](https://blog.csdn.net/linux_ever/article/details/50533149) + +| | new/delete | malloc/free | +| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 类型 | C++的关键字 | C语言库函数 | +| 返回类型安全性 | 只需要对象名即可创建对象, 返回的是对象类型指针, 类型严格与对象匹配, 无需进行类型转换 | 开辟空间大小严格, 返回的是(void*), 需要通过强制类型转换成需要的类型 | +| | ==new调用构造函数, delete调用析构函数, 能保证对象的初始化和回收内存== | 不会调用构造析构函数, 无法满足动态对象要求 | +| | ==由于new对象返回的指针, 在底层空间还存储了这个对象开辟空间的大小, 因此在析构的时候能够根据这个存储进行回收内存== | | +| 内存分配失败 | ==抛出bac_alloc异常try { int *a = new int(); } catch (bad_alloc) { }== | 返回NULL | +| 是都需要指定内存 | ==new无需指定, 编译器会根据类型自行计算== | 需要显式指出所需内存 | +| 实际创建步骤 | 1, 调用operator new函数, 分配一块足够大的内存, 方便存储特定类型对象, 2, 编译器运行相应的构造函数, 构造对象, 并传入初始值, 3, 对象构造完成, 返回一个指向该对象的指针 | | +| delete释放对象步骤 | 1, 调用对象析构函数, 2, 编译器调用operator delete函数释放内存空间 | | + +new/delete 是 C++的关键字,而 malloc/free 是 C 语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数 + +*** + +## 构造函数和析构函数 + +| 构造函数 | 析构函数 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| 无类型, 没有返回值, 名字和类名相同, 可重载 | 无类型, 无返回值, 名字和类名相同, 不带参数, 不可重载, 析构函数只有一个, 前面加个`~` | +| 作用: 完成对象的初始化 | 对象被删除前有系统自动执行清理工作 | +| 当对象d被创建时, 会自动调用构造函数, 当未定义构造函数时,编译器会自动假设存在两个默认构造函数cdata::cdata(){} | | +| Cdate::Cdate(const Cdate& a) | | +| 对象的析构函数在被销毁前调用, 对象何时销毁与其作用域相关 | | +| 全局对象在程序运行结束时销毁 | | +| 自动对象在离开作用域时销毁 | | +| 动态对象使用delete时销毁 | | + + + + +## allocator 内存分配和释放? + +1. STL分配器封装与STL容器在内存管理上的底层细节; +2. new(调用operate new配置内存,调用对象构造函数构造对象内容)delete(调用析构函数, 释放内存); +3. allocator将两个阶段操作区分开来,内存配置有alloc::allocate()负责, 释放alloc::deallocate(); 对象构造由construct负责,内存析构由destroy负责; +4. 为了提升内存管理效率, 减少申请小内存内存碎片问题, STL采用两级配置器, 当分配大小空间超过128B, 使用一级空间配置器(malloc, realloc, free进行内存管理和内存空间分配和释放),大于128B, 二级(内存池技术,通过空闲链表来管理内存) + +*** + +## malloc 的原理 + +malloc函数用于动态分配内存; 为了减少内存碎片和系统调用开销, malloc采用内存池的方式, 首先申请大块内存作为堆, 再将堆分成多个内存块, 以块作为内存管理的基础单位; 当用户申请内存时, 直接从堆区分配一块合适的空闲块; malloc采用隐式链表结构将堆区分成连续,大小不一的块, 包含已分配和未分配块; 同时malloc采用显示链表结构管理所有空闲块, 双向链表, 每个空闲块记录一个连续的, 未分配的地址; +当进行内存分配时,Malloc 会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;当进行内存合并时,malloc 采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并。 +Malloc 在申请内存时,一般会通过 brk 或者 mmap 系统调用进行申请。其中当申请内存小于128K 时,会使用系统函数 brk 在堆区中分配;而当申请内存大于 128K 时,会使用系统函数 mmap在映射区分配。 + + +## STL迭代器删除元素: + +1. 对于序列容器vector,deque来讲,使用erase, 后面元素前移一位,erase返回下一个有效的迭代器; +2. 对于map,set,使用erase,当前元素迭代器失效,但是因为结构为红黑树,所以删除元素不会影响下一元素迭代器,在调用erase之前,记录下一个元素的迭代器即可, +3. 对于list,使用不连续分配内存, erase返回下一个有效迭代器 + +## vector和list 的区别 + +| Vector | List | +| :----------------------------------------------------------: | :------------------------------------------------------: | +| 连续存储的容器,动态数组,在堆上分配空间, 两倍容量增长, 顺序内存 | 动态双向链表, 堆上空间, 每删除一个元素会释放一个空间 | +| 访问:O(1)(随机访问);插入:后插快, 中间需要内存拷贝, 内存申请和释放; 删除: 后删快, 中间需要内存拷贝 | 访问: 随机访问差, 只能开头和结尾; 插入和删除快, 常数开销 | +| 适用场景:经常随机访问,且不经常对非尾节点进行插入删除 | 适用于经常插入和删除 | +| 下面是区别 | | +| 数组 | 双向链表 | +| 支持随机访问 | 不支持随机访问 | +| 顺序内存 | 离散内存 | +| 中间节点插入删除会导致拷贝 | 不会 | +| 一次性分配好内存, 二倍扩容 | list每次在新节点插入会进行内存申请 | +| 随机访问性能好,插入性能差 | 相反 | + +## STL迭代器的作用, 为何不用指针而用迭代器? + +1. 迭代器提供一种方法顺序访问一个聚合对象各个元素, 而又不暴露该对象的内部表示; 或者说运用这种方法, 是的我们可以在不知道对象内部结构情况下, 按照一定顺序规则直接反问聚合对象的各个元素 +2. 与指针的区别: 迭代器不是指针, 而是类模板, 表现像指针,模拟指针功能,重载指针操作符如->, *, ++等, 相当于一种智能指针, 根据不同类型的数据结构实现不同的操作 +3. 迭代器类的访问方式就是把不同集合类的访问逻辑抽象出来, 是的不用暴露集合内部的结构而达到循环遍历的效果; + +## C++中类成员的访问权限 + +C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符 +类内部, 不区分, 无限制 +子类, 能访问父类的private以外的属性和方法 +其他类, 只能访问public + +## struct和class的区别 + +在 C++中,可以用 struct 和 class 定义类,都可以继承。区别在于:structural 的默认继承权限和默认访问权限是 public,而 class 的默认继承权限和默认访问权限是 private。另外,class 还可以定义模板类形参,比如 template + +## C++源文从文本到可执行文件经历过程 + +1. 预处理: 源代码文件包含的头文件, 预编译语句, 分析替换, 生成预编译文件 +2. 编译阶段: 特定编码 +3. 汇编阶段: 转化为机器码, 重定位目标文件 +4. 链接阶段: 多个目标文件及所需要的库链接成为最终可执行文件 + +## include "" 和include <>的区别 + +1. 编译器预处理阶段查找头文件的路径不一样 +2. 双引号查找路径: 当前头文件目录, 编译器设置的头文件路径, 系统变量路径path指定的路径 +3. <>查找路径: 编译器设置的头文件, 系统变量 + + +## fork,wait,exec 函数 + +父进程产生子进程使用 fork 拷贝出来一个父进程的副本,此时只拷贝了父进程的页表,两个进程都读同一块内存,当有进程写的时候使用写实拷贝机制分配内存,exec 函数可以加载一个 elf文件去替换父进程,从此父进程和子进程就可以运行不同的程序了。fork 从父进程返回子进程的 pid,从子进程返回 0.调用了 wait 的父进程将会发生阻塞,直到有子进程状态改变,执行成功返回 0,错误返回-1。exec 执行成功则子进程从新的程序开始运行,无返回值,执行失败返回-1 + +## STL 里 resize 和 reserve 的区别 + +1. resize(): 改变当前容器内含有元素的数量 + vectorv; + v.resize(20); + v.push_back(2); // 此时的2是21位置 +2. reserve(len): 改变当前容器最大容量, 不会生成元素; 如果reserve大于capacity, 重新分配个len的对象空间, 原始对象复制过来 + + + +## BSS端等六段: C++的内存管理? + +![代码存储结构](D:/0_2024/work/README.assets/代码存储结构.jpg) +在C++中, 虚拟内存分为代码段,数据段, BSS段, 堆区, 文件映射区, 栈区六个部分 + +1. 代码段: 包括只读存储区(字符串常量)和文本区(程序的机器代码), 只读 +2. 数据段: 存储程序中已初始化的全局变量和静态变量; 属于静态内存分配 +3. BSS段: 存储未初始化或初始化为0的全局变量和静态变量(局部+全局); 属于静态分配, 程序结束后静态变量资源由系统自动释放。 +4. 堆区: 调用 new/malloc 函数时在堆区动态分配内存,同时需要调用 delete/free 来手动释放申请的内存。频繁的malloc free造成内存空间不连续, 产生碎片, 因此堆比栈效率低 +5. 映射区:存储动态链接库以及调用 mmap 函数进行的文件映射 +6. 栈区: 存储函数的返回地址,返回值, 参数, 局部变量; 编译器自动释放, + +## 内存泄漏 + +1. 堆内存泄漏, 如果malloc, new, realloc从堆分配的内存, 由于程序错误造成内存未释放, 产生的 +2. 系统资源泄漏: 程序使用系统资源: bitmap, handle, socket忘记释放, 将导致系统效能和稳定差 +3. 没有将基类析构函数定义为虚函数, 基类指针指向子类对象后, 释放基类时, 子类资源不会被正确释放 + + +## 判断内存泄漏: + +1. 内存泄漏原因: 通常调用malloc/new等内存申请操作, 缺少对应的free/delete +2. 判断内存是否泄漏, 可以使用Linux环境下的内存泄漏检测工具, 也可以在写代码时添加内存申请和释放统计功能, 统计申请和释放是否一致, 以此判断内存泄漏 + varglind,mtrace 检测 + + +## 如何采用单线程的方式处理高并发? + +I/O 复用 异步回调 + +## 大端小端? + +大端是指低字节存储在高地址;小端存储是指低字节存储在低地址。我们可以根据联合体来判断该系统是大端还是小端。因为联合体变量总是从低地址存储。 + +## 设计一个server, 实现多个客户端请求 + +1. 多线程, +2. 线程池 , +3. IO复用 + +## [C++的线程锁你知道几种?](https://www.cnblogs.com/steamedbun/p/9376458.html) + +### 互斥锁mutex + +用于控制多线程对他们共享资源互斥访问的一个信号量, 也就是说为了避免多个线程同一个时刻操作一个共同资源;例如线程池中的多个空闲线程核一个任务队列, 任何一个线程都要使用互斥锁互斥访问任务队列, 避免多个线程同时访问任务队列发生错乱, 如果其他线程想要获取互斥锁, 只能阻塞等待 + +### 条件锁 + +条件锁就是所谓的条件变量, 某一个线程因为某个条件未满足时, 可以使用条件变量是程序处于阻塞状态, 一旦条件满足以信号量的方式唤醒一个因为该条件而被阻塞的线程 + +### 自旋锁 + +假设我们有一个两个处理器core1和core2计算机,现在在这台计算机上运行的程序中有两个线程:T1和T2分别在处理器core1和core2上运行,两个线程之间共享着一个资源。 + +首先我们说明互斥锁的工作原理,互斥锁是是一种sleep-waiting的锁。假设线程T1获取互斥锁并且正在core1上运行时,此时线程T2也想要获取互斥锁(pthread_mutex_lock),但是由于T1正在使用互斥锁使得T2被阻塞。当T2处于阻塞状态时,T2被放入到等待队列中去,处理器core2会去处理其他任务而不必一直等待(忙等)。也就是说处理器不会因为线程阻塞而空闲着,它去处理其他事务去了。 + +而自旋锁就不同了,自旋锁是一种busy-waiting的锁。也就是说,如果T1正在使用自旋锁,而T2也去申请这个自旋锁,此时T2肯定得不到这个自旋锁。与互斥锁相反的是,此时运行T2的处理器core2会一直不断地循环检查锁是否可用(自旋锁请求),直到获取到这个自旋锁为止。 + +### 读写锁 + +说到读写锁我们可以借助于“读者-写者”问题进行理解。首先我们简单说下“读者-写者”问题。计算机中某些数据被多个进程共享,对数据库的操作有两种:一种是读操作,就是从数据库中读取数据不会修改数据库中内容;另一种就是写操作,写操作会修改数据库中存放的数据。因此可以得到我们允许在数据库上同时执行多个“读”操作,但是某一时刻只能在数据库上有一个“写”操作来更新数据。这就是一个简单的读者-写者模型。 + +1 如果一个线程用读锁锁定了临界区,那么其他线程也可以用读锁来进入临界区,这样可以有多个线程并行操作。这个时候如果再用写锁加锁就会发生阻塞。写锁请求阻塞后,后面继续有读锁来请求时,这些后来的读锁都将会被阻塞。这样避免读锁长期占有资源,防止写锁饥饿。 + +2 如果一个线程用写锁锁住了临界区,那么其他线程无论是读锁还是写锁都会发生阻塞。 + +## 什么类不能被继承 + +1. 将自身的构造函数与析构函数放在private作用域 +2. 友元类 friend +3. class FinalClass final { }; + +## 结构体和类大小 + +空的为1, 内存对齐, int double, char 则4,8,1+3, 后面的char需要对齐 + +## 类的什么方法不能是虚函数 + +普通函数, 友元函数, 构造函数, 内联成员, 静态态成员函数 + + + +## hash扩容 + +HashMap初始容量大小16,扩容因子为0.75,扩容倍数为2; + +底层是数组加链表, 随着数据的增加, hash冲突会增加, 因此设置扩容因子, 当数据数量到达hash容量的扩容因子倍, 就会以二倍扩容, 16*2=32, 然后重新计算每个元素在数组中的位置. + +## C++派生类的构造函数和析构函数执行顺序及其构造形式 + +### 派生类的构造函数和析构函数的执行顺序 + + 先执行基类的构造函数,随后执行派生类的构造函数,当撤销派生类对象时,先执行派生类的析构函数,再执行基类的析构函数。 + +### 派生类构造函数和析构函数的构造原则 + + 1)派生类不能继承基类中的构造函数和析构函数。 + 当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径。 + 2)当派生类中还有对象成员时,其构造函数的一般形式为: + +### 注意 + +1. 当基类构造函数不带参数时,派生类不一定需要定义构造函数,然而当基类的析构函数哪怕只有一个参数,也要为派生类定义构造函数,甚至所定义的派生类析构函数的函数体可能为空,仅仅起到传递参数的作用 +2. 当基类使用缺省构造函数时或不带参数的构造函数时,则在派生类中定义构造函数时,可以省略:基类构造函数名(参数表),此时若派生类不需要构造 函数,则可以不定义构造函数。 +3. 如果派生类的基类也是一个派生类,则每个派生类只需负责其直接基类的 构造,依次上溯。 +4. 如果析构函数是不带参数的,在派生类中是否要定义析构函数与它所属的 基类无关,故基类的析构函数不会因为派生类没有析构函数而得不到执行,他们各自是独立的 + +## 虚函数调用的工作原理 基于虚函数多态的机制 + +### 虚函数和纯虚函数 + +虚函数: C++中用于实现多态的机制, 核心理念是通过基类访问派生类定义的函数, 是C++中多态的一个重要体现; 利用基类指针访问派生类中的虚函数, 这种情况采用的是动态绑定技术; + +纯虚函数: 基类声明的虚函数, 基类无定义, 要求任何派生类都需要定义自己的实现方法, 在基类中实现纯虚函数的方法是在函数原型后面加 `=0` 纯虚函数不能实例化对象; + +### 抽象类 + +特殊类, 为了抽象和设计的目的建立的, 处于继承层次结构的较上层; + +定义: 带有纯虚函数的类为抽象类 + +作用: 将有关操作作为结果接口组织在一个继承层次结构中, 由他来为派生类提供一个公共根, 派生类将具体实现在其积累中作为接口的操作. 所以派生类实际上刻画了一组子类的操作接口的通用语义, 这些语义传给子类, 子类可以具体实现这些语义, 在将这些语义传给自己的子类 + +注意: 抽象类只能作为基类, 纯虚函数的实现由派生类给出; 如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。 + + + +### 虚函数表 + +```cpp +class B { + virtual int f1 (void); // 0 + virtual void f2 (int); // 1 + virtual int f3 (int); // 2 +}; + +// 虚函数表 +vptr -> [B::f1, B::f2, B::f3] + 0 1 2 +``` + +首先对于包含虚函数的类, 编译器会为每个包含虚函数的类生成一张虚函数表,即存放每个虚函数地址的函数指针的数组,简称虚表(vtbl),每个虚函数对应一个虚函数表中的下标。 + +除了为包含虚函数的类生成虚函数表以外,编译器还会为该类增加一个隐式成员变量,通常在该类实例化对象的起始位置,用于存放虚函数表的首地址, +该变量被称为虚函数表指针,简称虚指针(vptr)。例如: + +```cpp +B* pb = new B; +pb->f3 (12); +// 被编译为 +pb->vptr[2] (pb, 12); // B::f3 参数pb是this指针 + +// 注意:虚表是一个类一张,而不是一个对象一张,同一个类的多个对象,通过各自的虚指针,共享同一张虚表。 +vptr-> | vptr1 | vptr2 | vptr3 | +``` + +### 多态的工作原理(底层实现机制) + +```cpp +// 继承自B的子类 +class D : public B { + int f1 (void); + int f3 (int); + virtual void f4 (void); +}; + +// 虚函数表 +// 子类覆盖了基类的f1和f3,继承了基类的f2,增加了自己的f4,编译器同样会为子类生成一张专属于它的虚表。 +vptr(子类)-> D::f1, B::f2, D::f3, D::f4 + 0 1 2 3 +``` + +```cpp +// 指向子类虚表的虚指针就存放在子类对象的基类子对象中。例如: +B* pb = new D; // 父类指向子类, 调用子类的方法 +pb->f3 (12); +// 被编译为 +pb->vptr(子类)[2] (pb, 12); // D::f3 +``` + + +```cpp +// 示例 +class A{ +public: + A():m_ch('A'){} + virtual void foo() { + cout << m_ch << "::foo()" << endl ; + } + virtual void bar(){ + cout << m_ch << "::bar()" << endl ; + } +private: + char m_ch ; +} ; +class B:public A{ +public: + B():m_ch('B'){} + void foo(){ + cout << "B::foo()" <