左值右值和移动语义、论文阅读和LeetCode刷题

This commit is contained in:
Estom
2021-07-23 22:48:52 +08:00
parent e9b49593a3
commit c93cfedaf0
20 changed files with 1022 additions and 43 deletions

View File

@@ -1,12 +1,7 @@
# 拷贝控制
> 目录
> * 拷贝、赋值与销毁
> * 拷贝控制和资源管理
> * 交换操作
> * 拷贝控制实例
> * 动态内存管理类
> * 对象移动
> 参考文献
> * [https://www.cnblogs.com/sunchaothu/p/11392116.html](https://www.cnblogs.com/sunchaothu/p/11392116.html)
> 类的特殊函数
> * 初始化——构造函数
@@ -14,10 +9,20 @@
> * 移动——移动构造函数
> * 赋值——拷贝赋值运算符、移动赋值运算符
> * 销毁——析构函数
```
默认构造(无参)   T()
拷贝构造       T(const T& )
移动构造       T()
拷贝赋值       T& operator=(T& )
移动赋值       T& operator=(T&& )
析构         ~T()
```
## 1 构造函数
> 与类同名的,没有返回值的函数,用来创建、赋值、移动、销毁该类的对象。
> 与类同名的,没有返回值的函数,用来创建、拷贝、移动、销毁该类的对象。
## 1.1 合成构造函数
编译器自动生成的一系列构造函数。包括以下几种
@@ -29,11 +34,11 @@
* 合成析构函数
* 系统自动生成的析构函数。
## 1.2 默认构造函数
## 1.2 默认构造函数和普通构造函数
* 默认构造函数是无参构造函数
* 普通构造函数是一系列有参数的构造函数。
无参构造函数。
## 1.3 拷贝构造函数
## 1.3 拷贝构造函数和拷贝赋值运算符
唯一参数是当前类类型或者当前类型的const引用。
@@ -57,15 +62,88 @@ string nies = string("efji");
* 从一个返回类型为费引用类型的函数返回一个对象
* 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
### 赋值运算符(拷贝运算符)
### 赋值运算符(拷贝赋值运算符)
> 普通赋值的时候,会调用重载的赋值运算符。
* 编译器会自动生成合成拷贝赋值运算符
* 需要重载赋值运算符。
## 1.4 移动构造函数
## 1.4 移动构造函数和移动赋值运算符
* 在面向对象中,有的类是可以拷贝的,例如车、房等他们的属性是可以复制的,可以调用拷贝构造函数,有点类的对象则是独一无二的,或者类的资源是独一无二的,比如 IO 、 std::unique_ptr等他们不可以复制但是可以把资源交出所有权给新的对象称为可以移动的。
* C++11最重要的一个改进之一就是引入了move语义这样在一些对象的构造时可以获取到已有的资源如内存而不需要通过拷贝申请新的内存这样移动而非拷贝将会大幅度提升性能。例如有些右值即将消亡析构这个时候我们用移动构造函数可以接管他们的资源。
```
#include <iostream>
#include <cstring>
using namespace std;
class A{
public:
//默认构造函数
A():i(new int[500]){
cout<<"class A construct!"<<endl;
}
//拷贝构造函数
A(const A &a):i(new int[500]){
memcpy(i, a.i,500*sizeof(int));
cout<<"class A copy!"<<endl;
}
//拷贝赋值运算符
A &operator =(A &rhs) noexcept{
// check self assignment
if(this != &rhs){
delete []i;
i = rhs.i;
}
cout<< "class A copy and assignment"<<std::endl;
return *this;
}
//移动构造函数
A(A &&a)noexcept:i(a.i)
{
a.i = nullptr;
cout<< "class A move"<<endl;
}
//移动赋值运算符
A &operator =(A &&rhs) noexcept{
// check self assignment
if(this != &rhs){
delete []i;
i = rhs.i;
rhs.i = nullptr;
}
cout<< "class A move and assignment"<<std::endl;
return *this;
}
//析构函数
~A(){
delete []i;
cout<<"class A destruct!"<<endl;
}
private:
int *i;
};
A get_A_value(){
return A();
}
void pass_A_by_value(A a){
}
int main(){
A a = get_A_value();
return 0;
}
```
* 在移动构造函数中我们做了什么呢我们只是获取了被移动对象的资源这里是内存的所有权同时把被移动对象的成员指针置为空以避免移动过来的内存被析构这个过程中没有新内存的申请和分配在大量对象的系统中移动构造相对与拷贝构造可以显著提高性能这里noexcept告诉编译器这里不会抛出异常从而让编译器省一些操作(这个也是保证了STL容器在重新分配内存的时候知道是noexpect而使用移动构造而不是拷贝构造函数),通常移动构造都不会抛出异常的。
注意事项:
* 偷梁换柱直接“浅拷贝”右值引用的对象的成员;
* 需要把原先右值引用的指针成员置为 nullptr,以避免右值在析构的时候把我们浅拷贝的资源给释放了;
* 移动构造函数需要先检查一下是否是自赋值然后才能先delet自己的成员内存再浅拷贝右值的成员始终记住第2条。
## 1.5 委托构造函数
@@ -110,4 +188,32 @@ class Foo{
### 析构函数必须是虚函数
1. 删除动态运行时的具体对象。
2. 普通对象如果不被继承,析构函数可以不使用虚函数。避免生成虚函数表和虚函数指针,浪费内存空间。
2. 普通对象如果不被继承,析构函数可以不使用虚函数。避免生成虚函数表和虚函数指针,浪费内存空间。
## 4 三五法则
在较新的 C++11 标准中为了支持移动语义又增加了移动构造函数和移动赋值运算符这样共有五个特殊的成员函数所以又称为“C++五法则”;也就是说,“三法则”是针对较旧的 C++89 标准说的,“五法则”是针对较新的 C++11 标准说的为了统一称呼后来人们干把它叫做“C++ 三/五法则”;
1. 需要析构函数的类也需要拷贝构造函数和拷贝赋值函数。
2. 需要拷贝操作的类也需要赋值操作,反之亦然。
3. 析构函数不能是删除的
4. 如果一个类有删除的或不可访问的析构函数,那么其默认和拷贝构造函数会被定义为删除的。
5. 如果一个类有const或引用成员则不能使用合成的拷贝赋值操作。
## 5 概念区分声明declare、定义define、初始化initialize、赋值assign
### 声明declare
* 声明一个符号。如果有extern则表示变量在外部顶底链接其他问件事匹配外部定义的变量。
### 定义define
* 分配内存、指定变量名。
### 初始化initialize
* 对象创建时获得的初始值。初始化的含义是,在创建变量的时候赋予其一个初始值。
### 赋值assign
* 赋值的含义是,将当前的值擦除,而以一个新的值来代替。
* 不能是简单的覆盖,将当前值擦除,需要调用当前对象的析构函数,对指针变量进行析构,如果只是简单的覆盖,肯定不行。

View File

@@ -0,0 +1,183 @@
# 左值右值
> 参考文献
> * [cnblogs.com/sunchaothu/p/11343517.html](cnblogs.com/sunchaothu/p/11343517.html)
> 为了导入右值和移动语义,首先复习了以下临时对象在函数返回值和传参数时构造了几次;然后对比介绍了左值和右值,以及右值引用的形式和含义。为移动语义和完美转发的介绍做铺垫。
## 1 问题导入
C++11 引入了 std::move 语义、右值引用、移动构造和完美转发这些特性。
* 函数返回值是传值的时候发生几次对象构造、几次拷贝?
* 函数的形参是值传递的时候发生几次对象构造?
### 函数返回值原理
```
// main.cpp
#include <iostream>
using namespace std;
class A{
public:
A(){
cout<<"class A construct!"<<endl;
}
A(const A&){
cout<<"class A copy!"<<endl;
}
A& operator=(const A&){
cout<<"assignment called!"<<endl;
}
~A(){
cout<<"class A destruct!"<<endl;
}
};
A get_A_value(){
return A();
}
int main(){
A a = get_A_value();
return 0;
}
```
使用 g++ 编译;注意使用 -fno-elide-constructors关闭省略构造优化
```
g++ main.cpp -fno-elide-constructors
```
可以得到以下输出
```
class A construct!
class A copy!
class A destruct!
class A copy!
class A destruct!
class A destruct!
```
可以看到A a=get_A_value(); 一行代码居然产生1次对象构造和2次对象的拷贝构造具体为
1. 在 get_A_value() 里 A() 构造了临时对象,发生了一次默认构造;
2. 函数返回的时候会把临时对象拷贝后作为返回值,发生一次拷贝构造;
3. A a = 函数返回值发生了拷贝构造。
如果使用编译器优化(默认), 则会把临时对象拷贝的那次 和 用返回值构造最终对象的拷贝的给省略了;也即,只有一次构造和析构。
```
class A construct!
class A destruct!
```
### 函数参数原理
```
// ... A
void pass_A_by_value(A a){
}
int main(){
A a;
pass_A_by_value(a);
return 0;
}
```
在去掉优化 g++ main.cpp -fno-elide-constructors时输出为
```
class A construct!
class A copy!
class A destruct!
class A destruct!
```
* 1次构造加上1次拷贝。
### 总结拷贝构造函数
事实上,在未经优化的情况下,以下时候拷贝构造函数会被调用:
1. 函数内的局部对象做为返回值返回(不是引用)的时候会发生拷贝(拷贝为临时对象返回)
2. 函数形参为传值的时候,会发生拷贝构造
3. 一个对象以另外一个对象进行初始化的时候
对象的频繁构造是程序的开销,特别是当对象内部有堆上内存(比如有 new 出来的成员)的时候,每次拷贝构造的时候都需要用 new 申请一块内存造成性能的降低。对于情况2好习惯是如果函数参数是只读的也即不会在程序内进行修改传引用作为参数也即 pass_A_by_refrence(const A &a); 对于情况1编译器会为我们进行优化; 对于情况3C++11 引入了一种移动构造函数的概念,它将获取**右值引用*,右值的“资源” move 到新对象中,这个过程中不会申请新的内存,从而达到提高了效率和性能。
所以,要理解些关键词 “移动构造”、“移动语义” ,首先要理解右值和右值引用。
## 2 左值(lvalue)和右值(rvalue)
### 概念
左值的英文简写为“lvalue”右值的英文简写为“rvalue”。很多人认为它们分别是"left value"、"right value" 的缩写其实不然。lvalue 是“loactor value”的缩写可意为存储在内存中、有明确存储地址可寻址的数据而 rvalue 译为 "read value",指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。
```C++
A a = foo(); // foo() 为右值
int a = 5; //5字面量为右值
char *x = "thu"; // “thu”为字面值也为右值
a = b + c; // b + c这个结果也是一个右值
return a //临时对象,也即函数返回值的时候只会“临时”存在的对象(运行超过那一行就会结束它的生存期),这个临时返回值就是一个右值;
```
* 位于赋值运算符 = 右边的值,为右值;在左边的则为左值
* 左值可以取得地址、有名字; 不可以取得地址、没有名字的为右值。所以 A a = foo()可以用 &a取得a的地址a 是左值,然是不能取得 foo()的地址,(&foo())无法通过编译, foo()返回的临时对象也是没有名字的,所以是右值。
* 在C++11中右值包括两种一中是将亡值(xvalue, eXpiring Value),一种是纯右值(prvalue,Pure Rvalue)[1]。函数非引用返回的临时对象、运算表达式的结果、1, 3.14,'c'这样的字面值等都属于纯右值。而xvalue则是由 C++11引入的 如返回值为 A&& 的函数返回值或者std::move()的返回值等。
## 3 左值引用和右值引用
### 左值引用
左值引用就是一般的引用,一般用一个&表示,例如
```
const A &a_ref = a; // 取得对象 a 的引用
```
左值引用相当于别名,指向一个具体的对象。
### 右值引用
右值引用顾名思义,就是右值的引用, 用 &&表示;
```
A &&r_ref = getRvalue(); // r_ref 是一个右值引用
```
右值引用也相当于别名,与左值的区别为右值引用是无名变量的别名。
getRvalue() 是一个返回右值的函数,右值在这一句执行完就该结束他的生存期了,如果是对象就该调用析构函数了;但是右值引用让它强行续命;使用右值引用指向右值,右值的生存期和右值引用一样长了,这也就少一次对象的析构和构造了。
C++的右值引用主要有两个用处,
* 一个是移动语义
* 一个是完美转发。
### 性质补充
* 虽然 C++98/03 标准不支持为右值建立非常量左值引用,但允许使用常量左值引用操作右值。也就是说,常量左值引用既可以操作左值,也可以操作右值,例如:
```
int num = 10;
const int &b = num;
const int &c = 10;
```
* 和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化
```
int num = 10;
//int && a = num; //右值引用不能初始化为左值
int && a = 10;
```
* 和常量左值引用不同的是,右值引用还可以对右值进行修改。例如:
```
int && a = 10;
a = 100;
cout << a << endl;
````
![](image/2021-07-21-11-45-51.png)

View File

@@ -0,0 +1,220 @@
# C++移动语义
> 参考文献
> * [https://www.cnblogs.com/sunchaothu/p/11392116.html](https://www.cnblogs.com/sunchaothu/p/11392116.html)
> * [
https://stackoverflow.com/questions/3106110/what-are-move-semantics/3109981#3109981](
https://stackoverflow.com/questions/3106110/what-are-move-semantics/3109981#3109981)
## 1 可拷贝和可移动的概念
* 在面向对象中,有的类是可以拷贝的,例如车、房等他们的属性是可以复制的,可以调用拷贝构造函数和拷贝赋值函数。
* 有点类的对象则是独一无二的,或者类的资源是独一无二的,比如 IO 、 std::unique_ptr等他们不可以复制但是可以把资源交出所有权给新的对象称为可以移动的可以调用移动构造函数和移动赋值函数。
* C++11最重要的一个改进之一就是引入了move语义这样在一些对象的构造时可以获取到已有的资源如内存而不需要通过拷贝申请新的内存这样移动而非拷贝将会大幅度提升性能。例如有些右值即将消亡析构这个时候我们用移动构造函数可以接管他们的资源。
## 2 移动构造函数和移动赋值函数
```C++
#include <iostream>
#include <cstring>
using namespace std;
class A{
public:
A():i(new int[500]){
cout<<"class A construct!"<<endl;
}
//拷贝构造函数
A(const A &a):i(new int[500]){
memcpy(i, a.i,500*sizeof(int));
cout<<"class A copy!"<<endl;
}
//移动构造函数
A(A &&a)noexcept
:i(a.i)
{
a.i = nullptr;
cout<< "class A move"<<endl;
}
//移动赋值运算符
A &operator =(A &&rhs) noexcept{
// check self assignment
if(this != &rhs){
delete []i;
i = rhs.i;
rhs.i = nullptr;
}
cout<< "class A move and assignment"<<std::endl;
return *this;
}
~A(){
delete []i;
cout<<"class A destruct!"<<endl;
}
private:
int *i;
};
A get_A_value(){
return A();
}
void pass_A_by_value(A a){
}
int main(){
A a = get_A_value();
return 0;
}
```
对于拷贝构造函数运行时可以看到
```
class A construct!
class A copy!
class A destruct!
class A copy!
class A destruct!
class A destruct!
```
发生了一次构造和两次拷贝!在每次拷贝中数组都得重新申请内存,而被拷贝后的对象很快就会析构,这无疑是一种浪费。
对于移动移动构造函数;可以看到输出为
```
class A construct!
class A move
class A destruct!
class A move
class A destruct!
class A destruct!
```
原先的两次构造变成了两次移动在移动构造函数中我们做了什么呢我们只是获取了被移动对象的资源这里是内存的所有权同时把被移动对象的成员指针置为空以避免移动过来的内存被析构这个过程中没有新内存的申请和分配在大量对象的系统中移动构造相对与拷贝构造可以显著提高性能这里noexcept告诉编译器这里不会抛出异常从而让编译器省一些操作(这个也是保证了STL容器在重新分配内存的时候知道是noexpect而使用移动构造而不是拷贝构造函数),通常移动构造都不会抛出异常的。
### 移动构造和移动赋值
1. 偷梁换柱直接“浅拷贝”右值引用的对象的成员;
2. 需要把原先右值引用的指针成员置为 nullptr,以避免右值在析构的时候把我们浅拷贝的资源给释放了;
3. 移动构造函数需要先检查一下是否是自赋值然后才能先delete自己的成员内存再浅拷贝右值的成员始终记住第2条。
## 2 std::move()
* std::move(lvalue) 的作用就是把一个左值转换为右值。关于左右值的含义我们上一篇博客C++11的右值引用进行过阐述。
```
int lv = 4;
int &lr = lv;// 正确lr是l的左值引用
int &&rr = lv; // 错误,不可以把右值引用绑定到一个左值
```
如果使用std::move 函数
```
int &&rr = std::move(lv); // 正确,把左值转换为右值
```
可以看到 std::move的作用是把左值转换为右值的。
让我们看一看 std::move 的源码实现:
```
// FUNCTION TEMPLATE move
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
```
可以看到std::move 是一个模板函数通过remove_reference_t获得模板参数的原本类型然后把值转换为该类型的右值。用C++大师 Scott Meyers 的在《Effective Modern C++》中的话说, std::move 是个cast not a move.
值得注意的是: 使用move意味着把一个左值转换为右值原先的值不应该继续再使用承诺即将废弃
## 4 使用 std::move 实现一个高效的 swap 函数
我们可以使用 move语义实现一个 交换操作swap;在不使用 Move 语义的情况下
```
swap(A &a1, A &a2){
A tmp(a1); // 拷贝构造函数一次,涉及大量数据的拷贝
a1 = a2; // 拷贝赋值函数调用,涉及大量数据的拷贝
a2 = tmp; // 拷贝赋值函数调用,涉及大量数据的拷贝
}
```
如果使用 Move语义即加上移动构造函数和移动赋值函数
```
void swap_A(A &a1, A &a2){
A tmp(std::move(a1)); // a1 转为右值,移动构造函数调用,低成本
a1 = std::move(a2); // a2 转为右值,移动赋值函数调用,低成本
a2 = std::move(tmp); // tmp 转为右值移动给a2
}
```
可以看到move语义确实可以提高性能事实上 move语义广泛地用于标准库的容器中。C++11标准库里的std::swap 也是基于移动语义实现的。
说到了 swap, 那就不得不说一下啊 move-and-swap 技术了
## 5 Move and swap 技巧
看下面一段代码,实现了一个 unique_ptr ,和标准的std::unqiue_ptr的含义一致智能指针的一种。
```C++
template<typename T>
class unique_ptr
{
T* ptr;
public:
explicit unique_ptr(T* p = nullptr)
{
ptr = p;
}
~unique_ptr()
{
delete ptr;
}
// move constructor
unique_ptr(unique_ptr&& source) // note the rvalue reference
{
ptr = source.ptr;
source.ptr = nullptr;
}
/* unique_ptr& operator=(unique_ptr&& source) // 这里使用右值引用
{
if (this != &source) // beware of self-assignment
{
delete ptr; // release the old resource
ptr = source.ptr; // acquire the new resource
source.ptr = nullptr;
}
return *this;
} */
// move and swap idiom replace the move assignment operator
unique_ptr& operator=(unique_ptr rhs) // 这里不用引用,会调用移动构造函数
{
std::swap(ptr, rhs.ptr);
// std::swap(*this,rhs) // is also ok
return *this;
}
T* operator->() const
{
return ptr;
}
T& operator*() const
{
return *ptr;
}
};
```
在这里如果要按照常规办法写移动赋值函数,函数体内需要写一堆检查自赋值等冗长的代码。使用 move-and-swap语义只用简短的两行就可以写出来。 在移动赋值函数中 source 是个局部对象,这样在形参传递过来的时候必须要调用拷贝构造函数(这里没有实现则不可调用)或者移动构造函数
,(事实上仅限右值可以传进来了)。然后 std::swap 负责把原先的资源和source 进行交换,完成了移动赋值。这样写节省了很多代码,很优雅。

View File

@@ -0,0 +1,126 @@
# 完美转发
## 1 概念
首先解释一下什么是完美转发,它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
```
template<typename T>
void function(T t) {
otherdef(t);
}
```
function() 函数模板中调用了 otherdef() 函数。在此基础上,完美转发指的是:如果 function() 函数接收到的参数 t 为左值,那么该函数传递给 otherdef() 的参数 t 也是左值;反之如果 function() 函数接收到的参数 t 为右值,那么传递给 otherdef() 函数的参数 t 也必须为右值。
## 2 C++98实现完美转发
* C++98通过左值引用和常量左值引用+函数重载实现完美转发
* C++98/03 标准下的 C++ 也可以实现完美转发只是实现方式比较笨拙。通过前面的学习我们知道C++ 98/03 标准中只有左值引用,并且可以细分为非 const 引用和 const 引用。其中,使用非 const 引用作为函数模板参数时,只能接收左值,无法接收右值;而 const 左值引用既可以接收左值,也可以接收右值,但考虑到其 const 属性,除非被调用函数的参数也是 const 属性,否则将无法直接传递。
* 如果使用 C++ 98/03 标准下的 C++ 语言,我们可以采用函数模板重载的方式实现完美转发
```C++
#include <iostream>
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherdef(int & t) {
cout << "lvalue\n";
}
void otherdef(const int & t) {
cout << "rvalue\n";
}
//重载函数模板,分别接收左值和右值
//接收右值参数
template <typename T>
void function(const T& t) {
otherdef(t);
}
//接收左值参数
template <typename T>
void function(T& t) {
otherdef(t);
}
int main()
{
function(5);//5 是右值
int x = 1;
function(x);//x 是左值
return 0;
}
// 程序执行结果为:
// rvalue
// lvalue
```
从输出结果中可以看到,对于右值 5 来说,它实际调用的参数类型为 const T& 的函数模板,由于 t 为 const 类型,所以 otherdef() 函数实际调用的也是参数用 const 修饰的函数所以输出“rvalue”对于左值 x 来说2 个重载模板函数都适用C++编译器会选择最适合的参数类型为 T& 的函数模板,进而 therdef() 函数实际调用的是参数类型为非 const 的函数输出“lvalue”。
## 3 C++ 11实现完美转发。
* C++ 11 标准中允许在函数模板中使用右值引用来实现完美转发
### 万能引用规则
* C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)
* 在 C++11 标准中实现完美转发,只需要编写如下一个模板函数即可
```
template <typename T>
void function(T&& t) {
otherdef(t);
}
```
### 引用折叠规则
* 此模板函数的参数 t 既可以接收左值,也可以接收右值。但仅仅使用右值引用作为函数模板的参数是远远不够的,还有一个问题继续解决,即如果调用 function() 函数时为其传递一个左值引用或者右值引用的实参。
* C++ 11标准为了更好地实现完美转发特意为其指定了新的类型匹配规则又称为引用折叠规则假设用 A 表示实际传递参数的类型):
* 当实参为左值或者左值引用A&)时,函数模板中 T&& 将转变为 A&A& && = A&
* 当实参为右值或者右值引用A&&)时,函数模板中 T&& 将转变为 A&&A&& && = A&&)。
```
int n = 10;
int & num = n;
function(num); // T 为 int&
int && num2 = 11;
function(num2); // T 为 int &&
```
### Foward<T>模板函数
* 引入了一个模板函数 forword<T>(),我们只需要调用该函数,将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数呢?
```C++
#include <iostream>
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherdef(int & t) {
cout << "lvalue\n";
}
void otherdef(const int & t) {
cout << "rvalue\n";
}
//实现完美转发的函数模板
template <typename T>
void function(T&& t) {
otherdef(forward<T>(t));
}
int main()
{
function(5);
int x = 1;
function(x);
return 0;
}
// 程序执行结果为:
// rvalue
// lvalue
```
* 总的来说,在定义模板函数时,我们采用右值引用的语法格式定义参数类型,由此该函数既可以接收外界传入的左值,也可以接收右值;其次,还需要使用 C++11 标准库提供的 forword() 模板函数修饰被调用函数中需要维持左、右值属性的参数。由此即可轻松实现函数模板中参数的完美转发。

27
C++/高级特性/3.cpp Normal file
View File

@@ -0,0 +1,27 @@
#include <iostream>
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherdef(int & t) {
cout << "lvalue\n";
}
void otherdef(const int & t) {
cout << "rvalue\n";
}
//重载函数模板,分别接收左值和右值
//接收右值参数。常量左值引用可以接受左值、常量左值、右值、常量右值。
template <typename T>
void function(const T& t) {
otherdef(t);
}
//接收左值参数
template <typename T>
void function(T& t) {
otherdef(t);
}
int main()
{
function(5);//5 是右值
int x = 1;
function(x);//x 是左值
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1 @@
> 主要用来记录 effectiveC++ 和more effective c++中涉及到的基础知识和高级特性。

View File

@@ -3,25 +3,12 @@
* 十篇文章(2021.04文件夹下的内容)
* [x] FedFMC
* [x] EWC(FedFMC中用到的一种终身学习的方法需要了解作为参考文献)是一种增量学习方案
* [ ] Overcoming Forgetting in Federated Learning on Non-IID Data 利用增量学习方案解决联邦学习中非独立同分布问题的另外一个方法。
* [ ] Federated Meta-Learning for Fraudulent Credit Card Detection 联邦元学习解决信用卡诈骗检测
* [x] Overcoming Forgetting in Federated Learning on Non-IID Data 利用增量学习方案解决联邦学习中非独立同分布问题的另外一个方法。
* [x] Federated Meta-Learning for Fraudulent Credit Card Detection 联邦元学习解决信用卡诈骗检测
* [x] Learning Classifiers When the Training Data Is Not IID
* [ ] on the convergence of fl on noniid data
* [x] on the convergence of fl on noniid data
* [x] federated learning with non iid data
* [ ] federated learning on non iid data silos an experiment
* [ ] federated learning with non iid data : a survey
* [ ] fedAT
* [ ] Android Malware Detection using Deep Learning on API Method Sequences
* [ ] LoAdaBoost: loss-based AdaBoost federated machine learning with reduced computational complexity on IID and non-IID intensive care data
* [ ] A scalable and extensible framework for android malware detection and family attribution
* [ ] CTDroid: Leveraging a Corpus of Technical Blogs for Android Malware Analysis
* [ ] Optimizing symbolic execution for malware behavior classification
* [ ] Cross-Gradient Aggregation for Decentralized Learning from Non-IID data
* [ ] Federated Learning on Non-IID Data Silos: An Experimental Study
* [ ] JOWMDroid: Android malware detection based on feature weighting with joint optimization of weight-mapping and classifier parameters
* [ ] Inverse Distance Aggregation for Federated Learning with Non-IID Data
* [ ] DAMBA: Detecting Android Malware by ORGB Analysis
* [ ] DeepIntent: Deep Icon-Behavior Learning for Detecting Intention-Behavior Discrepancy in Mobile Apps
* [x] federated learning with non iid data : a survey
## 总结

View File

@@ -0,0 +1,8 @@
## 计划
### 毕设——论文阅读2.00-6.00
* [x] federated learning on non iid data silos an experiment
* [x] fedAT
* [x] 自适应联邦学习算法
## 总结

View File

@@ -0,0 +1,18 @@
## 计划
### 毕设——论文阅读2.00-6.00
* [x] mocha 联邦多任务学习
* [x] Improving Federated Learning Personalization via Model Agnostic Meta Learning
* [x] Personalized Federated Learning for Intelligent IoT Applications: A Cloud-Edge Based Framework
* [x] Personalized Federated Learning With Differential Privacy
* [x] Personalized Federated Learning with Moreau Envelopes
* [x] Personalized Federated Learning: A Meta-Learning Approach
* [x] Salvaging Federated Learning by Local Adaptation
* [x] Survey of Personalization Techniques for Federated Learning
## 总结
* 基本把非独立同分布问题的经典方法看完了。接下来进行复现。尝试对各种方法进行对比,主要是基于手写体数据集构建非独立同分布的实验。然后将数据集更改为自己的数据集(等选择好数据集之后,将数据及进行更换。)

View File

@@ -0,0 +1,23 @@
## 计划
### 毕设——论文阅读2.00-6.00
* [x] Android Malware Detection using Deep Learning on API Method Sequences
* [x] LoAdaBoost: loss-based AdaBoost federated machine learning with reduced computational complexity on IID and non-IID intensive care data
* [x] A scalable and extensible framework for android malware detection and family attribution
* [x] CTDroid: Leveraging a Corpus of Technical Blogs for Android Malware Analysis
* [x] Optimizing symbolic execution for malware behavior classification
* [x] Cross-Gradient Aggregation for Decentralized Learning from Non-IID data
* [x] JOWMDroid: Android malware detection based on feature weighting with joint optimization of weight-mapping and classifier parameters
* [x] DAMBA: Detecting Android Malware by ORGB Analysis
* [x] DeepIntent: Deep Icon-Behavior Learning for Detecting Intention-Behavior Discrepancy in Mobile Apps
### 就业
- [x] LeetCode一道困难题。10.00-12.00
- [ ] C++基础知识看完。8.00-10.00
## 总结

View File

@@ -2,13 +2,16 @@
## 任务
### 毕设
* 阅读论文两周7.16-7.25(每天十篇可能读完!!!)
* 恶意软件相关的论文
* 联邦学习相关的论文
* 非独立同分布和半监督学习相关的文章
* 完成实验三周7.25-8.8
* 特征提取的实验
* 联邦学习针对非独立同分布的实验
* 联邦半监督学习的实验
* 恶意软件相关的论文7.30
* 联邦学习相关的论文7.25
* 非独立同分布相关的文章7.30
* ~~联邦半监督学习的文章(这一个点确实可以去掉了)~~
* 完成实验四周7.25-8.8
* 联邦学习针对非独立同分布(手写体数据实验实现)的实验(一周)
* 恶意软件特征提取的实验(一周) 什么样的数据集?怎样的方法提取特征?提取哪些特征用于实验?使用什么样的模型?
* 联邦学习针对恶意软件数据集的实验。(一周)
* 差分隐私应用的实验(一周)
* ~~联邦半监督学习的实验~~
* 完成小论文(三周)
* 一篇关于非独立同分布联邦学习的小实验
* 完成大论文(四周)

View File

@@ -55,9 +55,14 @@
2. 联邦半监督学习方案——半监督方案?
3. 联邦非独立同分布学习方案——某一个方案
论文贡献
1. 实现以上方法
2. 实现了仿真系统和实验验证
论文贡献(到时候再展开或者压缩内容。)
1. 实现了差分隐私,并通过实验验证了差分隐私对参数隐私的保护
2. ~~实现了半监督学习方案。这个可以去掉,如果最后实现起来比较简单,则可以加上~~
3. 使用了联邦元学习解决了非独立同分布问题
4. 一个恶意软件特征提取和分析工具
5. 完成了相关的实验验证。证明了balabala
6. 构建了联邦学习框架和恶意软件检测系统。能够使用半监督学习,差分隐私保护用户的隐私,能够针对非独立同分布场景,训练机器学习模型。恶意软件检测系统包括特征提取模块和模型检测模块。
7. 提供了一个训练框架,以及一个可用的模型。
### 论文结构

View File

@@ -121,7 +121,7 @@ public int climbStairs(int n) {
### 算法设计
* **状态定义** 设动态规划列表 dpdp dp[i]dp[i] 代表以元素 nums[i]nums[i] 为结尾的连续子数组最大和。
* **状态定义** 设动态规划列表 dp dp[i]dp[i] 代表以元素 nums[i]nums[i] 为结尾的连续子数组最大和。
* 为何定义最大和 dp[i]dp[i] 中必须包含元素 nums[i]nums[i] :保证 dp[i]dp[i] 递推到 dp[i+1]dp[i+1] 的正确性;如果不包含 nums[i]nums[i] ,递推时则不满足题目的 连续子数组 要求。
* **转移方程** 若 dp[i-1] \leq 0dp[i1]≤0 ,说明 dp[i - 1]dp[i1] 对 dp[i]dp[i] 产生负贡献,即 dp[i-1] + nums[i]dp[i1]+nums[i] 还不如 nums[i]nums[i] 本身大。
* 当 dp[i - 1] > 0dp[i1]>0 时:执行 dp[i] = dp[i-1] + nums[i]dp[i]=dp[i1]+nums[i]
@@ -183,4 +183,102 @@ public int climbStairs(int n) {
}
return max;
}
```
## 4 传递信息
### 问题描述
小朋友 A 在和 ta 的小伙伴们玩传信息游戏,游戏规则如下:
有 n 名玩家,所有玩家编号分别为 0 n-1其中小朋友 A 的编号为 0
每个玩家都有固定的若干个可传信息的其他玩家(也可能没有)。传信息的关系是单向的(比如 A 可以向 B 传信息,但 B 不能向 A 传信息)。
每轮信息必须需要传递给另一个人,且信息可重复经过同一个人
给定总玩家数 n以及按 [玩家编号,对应可传递玩家编号] 关系组成的二维数组 relation。返回信息从小 A (编号 0 ) 经过 k 轮传递到编号为 n-1 的小伙伴处的方案数;若不能到达,返回 0。
示例 1
```
输入n = 5, relation = [[0,2],[2,1],[3,4],[2,3],[1,4],[2,0],[0,4]], k = 3
输出3
解释:信息从小 A 编号 0 处开始,经 3 轮传递,到达编号 4。共有 3 种方案,分别是 0->2->0->4 0->2->1->4 0->2->3->4。
```
### 问题分析
### 策略选择
* 有三种不同的策略选择广度优先搜索、深度优先搜索、动态规划。本文采用动态规划的思路实际上与广度优先搜索的思路是一致的。看在第k步能到达的所有节点。
### 算法设计
* **状态定义**dp[j] 为经过 i轮传递到编号j 的玩家的方案数其中k0≤i≤kn0≤j<n。可以不用记住更久远的历史状态。
* **转移方程** ![](image/2021-07-23-22-46-02.png)
* **初始状态** dp[0]=1
* **终止状态** 最终得到dp[n1] 即为总的方案数
### 算法分析
* 时间复杂度O(km)。其中 m 为 relation 数组的长度。
* 空间复杂度O(n)O(n)。
### 算法实现
```c++
class Solution {
public:
int numWays(int n, vector<vector<int>>& relation, int k) {
// // 构建邻接矩阵
// vector<vector<int>> vec(n,vector<int>(n,0));
// for(auto a:relation){
// vec[a[0]][a[1]]=1;
// }
// // for(int i=0;i<n;i++){
// // for(int j=0;j<n;j++){
// // cout<<vec[i][j]<<"\t";
// // }
// // cout<<endl;
// // }
// // 构建有向图
// vector<int> result;
// result.push_back(0);
// for(int i=0;i<k;i++){
// // for(int k:result){
// // cout<<k<<"\t";
// // }
// // cout<<endl;
// vector<int> temp;
// for(auto a:result){
// for(int j=0;j<n;j++){
// if(vec[a][j]==1)temp.push_back(j);
// }
// }
// result.assign(temp.begin(),temp.end());
// }
// int sum =0;
// for(auto a:result){
// if(a==n-1)sum++;
// }
// return sum;
// 尝试使用动态规划求解。可以不用构建关系矩阵,每次遍历关系矩阵就好了。。然后每轮迭代能够到的位置。
vector<int> dp(n,0);
dp[0]=1;
for(int i=0;i<k;i++){
vector<int> temp(n,0);
for(auto a: relation){
temp[a[1]]+=dp[a[0]];
}
dp.assign(temp.begin(),temp.end());
}
return dp[n-1];
}
};
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -94,4 +94,81 @@ public:
return ans;
}
};
```
## 2 区间覆盖问题
### 问题描述
给你一个二维整数数组 ranges 和两个整数 left  right 。每个 ranges[i] = [starti, endi] 表示一个从 starti  endi  闭区间 
如果闭区间 [left, right] 内每个整数都被 ranges  至少一个 区间覆盖那么请你返回 true 否则返回 false 
已知区间 ranges[i] = [starti, endi] ,如果整数 x 满足 starti <= x <= endi 那么我们称整数x 被覆盖了。
 
示例 1
```
输入ranges = [[1,2],[3,4],[5,6]], left = 2, right = 5
输出true
解释2 到 5 的每个整数都被覆盖了:
- 2 被第一个区间覆盖。
- 3 和 4 被第二个区间覆盖。
- 5 被第三个区间覆盖。
```
### 问题分析
* 区间覆盖问题
### 策略选择
* 数据结构:数组
* 算法选择:线性区间操作
### 算法设计
* 遍历 \textit{ranges}ranges 中的所有区间 [l, r][l,r],将区间内每个整数的 \textit{cnt}cnt 值加上 11。遍历结束后检查 [\textit{left},\textit{right}][left,right] 内的每个整数的 \textit{cnt}cnt 值是否均大于 00是则返回 \texttt{true}true否则返回 \texttt{false}false。
* 在维护完差分数组 \textit{diff}diff 后,我们遍历 \textit{diff}diff 求前缀和得出覆盖每个整数的区间数量。下标 ii 对应的被覆盖区间数量即为初始数量 00 加上 [1, i][1,i] 闭区间的变化量之和。在计算被覆盖区间数量的同时,我们可以一并判断 [\textit{left}, \textit{right}][left,right] 闭区间内的所有整数是否都被覆盖。
### 算法分析
* 时间复杂度O(n + l)
* 空间复杂度O(l)
### 算法实现
```C++
class Solution {
public:
bool isCovered(vector<vector<int>>& ranges, int left, int right) {
// 一种差分数组的方法。
vector<int> vec(right-left+2,0);
for(auto k:ranges){
if(k[0]>right || k[1]<left)continue;
if(k[0]<left){
vec[left-left]++;
}
else{
vec[k[0]-left]++;
}
if(k[1]>right){
vec[right-left+1]--;
}
else{
vec[k[1]-left+1]--;
}
}
int m = 0;
for(int a =left;a<=right;a++){
m+=vec[a-left];
if(m<=0){
return false;
}
}
return true;
}
};
```

View File

@@ -0,0 +1,94 @@
# 链表与映射
## 复制带随机指针的链表
### 问题描述
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val一个表示 Node.val 的整数。
random_index随机指针指向的节点索引范围从 0  n-1如果不指向任何节点则为  null 
你的代码 只 接受原链表的头节点 head 作为传入参数。
 
示例 1
![](image/2021-07-23-13-09-12.png)
```
输入head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
```
### 问题分析
* 需要建立原链表节点到新链表节点的映射。这个映射本可以通过添加子段实现但是不能添加。只能使用一个map完成链表映射了。
### 策略选择
### 算法设计
### 算法分析
### 算法实现
```C++
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head==nullptr)return nullptr;
// 使用vector修改原链表的方法不行。
// 使用map建立原链表到新链表的映射
map<Node*,Node*> m;
Node* old_head = head;
Node* temp_old = head;
Node* new_head = new Node(head->val);
Node* temp_new = new_head;
while(temp_old->next!=nullptr){
m[temp_old]=temp_new;
temp_new->next = new Node(temp_old->next->val);
temp_old=temp_old->next;
temp_new=temp_new->next;
}
m[temp_old]=temp_new;
temp_old = head;
while(temp_old!=nullptr){
if(temp_old->random!=nullptr){
m[temp_old]->random=m[temp_old->random];
}
temp_old=temp_old->next;
}
return new_head;
}
};
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

3
读书思考/简介.md Normal file
View File

@@ -0,0 +1,3 @@
学而不思则罔,思而不学则殆。
深刻体会。每天思考一堆东西,沉浸在自己发现的小规律和小感悟当中,实际上,书中早有成套的理论,概括升华了你的结论。仅仅通过个人的无休止的思考得到的知识和觉悟始终是优先的,思而不学则殆,非常关键。以后多读书,在这里整理一下自己的读书笔记吧。