文章目录
一、运算符tips
- c++中&&的优先级高于||
- c++中+的优先级高于三元表达式,a+b==c?1:0:此写法错误
二、lambda表达式
1.基本概念
- [捕获列表](参数列表)mutable(可选) 异常属性 -> 返回类型 { // 函数体 }
- auto 函数名=[捕获列表](参数列表)mutable(可选) 异常属性 -> 返回类型 { // 函数体 }
写法2调用:函数名(注入的实参列表)
常用格式:[捕获列表](参数列表) -> 返回类型 { // 函数体 }
关于捕获列表:捕获调用者上下文环境的需要访问的变量,可按值捕获或按引用捕获,其规则如下:
[]:什么也不捕获
[=]:捕获所有一切变量,都按值捕获
[&]:捕获所有一切变量,都按引用捕获
[=, &a]:捕获所有一切变量,除了a按引用捕获,其余都按值捕获
[&, =a]:捕获所有一切变量,除了a按值捕获,其余都按引用捕获
[a]:只按值捕获a
[&a]:只按引用捕获a
[a, &b]:按值捕获a,按引用捕获b
例子:内部排序,捕获列表为空,所以直接内部排序
sort(a.begin(),a.end(),[](vector<int>&v1,vector<int>&v2)->bool{
if(v1[0]==v2[0]) return v1[1]<v2[1];
return v1[0]<v2[0];
});//二维数组排序,先按第一个维度排序,再按第二个维度排序,v1,v2的比较实际上是a内数据的比较
2.值捕获vs引用捕获
拷贝捕获变量的值,在匿名函数被创建时就已经捕获了
#include <iostream>
using namespace std;
void learn_lambda_func_1() {
int value_1 = 1;
auto copy_value_1 = [value_1] {
return value_1;
};
value_1 = 100;
auto stored_value_1 = copy_value_1();
// 这时, stored_value_1 == 1, 而 value_1 == 100.
// 因为 copy_value_1 在创建时就保存了一份 value_1 的拷贝
cout << "value_1 = " << value_1 << endl;
cout << "stored_value_1 = " << stored_value_1 << endl;
}
int main()
{
learn_lambda_func_1();
return 0;
}
// 输出:
// value_1 = 100
// stored_value_1 = 1
类似于引用,即使捕获的值发生的变化在匿名函数创建后,匿名函数内的引用值也会发生变化
void learn_lambda_func_2() {
int value_2 = 1;
auto copy_value_2 = [&value_2] {
return value_2;
};
value_2 = 100;
auto stored_value_2 = copy_value_2();
// 这时, stored_value_2 == 100, value_1 == 100.
// 因为 copy_value_2 保存的是引用
cout << "value_2 = " << value_2 << endl;
cout << "stored_value_2 = " << stored_value_2 << endl;
}
三、lambda表达式+auto定义函数
lambda表达式:[捕获列表](参数列表) -> 返回类型 { 函数体 }
函数定义:
auto 函数名=[捕获列表](参数列表) -> 返回类型 { 函数体 }
四、内嵌函数function
目的:语法糖,内嵌子函数,子函数仅需读取而不做操作的参数可以不在子函数中定义,直接默认可以读取
function<void(TreeNode*)> DFS = [&] (TreeNode root*) {};
function<int(int,int)> DFS = [&] (int u, int p) {};
1.内嵌function写法
代码如下(示例):
class Solution {
public:
int maxOperations(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> memo(n, vector<int>(n));
for (auto& row : memo) {
ranges::fill(row, -1); // -1 表示没有计算过
}
//代码具体内容不重要,看一个格式
// 注意不能写成auto dfs=...,
// dfs函数需要在其定义中引用自身来进行递归调用。如果你试图直接用auto关键字来进行声明,
// 那么在Lambda表达式开始的时候,这个dfs还未定义完成,是不能引用它自己的。
function<int(int, int,int)> dfs = [&](int i, int j,int target) -> int {
if (i >= j) return 0;
int& res = memo[i][j]; // 注意这里是引用
if (res != -1) return res; // 之前计算过
res = 0;
if (nums[i] + nums[i + 1] == target) res = max(res, dfs(i + 2, j,target) + 1);
if (nums[j - 1] + nums[j] == target) res = max(res, dfs(i, j - 2,target) + 1);
if (nums[i] + nums[j] == target) res = max(res, dfs(i + 1, j - 1,target) + 1);
return res;
};
int res1 = dfs(2, n - 1, nums[0] + nums[1]); // 删除前两个数
int res2 = dfs(0, n - 3, nums[n - 2] + nums[n - 1]); // 删除后两个数
int res3 = dfs(1, n - 2, nums[0] + nums[n - 1]); // 删除第一个和最后一个数
return max({res1, res2, res3}) + 1; // 加上第一次操作
}
};
2.闭包写法,仅展示闭包,实际可以不这么写
题外话:在此处理解一下闭包,读取其他函数的局部变量,不用像函数内调用外部函数一样显式的传递值
代码如下(示例):
class Solution {
public:
int maxOperations(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> memo(n, vector<int>(n));
// 在这里,helper是一个Lambda函数,
// 它定义了另一个Lambda函数dfs作为其内部函数(在一些语言里,可能会被称为"闭包")。
// 这种结构使得dfs函数可以访问和修改helper函数定义的变量。
// 特别的,在这段代码中,dfs函数需要访问和修改memo数组
// 这个数组是在helper函数中定义的,因此必须把dfs函数定义在helper函数内部,这样才能访问到memo。
auto helper = [&](int i, int j, int target) -> int {
for (auto& row : memo) {
ranges::fill(row, -1); // -1 表示没有计算过
}
function<int(int, int)> dfs = [&](int i, int j) -> int {
if (i >= j) return 0;
int& res = memo[i][j]; // 注意这里是引用
if (memo[i][j] != -1) return memo[i][j]; // 之前计算过
res = 0;
if (nums[i] + nums[i + 1] == target) res = max(res, dfs(i + 2, j) + 1);
if (nums[j - 1] + nums[j] == target) res = max(res, dfs(i, j - 2) + 1);
if (nums[i] + nums[j] == target) res = max(res, dfs(i + 1, j - 1) + 1);
return res;
};
return dfs(i, j);
};
int res1 = helper(2, n - 1, nums[0] + nums[1]); // 删除前两个数
int res2 = helper(0, n - 3, nums[n - 2] + nums[n - 1]); // 删除后两个数
int res3 = helper(1, n - 2, nums[0] + nums[n - 1]); // 删除第一个和最后一个数
return max({res1, res2, res3}) + 1; // 加上第一次操作
}
};
3.再列一个函数的写法,该写法明显函数参数变多
代码如下(示例):
class Solution {
public:
int maxOperations(vector<int>& nums) {
int n=nums.size();
vector<vector<int>> dp(n, vector<int>(n, -1)); // 使用动态大小的二维数组
int ans=0;
ans=max(ans,1+dfs(dp,nums[0]+nums[1],2,n-1,nums));
ans=max(ans,1+dfs(dp,nums[0]+nums[n-1],1,n-2,nums));
ans=max(ans,1+dfs(dp,nums[n-2]+nums[n-1],0,n-3,nums));
return ans;
}
int dfs(vector<vector<int>>& dp,int target,int left,int right,vector<int>& nums){ // 修改函数参数
if(left>=right) return 0;
if(dp[left][right] != -1) return dp[left][right];
int ans=0;
if(nums[left]+nums[left+1]==target)
ans=max(ans,1+dfs(dp,target,left+2,right,nums));
if(nums[left]+nums[right]==target)
ans=max(ans,1+dfs(dp,target,left+1,right-1,nums));
if(nums[right]+nums[right-1]==target)
ans=max(ans,1+dfs(dp,target,left,right-2,nums));
dp[left][right] = ans; // 修改dp数组的设定
return ans;
}
};
五、值引用和引用传递
在C++中以值传递参数时,实际上是创建了实参的一个副本。这就意味着你在每次进行递归调用时都在复制整个数组。这导致了你的时间和空间复杂度大大增加
而引用传递参数,可以实现不复制整个数组,直接引用原数组,很大程度减少空间和时间复杂度
总结!在递归调用,只需要读取的参数最好也是用引用传递
六、make_pair()和pair()
● 作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型。
● C++标准程序库中凡是“必须返回两个值”的函数, 也都会利用pair对象。
当数据以make_pair/pair 绑定到了一起则想要访问不是[0][1],而是first和second
定义数据结构时:deque<pair<string,int>> haha;
运行普通函数:haha.push_front(make_pair(“123”,1));
七、数据插入:insert/push/empalce
insert/push是插入一个完全的对象,而emplace是先调用该对象的构造函数生成该对象,再将该对象插入vector中,使用emplace时,该对象类必须有相应的构造函数
struct A{
int a;
float b;
A(int _a,float _b): a{_a},b{_b} {};//使用emplace必须要有构造函数
//没有会产生报错
//no matching function for call to 'main()::A::A(int)'
A(int _a):a{_a},b{0.0} {};
};
A x1(1,1.1);
vector<A> v1;
v1.insert(v1.end(),x1);
v1.emplace(v1.begin(),2,2.2);
v1.emplace_back(3,3.3);
v1.emplace_back(4);
for(int i=0;i<4;i++){
cout<<v1[i].a<<" "<<v1[i].b<<endl;
}
比较push_back和emplace_back(写法很多给自己统一一下
插入vector数组
a.push_back({1,2,3});
a.emplace_back(vector<int>{1,2,3});
插入pair型数据
a.push/emplace_back(pair(1,1));
八、c++构造类
class LockingTree{
public:
//注意有些题目不需要通过构造去初始化,外部通过调用内部函数去给类传递数据(如下一示例)
LockingTree(可有外部系数可没有){//构造函数
this->a=a;
this->b=vector<int>9=(n,-1);
}
bool lock(){//内部函数
int y=b[i];
int x=a;
int a=x;//错误写法
a=x;//正确写法,int a是内部函数的局部变量,a才是整个构造数据类型的private变量
...}
private:
//属性并不一定要写在private里面,可以写在public或者protected里面
//在该类中定义的属性,内部都能直接访问
int a;
vector <int> b;//写明属性
//注意!!!数据不能直接在构造函数中初始化,一定要先写明
//以及!!!不能内部函数中再定义属性,而是应该直接使用属性
//以及!!!如果数据有自定义的数据结构,可以在public:或者private:定义数据结构
//eg:
struct Node{
int c,d;
}
vector<Node> e;
}
eg:
class aa{
public:
aa(){}
int next(int p){
...;
return xxx;
}
private:
stack<int>q;
stack<int>p;
};
// 调用
aa a=new aa();
aa.next(100);
九、c++构造体
- 结构体自带初始化
- 运算符重载(指定运算符进行操作)
struct Node{
int cnt,time;
//自带初始化
Node(int_cnt,int_time):cnt(_cnt),time(_time){}
//运算符重载
bool operator < (const Node&rhs) const{
return cnt==rhs.cnt?time<rhs.time:cnt<rhs.cnt;
}
}
- 结构体中带可变长数组
struct Node {
vector<int> nums;
int num;
Node() = default;
Node(const Node&) = default;
Node& operator = (const Node&) = default;
Node(vector<int> _nums, int _num) : nums(_nums), num(_num) {} // 修改构造函数
};
初始化
创建对象
node cache=node(1,time);
创建指针
node*pnode=new node(1,time);
创建引用
attention:c++中,引用一旦被初始化为某个对象的引用,就无法改变为引用另一个对象
node cache=node(1,time);
node&c=cache;//绑定引用
attention
对象和指针获取值的方式相同,直接用.
引用获取值的方式为->
运算符重载
sets;
s.begin();//s.begin()是以上规则中的min值
因为s为set类型数据集合,set自动进行排序,这个写法相当于重置set的排序规则
unordered_map插入自定义结构体小tips
方法1:调用pair函数
- make_pair会返回一个pair对象,里面使用了pair的构造函数初始化值。
- pair内部的两个元素会使用默认构造函数构造,而不是复制构造。
- insert方法接受pair作为参数,实际上是通过二进制拷贝pair到内部实例,不涉及对象的复制构造。
Node cache=Node(1,time);
unordered_map<int, Node> m;
key_table.insert(make_pair(1,cache));
方法2:实现构造函数和拷贝构造函数
struct Node {
// ...
Node() = default;
Node(const Node&) = default;
Node& operator=(const Node&) = default;
};
unordered_map<int, Node> m;
Node cache=Node(1,time)/new Node(1,time);;
m[1]=cache;
十、memset函数
函数原型
void memset(voidstr,int c,size_t n)
- 解释:复制字符c(一个无符号字符 到参数str所指向的字符串的前n个字符
- 作用:是在一段内存块中填充某个给定的值,对较大的结构体或数组进行清零操作的较快方法
- 头文件:c++
memset赋值为按字节赋值,将参数转化为二进制之后按字节填入
eg:memset(a,100,sizeof a)给int类型的数组a赋值,会将100转化为二进制,01100100,因为int有4个字节,则会扩充为01100100 01100100 01100100 01100100 赋值给数组
因此,memset的赋值最好赋值为0/-1
结论:
- 数组类型为char型,value可为任意字符值
- 非char型,如int型,赋值为-1/0
十一、分割字符串
方法1,两层while循环
int m=s.size();
int wordStart=0;//单词的起点索引
int wordEnd=0;//单词的终点索引(边界或指向空格)不包含
string word;
vector<string> resul;
while(wordStart<m){
while(wordEnd<m&&s[wordEnd]!=' ')wordEnd++;
word=s.substr(wordStart,wordEnd-wordStart);//截取单词
resul.push_back(word);
// 更新单词区间,起点为当前终点的下一个位置;终点初始与起点相同
wordStart = wordEnd + 1;
wordEnd = wordStart;
}
方法2,匿名函数
auto split = [](const string& s, char delim) -> vector<string> {
vector<string> ans;
string cur;
for (char ch: s) {
if (ch == delim) {
ans.push_back(move(cur));
cur.clear();
}
else {
cur += ch;
}
}
ans.push_back(move(cur));
//move() 将对象的状态或者所有权从一个对象转移到另一个对象
//只是转移,没有内存的搬迁或者内存拷贝,所以可以提高效率,改善性能。
return ans;
};
vector<string> result=split(path,'/');
//将字符串数组path以/分割为多个子字符串
十二、翻转链表
定义三个辅助节点,一个指向当前需要进行指向翻转的节点(cur),一个指向cur的前一个节点(pre),一个指向cur的后一个节点(nextt)
listnode* reverse(listnode*head){
listnode*pre=nullptr,*cur=head;
while(cur){
listnode*nextt=cur->next;
cur->next=pre;
pre=cur;
cur=nextt;
}
return pre;//遍历到后面cur指向空节点,pre指向原链表尾节点,即新链表头节点
}
十三、最大最小值
INT_MIN:-2147483648(-2^31)
INT_MAX:2147483647(2^31-1)
LONG_MIN:-2147483648(-2^31)
LONG_MAX:2147483647(2^31-1)
LLONG_MIN:-9223372036854775808(-2^63)
LLONG_MAX:9223372036854775807(2^63-1)
十四、仿函数/函数对象 function objects
定义:仿函数是一个类或者结构体,重载了函数调用运算符operator(),使得对象可以像函数一样被调用
// 可以加模板,加模板后它可以比较int类型,也可以比较double类型
class Greater
{
public:
bool operator()(const int& x, const int& y)
{
return x > y;
}
};
int main()
{
Greater com;//com是一个对象
//情况1它可以像函数一样去使用
cout << com(10, 100) << endl; // false->0
vector<int> v = { 1,9,3,6,7,5,8,2 };
//默认情况下是升序
sort(v.begin(), v.end(), com);//降序
//情况2也可以传匿名对象
//注意传的对象是形参,()内是形参
priority_queue<int, vector<int>, Greater> pq;
//<>内是模板参数,直接取类名
return 0;
}
十五、const type& 和auto比较(遍历容器元素/接收函数参数优先选择const type&
使用 const type& 而不是 auto 来1,接收函数参数或2,遍历容器元素,在处理大型对象(如字符串、容器等)时,性能会有显著提升
● const type&:传递对象的引用,而非对象本身
● auto:默认对象拷贝
注意:如果auto不是很适合具象化成某一个类,可以直接const auto&
eg:
unordered_map<char,unordered_set> mapp;
遍历容器元素:for (const auto& [pre_a, set_a] : mapp)
总结
写leetcode题目碰到的需要了解的c++基本知识,一直更新