智能指针
unique_ptr
独占资源所有权的指针。由于没有引用计数,因此性能较好。
离开 unique_ptr 对象的作用域时,会自动释放资源。
unique_ptr 本质是一个类,将复制构造函数和赋值构造函数声明为 delete 就可以实现独占式,只允许移动构造和移动赋值。unique_ptr 所持有的对象只能通过 转移语义(move) 将所有权转移到另外一个 unique_ptr 。
123// 自定义实现 unique_ptrUniquePtr(UniquePtr<T> const &) = delete;UniquePtr & operator=(UniquePtr<T> const &) = delete;
123std::unique_ptr<int> uptr = std::make_unique<int>(200);// ...// 离开 uptr 的作用域的时候自动释放内存
std::unique_ptr 是 move-only 的。
12345std::unique_ptr<int> ...
左值、右值、纯右值、将亡值
左值、右值、纯右值、将亡值
C++11使用下面两种独立的性质来区别类别:
拥有身份:指代某个非临时对象。
可被移动:可被右值引用类型匹配。
每个C++表达式只属于三种基本值类别中的一种:左值 (lvalue)、纯右值 (prvalue)、将亡值 (xvalue)
拥有身份且不可被移动的表达式被称作 左值 (lvalue) 表达式,指持久存在的对象或类型为左值引用类型的返还值。
拥有身份且可被移动的表达式被称作 将亡值 (xvalue) 表达式,一般是指类型为右值引用类型的返还值。
不拥有身份且可被移动的表达式被称作 纯右值 (prvalue) 表达式,也就是指纯粹的临时值(即使指代的对象是持久存在的)。
不拥有身份且不可被移动的表达式无法使用。
如此分类是因为移动语义的出现,需要对类别重新规范说明。例如不能简单定义说右值就是临时值(因为也可能是 std::move 过的对象,该代指对象并不一定是临时值)。
左值
左值是一个数据的表达式(如变量名或引用的指针),我们可以获取到它的地址,正常情况下是可以能够对它赋值
定义const修饰后的左值,不能给它赋值,但是可以取出它的地址
...
排序算法
各种排序算法的原理和时间复杂度
快速排序:一轮划分,选择一个基准值,小于该基准值的元素放到左边,大于的放在右边,此时该基准值在整个序列中的位置就确定了,接着递归地对左边子序列和右边子序列进行划分。时间复杂度O(nlogn),最坏的时间复杂度是O(n^2)。需要排序的对象越有序,快速排序的退化程度越高,即时间复杂度越趋向于O(n ^ 2)。通过随机选择枢轴,可以有效地避免这种情况。随机选择使得每次分区都有较大概率是平衡的,从而保持了快速排序的平均时间复杂度 O(n * logn);
123456789101112131415161718192021222324252627282930313233343536void QuickSort(vector<int>& nums, int left, int right){ if (left >= right) return; int pivot = rand() % (right - left + 1) + left; swap(nums[pivot], nums[right ...
C++ 的几种类型转换
C++ 的几种类型转换
在 C 语言中,我们大多数是用 (type_name) expression 这种方式来做强制类型转换,但是在 C++ 中,更推荐使用四个转换操作符来实现显式类型转换:
static_cast
dynamic_cast
const_cast
reinterpret_cast
static_cast
用法: static_cast <new_type> (expression) 。其实 static_cast 和 C 语言 () 做强制类型转换基本是等价的。主要用于以下场景:
基本类型之间的转换
将一个基本类型转换为另一个基本类型,例如将整数转换为浮点数或将字符转换为整数。
12int a = 42;double b = static_cast<double>(a); // 将整数a转换为双精度浮点数b
指针类型之间的转换
将一个指针类型转换为另一个指针类型,尤其是在类层次结构中从基类指针转换为派生类指针。这种转换不执行运行时类型检查,可能不安全,要自己保证指针确实可以互相转换。
12345class Base { ...
C++ 模板相关问题
为什么模板声明定义不能分离?前言
模板函数一般不能声明定义分开,但是普通函数函数就可以,为什么呢?那就要先介绍一下在软件开发过程中,从源代码到可执行程序,通常会经历预处理、编译、汇编、链接的这四个主要步骤。
预处理 ( Preprocessing ):
任务:预处理器的任务是处理源代码中的预处理指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef、#ifndef、#endif等)指令。它会删除所有注释,展开所有宏定义,处理条件编译指令,并插入包含文件的内容。
生成文件:预处理后的文件通常以 .i 或 .ii 结尾(在C/C++中),这是预处理后的文本文件,仍然保持高级语言的形式。
编译 ( Compilation ):
任务:编译器将预处理后的源代码转换成汇编语言。这个过程包括词法分析、语法分析、语义分析、中间代码生成、代码优化等步骤。编译器检查源代码中的错误,如语法错误、类型错误等,并将源代码转换成汇编指令。
生成文件:编译阶段生成的文件通常称为目标文件(Object File),以 .s结尾。这个文件包含机器代码,但是它还不能直接执行(计算 ...
虚调用
虚调用的定义
虚调用 是相对于 实调用 而言,它的本质是 动态联编 。
在发生函数调用的时候,如果函数的入口地址是在编译阶段静态确定的,就是是 实调用 。
如果函数的入口地址要在运行时通过查询虚函数表的方式获得,就是 虚调用 。
虚函数 的几种 实调用 的情形不通过指针或者引用调用虚函数
虚调用 不能简单的理解成 “对虚函数的调用” ,因为对虚函数的调用很有可能是实调用。
12345678910111213141516171819202122232425262728293031#include <iostream>using namespace std;class A {public: virtual void show() { cout << "In A" << endl; }};class B : public A {public: void show() { cout << "I ...
虚函数与虚表
虚函数与虚表
普通类的内存布局和带虚函数类的内存布局123456789101112131415161718192021#include <iostream>using namespace std;class NonVirtualClass {public: void foo(){}};class VirtualClass {public: virtual void foo(){}};int main() { cout << "Size of NonVirtualClass: " << sizeof(NonVirtualClass) << endl; cout << "Size of VirtualClass: " << sizeof(VirtualClass) << endl;}
这里 NonVirtualClass 的大小为1,而 Vir ...
Cache对代码的影响
问题背景
代码片段一
12345int array[10][128];for (i = 0; i < 10; i++) for (j = 0; j < 128; j++) array[i][j] = 1;
代码片段二
12345int array[10][128];for (i = 0; i < 128; i++) for (j = 0; j < 10; j++) array[j][i] = 1;
我们假设使用的L1 Cache Line大小是64字节,采用写分配及写回策略。继续假设数组 array 内存首地址是64字节对齐。
问题分析
在有了以上背景假设后,我们先分析下片段1导致的Cache Miss / Hit情况。
当执行 array[0][0] = 1 时,Cache 控制器发现 array[0][0] 的值不在Cache中,此时发生一次 Cache Miss。然后从主存中读取 array[0][0] 到 array[0][15] 的内存值到 Cache 中。
当执行访问 array[0][1] = 1 ...
Cache的基本原理
为什么需要Cache程序是如何运行起来的
程序是运行在 RAM(随机存储器) 之中,我们称之为 main memory(主存)。当我们需要运行一个进程的时候,首先会从磁盘设备(例如,eMMC、UFS、SSD等)中将可执行程序load到主存中,然后开始执行。
在CPU内部存在一堆的通用寄存器(register)。如果 CPU 需要将一个变量(假设地址是A)加1,一般分为以下3个步骤:
CPU 从主存中读取地址 A 的数据到内部通用寄存器 x0(ARM64架构的通用寄存器之一)。
通用寄存器 x0 加1。
CPU 将通用寄存器 x0 的值写入主存。
其实现实中,CPU通用寄存器的速度(< 1ns)和主存(~ 65ns)之间存在着太大的差异。
因此,上面举例的3个步骤中,步骤1和步骤3实际上速度很慢。当CPU试图从主存中 Load / Store 操作时,由于主存的速度限制,CPU不得不等待这漫长的65ns时间。
如果我们采用更快材料制作更快速度的主存,并且拥有几乎差不多的容量, 其成本将会大幅度上升。我们试图提升主存的速度和容量,又期望其成本很低,这就有点难为人了。
因此, ...














