C++技巧

卡常小技巧:

数组长度为奇数,循环展开,多维数组越小放前面,提高访址连续性,非递归函数加 inline

cin语句的使用 :

在使用 cin 输入的时候,系统会根据变量的类型从输入流中提取相应长度的字节

  • cin语句把 空格,制表,换行,回车 作为分隔符,不输入给变量
  • cin语句忽略多余的输入数据,要组织好输入流数据

这种做法得看数据的空白字符吧,可以用更普适的方法。

char get()
{
unsigned char c=getchar();
while(c<=32)c=getchar();
return c;
}

或者是用 scanf(" %c",&ch);,读入下一个非空白字符。

字符串题大全:

https://www.maxuetang.cn/submit/7/9594/

https://blog.csdn.net/wei2019_/article/details/112756741

https://www.acwing.com/problem/content/761-769/

全局变量、局部变量的作用域

在函数外部定义的变量称作全局变量,在函数内部定义的称为局部变量。

全局变量作用域是从定义位置开始到文件结束,可在文件中位于定义变量后的所有函数中使用。

过多的使用全局变量,会增加调试难度和程序的通用性,代码移植过程中可能会出现重名的问题

全局变量在程序运行过程中一直占用内存单元,若没赋初值,则默认为 0。

局部变量作用域是在定义该变量的函数内部。函数的形参也是局部变量,函数执行完毕时,局部变量的空间就会被释放。

不同函数中的局部变量名可以相同。一个全局变量和局部变量的也是可以重名的,在相同作用域时局部变量有效时全局变量无效,即局部变量可以屏蔽全局变量。

在代码块中定义的变量的存在时间和作用将被限制在该代码块中,如for循环的i

局部变量的初始值是随机的,且受栈空间大小限制,大数组需要注意。全局变量随便。

结构体

  1. 结构类型和结构变量是两个不同的概念
  2. 结构类型中的成员名,可以与程序中的变量重名,它们代表不同给的对象,互不干扰
  3. 结构类型的数据项,既可以是基本数据类型,也可以是结构体类型
//typedef 类型名 别名
typedef long long ll;
struct student{
int name,age;
struct teacher{
int age;
}th;
}stu;
typedef struct student{//typdef为数据类型起一个别名
int name,age;
}stu;//此时stu是一个数据类型
//结构体指针
//指向结构变量的指针来访问结构变量的成员,与直接使用结构变量得效果一样
struct student{
int name,age;
student(int _name,int _age){
name=_name;age=_age;
}
student(){}
}*p;
p->name;p->age;
(*p).name,(*p).age

引用

引用可以看做是 C++ 封装过的指针,用来传递它所指向的对象。引用的基本原则是在声明时必须指向对象,以及对引用的一切操作都可以看成对原对象的操作。引用不是对象。

void swap(int &x,int &y){
int t=x;x=y;y=t;
}
swap(a,b);

上述代码中,我们在调用函数的时候需要改变实参的值,则需要采用 “传引用” 的方式

我们正在 int 后面加了一个 & ,表示对于 int引用。在调用函数 swap时,调用处的 ab 变量分别初始化了 swap 中两个对 int 类型的引用 xy ,可以理解为 xy 分别为 ab 的别名,即 swap 中对 xy 的操作就是对 ab 的操作 。

指针

https://www.bilibili.com/video/BV1JL4y1q7GR/?spm_id_from=333.788.recommend_more_video.0&vd_source=0c1df9946900f041b9d270fa6550854f

指针定义:类型说明符 *指针变量名,类型说明符表示指针所指向变量的类型,指针实际上是指向一块内存空间

指针赋值:int *p=&a; 代表将p指向a的地址

指针间接操作:要访问指针所指向的空间,需要对变量进行解引用,*p=20,即指向地址变量的值

指针的初始化:

int *p=NULL;
int *p=new int;
int *p=new int(1234);//向系统申请可一块 int 大小的空间,值初始化为 1234

指针的运算:

指针变量的内容是内存地址,+,-操作对应指针的移动,也称为指针的偏移,例:
int *p=&a[1];cout<<*(p+1);
这里的加1是广义的,并不是指向的地址加1,根据一个int 4个字节,其实是跳过”一个整数空间,到达下一个整数

无类型指针:

定义:void *p; p=&a; cout<<*(int*)p; p=&b; cout<<*(double*)p;
注:用的时候必须明确 p 指向空间的数据类型,需要强制类型转换

指针与数组:

数组在内存空间里是一段连续的地址,数组名就是该数组首地址,即 a=&a[0],所以 a 也是一种指针,

a+i=&a[i]
int a[5],*p; p=a; cout<<*(p+2);

指针也可以看成数组名,动态数组

int *a=new int[n+1]; (之后可以当成一个长度为n+1的数组用)
delete[] a;// 释放空间
cin>>a[i];a[i]+=a[i-1];cout<<a[i]

二维数组:

二维数组是数组的数组。而计算机内存可视为一个很长的一维数组。

连续:即二维数组的任意一行的末尾与下一行的起始,在物理地址上是相邻的。换句话说,整个二维数组可视为一个一维数组;反之,则二者在物理地址上不一定相邻。

对于连续的二维数组,只需要一层循环、一个不断递增指针可遍历数组中的所有元素。而对于不连续的,则需要不断访问每一行的首地址,再不断访问这一行的元素。注:存储方式可分为行优先存储和列优先存储。

指针与字符串:
字符指针:

char str[]="fuck"=char *str="fuck";printf("%s",str);

C++对字符串常量当成字符数组处理,字符串的名字对应首地址,指向了第一个字符的内存空间

字符串指针可做函数参数,用字符数组名或用指向字符的指针变量做参数是等价的,实参可以被改变

指针与函数:

调用函数时使用的参数,均以拷贝的形式传入。函数仅仅能通过返回值,将结果返回到调用处。但是如果某个函数希望修改外部的数据,或者某个结构体数据的量过于庞大,不宜进行拷贝,这时可以传入外部数据的地址,达到在函数内部访问或修改的目的。引用

指针可做为函数参数,传指针,而并非简单的赋值

void swap(int *x,int *y){int t=*x;*x=*y;*y=t;}
swap(&a,&b);

函数返回指针:

int *solve(int a,int b)

调用后可得到一个指向int类型的指针,注意()的优先级高于*,所以a先于()结合

函数指针:

指针也可以指向一个函数,函数名就是该函数的指针。返回类型,形参格式必须相同。

int (*hs)(int a,int b),hs=swap;之后hs可当成函数使用

结构体指针:
上面有
自引用结构:

struct Node{
int l,r;
Node *p;
}node;

在动态数据结构中有重要作用

递归栈

  1. 将调用程序的返回地址、相应的调用前的变量都保存在系统堆栈中;
  2. 执行被调用的函数;
  3. 若满足退出递归的条件,则退出递归,并从栈顶上弹回返回地址、取回保存起来的变量值,延着返回地址,向下执行程序
  4. 否则继续递归调用,只是递归调用的参数发生变化;增加一个量减少一个量,执行直到递归结束

自动类型转换

在混合运算中,会隐式的进行数据类型转换

自动类型转换遵循以下规则:

  1. 若参与运算的数据类型不同,则先转换为同一类型,然后进行运算
  2. 转换按数据长度增加的方向进行,以保证精度不降低,即当参加算术或比较的两个操作数类型不统一时,将简单类型向复杂类型转换,char -> int ->float -> double
  3. 在赋值运算中,赋值号两边的数据类型不相同时,将把右边表达式的类型转换为左边表达式的类型,易丢失数据
  4. 在赋值语句中,两边的数据类型一定是相兼容的,否则会报错

混合运算时的类型转换规则

char,short->int-->unsigned int -->long long -->double <-float

短箭头指必定会进行的转换,
长箭头指运算对象为不同数据类型时的转换方向,例如 int 与 unsigned 型做运算, int 型会转换成 unsigned 型

赋值时的类型转换规则

赋值时,若等号两边的数据类型不一样,则需要发生类型转换,遵循最终右边的数据类型与左边一致。

当左边的精度大于右边时,提升右边精度,不影响答案。

否则:

  1. int = double,截断小数点保留整数位。
  2. int = long long,截断二进制高位,保留低位。
  3. short = unsigned int,一般情况下,可以认为保留为 mod216 的结果
  4. int = unsigned int ,由于二进制位数相同,将会保留 mod232 意义下范围内的数
  5. unsignded int = int,将会保留 mod232 的结果

强制类型转换

只是临时转换

(类型名)变量, (类型名)(表达式)

字符串

用cin输入字符串时,空格和换行符都被认为是字符串的结束

两个字符串常量不能用加号连接,其中一个必须是字符串变量

string可以做到字符串间的 复制,连接,比较,倒置,获取长度,查找,输入,删除,截取

string有很多成员函数,默认比较为比较字典序,即不考虑长度

getline默认碰到换行符才结束,getline(cin,s)

注意,对于输入格式

3 5
abc deiide fef
string s;int a,b;cin>>a>>b;
getline(cin,s);
cout<<s;

s为空串,因为有换行符

  • size()
  • substr(开始位置,长度)
  • find(字符串):查找到就返回第一个出现S的位置,否则返回string::npos
  • replace(开始位置,长度,要换上的符串)
  • erase(开始位置,长度)
  • insert(开始位置,字符串)

二进制从低位开始,每四位对应十六进制的一位

灵活运用stringstream流,比如将一个浮点数转化为一个字符串,可以对各种类型进行相互转换

迭代器

在 STL 中 ,迭代器用来访问和检查 STL 容器中元素的对象,与指针类似但不等价。

支持两个运算符:自增 ++ 和 解引用 * ,自增实现迭代器的移动,解引用可以获取和修改容器中它指向的元素。

输入迭代器:只要求支持拷贝、自增、解引访问。

输入迭代器:只要求支持拷贝、自增、解引赋值。

向前迭代器:满足输入和输出迭代器的要求。

双向迭代器:在向前的基础上支持自减。

随机访问迭代器:在双向的基础上支持加减运算和比较运算(即随机访问)。

不同的 STL 容器的迭代器类型不同,指针是一种随机访问迭代器。迭代器的类型之间并不互斥,比如要求使用向前时也可以使用双向。

advance(it,n)it 向后移动 n 步,若 n 为负数,则向前移动,必须满足是双向迭代器,否则是未定义行为。

next(it) 获得向前迭代器 it 的后继(此时 it 不变),next(i,n) 获得 it 的第 n 个后继

prev(it) 获得双向迭代器 it 的前驱(此时 it 不变),prev(i,n) 获得 it 的第 n 个前驱

begin() :指向容器第一个元素的迭代器

rbegin():指向容器最后一个元素的迭代器

end():指向容器尾端的迭代器

序列式容器

序列式容器包括:vector,array,deque,list,forward_list

vector

内存连续,可变长度的数组,线性复杂度的插入和删除

支持动态分配内存

当我们不能提前开好足够大的空间时(比如处理 1~n 的约数),尽管我们知道数据总量在空间允许的级别,但是单份数据量可能非常大,这时候我们就需要 vector 把内存占用量控制在合适范围之内。vector 还支持动态扩容,内存紧张时可排上用处。

重载了比较运算符及赋值运算符

vector 重载了比较运算符,并以字典序为关键字。所以比较两个容器是否相等是可行的(复杂度与容器大小成线性关系),且我们可以拷贝容器。

初始化/构造函数

vector<int> v 大小为 0 的空vector,保证常数时间的复杂度

vector<int> v(N,i) 初始化为一个大小为 N 的容器,每个元素的值为 i 。特殊地将 i 去掉后每个元素的值为 0.,与 N 的大小成线性关系

vector<int> v{1,2,3} 列表初始化

vector<int> v(move(vv)) 移动 vv 到新创建的 v,不发生拷贝,常数复杂度

元素访问

front() 首元素的引用

back() 尾元素的引用

迭代器

begin() 指向首元素的迭代器

end() 指向数组尾端没有占位符的迭代器

rbegin() 指向尾元素的迭代器

与长度相关

size() 容器长度

empty() 判断容器是否为空

resize(n,m) 将元素个数调整为 n ,多退少补,补的元素初始化为 m

元素插删及修改

clear() 清空容器

insert(it,x) 支持在一个迭代器后面插入元素,复杂度与 pos 距末尾长度是线性的

erase(it) 支持删除某个迭代器,复杂度与 insert 一样

push_back(x) 在尾端插入元素,复杂度均摊为常数

pop_back() 删除末尾元素,常数

swap(v1,v2) 将两个容器进行交换,常数

deque

双端队列,支持线性复杂度的插入和删除,以及常数复杂度的随机访问。

初始化/构造函数

deque<int> v 空双端队列

deque<int> v(n,m) 设置初始大小为 n ,且每个值为m,线性复杂度

元素访问

front() 首元素的引用

back() 尾元素的引用

迭代器

与 vector 一致

与长度相关

与 vector 一致

元素插删及修改

clear() 清空所有元素

insert(it,x) 支持在一个迭代器后面插入元素,复杂度与 pos 距两端距离较小者长度是线性的

erase(it) 支持删除某个迭代器,复杂度与 insert 一样

push_front(x) 在头部插入一个元素,常数

pop_front() 删除头部元素,常数

push_back(x) 在末尾插入元素,常数

pop_back() 删除末尾元素,常数

swap(v1,v2) 将两个容器进行交换,常数

关联式容器:set,map,multiset,multimap

list

双向链表

初始化/构造函数

list<int> a;

int arr[5]={1,2,3};list<int> a(arr,arr+3) 从 arr 中的前 3 个元素作为链表的初始值

list<int>::iterator it 定义一个为 it 的迭代器(指针),是双向迭代器

成员函数

size() 返回链表的结点数量

begin() 链首指针

end() 链尾后一个的迭代器

insert(it,x) 在 it 前插入元素 x

erase(it) 删除迭代器 it

push_front(x) 在链首插入一个值为 x 的元素

push_back(x) 在链尾插入一个值为 x 的元素

pop_front() 删除链首元素

pop_back() 删除链尾元素

set

插入与删除

insert(x) 当set中不含有等价元素时,将 x 插入到 set 中

erase(x) 删除值为 x 的 所有 元素

erase(it) 删除迭代器为 it 的元素

erase(first,last) 删除迭代器在 [first,last) 范围内的所有元素

clear 清空 set

迭代器

begin() 指向首元素的迭代器

end() 指向集合尾端没有占位符的迭代器

rbegin() 指向尾元素的迭代器

查找操作

count(x) 返回 x 的元素个数

find(x) 若存在 x 则返回 x 的迭代器,否则返回 end()

lower_bound(x) 返回首个大于等于 x 的迭代器。否则返回 end()

upper_bound(x) 返回首个大于 x 的迭代器。否则返回 end()

empty() 判断容器是否非空

size() 返回容器的元素个数

  • 关于lower_bound和upper_bound()的时间复杂度

    如果用 set 自带的复杂度为 O(logn),用 <algorithm> 库的复杂度为 O(n)

  • 关于nth_element 的时间复杂度

    set 不自带,所以用 <algorithm> 里的复杂度为 O(n)

set 的应用

在贪心问题中,经常遇到类似 找出并删除最小的满足大于等于 x 的元素

map

map 是 有序键值对 的容器,元素键是唯一的。搜索,插入和删除具有 log 的复杂度

可以存储一些键值对(比如:<Alice,0><Bob,1>),但数组下标只能是非负整数。

map 重载了 operator,可以用任意定义了 operator< 的类型作为下标(key,索引 )

初始化

map<key,val> mp;,其中 key 是键值的类型,val 是值的类型

map 不会存在同一键值的元素,但 multimap 存在不同元素拥有同一键值

插入与删除

mp["Alice"]=233 通过下标访问来进行查询和插入操作

mp.insert(pair<string,int>("Bob",114514)) 通过向 map 里插入一个类型为 pair<key,val> 的值可达到插入元素的目的

erase(key) 删除键值为 key 的所有元素

erase(it) 删除迭代器为 it 的元素

erase(first,last) 删除迭代器在 [first,last) 范围内的所有元素

clear 清空 map

查询

count(x) 返回容器内键值为 x 的元素个数,复杂度为 O(log(size)+ans)

find(x) 若存在键值为 x 的元素,则返回该元素的迭代器,否则 end

lower_bound(x) 返回首个大于等于键值 x 的迭代器

upper_bound(x) 返回首个大于键值 x 的迭代器

empty() 返回容器是否为空

size() 返回容器内元素个数

map 应用

map 可用于存储复杂状态,在搜索中,我们常常需要存储一些复杂状态(坐标,无法离散化的数值,字符串)以及关于它们的答案。map 可以做法,其中 key 是状态,val 是答案

遍历容器

set<int> s;
typedef set<int>::iterator si
for(si it=s.begin();it!=s.end();it++){
cout<<*it<<'\n';
}
for(auto x:s){
cout<<x<<'\n';
}

需要注意的是,对 map 进行解引用的时候,得到的是类型为 pair<key,val> 的键值对

自定义比较方式

set 默认情况下比较函数为 < ,我们可以自定义 set 内部的比较方式,这时可以传入 自定义比较器

我们需要一个类,并在这个类中重载 ()

struct cmp{
bool operator()(int a,int b){return a>b;}
}
set<int,cmp> s;

容器适配器:stack,queue

stack

top() 访问栈顶元素(如果队栈为空,此处会出错)

push(x) 将 stack 中放入 x

pop() 删除栈顶元素

size() 查询容器中的元素个数

empty() 询问容器是否为空

这些成员函数均为 常数复杂度

queue

front() 访问队首元素(如果队列为空,此处会出错)

push(x) 将 x 放入队尾

pop() 弹出队首元素

size() 查询容器中的元素个数

empty() 询问容器是否为空

这些成员函数均为 常数复杂度

priority_queue

初始化/定义

priority_queue<TypeName> q 定义一个数据类型为 TypeName 的大根堆 q

priority_queue<TypeName,Container,Compare> q 使用 container 为底层容器,Compare 为比较类型

以下为 常数 复杂度

top() 访问堆顶元素(此时优先队列不能为空)

size() 查询容器中的元素个数

empty() 询问容器是否为空

以下为 log 复杂度

push(x) 将 stack 中放入 x

pop() 删除栈顶元素

bitset

bitset 是一个 01 串,每位占 1 bit ,可支持 单点 0/1 修改,位运算等操作。一个很好的用处是统计每个数是否出现过,类似一个桶。同时也可以获得两个集合是否有重复元素的信息。

初始化与声明

bitset<N> s 其中 N 为 s 的长度,默认全部为 0

string t="00110101";bitset<N> s(string("00110101")) 支持用同长度的 01 串进行赋值,这时把字符串当成了一个二进制数,赋值到bitset里对应的每一位上

qwq

由于 bitset 的字长放在 RAM 模型中,所以时间大部分为 O(nw) ,空间为 O(nw)

修改与运算

bitset 中的每个元素可以通过下标来访问,一个长度为 N 的bitset 编号范围为 [0,N)

单点修改:s[pos]=1 O(1)

左移/右移:s=s<<x;

与,或,异或:s=s1&s2

输入输出与转化

可以直接用 cin,cout 输入/输出一个bitset 的所有元素

cin>>s;//1101
cout<<s;//00001101

注意到,输入与初始化是类似的。

bitset 提供的转换函数:to_string(), to_ulong, to ullong(),即转换成 string,unsigned int,unsigned long long,注意当 bitset 的位数超过目标类型时,会导致 RE

成员函数

reset() 将 bitset 的所有元素置成 0,n/w

set()// 将所有元素赋值为 1, n/w
set(pos)//将 pos 位置的数赋值为 1, O(1)
set(pos,v)//将 pos 位置的数赋值为 v, O(1)

test(pos) 返回 pos 位置的值,O(1)

any() 返回整个bitset 是否存在 1, n/w

none() 返回整个 bitset 是否不存在 1 ,即全为 0, n/w

count(x) 返回 bitset 中值为 x 的元素个数,n/w

bitset& flip();
bitset& flip (size_t pos);//两种声明
filp()//将所有元素按位取反,n/w
filp(pos)//将pos按位取反, O(1)

技巧性函数

https://www.bilibili.com/video/BV1JL4y1h76U?spm_id_from=333.337.search-card.all.click&vd_source=0c1df9946900f041b9d270fa6550854f

注:所有的传参规则(首位置,末位置+1)

  • upper_bound,lower_bound,目标序列具有有序性

    lower_bound(a+begin,a+end,val)返回的是迭代器

    在数组中复杂度为 O(log) ,但在 set 的关联式容器将会退化成 O(n) ,直接用它们内部封装的 lower 和upper 就行了

  • deque 可以 sort ,sort 不单纯是快排,

  • partial_sort(first,middle,last)

    http://m.biancheng.net/view/7469.html

    将前 [first,last) 范围内最小/大的元素放在 [first,middle) 里,并按升序/降序排序

    对一个长为 N 的数组,排出前 M 小个来,比如

    partial_sort(a+begin,a+begin+mid,a+end)
    vector<int> a;
    D(i,10,1)a.pb(i);
    partial_sort(a.begin(),a.begin()+3,a.end());
    //partial_sort(...,...,...,greater<int>());
    for(auto it:a)cout<<it<<" ";
    1 2 3 10 9 8 7 6 5 4

    平均复杂度为 O(NlogM)

  • nth_element(first,nth,last)

    找到范围 [first,last) 内第 n 小的数 K,并把 K 放到第 n 个位置,保证 n 之前的数都比 K 小,之后的都比 K 大。

    nth_element(a+begin,a+begin+mid,a+end,cmp);

  • rbegin,back

    vector<int> a;
    F(i,1,10)a.pb(i);
    cout<<a.front()<<'\n';
    cout<<a.back()<<'\n';
    cout<<*a.begin()<<'\n';
    cout<<*a.rbegin()<<'\n';
    set<int> b;
    F(i,1,10)b.insert(i);
    cout<<*b.begin()<<'\n';
    cout<<*b.rbegin()<<'\n';
  • min, max

    min(a,min(b,c)) <=> min({a,b,c}) (C++11)
  • max_element, min_element

    查找最大值/最小值对应的地址

    int a[]={1,4,3,2};
    cout<<max_element(a,a+4)-a;
  • prev_permutation, next_permutation

    next 将当前排列更改为 下一个排列,返回 1;若当前排列已经是最后一个排列,返回 0,并将排列更改为 全排列中的第一个排列

    do{
    }while(next_permutation(a+1,a+n+1));
    next_permutation(a+begin,a+end)

    全遍历 O(n!),单次均摊 O(n)

  • __int128

    128位整数

    输入输出要手写快读快写(注意0的特判

    如果有些题中间过程爆了 long long ,可以强制转换成 __int128 处理

  • to_string

    将数字按位转化成字符串

  • emplace_back

    写了构造函数这样减小常数

    struct TSY{
    int age;
    TSY(int x){
    age=x;
    }
    };
    vector<TSY> v;
    v.emplace_back(21);
    v.push_back(TSY{21});
  • __gcd

  • __lg

    在二进制下数的最高位的位数,如 12=(1100),__lg(12)=3,由此可以计算二进制下的位数

    注意—__lg(0) 会出现问题!结果是未定义的

    O(1),挺快的

  • rotate

  • reverse

    写字符串常用 reverse(s.begin(),s.end());

  • __builtin_popcount()

    快,统计二进制下有多少个位为1

  • shuffle

    srand(time(NULL));
    random_shuffle(a+1,a+n+1);

    复杂度 O(n)

  • memset, fill

    memset是将某一块内存的内容全部设置为以指定的值按字节为单位 进行填充的

    所以用 fill

    memset(a,0x3f,sizeof(a)); ,1e9 左右

    fill(a,a+n+1,1)

  • iota

    [a,a+n1] 赋一段连续且递增的值

    iota(a,a+n,0)//0 1 2 3 4 5..

  • next, prev

    移动迭代器,大部分容器为 O(n)

    auto it=s.begin();
    auto newit=next(it,..)
  • isalpha, iaalnum, isdigit

    判断字符是否为 字母,字母或数字,数字

  • random 库

  • bit 库

  • bitset

  • pb_ds

STL排序函数的运算符必须满足严格弱序

https://oi-wiki.org/basic/stl-sort/

posted @   hs_wfz_orz  阅读(86)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示