鉴于写博客有助于自己的学习,于是博主将自己所学的c++总结了一下

总体知识

总述

基础知识分为: 变量类型–>基础语法–>函数–>类–>库(细说stl)–>多线程(进程)–>堆栈–>模板

工程目录

我的工程目录–>有助于分类

1
2
3
4
5
6
7
Projects/Project_1
├── bin --> 放exe文件
├── build --> 放CMakeLists.txt
├── data --> 放图片等资源
├── include --> 放头文件
├── lib --> 放静态库,动态库
└── src --> 放c++源文件

另外: 头文件放结构体, 宏定义, 函数声明,类声明, 全局变量…而源文件放函数原型, 类原型.
Thinking: 可以把库底层写成xxx_Base.cpp, 把调用接口写成xxx_API.cpp

静态库的创建

1
2
g++ -c 1.cpp 2.cpp 3.cpp
ar rcs libxxx.a 1.o 2.o 3.o

动态库的创建

1
2
// PIC -> Position Independence Code
gcc -shared -fPIC -o libxxx.so 1.cpp 2.cpp 3.cpp
  • Tips: 使用g++创建动态库时, 一定要在函数的外部加上extern "C"{函数}, 否则会出现函数符号无法找到.
    例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// mylib.c
#include <stdio.h>
#ifdef __cplusplus
extern "C"{
#endif
int add(int a, int b) {
return a + b;
}
#ifdef __cplusplus
}
#endif


// 使用dlfcn.h需要导入动态库 -ldl dl -- dynamic link
// main.c
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 1. 打开共享库
void *handle = dlopen("./libmylib.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "Error loading shared library: %s\n", dlerror());
return 1;
}

// 2. 获取函数指针
int (*add_func)(int, int) = (int (*)(int, int)) dlsym(handle, "add");
char *error;
if ((error = dlerror()) != NULL) {
fprintf(stderr, "Error finding symbol: %s\n", error);
dlclose(handle);
return 1;
}

// 3. 调用函数
int result = add_func(5, 7);
printf("Result: %d\n", result);

// 4. 关闭共享库
dlclose(handle);
return 0;
}

g++的使用

g++ main.cpp 1.cpp 2.cpp -o main.exe

选项 作用
-o 指定输出文件名
-std=c++17 指定标准版本
-g 生成调试信息
-O0/-O1/-O2/-O3 优化级别
-Wall 生成所有警告信息
-S 生成汇编.s文件
-c 生成.o文件
-E 生成预处理.i文件
-I/path/to/include 头文件路径
-L/path/to/lib/ 库路径
-lmylib 链接libmylib.a静态库/libmylib.so(dll)的动态库(对库的引用放在命令的结尾)
-DDEBUG 相当于添加#define DEBUG

变量类型

区分数据类型的唯一标准是内存的字节占用,所有的数据都是二进制(01)表示的,不同的数据类型是编译器把二进制数据跟人进行交流的"桥".

  • 初识变量
名称 符号 占用字节
字符 char 1
布尔值 bool 1
短整型 short 2
整型 int 4
长整型 long 8
单精度浮点数 float 4
双精度浮点数 double 8
指针 类型* 4(32位系统)/8(64位系统)
联合体 union 与最大的数据类型一样大
枚举体 enum 1/2/4(通常)
结构体 struct 有内存补齐操作无法确定

字符

  • 'x'使用''包裹的一个字符<–man ascii可以在线查看ascii表

整数

  • 有符号整数: 最高位0表示正数,1表示负数
    假如int类型来解释100…100,该值为231+22-2^{31} + 2^2
  • 无符号整数:

浮点数

遵循IEEE 754 标准

  • float(32)
部分 符号位 指数位 尾数位
位数 1 8 23
  • double(64)
部分 符号位 指数位 尾数位
位数 1 11 52

规格化值(Exp不全为0或1)
解释:

  • sign(符号位): 0代表正,1代表负数
  • Exp(指数位): 实际指数为(Exp - 127(float)/1023(double)),范围是(-126 ~ 127)/(-1022~1023)
  • Man(尾数位):

Value=(1)Sign  x  1.Man  x2ExpBiasValue = (-1)^{Sign} \;\text{x}\; 1.Man \;\text{x} 2^{Exp-Bias}

非规格化值:

  • Exp全为0时: 0值
  • Exp全为1,尾数全为0时: 无穷大\infty
  • Exp全为1,尾数不全为0时: NaN

数组

须知:

  1. 数组里可以装任何数据类型
  2. 数组实际上是在栈上的一连串的内存,数组名代表这一连串地址的起始地址(小地址<–栈向低地址增长)
  3. 数组名不能更改(arr+2)
  4. 字符串(字符组成的数组,结尾是0(不是’0’))
  5. 数组的长度必须在编译前就知道(不能用变量,而必须用常量const)
  6. 指针的加减数字是根据指针指向的数据类型而定的,如(int*)p + 1是地址加4(int是4个长度)
  • 性能优化:
    将多维数组写成一维数组(防止内存碎片)
1
2
3
4
5
6
int a2d = new int[5*5];
for (int i = 0; i< 5; i++){ // i代表行,j代表列
for (int j = 0; j<5; j++)
a2d[j + i*5] = 2;
}
delete[] a2

结构体

  • 用处: 处理多返回值的函数
1
2
3
4
5
6
7
8
9
10
11
12
struct source
{
std::string first;
std::string second;
};
struct source function()
{
//code
vs = ..
fs = ..
return struct source{vs, fs};
}
  • 与类的区别: 结构体默认public, 类默认private

Tips: 我们当然学习类啦

指针

注意

  • 指针需要初始化int* ptr = nullptr(推荐使用nullptr()而非0或者NULL)
  • 通过new创建的(堆)内存块被释放后要将指向内存块的指针赋nullptr防止指针悬空,栈上的指针自动释放

优点

  • 通过指针可以不用复制大量内存来传递参数,而是可以直接传递指针优化性能

智能指针

  • #include <memory>
名称 介绍
std::unique_ptr<类型> 超出作用域自动释放,无法复制,只能移动std::unique<int> p2 = std::move(p1)
std::shared_ptr<类型> 计数为0自动释放,可以复制
std::weak_ptr<类型> lock()可以转化为shared_ptr来观察值,解决shared_ptr循环引用导致的计数无法归零

枚举体

1
2
3
enum COLORS : char{ // 指定枚举体基础类型是char
RED = 'R', BLUE = 'B', GREEN = 'G'
}

enum中的例子会隐式转化,enum class不会隐式转换,从而可以在不同的enum使用同名的名字

结构体

内存对齐规则
从第一个开始遇到的最大的哪个数据类型为基础,之后的每个数据的起始位置必须是目前为止最大的数据的整数倍

1
2
3
4
5
struct TestStruct {
short a; // 2 bytes
char b; // 1 byte
int c; // 4 bytes
};

结构体大小是0->2->4->8,大小是8bytes

联合体

须知:

  • 联合体中的所有成员共享同一块内存空间。联合体的大小等于其最大成员的大小,利用类型双关.
1
2
3
4
5
6
7
8
9
union Price{
int a;
float b;
}
Price p;
p.a = 4;
cout << p.a << endl;
p.b = 5.9;
cout << p.b << endl;

函数

函数说明符

1
2
3
4
5
6
7
8
9
void f() noexcept;        // 函数 f 不会抛出异常
int getValue() const; // 承诺getValue() 函数不会修改类的任何成员变量
static void myFunction(); // 表示 myFunction() 函数具有静态存储期
extern void anotherFunction(); // 声明 anotherFunction() 函数在其他文件中定义
inline int add(int a, int b) { return a + b; } // 建议编译器将 add() 函数内联
virtual void draw(); // 声明 draw() 函数为虚函数
explicit MyClass(int value); // 禁止将 int 类型隐式转换为 MyClass 类型
friend void friendFunction(MyClass& obj); // 声明 friendFunction() 为 MyClass 的友元函数
constexpr int factorial(int n) // 声明 factorial() 函数为常量表达式函数

函数指针

返回类型 (*指针变量名)(参数类型列表);
举例

1
2
3
4
int add(int a, int b){
return a+b;
}
int (*add_ptr)(int, int) = add // 或者&add

更灵活的方式

1
2
3
#include <functional>
std::function<int<int,int>> add_ptr = add; //或者&add
int res = add_ptr(3,4);

lambda函数

格式

1
2
3
4
5
6
auto f = [](int a, float b)
{
// code
}
// []是捕获列表---[=]传值捕获(只读,mutable后可改值)[&]引用捕获[a,&b]混合捕获
// ()是传参列表---()

函数重载

在 C++ 中.如果两个函数名相同.但参数列表(个数、类型或顺序)不同.它们是不同的函数.这是通过函数重载实现的

1
2
3
4
5
6
7
8
9
10
11
12
void log(int x)
{
Log(x);
}
void log(char x)
{
Log(x);
}
int main()
{
log('a'); // a
}

类是用户自定义的数据类型.包含数据成员(属性)和成员函数(方法)

基本结构

1
2
3
4
5
6
7
8
class ClassName {
public: // 访问修饰符(默认private)
// 成员函数声明
void method();
private:
// 数据成员声明
int data;
};

类的可见性

关键字 可允许访问的范围
private 这个类和友类
protected 这个类及子类及友类
public All

类的特殊函数

构造函数

  • ClassName(int val, int dot) : data(val),data2(dot) {} // 初始化列表—data,data2的顺序要和声明的一样
  • ClassName a(1,2);—利用值构造类

析构函数

  • ~ClassName() { /* 清理资源 */ }

拷贝构造函数

  • ClassName(const ClassName& other);
  • ClassName b = a—利用类构造类

拷贝赋值函数

  • ClassName& operator=(const ClassName& other);
  • c = a—类给类赋值

移动构造函数

  • ClassName(ClassName&& other) noexcept;
  • ClassName d = std::move(a)—利用类构造类

移动赋值函数

  • ClassName& operator=(ClassName&& other) noexcept;
  • e = std::move(a)—类给类赋值

单例

删除赋值构造函数,private构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton
{
public:
Singleton(const Singleton &) = delete; // 删除赋值构造函数

static Singleton &Get() // 调用方法返回单一实例
{
static Singleton s_instance; // 类属性
return s_instance;
}
private : Singleton() {} // 不能在外界直接创造实例
};
int main()
{
Singleton &instance = Singleton::Get();
}

类的其他特征

类的static数据

为类的属性而非实例,所有该类的实例共享这一份数据

  • static int val;

类的static函数

static方法只能访问static数据

  • static void printCount() { cout << count; }

友元函数

  • friend void friendFunction(ClassName obj);

友元类的声明

  • friend class ClassName

运算符重载

1
2
3
4
5
6
7
8
ClassName operator+(const ClassName& other) {
return ClassName(data + other.data);
}
std::ostream &operator<<(std::ostream &stream, const Vector &other) //重载<<
{
stream << other.x << "," << other.y;
return stream;
}

继承

class Derived : public Base { /* ... */ };

多态

虚函数存在于父类,目的是为子类提供函数模板.重写函数存在子类,为子类提供不同的函数

虚函数

virtual void method() { /* 基类实现 */ }

纯虚函数

Tips: 包含纯虚函数的类无法实例化
virtual void pureMethod() = 0;

举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class printable
{
public:
virtual std::string Getclassname() = 0;
};
// 定义一个接口,是所有类的父类,里有纯虚函数Getclassname
class Entity : public printable // 继承
{
public:
std::string Getclassname() override { return "Entity"; }
};
// 重写Getclassname
class Player : public Entity
{
std::string Getclassname() override { return "Player"; }
};
// 重写Getclassname
void print(printable *obj)
{
std::cout << obj->Getclassname() << std::endl;
}// 传入指针对象来操作其对应的函数(已经override了)
int main()
{
Entity *e = new Entity;
print(e);
Player *p = new Player;
print(p);
}
虚析构函数

当基类的析构函数是虚函数时.通过基类指针删除派生类对象时.会正确调用派生类的析构函数。如果基类的析构函数不是虚函数.则只会调用基类的析构函数.导致派生类的资源泄漏。

1
2
Base *poly = new Diversy(); //Base是父类, Diversy是派生类
delete poly;//若Base,Diversy的析构函数是vitural,那么会正常调用Diversy的析构函数,否则会调用Base的析构函数

类的高级特征

override

  • void mtthod override;

final

  • class ClassName final{};禁止继承

成员初始化列表

  • ClassName a{1,2};

this

  • this代指本实例的指针

语法

运算符

算术运算符

  • [ ] +
  • [ ] -
  • [ ] *
  • [ ] /
  • [ ] %
  • [ ] ++ # 注意前后置的区别
  • [ ] –

关系运算符

  • [ ] ==
  • [ ] !=
  • [ ] >
  • [ ] <
  • [ ] >=
  • [ ] <=

逻辑运算符

  • [ ] &&
  • [ ] ||
  • [ ] ! # 不全为0则为1

位运算符

  • [ ] &
  • [ ] |
  • [ ] ^ # a^b==0 --> a==b
  • [ ] ~ # 按位取反
  • [ ] << # 一般有符号是算术左移符号逻辑左移
  • [ ] >>

赋值运算符

  • [ ] =
  • [ ] +=
  • [ ] -=
  • [ ] *=
  • [ ] /*
  • [ ] %=
  • [ ] <<=
  • [ ] >>=
  • [ ] &=
  • [ ] ^=
  • [ ] |=

其他运算符

  • [ ] sizeof
  • [ ] condtion?x:y
  • [ ] ,
  • [ ] .
  • [ ] ->
  • [ ] cast # int(2.2)
  • [ ] & # 取地址
  • [ ] * # 取值

条件分支和循环

条件分支

if
if else
if else if
switch case
1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch(var){  //var是整型或enum
case val1
//code
break;
case val2
//code
break;
case val3
//code
break;
default:
//code
}

循环

while
do…while
for
关键词
  • continue
  • break
  • return

Tips: 进入循环时最好将结果存储在循环体外的变量,减少内存访问

修饰符

访问控制修饰符

public 类内外均可
private 类内,友元类可以访问
protected 类内,友元类和子类可以访问

存储类修饰符

static

  • 局部: 只初始化一次
  • 全局/函数: 作用域限定在本文件
  • 类成员: 所有实例共享这个数据

extern

  • 声明变量/函数已经在其他文件被定义了

mutable

  • 可以在const成员函数中修改类的成员
    eg:
1
2
3
4
5
6
7
8
9
class xx{
int a;
mutable int b;
int Getx(int x) const{
a = 2; // ok
b = 3; // inhabit
return m_x;
}
};

thread_local
详见此处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <thread>
#include <stdio.h>

thread_local int g_n = 1;

void f()
{
g_n++;
printf("id=%d, n=%d\n", std::this_thread::get_id(),g_n);
}

void foo()
{
thread_local int i=0;
printf("id=%d, i=%d\n", std::this_thread::get_id(), i);
i++;
}

void f2()
{
foo();
foo();
}

int main()
{
g_n++; // n=2
f(); // n=3 主进程
std::thread t1(f); // n=2 进程1
std::thread t2(f); // n=2 进程2

t1.join();
t2.join();


f2(); // i=0 i=1 主进程
std::thread t4(f2); // i=0 i=1 进程3
std::thread t5(f2); // i=0 i=1 进程4

t4.join();
t5.join();
return 0;
}

类型限定符

const

  • const 变量 --> 常量
  • 成员函数 const -->禁止修改成员状态
  • const int* 指针指向常量;int *const 指针不可变

volatile

  • 变量可能被外部修改,禁止编译器优化

类型修饰符

signed/unsigned
short/long/long long

函数修饰符

inline

  • 内联函数, 直接将函数体的源代码复制到调用处(适用于函数体小的函数)

vitural

  • 定义虚函数

override

  • 重写虚函数

final

  • 禁止派生类覆盖虚函数, 或者禁止子类

constexpr

  • 编译时可求出函数的返回值

noexcept

  • 函数不可抛出异常

explict

  • 禁止构造函数隐式转换

输入输出

cin

  • cin >> data; // 忽略前导空格,到下一个空白符或换行符或EOF停止读入
  • cin.getline(); // 丢弃换行符
  • cin.get(); // 逐个字符读取,不会丢弃空格和换行符

文件

1
2
3
4
5
6
7
8
9
10
11
12
#include <fstream>
int main()
{
std::ofstream Outfile; // cout 用法基本一样 setf()
Outfile.open(filename); // binding name
fOut << content;
Outfile.close();

std::ifstream Infile // cin
Infile.open(filename);
fin >> content;
}

堆和栈

虚拟内存管理图

  1. 栈(Stack)
    特点
  • 内存分配方式:由编译器自动分配和释放,遵循“后进先出”(LIFO)原则。

  • 存储内容:局部变量、函数参数、函数调用信息(如返回地址)等。

  • 内存管理:内存分配和释放由系统自动完成.效率高。

  • 大小限制:栈的大小通常较小(如几MB)具体取决于操作系统和编译器设置。

  • 访问速度:访问速度快.因为内存分配是连续的。

  • 栈向下增长

优点

  • 高效:内存分配和释放由系统自动完成。

  • 简单:无需手动管理内存。

缺点

  • 大小受限:栈空间有限.不适合存储大量数据。

  • 生命周期固定:变量的生命周期仅限于函数作用域。

栈帧结构

  1. 堆(Heap)
    特点
  • 内存分配方式:手动分配和释放(new/delete或者智能指针)。

  • 存储内容:存储动态分配的内存(如对象、数组等)。

  • 内存管理:需要程序员手动管理.否则可能导致内存泄漏或野指针。

  • 大小限制:堆的大小通常较大.受系统可用内存限制。

  • 访问速度:访问速度较慢.因为内存分配是动态且不连续的。

  • 堆大体上向上增长(但是内存不是连续分配的)

  • 假设堆的起始地址为 0x1000,当程序需要分配一块 100 字节的内存时,操作系统可能会分配地址范围为 0x1000 到 0x1064 的内存块。如果程序再次需要分配内存,新分配的内存块的地址将会大于 0x1064。

元编程模板

template

1
2
3
4
5
6
7
8
9
10
template <typename T, int N>
class Array
{
private:
T array[N];
public:
void Get_Size() const {return N;}
};
Array<int, 5> array;

泛型设计的理想状态是一个查找算法将可以作用于数组,联表,树,图等各种数据结构之上,变成一个通用的,泛型的算法。

STL(Standard Template Library)

#include <bits/stdc++.h>

总览

  • [ ] 基础知识
  • [ ] 算法(Algorithm)
  • [ ] 容器(Container)
  • [ ] 迭代器(Iterator)
  • [ ] 仿函数(Function Object)

基础知识

类型转换

1
2
3
4
5
6
7
8
9
10
11
float f = 3.14;
int i = static_cast<int>(f); // 静态类型转换,类似C,不允许类似int* ->float*

Base *b = new Derived();
Derived *d = dynamic_cast<Derived*>(b); // 动态类型转换,会检查b是不是Derived 类型(提供类型检查),不是就会返回nullptr

const int a = 10;
int *p = const_cast<int*>(&a); // 去掉常量性,不允许跨类型转换,只改变const性

int a = 42;
void* p = reinterpret_cast<void*>(&a); // 转换指针类型

容器

pair

1
2
3
pair<int, pair<int, int>> p = {1, {3,4}}; // 初始化 
pair<int, int> arr[2] = {{1,2}, {3,4}} // 初始化数组
cout << p.first << p.second.first; // 访问

vector

特征: 自由伸缩(对性能有影响), 存储在堆上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// init
vector<类型> v([大小],[初始值]);
// add
vector.emplace_back();
// pop
vector.pop_back();
// return size
vector.size();
// resize
vector.resize(length,initvalue);
// clear -- erase the entire vector
v.clear();
// empty -- to say if the vector is empty
cout << v.empty();
// iterator
auto it = vector.begin(), vector.rbegin(), vec.end(), vec.rend()
// value at an index
vector[index], vector.back()
// erase
vector.erase(迭代器)
vector.erase(v.begin()+2, v.begin()+4) // 删除[3,5)
// loop
for(auto it : v){
cout << it << endl;
}
for (auto it = v.begin(); it != v.end(); it++){
cout << *it << endl;
}
// insert
v.insert(迭代器, 值)
v.insert(迭代器, 个数, 值)
v.insert(迭代器1, 迭代器2, 迭代器3) // 从迭代器1插入[迭代器2,迭代器3)的值
// swap
v1.swap(v2);

list

特征: 可以双向插入

1
2
3
4
5
6
7
8
9
10
// init 
list<int> ls;

ls.push_back();
ls.emplace_back();
ls.push_front();
ls.emplace_front();

// rest like vector

stack

特征: 单向操作,LIFO(后进先出)

1
2
3
4
5
6
7
8
9
10
11
12
//init
stack<int> st;
// push
st.push();
//emplace
st.emplace();
// top
std::cout << st.top();
// pop
st.pop()

// rest like vector

queue

特征: 单向操作. FIFO(先进先出)

1
2
3
4
5
6
7
8
9
10
// init
queue<int> q;
// push
q.push();
q.emplace()
// back/front
q.back();
q.front();
// pop
q.pop();

priority_queue

特征:最大值在顶部,其余像queue

1
2
3
4
5
6
7
8
9
10
11
// init
priority_queue<int> pq; // {5,4,2,1}
priority_queue<int, vector<int>, greater<int>> pq; // {1,2,4,5}
// top
pq.top(); // 5
// pop
pq.pop();
// push
pq.push();
pq.emplace();

set

特征: 按大小排序记录唯一性元素
时间复杂度: O(lgn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// init
set<int> s;
// insert
s.insert(1); // {1}
s.insert(2); // {1,2}
s.insert(2); // {1,2}
s.insert(4); // {1,2,4}
s.insert(3); // {1,2,3,4}
// find
auto it = s.find(3);
// erase
s.erase(4);s.erase(it); // number or iterator
s.erase(it1, it2); // erase [it1, it2)
// count
s.count(1);
// lower_bound
auto it = s.lower_bound(2) // return the first x like 2<=x
auto it = s.upper_bound(2) // return the first x like 2>=x

multiset

特征: 按大小排序记录元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// init
multiset<int> ms;
// insert
ms.insert(1); // {1}
ms.insert(1); // {1}
ms.insert(1); // {1}
// find
auto it = ms.find(1); // 如果找不到就在ms.end()
// erase single
ms.erase(it);
// erase range
ms.erase(it, it+2);
// erase all;
ms.erase(1);


unorder_set

特征: 无序记录唯一性元素

1
2
3
4
// init
unorder_set<int> us;

// 无upper_bound, lower_bound

map

特征 : 以键的大小顺序排序,类似字典(但是字典的类型是任意的)

1
2
3
4
5
6
7
8
9
10
11
12
13
// init
map<int, int> mp;
// get value
value = mp[key];
// get iterator
auto it = mp.find(key);
// insert/emplace
mp.insert({1,2});
mp.emplace({1,2});
// see the every pair in map
for (auto it : mp){
cout << it.first << "," << it.second << endl;
}

multimap

特征: 类似map(但是可以存储多个键)

unordered_map

特征: 不会按照键的大小排序

算法

sort

快速排序
sort(it_begin, it_end(), cmp)

默认是升序, greater<int>是降序
cmp返回结果为真, 上一个元素在前(即排序正确不需要重排)

__builtin_popcount

  • 计算二进制位数
1
2
3
4
int bits = __builtin_popcout(int x);
int bits = __builtin_popcoutl(long x);
int bits = __builtin_popcoutll(long long x);

next_permutation

1
2
3
4
5
6
7
string s = "123"
do{
cout << s << endl;
}while(s.begin(), s.end());
// 123, 132, 213, 231, 312, 321


max_element

1
2
int maxi = *max_element(it_begin, it_end);

reverse

reverse(arr.begin(), arr.end()反转元素

gcd/lcm

gcd(int a, int b)最大公倍数
lcm(int a, int b)最小公因数

向上取整, 向下取整

a/b --> floor
a/b + 1 --> ceil

快速幂

利用位运算

1
2
3
4
5
6
7
8
9
10
int quickpow(int a, int b)
{
int res = 1;
while (b > 0){
if (b & 1) res *= a;
a *= a;
b >>= 1;
}
return res;
}