当对一个对象调用成员函数时,编译程序先将对象的地址赋给 this 指针,然后调用成员函数,每次成员函数存取数据成员时,都隐式使用 this 指针。
当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向这个成员函数所在的对象的指针。
this 指针被隐含地声明为: ClassName *const this,这意味着不能给 this 指针赋值;在 ClassName 类的 const 成员函数中,this 指针的类型为:const ClassName* const,这说明不能对 this 指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作);
this 并不是一个常规变量,而是个右值,所以不能取得 this 的地址(不能 &this)。
在以下场景中,经常需要显式引用 this 指针:
为实现对象的链式引用;
为避免对同一对象进行赋值操作;
在实现一些数据结构时,如 list。
inline 内联函数
特征
相当于把内联函数里面的内容写在调用内联函数处;
相当于不用执行进入函数的步骤,直接执行函数体;
相当于宏,却比宏多了类型检查,真正具有函数特性;
编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数;
在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
使用
inline 使用
// 声明1(加 inline,建议使用)
inline int functionName(int first, int second,...);
// 声明2(不加 inline)
int functionName(int first, int second,...);
// 定义
inline int functionName(int first, int second,...) {/****/};
// 类内定义,隐式内联
class A {
int doA() { return 0; } // 隐式内联
}
// 类外定义,需要显式内联
class A {
int doA();
}
inline int A::doA() { return 0; } // 需要显式内联
struct A
{
A(int) { }
operator bool() const { return true; }
};
struct B
{
explicit B(int) {}
explicit operator bool() const { return true; }
};
void doA(A a) {}
void doB(B b) {}
int main()
{
A a1(1); // OK:直接初始化
A a2 = 1; // OK:复制初始化
A a3{ 1 }; // OK:直接列表初始化
A a4 = { 1 }; // OK:复制列表初始化
A a5 = (A)1; // OK:允许 static_cast 的显式转换
doA(1); // OK:允许从 int 到 A 的隐式转换
if (a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a6(a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a7 = a1; // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a8 = static_cast<bool>(a1); // OK :static_cast 进行直接初始化
B b1(1); // OK:直接初始化
B b2 = 1; // 错误:被 explicit 修饰构造函数的对象不可以复制初始化
B b3{ 1 }; // OK:直接列表初始化
B b4 = { 1 }; // 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化
B b5 = (B)1; // OK:允许 static_cast 的显式转换
doB(1); // 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
if (b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b6(b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b7 = b1; // 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换
bool b8 = static_cast<bool>(b1); // OK:static_cast 进行直接初始化
return 0;
}
friend 友元类和友元函数
能访问私有成员
破坏封装性
友元关系不可传递
友元关系的单向性
友元声明的形式及数量不受限制
using
using 声明
一条 using 声明 语句一次只引入命名空间的一个成员。它使得我们可以清楚知道程序中所引用的到底是哪个名字。如:
using namespace_name::name;
构造函数的 using 声明
在 C++11 中,派生类能够重用其直接基类定义的构造函数。
class Derived : Base {
public:
using Base::Base;
/* ... */
};
如上 using 声明,对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数。生成如下类型构造函数:
Derived(parms) : Base(args) { }
using 指示
using 指示 使得某个特定命名空间中所有名字都可见,这样我们就无需再为它们添加任何前缀限定符了。如:
using namespace namespace_name;
尽量少使用 using 指示 污染命名空间
一般说来,使用 using 命令比使用 using 编译命令更安全,这是由于它只导入了指定的名称。如果该名称与局部名称发生冲突,编译器将发出指示。using编译命令导入所有的名称,包括可能并不需要的名称。如果与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告。另外,名称空间的开放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。
using 使用
尽量少使用 using 指示
using namespace std;
应该多使用 using 声明
int x;
std::cin >> x ;
std::cout << x << std::endl;
或者
using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
cout << x << endl;
int main()
{
T* t = new T(); // 先内存分配 ,再构造函数
delete t; // 先析构函数,再内存释放
return 0;
}
定位 new
定位 new(placement new)允许我们向 new 传递额外的地址参数,从而在预先指定的内存区域创建对象。
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }
Class unique_ptr 实现独占式拥有(exclusive ownership)或严格拥有(strict ownership)概念,保证同一时间内只有一个智能指针可以指向该对象。你可以移交拥有权。它对于避免内存泄漏(resource leak)——如 new 后忘记 delete ——特别有用。
shared_ptr
多个智能指针可以共享同一个对象,对象的最末一个拥有着有责任销毁对象,并清理与该对象相关的所有资源。
支持定制型删除器(custom deleter),可防范 Cross-DLL 问题(对象在动态链接库(DLL)中被 new 创建,却在另一个 DLL 内被 delete 销毁)、自动解除互斥锁
将文件间的编译依存关系降至最低(如果使用 object references 或 object pointers 可以完成任务,就不要使用 objects;如果能够,尽量以 class 声明式替换 class 定义式;为声明式和定义式提供不同的头文件)
确定你的 public 继承塑模出 is-a(是一种)关系(适用于 base classes 身上的每一件事情一定适用于 derived classes 身上,因为每一个 derived class 对象也都是一个 base class 对象)
避免遮掩继承而来的名字(可使用 using 声明式或转交函数(forwarding functions)来让被遮掩的名字再见天日)
区分接口继承和实现继承(在 public 继承之下,derived classes 总是继承 base class 的接口;pure virtual 函数只具体指定接口继承;非纯 impure virtual 函数具体指定接口继承及缺省实现继承;non-virtual 函数具体指定接口继承以及强制性实现继承)
明智而审慎地使用 private 继承(private 继承意味着 is-implemented-in-terms-of(根据某物实现出),尽可能使用复合,当 derived class 需要访问 protected base class 的成员,或需要重新定义继承而来的时候 virtual 函数,或需要 empty base 最优化时,才使用 private 继承)
了解 typename 的双重意义(声明 template 类型参数是,前缀关键字 class 和 typename 的意义完全相同;请使用关键字 typename 标识嵌套从属类型名称,但不得在基类列(base class lists)或成员初值列(member initialization list)内以它作为 base class 修饰符)
学习处理模板化基类内的名称(可在 derived class templates 内通过 this-> 指涉 base class templates 内的成员名称,或藉由一个明白写出的 “base class 资格修饰符” 完成)
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
// 第一次将一个DLL映射到进程地址空间时调用
// The DLL is being mapped into the process' address space.
break;
case DLL_THREAD_ATTACH:
// 当进程创建一个线程的时候,用于告诉DLL执行与线程相关的初始化(非主线程执行)
// A thread is bing created.
break;
case DLL_THREAD_DETACH:
// 系统调用 ExitThread 线程退出前,即将终止的线程通过告诉DLL执行与线程相关的清理
// A thread is exiting cleanly.
break;
case DLL_PROCESS_DETACH:
// 将一个DLL从进程的地址空间时调用
// The DLL is being unmapped from the process' address space.
break;
}
return (TRUE); // Used only for DLL_PROCESS_ATTACH
}
// A simple program that uses LoadLibrary and
// GetProcAddress to access myPuts from Myputs.dll.
#include <windows.h>
#include <stdio.h>
typedef int (__cdecl *MYPROC)(LPWSTR);
int main( void )
{
HINSTANCE hinstLib;
MYPROC ProcAdd;
BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;
// Get a handle to the DLL module.
hinstLib = LoadLibrary(TEXT("MyPuts.dll"));
// If the handle is valid, try to get the function address.
if (hinstLib != NULL)
{
ProcAdd = (MYPROC) GetProcAddress(hinstLib, "myPuts");
// If the function address is valid, call the function.
if (NULL != ProcAdd)
{
fRunTimeLinkSuccess = TRUE;
(ProcAdd) (L"Message sent to the DLL function\n");
}
// Free the DLL module.
fFreeResult = FreeLibrary(hinstLib);
}
// If unable to call the DLL function, use an alternative.
if (! fRunTimeLinkSuccess)
printf("Message printed from executable\n");
return 0;
}
运行库(Runtime Library)
典型程序运行步骤
操作系统创建进程,把控制权交给程序的入口(往往是运行库中的某个入口函数)
入口函数对运行库和程序运行环境进行初始化(包括堆、I/O、线程、全局变量构造等等)。
入口函数初始化后,调用 main 函数,正式开始执行程序主体部分。
main 函数执行完毕后,返回到入口函数进行清理工作(包括全局变量析构、堆销毁、关闭I/O等),然后进行系统调用结束进程。
💡 关于
📚 本仓库是面向 C/C++ 技术方向校招求职者、初学者的基础知识总结,包括语言、程序库、数据结构、算法、系统、网络、链接装载库等知识及面试经验、招聘、内推等信息。
💡 侧边目录支持方式:📚 Docsify 文档、Github + TOC 导航(TOC预览.png)
📄 保存为 PDF 方式:使用 Chrome 浏览器打开 📚 Docsify 文档 页面,缩起左侧目录-右键 - 打印 - 选择目标打印机是另存为PDF - 保存(打印预览.png)
🙏 仓库内容如有错误或改进欢迎 issue 或 pr,建议或讨论可在 #12 提出。由于本人水平有限,仓库中的知识点有来自本人原创、读书笔记、书籍、博文等,非原创均已标明出处,如有遗漏,请 issue 提出。本仓库遵循 CC BY-NC-SA 4.0(署名 - 非商业性使用 - 相同方式共享) 协议,转载请注明出处,不得用于商业目的。
📑 目录
➕ C/C++
const
作用
const 的指针与引用
使用
const 使用
宏定义 #define 和 const 常量
#undef取消static
作用
this 指针
this指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向调用该成员函数的那个对象。this指针,然后调用成员函数,每次成员函数存取数据成员时,都隐式使用this指针。this指针被隐含地声明为:ClassName *const this,这意味着不能给this指针赋值;在ClassName类的const成员函数中,this指针的类型为:const ClassName* const,这说明不能对this指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作);this并不是一个常规变量,而是个右值,所以不能取得this的地址(不能&this)。this指针:list。inline 内联函数
特征
使用
inline 使用
编译器对 inline 函数的处理步骤
优缺点
优点
缺点
虚函数(virtual)可以是内联函数(inline)吗?
inline virtual唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。虚函数内联使用
volatile
assert()
断言,是宏,而非函数。assert 宏的原型定义在
<assert.h>(C)、<cassert>(C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义NDEBUG来关闭 assert,但是需要在源代码的开头,include <assert.h>之前。assert() 使用
sizeof()
编译器扩展与标准对齐控制
#pragma pack(n),将随后定义的struct/class/union的成员最大对齐限制为 n 字节。alignas(k),要求类型或变量至少按 k 字节对齐(向上取整到 ≥ 自然对齐)。alignof(T),获取类型 T 的自然对齐要求(编译时常量)。使用
位域
类可以将其(非静态)数据成员定义为位域(bit-field),在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。
extern 与 extern “C”
extern是存储类说明符(storage-class-specifier),用于声明变量或函数具有外部链接,表示实体的定义可能在其他翻译单元中。extern "C"是链接指示(linkage directive),它指定函数或变量使用 C 语言链接(不影响编译规则)。extern "C"使用struct 和 typedef struct
C 中
等价于
此时
S等价于struct Student,但两个标识符名称空间不相同。另外还可以定义与
struct Student不冲突的void Student() {}。C++ 中
由于编译器定位符号的规则(搜索规则)改变,导致不同于C语言。
一、如果在类标识符空间定义了
struct Student {...};,使用Student me;时,编译器将搜索全局标识符表,Student未找到,则在类标识符内搜索。即表现为可以使用
Student也可以使用struct Student,如下:二、若定义了与
Student同名函数之后,则Student只代表函数,不代表结构体,如下:C++ 中 struct 和 class
总的来说,struct 更适合看成是一个数据结构的实现体,class 更适合看成是一个对象的实现体。
区别
union 联合
联合(union)是一种节省空间的特殊的类,一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。当某个成员被赋值后其他成员变为未定义状态。联合有如下特点:
union 使用
C 实现 C++ 类
C 实现 C++ 的面向对象特性(封装、继承、多态)
explicit(显式)关键字
explicit 使用
friend 友元类和友元函数
using
using 声明
一条
using 声明语句一次只引入命名空间的一个成员。它使得我们可以清楚知道程序中所引用的到底是哪个名字。如:构造函数的 using 声明
在 C++11 中,派生类能够重用其直接基类定义的构造函数。
如上 using 声明,对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数。生成如下类型构造函数:
using 指示
using 指示使得某个特定命名空间中所有名字都可见,这样我们就无需再为它们添加任何前缀限定符了。如:尽量少使用
using 指示污染命名空间using 使用
尽量少使用
using 指示应该多使用
using 声明或者
:: 范围解析运算符
分类
::name):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间class::name):用于表示指定类型的作用域范围是具体某个类的namespace::name):用于表示指定类型的作用域范围是具体某个命名空间的:: 使用
enum 枚举类型
限定作用域的枚举类型
不限定作用域的枚举类型
decltype
decltype 关键字用于检查实体的声明类型或表达式的类型及值分类。语法:
decltype 使用
引用
左值引用
常规引用,一般表示对象的身份。
右值引用
右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。
右值引用可实现转移语义(Move Sementics)和精确传递(Perfect Forwarding),它的主要目的有两个方面:
引用折叠
X& &、X& &&、X&& &可折叠成X&X&& &&可折叠成X&&宏
成员初始化列表
好处
initializer_list 列表初始化
用花括号初始化器列表初始化一个对象,其中对应构造函数接受一个
std::initializer_list参数.initializer_list 使用
面向对象
面向对象程序设计(Object-oriented programming,OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。
面向对象三大特征 —— 封装、继承、多态
封装
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。关键字:public, protected, private。不写默认为 private。
public成员:可以被任意实体访问protected成员:只允许被子类及本类的成员函数访问private成员:只允许被本类的成员函数、友元类或友元函数访问继承
多态
静态多态(编译期/早绑定)
函数重载
动态多态(运行期/晚绑定)
注意:
动态多态使用
虚析构函数
虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
虚析构函数使用
纯虚函数
纯虚函数是一种特殊的虚函数,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。
虚函数、纯虚函数
虚函数指针、虚函数表
.rodata section,见:目标文件存储结构),存放虚函数指针,如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。虚继承
虚继承用于解决多继承条件下的菱形继承问题(浪费存储空间、存在二义性)。
底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
实际上,vbptr 指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。
虚继承、虚函数
类模板、成员模板、虚函数
抽象类、接口类、聚合类
内存分配和管理
malloc、calloc、realloc、alloca
malloc、free
用于分配、释放内存
malloc、free 使用
申请内存,确认是否申请成功
释放内存后指针置空
new、delete
new、delete 使用
申请内存,确认是否申请成功
定位 new
定位 new(placement new)允许我们向 new 传递额外的地址参数,从而在预先指定的内存区域创建对象。
place_address是个指针initializers提供一个(可能为空的)以逗号分隔的初始值列表delete this 合法吗?
合法,但:
new(不是new[]、不是 placement new、不是栈上、不是全局、不是其他对象成员)分配的delete this的成员函数是最后一个调用 this 的成员函数delete this后面没有调用 this 了delete this后没有人使用了智能指针
C++ 标准库(STL)中
头文件:
#include <memory>C++ 98
C++ 11
shared_ptr
多个智能指针可以共享同一个对象,对象的最末一个拥有着有责任销毁对象,并清理与该对象相关的所有资源。
weak_ptr
weak_ptr 允许你共享但不拥有某对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何 weak_ptr 都会自动成空(empty)。因此,在 default 和 copy 构造函数之外,weak_ptr 只提供 “接受一个 shared_ptr” 的构造函数。
unique_ptr
unique_ptr 是 C++11 才开始提供的类型,是一种在异常时可以帮助避免资源泄漏的智能指针。采用独占式拥有,意味着可以确保一个对象和其相应的资源同一时间只被一个 pointer 拥有。一旦拥有者被销毁或变成空或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源亦会被释放。
auto_ptr
被 c++11 弃用,原因是缺乏语言特性如 “针对构造和赋值” 的
std::move语义,以及其他瑕疵。auto_ptr 与 unique_ptr 比较
move语义;delete),unique_ptr 可以管理数组(析构调用delete[]);强制类型转换运算符
static_cast
float f=3.14; int i=static_cast<int>(f);Derived* d; Base* b=static_cast<Base*>(d);Base* b=new Base; Derived* d=static_cast<Derived*>(b);MyClass* p; MyClass* same=static_cast<MyClass*>(p);func(static_cast<std::string>("text"));int* p; void* vp=static_cast<void*>(p);enum Color{RED}; int c=static_cast<int>(RED);dynamic_cast
Derived* d; Base* b = dynamic_cast<Base*>(d);nullptr引用→
std::bad_castBase* b=new Base; Derived* d = dynamic_cast<Derived*>(b);nullptr引用→
std::bad_castB2* b2 = dynamic_cast<B2*>(b1); // 菱形继承中 B1*→B2*Derived* d2 = dynamic_cast<Derived*>(d1);nullptrvoid* p = dynamic_cast<void*>(obj);const_cast
const/volatile属性const int* cp; int* p=const_cast<int*>(cp);volatile int* vp; int* p=const_cast<int*>(vp);int* p; const int* cp=const_cast<const int*>(p);legacy_api(const_cast<char*>(str.c_str()));reinterpret_cast
MyClass* obj; void* p=reinterpret_cast<void*>(obj);intptr_t addr=reinterpret_cast<intptr_t>(&obj);bad_cast
dynamic_cast引用转换失败的异常类型。bad_cast 使用
运行时类型信息 (RTTI)
typeid
type_info
typeinfotypeid、type_info 使用
⭐️ Effective
Effective C++
const、enum、inline替换#define)operator=返回一个reference to *this(用于连锁赋值)operator=中处理 “自我赋值”new中使用[]则delete [],new中不使用[]则delete)(T)expression、T(expression);新式:const_cast<T>(expression)、dynamic_cast<T>(expression)、reinterpret_cast<T>(expression)、static_cast<T>(expression)、;尽量避免转型、注重效率避免 dynamic_casts、尽量设计成无需转型、可把转型封装成函数、宁可用新式转型)tr1::function成员变量替换 virtual 函数,将继承体系内的 virtual 函数替换为另一个继承体系内的 virtual 函数)this->指涉 base class templates 内的成员名称,或藉由一个明白写出的 “base class 资格修饰符” 完成)More Effective c++
static_cast、const_cast、dynamic_cast、reinterpret_cast)&&,||和,操作符(&&与||的重载会用 “函数调用语义” 取代 “骤死式语义”;,的重载导致不能保证左侧表达式一定比右侧表达式更早被评估)new operator、operator new、placement new、operator new[];delete operator、operator delete、destructor、operator delete[])Google C++ Style Guide
其他
📦 STL
STL 索引
STL 方法含义索引
STL 容器
头部插入、头部删除 O(n)
STL 算法
〽️ 数据结构
顺序结构
顺序栈(Sequence Stack)
SqStack.cpp
顺序栈数据结构和图片
队列(Sequence Queue)
队列数据结构
非循环队列
非循环队列图片
SqQueue.rear++循环队列
循环队列图片
SqQueue.rear = (SqQueue.rear + 1) % SqQueue.maxSize顺序表(Sequence List)
SqList.cpp
顺序表数据结构和图片
链式结构
LinkList.cpp
LinkList_with_head.cpp
链式数据结构
链队列(Link Queue)
链队列图片
线性表的链式表示
单链表(Link List)
单链表图片
双向链表(Du-Link-List)
双向链表图片
循环链表(Cir-Link-List)
循环链表图片
哈希表
HashTable.cpp
概念
哈希函数:
H(key): K -> D , key ∈ K构造方法
冲突处理方法
Hi = (H(key) + i) % mDi = 1^2, -1^2, ..., ±(k)^2,(k<=m/2)H = (H(key) + 伪随机数) % m线性探测的哈希表数据结构
线性探测的哈希表数据结构和图片
递归
概念
函数直接或间接地调用自身
递归与分治
递归与迭代
广义表
头尾链表存储表示
广义表的头尾链表存储表示和图片
扩展线性链表存储表示
扩展线性链表存储表示和图片
二叉树
BinaryTree.cpp
性质
存储结构
二叉树数据结构
顺序存储
二叉树顺序存储图片
链式存储
二叉树链式存储图片
遍历方式
分类
其他树及森林
树的存储结构
并查集
一种不相交的子集所构成的集合 S = {S1, S2, …, Sn}
平衡二叉树(AVL树)
性质
F(n)=F(n-1)+F(n-2)+1(1 是根节点,F(n-1) 是左子树的节点数量,F(n-2) 是右子树的节点数量)平衡二叉树图片
最小失衡树
平衡二叉树插入新结点导致失衡的子树
调整:
红黑树
RedBlackTree.cpp
红黑树的特征是什么?
调整
应用
红黑树、B 树、B+ 树的区别?
B 树(B-tree)、B+ 树(B+-tree)
B 树、B+ 树图片
特点
应用
区别
B树的优点
对于在内部节点的数据,可直接得到,不必根据叶子节点来定位。
B+树的优点
八叉树
八叉树图片
八叉树(octree),或称八元树,是一种用于描述三维空间(划分空间)的树状数据结构。八叉树的每个节点表示一个正方体的体积元素,每个节点有八个子节点,这八个子节点所表示的体积元素加在一起就等于父节点的体积。一般中心点作为节点的分叉中心。
用途
⚡️ 算法
排序
查找
图搜索算法
邻接链表
O(|v|+|E|)
O(|v|+|E|)
邻接链表
O(|v|+|E|)
O(|v|+|E|)
其他算法
❓ Problems
Single Problem
Leetcode Problems
剑指 Offer
Cracking the Coding Interview 程序员面试金典
牛客网
💻 操作系统
进程与线程
对于有线程系统:
对于无线程系统:
进程之间的通信方式以及优缺点
线程之间的通信方式
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制
进程之间私有和共享的资源
线程之间私有和共享的资源
多进程与多线程间的对比、优劣与选择
对比
优劣
选择
Linux 内核的同步方式
原因
在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实像多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问。尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问。
同步方式
死锁
原因
产生条件
预防
文件系统
主机字节序与网络字节序
主机字节序(CPU 字节序)
概念
主机字节序又叫 CPU 字节序,其不是由操作系统决定的,而是由 CPU 指令集架构决定的。主机字节序分为两种:
存储方式
32 位整数
0x12345678是从起始位置为0x00的地址开始存放,则:大端小端图片
判断大端小端
判断大端小端
可以这样判断自己 CPU 字节序是大端还是小端:
各架构处理器的字节序
网络字节序
网络字节顺序是 TCP/IP 中规定好的一种数据表示格式,它与具体的 CPU 类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。
网络字节顺序采用:大端(Big Endian)排列方式。
页面置换算法
在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。
分类
算法
全局:
局部:
☁️ 计算机网络
计算机网络体系结构:
各层作用及协议
物理层
通道:
通道复用技术:
数据链路层
主要信道:
点对点信道
三个基本问题:
SOH - 数据部分 - EOT点对点协议(Point-to-Point Protocol):
广播信道
广播通信:
网络层
IP 网际协议
IP 地址分类:
IP 地址 ::= {<网络号>,<主机号>}IP 数据报格式:
ICMP 网际控制报文协议
ICMP 报文格式:
应用:
内部网关协议
外部网关协议
IP多播
VPN 和 NAT
路由表包含什么?
0.0.0.0, Netmask:0.0.0.0)指向自治系统的出口。根据应用和执行的不同,路由表可能含有如下附加信息:
运输层
协议:
端口:
TCP
特征:
TCP 如何保证可靠传输:
TCP 报文结构
TCP 首部
TCP:状态控制码(Code,Control Flag),占 6 比特,含义如下:
URG=1时,表明紧急指针字段有效,代表该封包为紧急封包。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据), 且上图中的 Urgent Pointer 字段也会被启用。ACK=1时确认号字段才有效,代表这个封包为确认封包。当ACK=0时,确认号无效。RST=1时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。FIN=1时,表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。UDP
特征:
UDP 报文结构
UDP 首部
TCP 与 UDP 的区别
TCP 黏包问题
原因
TCP 是一个基于字节流的传输服务(UDP 基于报文的),“流” 意味着 TCP 所传输的数据是没有边界的。所以可能会出现两个数据包黏在一起的情况。
解决
\r\n标记。FTP 协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。TCP 流量控制
概念
流量控制(flow control)就是让发送方的发送速率不要太快,要让接收方来得及接收。
方法
利用可变窗口进行流量控制
TCP 拥塞控制
概念
拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。
方法
TCP的拥塞控制图
TCP 传输连接管理
TCP 三次握手建立连接
【TCP 建立连接全过程解释】
TCP 为什么要进行三次握手?
【答案一】因为信道不可靠,而 TCP 想在不可靠信道上建立可靠地传输,那么三次通信是理论上的最小值。(而 UDP 则不需建立可靠传输,因此 UDP 不需要三次握手。)
【答案二】因为双方都需要确认对方收到了自己发送的序列号,确认过程最少要进行三次通信。
【答案三】为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
TCP 四次挥手释放连接
【TCP 释放连接全过程解释】
TCP 为什么要进行四次挥手?
【问题一】TCP 为什么要进行四次挥手? / 为什么 TCP 建立连接需要三次,而释放连接则需要四次?
【答案一】因为 TCP 是全双工模式,客户端请求关闭连接后,客户端向服务端的连接关闭(一二次挥手),服务端继续传输之前没传完的数据给客户端(数据传输),服务端向客户端的连接关闭(三四次挥手)。所以 TCP 释放连接时服务器的 ACK 和 FIN 是分开发送的(中间隔着数据传输),而 TCP 建立连接时服务器的 ACK 和 SYN 是一起发送的(第二次握手),所以 TCP 建立连接需要三次,而释放连接则需要四次。
【问题二】为什么 TCP 连接时可以 ACK 和 SYN 一起发送,而释放时则 ACK 和 FIN 分开发送呢?(ACK 和 FIN 分开是指第二次和第三次挥手)
【答案二】因为客户端请求释放时,服务器可能还有数据需要传输给客户端,因此服务端要先响应客户端 FIN 请求(服务端发送 ACK),然后数据传输,传输完成后,服务端再提出 FIN 请求(服务端发送 FIN);而连接时则没有中间的数据传输,因此连接时可以 ACK 和 SYN 一起发送。
【问题三】为什么客户端释放最后需要 TIME-WAIT 等待 2MSL 呢?
【答案三】
TCP 有限状态机
TCP 有限状态机图片
应用层
DNS
域名:
域名 ::= {<三级域名>.<二级域名>.<顶级域名>},如:blog.huihut.comFTP
TELNET
TELNET 协议是 TCP/IP 协议族中的一员,是 Internet 远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。
HTTP(HyperText Transfer Protocol,超文本传输协议)是用于从 WWW(World Wide Web,万维网)服务器传输超文本到本地浏览器的传送协议。
SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。
Socket 建立网络通信连接至少要一对端口号(Socket)。Socket 本质是编程接口(API),对 TCP/IP 的封装,TCP/IP 也要提供可供程序员做网络开发所用的接口,这就是 Socket 编程接口。
WWW
URL
标准格式:
协议类型:[//服务器地址[:端口号]][/资源层级UNIX文件路径]文件名[?查询][#片段ID]完整格式:
协议类型:[//[访问资源需要的凭证信息@]服务器地址[:端口号]][/资源层级UNIX文件路径]文件名[?查询][#片段ID]HTTP
HTTP(HyperText Transfer Protocol,超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是万维网的数据通信的基础。
请求方法
状态码(Status-Code)
其他协议
🌩 网络编程
Socket
Socket 中的 read()、write() 函数
read()
write()
Socket 中 TCP 的三次握手建立连接
我们知道 TCP 建立连接要进行 “三次握手”,即交换三个分组。大致流程如下:
只有就完了三次握手,但是这个三次握手发生在 Socket 的那几个函数中呢?请看下图:
从图中可以看出:
Socket 中 TCP 的四次握手释放连接
上面介绍了 socket 中 TCP 的三次握手建立过程,及其涉及的 socket 函数。现在我们介绍 socket 中的四次握手释放连接的过程,请看下图:
图示过程如下:
这样每个方向上都有一个 FIN 和 ACK。
💾 数据库
基本概念
常用数据模型
关系名(属性1, 属性2, ..., 属性n)常用 SQL 操作
CREATE SCHEMACREATE SCHEMA,ALTER TABLECREATE VIEWCREATE INDEXSELECT,INSERT,UPDATE,DELETE,REFERENCES,ALL PRIVILEGESSELECT,INSERT,UPDATE,REFERENCES,ALL PRIVILEGES关系型数据库
索引
数据库完整性
关系数据理论
范式
数据库恢复
并发控制
📏 设计模式
设计模式工程目录
单例模式
单例模式例子
抽象工厂模式
抽象工厂模式例子
适配器模式
适配器模式例子
桥接模式
桥接模式例子
观察者模式
观察者模式例子
设计模式的六大原则
⚙️ 链接装载库
内存、栈、堆
一般应用程序内存空间有如下区域:
栈
栈保存了一个函数调用所需要的维护信息,常被称为堆栈帧(Stack Frame)或活动记录(Activate Record),一般包含以下几方面:
堆
堆分配算法:
“段错误(segment fault)” 或 “非法操作,该内存地址不能 read/write”
典型的非法指针解引用造成的错误。当指针指向一个不允许读写的内存地址,而程序却试图利用指针来读或写该地址时,会出现这个错误。
普遍原因:
编译链接
各平台文件格式
编译链接过程
#include、#define等预编译指令,生成.i或.ii文件).s文件).o文件).out文件)目标文件
编译器编译源代码后生成的文件叫做目标文件。目标文件从结构上讲,它是已经编译后的可执行文件格式,只是还没有经过链接的过程,其中可能有些符号或有些地址还没有被调整。
目标文件格式
.obj格式.o格式a.out格式.COM格式目标文件存储结构
链接的接口————符号
在链接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。我们将函数和变量统称为符号(Symbol),函数名或变量名就是符号名(Symbol Name)。
如下符号表(Symbol Table):
Linux 的共享库(Shared Library)
Linux 下的共享库就是普通的 ELF 共享对象。
共享库版本更新应该保证二进制接口 ABI(Application Binary Interface)的兼容
命名
libname.so.x.y.z路径
大部分包括 Linux 在内的开源系统遵循 FHS(File Hierarchy Standard)的标准,这标准规定了系统文件如何存放,包括各个目录结构、组织和作用。
/lib:存放系统最关键和最基础的共享库,如动态链接器、C 语言运行库、数学库等/usr/lib:存放非系统运行时所需要的关键性的库,主要是开发库/usr/local/lib:存放跟操作系统本身并不十分相关的库,主要是一些第三方应用程序的库环境变量
LD_LIBRARY_PATH:临时改变某个应用程序的共享库查找路径,而不会影响其他应用程序LD_PRELOAD:指定预先装载的一些共享库甚至是目标文件LD_DEBUG:打开动态链接器的调试功能so 共享库的编写
使用 CLion 编写共享库
创建一个名为 MySharedLib 的共享库
CMakeLists.txt
library.h
library.cpp
so 共享库的使用(被可执行项目调用)
使用 CLion 调用共享库
创建一个名为 TestSharedLib 的可执行项目
CMakeLists.txt
main.cpp
执行结果
Windows 应用程序入口函数
/SUBSYSTEM:WINDOWS/SUBSYSTEM:CONSOLE_tWinMain 与 _tmain 函数声明
Windows 的动态链接库(Dynamic-Link Library)
用处
注意
加载 Windows 程序的搜索顺序
DLL 入口函数
DllMain 函数
载入卸载库
LoadLibrary、LoadLibraryExA、LoadPackagedLibrary、FreeLibrary、FreeLibraryAndExitThread 函数声明
显示地链接到导出符号
GetProcAddress 函数声明
DumpBin.exe 查看 DLL 信息
在
VS 的开发人员命令提示符使用DumpBin.exe可查看 DLL 库的导出段(导出的变量、函数、类名的符号)、相对虚拟地址(RVA,relative virtual address)。如:LoadLibrary 与 FreeLibrary 流程图
LoadLibrary 与 FreeLibrary 流程图
LoadLibrary
FreeLibrary
DLL 库的编写(导出一个 DLL 模块)
DLL 库的编写(导出一个 DLL 模块) DLL 头文件
DLL 源文件
DLL 库的使用(运行时动态链接 DLL)
DLL 库的使用(运行时动态链接 DLL)
运行库(Runtime Library)
典型程序运行步骤
glibc 入口
_start -> __libc_start_main -> exit -> _exit其中
main(argc, argv, __environ)函数在__libc_start_main里执行。MSVC CRT 入口
int mainCRTStartup(void)执行如下操作:
C 语言运行库(CRT)
大致包含如下功能:
C语言标准库(ANSI C)
包含:
📚 书籍
语言
算法
系统
网络
其他
🔱 C/C++ 发展方向
后台/服务器
【后台开发】
桌面客户端
【PC 客户端开发】
图形学/游戏/VR/AR
【游戏客户端开发】
测试开发
【测试开发】
网络安全/逆向
【安全技术】
嵌入式/物联网
【嵌入式应用开发】
音视频/流媒体/SDK
【音视频编解码】
计算机视觉/机器学习
【计算机视觉研究】
💯 复习刷题网站
📝 面试题目经验
📆 招聘时间岗位
👍 内推
👬 贡献者
📜 License
本仓库遵循 CC BY-NC-SA 4.0(署名 - 非商业性使用 - 相同方式共享) 协议,转载请注明出处,不得用于商业目的。