C++常用函数&技巧总结(持续更新中……)

此文章用来总结记录自己在基于C++的刷题、开发过程中,使用过的技巧、常用函数等。
我会不断更新的!

语法相关

1. 闭包 closure

闭包是C++ 11开始支持的。可以不通过传参获取调用者的上下文环境,在函数中定义函数等。在LeetCode中,对sort函数中cmp函数的定义、递归函数的定义等来说很常用。

// 常用的两种形式

// 作为函数的参数,直接定义
// 常用于sort函数中cmp函数的定义
sort(v.begin(), v.end(), [](int a, int b) {
    
    
	return a < b;
});

// 在函数中定义函数
// 常用于递归函数的定义
void a(){
    
    
	vector<int> array;
	function<bool(int)> fun = [&](int i)
    {
    
    
	    if (array[i] > 0)
	        return array[i] == 2;
	};
	return;
}

可以看到,在第二个例子中,fun函数为bool型函数,参数为int型。fun函数没有传递array数组参数,却可以直接调用上下文的array数组。
其中,中括号[]为捕获列表,常见两种的含义是:
[]:什么也不捕获
[&]:捕获所有一切变量,都按引用捕获
即是捕获列表在传递上下文参数时起作用。

2. 负整数的整除与取余运算

C++除法的取整采用向零取整:向0方向取最接近精确值的整数,换言之就是舍去小数部分,因此又称截断取整。
在这种情况下,5 / 3 = 1, -5 / -3 = 1, -5 / 3 = -1, 5 / -3 = -1。

3. 动态分配与删除内存

new和delete运算符是用于动态分配和撤销内存的运算符。
使用new运算符时必须已知数据类型,new运算符会向系统堆区申请足够的存储空间,如果申请成功,就返回该内存块的首地址(指针),如果申请不成功,则返回零值。
对于数组进行动态分配的格式为:

指针变量名=new 类型名[下标表达式];
delete [ ] 指向该数组的指针变量名;

两式中的方括号是非常重要的,两者必须配对使用,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的指针,会产生回收不彻底的问题(只回收了第一个元素所占空间),加了方括号后就转化为指向数组的指针,回收整个数组。
delete []的方括号中不需要填数组元素数,系统自知。即使写了,编译器也忽略。

int *a = new int;
delete a; //释放单个int的空间

int *a = new int[5];
delete []a; //释放int数组空间

ListNode* a = new ListNode(); //默认构造函数
delete a; //释放单个结构体的空间

4. 整型溢出问题

类型名称 字节数 取值范围
signed char 1 -128~+127
short int 2 -32768~+32767
int 4 -2147483648~+2147483647
long int 4 -2147483648~+2141483647
long long long int 8 -9223372036854775808~+9223372036854775807

其中最常见的是int型溢出问题,在代码中可以通过技巧处理回避一些不必要的整型溢出:

// 取两数中间值
int mid = left + (right - left) / 2;

// 比较多数和与目标值的大小
if (a + b > target - c - d) ;

数据结构相关

1. 初始化

vector的初始化

// 一维数组的初始化
vector<int> v(n);     // 使用n个0来初始化,类似于v.resize(n)

vector<int> v(n, m);  // 使用n个m来初始化,类似于v.resize(n, m)

vector<int> v(v0);    // 使用另外一个数组v0来初始化v

// 二维数组的初始化

vector<vector<int>> v;
v.resize(n, v0);      // 使用n个一维数组v0来初始化

vector<vector<int>> v(3, vector<int>(4, 1));  // 直接使用vector(n, m)来表示v0

2. 添加元素 emplace_back

该函数是C++ 11新增加的,其功能和 push_back() 相同,都是在vector容器的尾部添加一个元素。
相比于push_back(),emplace_back()在实现上减少一次拷贝和赋值操作
注:push_back()还可以在string等容器的尾部添加元素,而emplace_back()只应用于vector容器。

vector<int> v;
v.emplace_back(1);

3. 插入元素 insert

insert(p, t);

在迭代器p之前插入值为t的元素

4. 区间赋值 fill

fill(array.begin(), array.end(), val);

将一个区间的元素都赋予val值。
不能用resize在已初始化的情况下进行赋值:resize并不会对原vector已经存在的元素进行重新初始化

算法相关

1. 求和 accumulate

int accumulate (首地址, 末地址, 累加的初值);
注:数组末地址是最后一个元素地址的下一个。

#include <numeric>

int sum = accumulate(array.begin(), array.end(), 0);

2. 排序 sort

void sort (首地址, 末地址, [排序方法]);
排序方法缺省时,默认的排序方法采用从小到大排序

#include <algorithm>

// 排序方法缺省
int a[] = {
    
    45, 12, 34, 77, 90, 11, 2, 4, 5, 55};
sort(a, a+10); // 从小到大排序

// 排序方法不缺省
bool cmp(int a,int b){
    
    
	return a>b;
}
int a[] = {
    
    45,12,34,77,90,11,2,4,5,55};
sort(a, a+10, cmp); // 从大到小排序

// 排序方法可以采用多层逻辑,也可以结合数据结构,灵活定义
typedef struct student{
    
    
	char name[20];
	int math;
	int english;
}Student;
bool cmp(Student a, Student b){
    
    
	if(a.math > b.math )
		return a.math < b.math ; // 按math从小到大排序 
	else if(a.math == b.math )
		return a.english > b.english; // math相等,按endlish从大到小排序
}
Student a[4] = {
    
    {
    
    "zhangsan", 67, 89}, {
    
    "lisi", 90, 56},{
    
    "wangwu", 90, 99}};
sort(a, a+3, cmp); // 先按math从小到大排序,math相等,按english从大到小排序 

3. 全排列 next_permutation

bool next_permutation(首地址, 末地址);
函数按照字典序产生排列,并且是从数组中当前的字典序开始依次增大直至到最大字典序。

#include <algorithm>
int a[3] = {
    
    1, 0, 2};
do
{
    
    
	for(int i = 0; i < n; i++)
		printf("%d ", a[i]);
	printf("\n");
}
while(next_permutation(a,a+n));

输出为:
1 0 2
1 2 0
2 0 1
2 1 0

4. 二分查找 lower_bound,upper_bound

lower_bound(起始地址,结束地址,要查找的数值)
upper_bound(起始地址,结束地址,要查找的数值)

在保证数组非降序排列下,
函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回 大于或等于 val的第一个元素位置。如果所有元素都小于val,则返回last的位置。(last的位置是越界的)
函数upper_bound()返回的在前闭后开区间查找的关键字的上界,返回 大于 val的第一个元素位置

5. 求最大值或最小值 *max_element,*min_element

对于vector容器

int maxValue = *max_element(v.begin(),v.end()); // 最大值
int minValue = *min_element(v.begin(),v.end()); // 最小值

对于普通数组

int maxValue = *max_element(a,a+6); // 最大值
int minValue = *min_element(a,a+6); // 最小值

6. 反转字符串strrev, reverse

使用string.h中的strrev函数

#include <string.h>

char s[]="hello";
strrev(s);

strrev函数只对字符数组有效,对string类型是无效的。

使用algorithm中的reverse函数

#include <algorithm>

string s = "hello";
reverse(s.begin(),s.end());

reverse函数也可以反转vector的元素。

7. 截取字符串子串 substr

string res = s.substr(1,4); // 从s[1]开始截取4个字符,即s[1]到s[4]

8. 生成随机数 rand

rand()不需要参数,返回一个从0到最大随机数的任意整数,最大随机数的大小通常是固定的一个大整数。
一般用法:

int num_99 = rand() % 100; // 生成0~99之间的随机整数
int num_ab = rand() % (b - a + 1) + a; // 生成a~b之间的随机整数 

若要产生0到1之间的小数,则可以先取得0到10的整数,再除以10即可得到十分位的随机小数。

刷题经验

1. 数组

多尝试双指针&滑动窗口,减小时间复杂度;
数组填充类的问题,可以先预先给数组扩容带填充后的大小,然后再用从后向前的双指针进行操作,这样时间空间复杂度都可以降到最低。

2. 链表

双指针,在单链表倒序等常用;
设置虚拟头节点dummyHead。

3. 哈希表

数据较少时用数组,较多时合理运用set和map,unordered_set和unordered_map;
常用于寻找集合中是否存在的题型(两数相加、四数相加等),无重复情况时用set,需要重复情况时用map统计出现次数等;
推荐:哈希表总结篇 - 代码随想录

4. 字符串

很多字符串问题都可以看作数组问题来解决,尤其是数组填充类问题
反转字符串问题:先局部再整体:剑指Offer58-II.左旋转字符串 & 先整体再局部:151.翻转字符串里的单词
看过的讲的最好的KMP算法理解:如何更好地理解和掌握 KMP 算法? - 阮行止的回答 - 知乎

5. 栈与队列

用双栈实现队列(一进栈,一出栈),用单队列实现栈(队列内循环);
优先级队列priority_queue<int>, 默认大顶堆;双端队列dequeue;
自定义队列&自定义优先队列规则:滑动窗口最大值前 K 个高频元素

猜你喜欢

转载自blog.csdn.net/qq_43734019/article/details/119413748