仅为个人经验总结,不具备官方权威,欢迎各路大佬批评指正,给予建议。
对于一份逻辑重复,冗长杂乱的代码,想必大家都不喜欢读。有时候,即使是自己写的,过了一段时间后都不愿意再重新看一遍。尝试去重新理解一份又臭又长的代码,简直就是啃那啥山。本文会介绍一点点
C++11语法的特性,纯属笔者之愚见,作为各位看官的它山之石,旨在分享好玩的C++编码小技巧。
| 编号 | 内容 |
|---|---|
| 1 | auto 自动推导 |
| 2 | max, min 小技巧 |
| 3 | lambda 函数 |
| 4 | 容器初始化 |
| 5 | 可变参数模板 |
| 6 | tuple 元组 |
| 7 | typedef 关键词 |
auto 用于遍历容器内元素当我们只关心遍历一个容器内的所有元素时,我们可以使用 auto 关键词来简化声明,达到精简代码的作用。
现在有自定义坐标类型 (Mark)
struct Mark{
int x,y;
Mark(){}
Mark(int x,int y):x(x),y(y){}
};对于一个装有 Mark 的 vector 容器,如果我们需要求这个点集的重心,我们需要先求坐标的累加。
普通的写法为:
Mark sum(vector<Mark>&points){
int n=points.size();
Mark ans(0,0);
for(int i=0;i<n;i++){
ans.x+=points[i].x;
ans.y+=points[i].y;
}
return ans;
}如果使用 auto 自动推导容器的数据结构类型,我们可以免去声明,也不用考虑下标以及边界问题,auto 还会自动调用容器的迭代器,以此来简化我们的书写,不可谓不方便。简化代码如下:
Mark sum(vector<Mark>&points){
Mark ans(0,0);
for(auto point:points){
ans.x+=point.x;
ans.y+=point.y;
}
return ans;
}更进一步,auto 其实只是作用于取值,没有取址,所以如果想要优化变量声明的空间大小,或者说,有需求修改原始数据,我们仅仅需要 auto auto & 即可:
Mark sum(vector<Mark>&points){
Mark ans(0,0);
for(auto&point:points){
ans.x+=point.x;
ans.y+=point.y;
}
return ans;
}又或者,使变量完全匿名化:
Mark sum(vector<Mark>&points){
Mark ans(0,0);
for(auto&[x,y]:points){
ans.x+=x;
ans.y+=y;
}
return ans;
}max and min 连续取极值这个想必大家都遇到过了,但是据不完全统计,会对于连续取极值进行代码书写优化的人,少之又少,这里提供一种让你原地起飞的奇技淫巧。
求 a,b,c,d 的最小值,最大同理。
家常版本:
int getMin(int a,int b,int c,int d){
return min(min(a,b),min(c,d));
}我们只需要在普通的 min() 函数内嵌套一个花括号即可,直接扩展成了多元素最小值。
int getMin(int a,int b,int c,int d){
return min({a,b,c,d});
}lambda 函数基于 auto 关键词,可以将简短的代码直接插入到代码块中间,增加代码的可读性和连贯性。
家常版本,一种常见的思路为重载自定义数据类型的 < 函数,当然是因为 C++ 各种排序都是优先基于从小到大写的啦,sort, map, set 啥啥啥的。
依然使用上面的例子,假如我们需要对点集进行排序,以 x 为第一关键字,y 为第二关键字。
struct Mark{
int x,y;
Mark(){}
Mark(int x,int y):x(x),y(y){}
bool operator<(const Mark&other)const{
if(x==other.x) return y<other.y;
return x<other.x;
}
};于此,可以调用 STL - sort() 进行排序
vector<Mark> MarkSort(vector<Mark>&points){
sort(begin(points),end(points));
//sort(points.begin(),points.end());//两种写法均可
}如果,我们使用上面提到的技巧。我们则不需要修改原先的 Mark 类型的申明。
struct Mark{
int x,y;
Mark(){}
Mark(int x,int y):x(x),y(y){}
// 这里不用重载了!
};引入 lambda 表达式
vector<Mark> MarkSort(vector<Mark>&points){
sort(begin(points),end(points)auto[](auto&a,auto&b){
if(a.x==b.x)return a.y<b.y;
return a.x<b.x;
});
}在一些预处理时,我们常常需要做一些重复的判断,我们可以用尽量少的代码来减少不必要的重复。
假设我们需要处理出字符串中的所有的非元音的字母,可能掺杂其他字符,我们也需要过滤掉。
家常版本:
string notVowel(string s){
string ans;
string vowels="aeiouAEIOU";
for(auto&c:s){
if(vowels.find(c)!=vowels.end() && isalpha(c)){
ans+=c;
}
}
return ans;
}如果我们觉得 if 判断中的条件太多了,或者说对于以上这种判断,我们需要重复调用几次的话,我们可以考虑封装一个 lambda 函数。这样我们减少了编码的耦合,易于调试和阅读。
string notVowel(string s){
string ans;
string vowels="aeiouAEIOU";
auto check=[&](char&c){
return vowels.find(c)!=vowels.end() && isalpha(c);
};
for(auto&c:s){
if(check(c)){
ans+=c;
}
}
return ans;
}
[]是取值申明,&是取址,允许函数内访问外部的内存申明。
经典的 N 皇后问题 为例。
解题思路
按行搜索,所以只需要考虑两个对角线方向和每列的状态,因为 N 不会太大(本问题是一个NPC问题)所以可以考虑使用位运算优化空间。
lambda 函数在进行自调用时,需要申明自己的地址进行传参。如下
auto&&dfs
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>>ans;
vector<string>board(n,string(n,'.'));
auto dfs=[&](auto&&dfs,int a,int b,int c,int k){
if(k==n){ans.push_back(board);return;}
for(int i=~(a|b|c)&((1<<n)-1);i;){
int p=i&-i;
board[k][log2(p)]='Q';
dfs(dfs,(a|p)<<1,(b|p)>>1,c|p,k+1);
board[k][log2(p)]='.';
i-=p;
}
};
dfs(dfs,0,0,0,0);
return ans;
}
};在引入 C++11 之前,只有数组能使用初始化列表,如果其他容器需要使用初始化列表需要借助以下方法。
int array[3]={3,5,7};
vector<int>v(array,array+3);现在,我们可以直接使用花括号来声明。
int array[3]{3,5,7};
vector<int>v{3,5,7};拓展到其他容器也是类似的,比如构造一个阿拉伯数字到罗马数字字符的映射:
map<int,char> map{
{1,'I'},
{5,'V'},
{10,'X'},
{50,'L'},
{100,'C'},
{500,'D'},
{1000,'M'}
}可变参数模板和普通模板的语义是一样的,只是写法上稍有区别。
声明可变参数模板时需要在
typename或者class之后加上...
template<typename... Types>一个日常应用是优化我们对于输出的书写,类比 Python - print() 的效果
void print(){}
template<typename T,typename... Types>
void print(const T& first, const Types&... args){
cout << first << " ";
print(args...);
}
int main(){
print(acos(-1.0),"Hello Wolrd",INT_MAX,pow(2.05,10));
return 0;
}tuple 元组实例化的对象可以存储任意数量、任意类型的数据。tuple 本质是一个以可变模板参数定义的类模板,它定义在头文件并位于 std 命名空间中。
其实,依我看来,相当于是 pair 容器的扩展。
接上文,使用 print(),我们声明-构造-获取-打印一个自定义 tuple 对象,呜呜呜,单身狗莫名想哭……
void tupleAPI(){
tuple<int,string,float> obj;
obj = make_tuple(25,"Alice",1.67);
auto & [age,name,height] = obj; // 也可以使用 tie(age,name,height) = obj; 不过需要提前声明变量
print(age,name,height);
}顺带一提的是 tuple 也是可以直接作为多维偏序排序的工具呢!
void tupleSort(){
tuple<int,string,float> obj;
obj = make_tuple(25,"Alice",1.67);
vector<tuple<int,string,float>>a;
a.push_back(make_tuple(25,"Alice",1.67));
a.push_back(make_tuple(25,"Alice",1.57));
a.push_back(make_tuple(25,"Alice",1.70));
a.push_back(make_tuple(25,"Bob",1.66));
a.push_back(make_tuple(25,"Bob",1.65));
a.push_back(make_tuple(25,"Bob",1.81));
a.push_back(make_tuple(18,"Tom",1.75));
a.push_back(make_tuple(10,"Amy",1.50));
sort(a.begin(),a.end());
for(auto&[age,name,height]:a){
print(age,name,height);
cout<<endl;
}
}也可以直接调用 vector 的 emplace_back 方法,直接使用元组的 (相应容器/数据类型的) 构造器
void tupleSort(){
tuple<int,string,float> obj;
obj = make_tuple(25,"Alice",1.67);
vector<tuple<int,string,float>>a;
a.emplace_back(25,"Alice",1.67);
a.emplace_back(25,"Alice",1.57);
a.emplace_back(25,"Alice",1.70);
a.emplace_back(25,"Bob",1.66);
a.emplace_back(25,"Bob",1.65);
a.emplace_back(25,"Bob",1.81);
a.emplace_back(18,"Tom",1.75);
a.emplace_back(10,"Amy",1.50);
sort(a.begin(),a.end());
for(auto&[age,name,height]:a){
print(age,name,height);
cout<<endl;
}
}测试结果如下:
10 Amy 1.5
18 Tom 1.75
25 Alice 1.57
25 Alice 1.67
25 Alice 1.7
25 Bob 1.65
25 Bob 1.66
25 Bob 1.81 typedef 关键词在大量使用某种自定义类型数据时,可以使用 typedef 来重命名。注意 typedef 并不创建新的类型。它仅仅为现有类型添加一个同义字。另一方面, typedef 还用来解决兼容不同平台的同类数据的处理,比如:对于实数类型,不同环境下可以提供可靠的精度不同
实数类型声明:
// 精度逐渐递减,通过声明不同类型兼容不同情况,而不需要修改已经完成的代码
typedef long double Real;
typedef double Real;
typedef float Real;简化声明举例:结合 (1) & (6) 拓展到空间向量求和,使用 typedef 简化代码
typedef double Real; // 一般 double 精度就够了,很少有卡精度卡到 long double 的
typedef tuple<Real,Real,Real> Mark;
Mark sum(vector<Mark>&points){
Mark P(0,0,0);
for(auto&[x,y,z]:points){
get<0>(P)+=x;
get<1>(P)+=y;
get<2>(P)+=z;
}
return P;
}佛系更新🤣