【C++】C++11——右值引用和移动语义|可变参数模板

news2024/11/18 3:49:25

文章目录

  • 一、左值引用和右值引用
    • 左值引用和右值引用的定义
    • 左值引用和右值引用的比较
  • 二、右值引用的使用场景和意义
    • 左值引用的短板
    • 移动构造和移动赋值
    • 万能引用和完美转发
  • 三、新的类功能
    • 类成员变量初始化
    • default 和 delete
  • 四、可变参数模板


一、左值引用和右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用无论左值引用还是右值引用,都是给对象取别名

左值引用和右值引用的定义

左值 是一个表示数据的表达式 (如变量名或解引用的指针),我们可以对左值取地址,也可以对左值赋值 (const 左值不能赋值); 左值既可以出现在赋值符号的左边,也可以出现在赋值符号的右边;左值引用 就是给左值的引用,给左值取别名。

int main()
{
	//以下的a、b、c均为左值
	int* a = new int(0);
	int b = 1;
	const int c = 2;

	// 左值引用给左值取别名
	int& ref1 = a;
	int& rb = b;
	return 0;
}

右值 也是一个表示数据的表达式,如字面常量、表达式返回值、函数返回值等等右值可以出现在赋值符号的右边,但不能出现出现在赋值符号的左边,右值不能取地址

常见的右值和右值引用:

int main()
{
	int a, b, x, y;

	//常见的右值
	10;
	x + y;
	fmin(x, y);

	// 左值引用给左值取别名
	int& ref1 = a;

	// 左值引用给右值取别名
	const int& ref2 = (a + b);

	// 右值引用给右值取别名
	int&& ref3 = (a + b);

	// 右值引用给move后左值取别名
	//int&& ref4 = a;
	int&& ref4 = move(a);

	return 0;
}

需要注意的是:

  • 为什么函数返回值是右值: 当函数返回的是一个局部变量时,因为局部变量出了函数生命周期就会结束,所以返回时会将该变量拷贝到寄存器中,然后返回这个寄存器中的内容,而寄存器中的变量是临时变量,临时变量具有常性,属于右值。
  • 为什么右值不能取地址: 在 C++中,右值则是一个临时使用的、不可寻址的内存值;右值没有独立的内存空间,它只是存储在寄存器或其他临时内存空间中的一个值;我们也不能把右值放入内存中,因为右值没有确定的内存位置,所以右值不能取地址。

注意: 虽然右值不能取地址,但是给右值取别名后,会导致右值被存储到特定位置,拥有独立的内存空间,所以可以取到该位置的地址;换句话来说,虽然右值引用引用的是右值,但右值引用本身是一个左值所以如果我们不希望改变右值引用,我们就需要将右值引用定义为 const 右值引用。

在这里插入图片描述


左值引用和右值引用的比较

  • 左值引用不能直接引用右值,但是 const 左值引用可以引用右值,因为 const 左值引用也是只读的,而权限可以平移:
int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
	int& ra1 = a;
	//int& ra2 = 10;  // 编译失败,因为10是右值

	// const左值引用既可引用左值,也可引用右值。
	const int& ra3 = 10;
	const int& ra4 = a;
	return 0;
}
  • 右值引用也不可以直接引用左值,但是右值引用可以引用 move 后的左值:
int main()
{
	// 右值引用只能右值,不能引用左值。
	int&& r1 = 10;
	// error C2440: “初始化”: 无法从“int”转换为“int &&”
	int a = 10;
	//int&& r2 = a;

	// 右值引用可以引用move以后的左值
	int&& r3 = std::move(a);

	return 0;
}

二、右值引用的使用场景和意义

左值引用的短板

我们先来看一下左值引用可以解决的问题:

  • 做参数:a、减少拷贝,提高效率。b、做输出型参数。
  • 做返回值:a、减少拷贝,提高效率。b、引用放回,可以修改返回对象(比如:operator[ ])

左值引用既可以引用左值又可以引用右值,那为什么C++11还要提出右值引用呢,其实左值引用无法解决一些场景问题,所以就提出了右值引用。

函数返回对象是一个局部变量时,就不能使用左值引用返回,而只能传值返回了,因为局部对象出了函数作用域就不存在了,此时引用的就是一个野指针;如下:

//左值引用的短板——不能解决局部对象的返回值问题
template <class T>
T func1(const T& x) {
	T tmp;
	//...

	return tmp;  //出这个函数tmp会自动销毁
}

这种情况下下编译器会使用这个局部对象拷贝构造一个临时对象,然后再返回这个临时对象,也就是说,会比引用返回多一次拷贝构造当局部对象是一个需要进行深拷贝的自动类型时,比如 vector<vector>,拷贝构造的代价就很大了。而右值引用的提出就是为了补足左值引用存在的这些短板的。


移动构造和移动赋值

假设我们要在自己实现的string类中实现一个 to_string 函数,如下:

cjl::string to_string(int value)
{
	bool flag = true;
	if (value < 0)
	{
		flag = false;
		value = 0 - value;
	}

	cjl::string str;
	while (value > 0)
	{
		int x = value % 10;
		value /= 10;

		str += ('0' + x);
	}

	if (flag == false)
	{
		str += '-';
	}

	std::reverse(str.begin(), str.end());
	return str;
}

由于 to_string 函数返回的 str 是一个局部对象,所以这里我们只能使用传值返回,而传值返回就需要进行深拷贝。

在这里插入图片描述

正常情况下应该是 两个拷贝构造 (多出来一次构造是 to_string 函数内部构造 str);但是我们发现这里只有一次拷贝构造。这其实是因为当遇到连续构造的场景时编译器会进行优化,直接使用 str 来拷贝构造得到 s,而不再创建临时对象。

但是优化只适用于少数场景,大部分情况下还是会拷贝构造产生临时对象,比如:

在这里插入图片描述

为了将 str 的资源直接转移给 s,中间不发生拷贝构造,我们就可以使用右值引用来发挥这个功能了。

C++11 中的右值广义的来说一共分为两种:

  • 纯右值: 内置类型表达式的值;
  • 将亡值: 自定义类型表达式的值;所谓的将亡值就是指生命周期马上就要结束的值,一般来说匿名对象、临时对象、move 后的自定义类型都可以看做是将亡值。

注: 上面我们说右值不能取地址其实是右值的严格定义,但其实将亡值也是可以被当作右值看待的,而将亡值有独立的内存空间,可以取地址;所以对于是否是右值我们要灵活看待。

既然将亡值的生命周期马上就要结束了,那么在拷贝构造中我们就可以直接将将亡值的资源拿过来给我自己使用,这样我就不用再去一个一个 new 节点了,将亡值也不用去一个一个释放节点了,两全其美。现在,我们重载一个右值引用版本的构造函数 – 移动构造,这样当实参类型为右值的对象需要进行拷贝构造时就会调用此函数;在函数中,我们直接拿走将亡值的资源,从而使得深拷贝变为了浅拷贝,显著提高了程序的效率。

// 移动构造
string(string&& s)
	:_str(nullptr)
{
	cout << "string(string&& s) -- 移动拷贝" << endl;
	swap(s);
}

在这里插入图片描述
在这里插入图片描述

本来这里 str 会先拷贝构造一个临时对象,由于临时对象属于右值,所以会直接调用移动拷贝来构造 s但是这里编译器进行了优化,直接将 str 识别为右值,让它来移动构造 s,所以通过移动构造 (右值引用) 我们成功将深拷贝变为了浅拷贝。

这里我们需要注意的是:

只有当实参为右值时才会匹配 移动构造构造函数进行优化,当实参为左值时编译器在匹配参数还是会匹配形参为 const T& 的拷贝构造函数;因为编译器不知道我们是否还会对左值进行操作,所以它不敢拿走左值的资源来构造新的对象。

💕 移动赋值

和移动构造同理,只是移动赋值中将亡值还需要释放掉我之前的资源,不过这个过程是自动的:

//移动赋值
string& operator=(string&& s)
{
    cout << "string& operator=(string&& s) -- 移动语义" << endl;
    swap(s);
    return *this;
}

网上有的人说右值引用延长了变量的生命周期,这种说法其实是不准确的;因为右值引用只是将该变量的资源转移给另外一个变量,让它的资源能够不随着该变量的销毁而被释放,而该变量本身的生命周期是没有变的

总结:

  • 左值引用 让形参成为实参的别名,直接减少拷贝;
  • 右值引用 通过实现移动构造和移动赋值,将将亡值的资源进行转移,间接减少拷贝。(浅拷贝的类不需要进行资源转移,所以也就没有移动赋值和移动拷贝)

STL中的容器也都是增加了移动构造和移动赋值的,同时,STL容器的插入接口函数也增加了右值引用的版本。


万能引用和完美转发

万能引用 是一个 函数模板,且函数的形参类型为右值引用;对于这样的函数模板,编译器能够自动根据实参的类型 – 左值/ const 左值/ 右值/ const 右值,自动推演实例化出不同的形参类型分别为 左值引用/ const 左值引用/ 右值引用/ const 右值引用 的函数;如下:

//万能引用
template<typename T>
void PerfectForward(T&& t)
{
	//fun(t);
}

int main()
{
	int a;
	const int b = 8;
	PerfectForward(a);	//左值
	PerfectForward(b);	//const 左值
	PerfectForward(10);	//右值
	PerfectForward(std::move(b));  //const 右值

	return 0;
}

不管实参为什么类型,模板函数都能正确接受并实例化为对应的引用类型,所以我们把形参为右值引用的函数模板叫做万能引用。其中,当实参为左值const 左值时,T&& 会被实例化为 T& 或 const T&,我们称其为 引用折叠,即 将 && 折叠为 &


💕 完美转发

我们上面讲解了万能引用,但是万能引用存在一个很大的问题: 万能引用实例化后函数的形参的属性全部都是左值 – 如果实参为左值/ const 左值,则实例化函数的形参是左值/ const 左值;如果实参是右值/ const 右值,虽然实例化函数的形参是右值引用/ const 右值引用,但是右值引用本身是左值;所以就会出现下面这种情况:

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// 万能引用(引用折叠):既可以引用左值,也可以引用右值
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}

int main()
{
	PerfectForward(10);           // 右值

	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b);		      // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

在这里插入图片描述

为了在传参的过程中能够保留对象原生类型属性,C++11 又设计出了完美转发forward

在这里插入图片描述
在这里插入图片描述

总结:C++11 的右值引用之旅:

  • 旅程一:为了弥补左值引用局部对象返回会发生拷贝构造的问题,C++11 设计出了右值引用;右值引用可以通过移动构造和移动赋值实现资源转移,将深拷贝转化为浅拷贝,从而提高程序效率,这是 C++11 中非常重要的一个设计;
  • 同时,C++11 还为 STL 中的容器都提供了右值版本的插入接口,但由于右值引用本身是左值,所以往下一层传递时不能保证其仍然是右值,所以C++11 又设计出了 move,但盲目的对左值进行 move 会导致错误。
  • 旅程二:为了让模板函数能同时接受 (const) 左值和 (const) 右值并正确实例化为对应的引用类型,C++11 又设计出了万能引用,附带的又引出了引用折叠这个概念;但是这样奇怪的设计让许多学习 C++11 的人苦不堪言。
  • 旅程三:万能引用的设计又带来了新的问题 – 不管是左值引用还是右值引用,其本身都是左值,所以往下一层传递时又要面对类型丢失的问题,但是这里使用之前的 move 已经不能解决问题了,所以 C++11 又又又设计出了完美转发,来保证传参的过程中对象原生类型属性能够保持不变。

三、新的类功能

原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数移动赋值运算符重载

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
    意一个。
    那么编译器会自动生成一个默认移动构造。 默认生成的移动构造函数,对于内置类
    型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
    如果实现了就调用移动构造,没有实现就调用拷贝构造
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
    的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
    置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
    值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
    完全类似)。
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

我们在讲解新的类的功能之前先看一个重要的问题,情况如下图所示:

在这里插入图片描述

  • 情况一 虽然 const& 会延长对象的生命周期,但是当我们返回局部对象的const&时,由于出了该函数的作用域,函数栈帧就会销毁,同时该对象所在的空间的数据就会被释放,这块空间已经被归还给操作系统了,但是我们却还要使用它给拷贝给新创建的对象,因此导致非法访问内存。程序奔溃。因此const& 会延长局部对象的生命周期仅仅是在同意作用域中起作用。出了作用域,无论如何该对象都会被销毁。
  • 情况二 这次我们依然返回局部对象的引用,但是这次我们使用一个const&来接受,由于const& 依然是给返回的局部对象的引用取别名,因此不会去操作内存,但是该局部对象所在的栈帧已经被销毁了, 当执行下一条语句时,会开辟新的栈帧,新的栈帧中的地址可能会占用该局部对象所在的地址,因此该地址就会被占用,所以该地址中的内容可能会被篡改,导致s1变成了随机值。

在这里插入图片描述

当我们不返回局部对象的引用时,并且使用引用来接受该值时则不会出现任何问题,因为编译器做优化后会在该函数return之前将str识别为将亡值,先将该对象移动拷贝一份,然后再释放掉该对象,这样该对象的资源就会被转移走了,然后在外面使用const& 来接收该返回值(移动拷贝后的值)完全没有问题。

下面我们来看一下新的类功能

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}

private:
	cjl::string _name; // 自定义类型
	int _age = 1;		   // 内置类型
};

在这里插入图片描述

写了析构函数后:

在这里插入图片描述

简单来说,如果你什么都没有实现,或者只实现了一个构造函数,那么编译器会自动生成移动拷贝和移动赋值;自动生成的对于内置类型完成值拷贝,对于自定义类型看自定义类型是否实现了移动构造或移动赋值,实现了就调用自定义类型的移动构造或移动赋值,没有实现就调用自定义类型拷贝构造和赋值重载。


类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这
个我们在前面类和对象的博客默认就讲了,这里就不再细讲了

在这里插入图片描述


default 和 delete

强制生成默认函数的关键字default

由于默认移动构造和移动赋值函数的生成条件十分苛刻,所以 C++11 提供了 default 关键字,它可以显示指定生成某个默认成员函数;比如我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成;如下:

在这里插入图片描述

禁止生成默认函数的关键字delete:

假如我们要设计一个类,它不允许被拷贝,传统的做法是将拷贝构造函数定义为私有函数,但这种做法只防止了在类外进行拷贝,而在类内我们仍然可以调用拷贝构造函数完成拷贝,此时编译器在编译时不会发生错误,只有运行起来对同一块空间析构两次时才会报错

class A {
public:
	A() {
		_ptr = new int[10]{ 0 };
	}

	~A() {
		delete[] _ptr;
	}

	//在类内进行拷贝
	void func() {
		A tmp(*this);
		//...
	}

private:
	//将拷贝构造定义为私有,防止在类外进行拷贝
	A(const A& a)
		: _ptr(a._ptr)
	{}

	int* _ptr;
};

int main()
{
	A a;
	a.func();
	return 0;
}

在这里插入图片描述

那么我们如何才能让一个类既不能在外部被拷贝,也不能在内部被拷贝呢? 其实我们可以只给出拷贝构造函数的声明,且声明为私有;这样,只要调用了拷贝构造函数,那么在链接时一定会发生错误:

在这里插入图片描述

C++11 中提供了一种更为便捷的方法 —— 在函数声明加上 =delete 即可,delete 关键字可以阻止函数的自动生成,我们称被 =delete 修饰的函数为删除函数;如下:

在这里插入图片描述

注意:default 关键字都只能针对默认成员函数使用;而 delete 关键字既可以对默认成员函数使用,也可以对非默认成员函数和普通函数使用


四、可变参数模板

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了,所以这里我们点到为止,以后大家如果有需要,再可以深入学习。

下面就是一个基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数 包”, 它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能 通过展开参数包的方式来获取参数包中的每个参数 ,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变
参数,所以我们的用一些奇招来一一获取参数包的值。

💕 计算可变参数的个数:

//可变参数的模板
template<class ...Args>
void ShowList(Args...args)
{
	cout << sizeof...(args) << endl;
}
int main()
{
	string str("hello");
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', str);
	return 0;
}

在这里插入图片描述

当我们看到可变参数模板时,很自然会联想到使用如下方法来依次取出参数包中的每个参数:

template <class ...Args>
void ShowList(Args... args)
{
	//求参数包中参数的个数
	cout << sizeof...(args) << endl;

	//依次取出参数包中的每个参数--error
	for (int i = 0; i < sizeof...(args); i++) {
		cout << args[i] << endl;
	}
}

虽然上面这种方法非常好理解,但是C++11标准中并不允许以这种方式来取出参数包中的参数,而是使用另外两种非常晦涩的方式来完成,如下:

💕 递归函数方式展开参数包

将参数包中的第一个参数赋值给 val,将剩下的 n-1 个参数以类似于递归子问题的方式逐个取出,当参数包为空时再调用最后一次,至此将参数包中的参数全部取出;

//递归终止函数
void ShowList()
{
	cout << "ShowList()" << endl;
}

//展开函数,参数包args包含N个参数(N>=0)
template<class T, class...Args>
void ShowList(const T& val, Args...args)
{
	cout << "ShowList(" << val << ",参数包args有" << sizeof...(args) << "个参数)" << endl;
	ShowList(args...); // -- 递归调用
}

int main()
{
	string str("hello");
	ShowList(1, 'A', str);
	return 0;
}

在这里插入图片描述

💕 逗号表达式方式展开参数包

这种方式是利用了数组初始化的特性,我们在用0初始化数组时需要知道列表中参数的个数,而参数的个数需要通过展开参数包获得。

可以看到,C++11 提供的这两种参数包展开的方式比起 args[i] 这种方式真的是晦涩太多了,特别是逗号表达式展开,但是没办法,语言就是这么规定的;不过也不用太在这里纠结,参数包展开能看懂就行,我们并不需要去深究它的底层原理。

//逗号表达式展开参数包
template <class T>
void PrintArg(const T& val)
{
	cout << val << " ";
}

//展开函数
template<class...Args>
void ShowList(Args...args)
{
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}

int main()
{
	string str("hello world");
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', str);
	return 0;
}

在这里插入图片描述

template <class T>
int PrintArg(T t)
{
	cout << t << " ";

	return 0;
}

template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... };
	cout << endl;
}

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/788879.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统资源限制问题(Memory Analyzer使用OutOfMemoryError)

java8使用Memory Analyzer大概是10左右的版本&#xff0c;用最新版本要java17 MemoryAnalyzer-1.10.0.20200225-win32.win32.x86_64 MAT(Memory Analyzer Tool)下载 https://www.cnblogs.com/zwh0910/p/15774590.html Eclipse downloads - Select a mirror | The Eclipse F…

"科技与狠活"企业级无代码开发MES系统,一周实现数字化

随着科技的不断发展&#xff0c;企业级无代码开发平台成为了一种新型的解决方案&#xff0c;能够有效降低软件开发门槛&#xff0c;提升开发效率。在制造业领域&#xff0c;MES系统&#xff08;Manufacturing Execution System&#xff09;作为一种关键的生产管理工具&#xff…

小程序开发收费多少

一、小程序开发费用一览表 ① 域名费用 域名的价格取决于选择的域名类型&#xff0c;常见的有如.com、.cn等这些&#xff0c;以及注册商的定价策略。比如&#xff0c;一个.com 域名一年的的注册费用大约在 50-200 元人民币之间。 ②服务器费用 服务器费用是根据服务器的配置…

day37-Pokedex(神奇宝贝图鉴卡牌展示)

50 天学习 50 个项目 - HTMLCSS and JavaScript day37-Pokedex&#xff08;神奇宝贝图鉴卡牌展示&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport&qu…

vscode默认gbk编码格式打开

目录 1. 问题描述2. 解决方案 1. 问题描述 每次打开vscode都是utf-8格式打开文件&#xff0c;然后满屏的中文乱码&#xff0c;自己手动换成gbk编码 后中文显示正常&#xff0c;但是换多了很烦。 2. 解决方案 ctrlshiftP 点首选项&#xff1a;打开用户设置 加上这行在最后&…

文件包含漏洞利用思路

简介 通过PHP函数引入文件时&#xff0c;传入的文件名没有经过合理的验证&#xff0c;从而操作了预想之外的文件&#xff0c;导致意外的文件泄漏甚至恶意代码注入。 常见的文件包含函数 php中常见的文件包含函数有以下四种&#xff1a; include()require()include_once()re…

教你合法安全匿名访问网站的10个技巧

大家常说互联网大数据使得每个人上网更透明&#xff0c;确实隐私性和安全性已成为人们在进行互联网浏览时所关注的焦点。无论您想要对匿名访问目标网站进行调查研究&#xff0c;又或者是爬虫业务、跨境业务&#xff0c;完全保持匿名都可能成为关键。 为了帮助您保护更多隐私和安…

【OC总结-weak的底层原理】

文章目录 1. SideTables1.1 StripedMap1.2 SideTable1.3 引用计数refcnts 存储结构RefcountMap1.4 weak_table_t结构体1.4.1 .weak_entry_t结构体 2.1 weak的实现及其调用的相关函数2.1 初始化时&#xff1a;2.1.1 objc_initWeak方法2.1.2 storeWeak方法 2.2 添加引用时&#x…

【C++从0到王者】第十站:手撕string

文章目录 一、String的基本结构二、String的构造函数1.string(const char* str)2.string()3.string(const char* str " ")4.string(const string& s) 二、String的析构函数三、获取String中的字符串四、获取有效元素个数五、operator[]运算符重载六、增删查改之…

Fastify系列-从0到1超详细手把手教你使用Fastify构建快速的API

什么是Fastify&#xff1f; Fastify是一个web框架&#xff0c;高度专注于以最少的开销和强大的插件架构提供最佳的开发体验。它的灵感来自于Hapi和Express&#xff0c;据我们所知&#xff0c;它是运行在Node.js上的最快的Web框架之一。 为什么使用Fastify&#xff1f; 这些是…

PostgreSQL--实现数据库备份恢复详细教学

前言 这是我在这个网站整理的笔记&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;RodmaChen PostgreSQL--实现数据库备份恢复详细教学 一. 数据库备份二. 数据库恢复三. 存留问题 数据库备份恢复功能是每个产品所需的&#xff0c;以下是简单的脚本案例&a…

遇到了一个存在XSS(存储型)漏洞的网站

第一个漏洞self xss&#xff08;存储型&#xff09; 存在漏洞的网站是https://www.kuangstudy.com/ 然后点击个人设置 在编辑主页中&#xff0c;我们可以用最简单的script语句进行注入&#xff0c;提交&#xff1b; 出现弹窗&#xff0c;说明它已经把代码进行解析&#x…

【设计模式学习1】什么是单例模式?单例模式的几种实现。

一、什么是单例模式 单例模式是在内存中只创建一个对象的模式&#xff0c;它保证一个类只有一个实例。 二、单例模式的几种实现 &#xff08;一&#xff09;懒汉式单例模式 /*** 懒汉式单例模式* &#xff08;懒加载&#xff0c;需要的时候在去加载&#xff09;* 优点&…

递归——另类加法、走方格的方案数

大家好&#xff0c;这里是bang_bang&#xff0c;今天来记录2道递归的典型题目 目录 1.另类加法 2.走方格的方案数 1.另类加法 另类加法__牛客网 (nowcoder.com) 给定两个int A和B。编写一个函数返回AB的值&#xff0c;但不得使用或其他算数运算符。 测试样例&#xff1a;…

【Linux】system V IPC原理分析

目录 System V IPC分类 key_t 键和ftok函数 ipc_perm结构 创建和打开IPC通道 IPC权限问题 理解IPC工作原理 System V IPC分类 System V 消息队列System V 共享内存System V 信号量 之所以称为System V IPC是因为这三种IPC机制都是来源于System V Unix的实现。 消息队列信…

一款好用的思维导图软件drawio

最近需要画思维导图&#xff0c;结果发现既然被人用来收费了。所以记录一下&#xff0c;免得大家上当。 首先说明&#xff0c;这个东东在github上是免费开源的&#xff0c;收费的是一些不法分子搞得。下面是收费版本得界面。 开源地址&#xff1a; https://github.com/jgraph…

坑爹的shadow -- 总结 与 各种坑

作者&#xff1a;snwrking 最近公司来了新UX总监, 很喜欢给设计添加浓重的, 而且是好几层的阴影. 这下就苦了我们Android开发了. 因为是Android不支持啊, 巧妇也难为无米之炊啊. (折中方法也不是没有, 就是自己把阴影做个view, 但它的blur这些比较麻烦, 做过Android的都知道这个…

DAY1,Qt [ 手动实现登录框(信息调试类,按钮类,行编辑器类,标签类的使用)]

1.手动实现登录框&#xff1b; ---mychat.h---头文件 #ifndef MYCHAT_H #define MYCHAT_H#include <QWidget> #include <QDebug> //打印信息 #include <QIcon> //图标 #include <QPushButton> //按钮 #include <QLineEdit> //行编辑器类 #in…

ERC20 allowance,approve 和 transferFrom

allowance&#xff0c;approve 和 transferFrom&#xff0c;这几个函数提供了一些高级功能&#xff0c;用于授权其他以太坊地址的所有者(spender)代表你使用你的token。这个“其他以太坊地址”可能是一个智能合约&#xff0c;也可能只是一个普通token账户。 ● approve函数。T…

output delay 约束

output delay 约束 一、output delay约束概述二、output delay约束系统同步三、output delay约束源同步 一、output delay约束概述 特别注意&#xff1a;在源同步接口中&#xff0c;定义接口约束之前&#xff0c;需要用create_generated_clock 先定义送出的随路时钟。 二、out…