【C++11】新的类功能、可变参数模板

news2025/1/21 20:29:35

C++11——新的类功能、可变参数模板

在这里插入图片描述

文章目录

  • C++11——新的类功能、可变参数模板
  • 一、新的类功能
    • 1.测试代码
    • 2.默认成员函数
    • 3.类成员变量初始化
    • 4.强制生成默认函数的关键字default
    • 5.禁止生成默认函数的关键字delete
    • 6.继承和多态中的final与override关键字
  • 二、可变参数模板
    • 1.可变参数模板的概念
    • 2.参数包的展开方式
      • 2.1.递归函数方式展开参数包
      • 2.2.逗号表达式展开参数包
    • 3.STL容器中的empalce相关接口函数

一、新的类功能

1.测试代码

下面的测试案例需用用到移动构造和移动赋值的测试代码,我们给出上篇博文封装过移动构造和移动赋值的简化版string类放到下面,以方便后续的测试代码中进行调用观察现象:

namespace cpp
{
	class string
	{
	public:
        // 构造函数
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// 交换两个对象的数据
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
        // 析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}

2.默认成员函数

原来C++类中(C++11之前),有6个默认成员函数:

  • 1、构造函数

  • 2、析构函数

  • 3、拷贝构造函数

  • 4、拷贝赋值重载

  • 5、取地址重载

  • 6、const 取地址重载

最重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个:移动构造函数和移动赋值运算符重载。针对移动构造和移动赋值,编译器也会默认生成,不过生成的条件极其苛刻,下面展开来讨论:

  • 1、移动构造:
  • 如果你没有自己实现移动构造函数,且均没有实现析构函数 、拷贝构造、拷贝赋值重载(只要实现其中一个,就不满足条件)。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 2、移动赋值:
  • 如果你没有自己实现移动赋值重载函数,且均没有实现析构函数 、拷贝构造、拷贝赋值重载。那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 3、总结:
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
  • 4、示例:
  • 对于如下的Person类,我们不需要写析构、拷贝构造、赋值重载,因为Person的成员变量_ name是自定义类型,会自动去调用string类的拷贝构造、析构、赋值重载完成深拷贝。而内置类型_ age完成值拷贝即可。
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	/*Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}*/
	/*Person& operator=(const Person& p)
	{
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}*/
	/*~Person()
	{}*/
private:
	cpp::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;//拷贝构造
	Person s3 = std::move(s1);//移动构造
	Person s4;
	s4 = std::move(s2);//移动赋值
	return 0;
}
  • 由于VS2013没有完全支持C++11,因此上述代码无法在VS2013当中验证,需要使用更新一点的编译器进行验证,比如VS2019。

因为我们都没写拷贝构造、析构、赋值,所以编译器会默认生成移动构造和移动赋值运算符重载。针对main函数的测试用例,很明显,s2 = s1是拷贝构造,下面的两个分别调用移动构造和移动赋值:

image-20230417210955416

但凡我把Person类中的任何一个拷贝构造或析构或赋值放出来,结果都是去调用string类的拷贝构造函数去完成深拷贝:

image-20230417211328995


3.类成员变量初始化

默认生成的构造函数,对于自定义类型会自动调用它的构造函数进行初始化,对于内置类型并不会进行处理,于是C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这个我们在类和对象默认就讲了,这里再简要提下。

class Person
{
public:
	//……
private:
	//C++11允许非静态成员变量在声明时进行初始化赋值
	string _name = "张三";
	int _age = 20;
	static int _num;//静态成员变量不能给缺省值
};

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

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定生成移动构造或移动赋值。

  • 示例:如下我们实现了拷贝构造,所以编译器就不会生成移动构造和移动赋值了,会去调用自定义类型string类的拷贝构造函数完成深拷贝:
class Person
{
public:
	//构造函数
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	//拷贝构造
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
private:
	cpp::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1; // 拷贝构造
	Person s3 = std::move(s1); // 拷贝构造
	Person s4;
	s4 = std::move(s2); // 拷贝构造
	return 0;
}

为了让编译器生成移动构造和移动赋值,我们可以使用default关键字显示指定生成移动构造或移动赋值:

class Person
{
public:
	//构造函数
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	//拷贝构造
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
	//强制生成移动构造
	Person(Person&& pp) = default;
	//强制生成移动赋值
	Person& operator=(Person&& pp) = default;
private:
	cpp::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1; // 移动构造
	Person s3 = std::move(s1); // 移动构造
	Person s4;
	s4 = std::move(s2); // 移动构造
	return 0;
}

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

在C++中,如果想禁止生成默认成员函数,我们有如下两种方式:

  1. 在C++98中,是该函数设置成private,并且只是声明不定义,这样只要其他人想要调用就会报错。
  2. 在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

示例:我不想让一个类被拷贝,那么在拷贝构造声明的后面加上=delete即可:

image-20230417212647730


6.继承和多态中的final与override关键字

  • 这个我们在继承和多态章节已经进行了详细讲解,这里再强调下:

  • 1、final:修饰虚函数,表示该虚函数不能再被重写,修饰类表示不能被继承。

这里我父类的虚函数Drive不想被其它人重写,在其后面加上final即可,此时子类就无法对Drive进行重写了,如下:

image-20230223201000912

final修饰一个类,让其不能被继承,如下:

image-20230223201052481

  • 2、override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

image-20230223200702293

二、可变参数模板

1.可变参数模板的概念

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

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

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
  • 上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。
  • 模板参数包Args和函数形参参数包args的名称可以自己进指定。

现在调用ShowList函数就可以传入任意个数的任意类型的参数了:

int main()
{
	ShowList(1);
	ShowList(1, 1.1);
	ShowList(1, 1.1, string("######"));
	// ShowList();
	return 0;
}

我们也可以通过sizeof获得参数包的个数,但注意格式:sizeof…(args)

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

我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。语法并不支持使用args[i]这样方式获取可变参数。也不支持auto范围for的方式获取可变参数:

template <class ...Args>
void ShowList(Args... args)
{
	/*不支持args[i]
	for (size_t i = 0; i < sizeof...(args); ++i)
	{
		cout << args[i] << endl;
	}*/
	/*不支持auto范围for
	for (auto& e : args)
	{
		cout << e << endl;
	}*/
}

由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。


2.参数包的展开方式

2.1.递归函数方式展开参数包

递归展开参数包需要实现两个函数,且二者同名:

  • 一、递归函数
  • 二、递归终止函数

我们分开来讨论:

一、递归函数:

  1. 给可变参数的函数模板增加一个模板参数,用于后续获得每一个参数的值
  2. 在该函数模板中递归调用函数模板,把剩下的参数包传进去
  3. 一直递归下去,每次分离参数包中的一个参数,直至全部分离出来

示例:

//递归函数
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << sizeof...(args) << endl;//获取参数包中参数的个数
	cout << val << "->" << typeid(val).name() << endl;
	ShowList(args...);
}

写好了递归函数,接下来完成终止函数,

二、递归终止函数:

这里我们也给出两个终止函数的方式:

  1. 带参的终止函数
  2. 无参的终止函数

先看带参的终止函数,结合递归函数和测试用例一起看:

//带参的终止函数
template <class T>
void ShowList(const T& val)
{
	cout << val << "->" << typeid(val).name() << " end" << endl;
}

根据参数的最匹配原则,当参数包只有一个参数的时候,编译器会优先匹配到此终止函数完成递归终止。接下来把整体的代码加上测试用例一起看:

//带参的终止函数
template <class T>
void ShowList(const T& val)
{
	cout << val << "->" << typeid(val).name() << " end" << endl;
}
//递归函数
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << sizeof...(args) << endl;//获取参数包中参数的个数
	cout << val << "->" << typeid(val).name() << endl;
	ShowList(args...);
}
int main()
{
	ShowList(1, 'x', 1.1);
	return 0;
}

image-20230427193818749

  • 首先,1传给val,把x和1.1传给参数包,推出T的类型为int,参数包的个数为2,打印后,继续递归把x传给val,把1.1传给参数包,推出T的类型为char,参数表的个数为1,打印后再继续递归,此时参数包的个数只有一个,根据模板的最匹配原则,这一个参数会匹配到递归终止函数,
  • 但该方法有一个弊端就是,我们在调用ShowList函数时必须至少传入一个参数,否则就会报错。因为此时无论是调用递归终止函数还是展开函数,都需要至少传入一个参数。

下面来看看无参的递归终止函数:

//无参的终止函数
void ShowList()
{}

此时当参数包的个数为0个的时候,就会走此函数,完成递归终止。

  • 如果外部调用ShowList函数时就没有传入参数,那么就会直接匹配到无参的递归终止函数。
  • 而我们本意是想让外部调用ShowList函数时匹配的都是函数模板,并不是让外部调用时直接匹配到这个递归终止函数。

结合测试用例一起看:

//无参的终止函数
void ShowList()
{}
//递归函数
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << sizeof...(args) << endl;//获取参数包中参数的个数
	cout << val << "->" << typeid(val).name() << endl;
	ShowList(args...);
}
int main()
{
	ShowList(1, 'x', 1.1);
	cout << endl;
	ShowList(1, 2, 3, 4, 5);
	cout << endl;
	ShowList();
	return 0;
}

image-20230427193926254

注意:递归终止的方式不能按照如下的方式写:

//递归函数
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	//错误的写法:
	if (sizeof...(args) == 0)
	{
		return;
	}
	cout << val << "->" << typeid(val).name() << endl;
	ShowList(args...);
}
  • 函数模板并不能调用,函数模板需要在编译时根据传入的实参类型进行推演,生成对应的函数,这个生成的函数才能够被调用。
  • 而这个推演过程是在编译时进行的,当推演到参数包args中参数个数为0时,还需要将当前函数推演完毕,这时就会继续推演传入0个参数时的ShowList函数,此时就会产生报错,因为ShowList函数要求至少传入一个参数。
  • 这里编写的if判断是在代码编译结束后,运行代码时才会所走的逻辑,也就是运行时逻辑,而函数模板的推演是一个编译时逻辑。

2.2.逗号表达式展开参数包

这里我们先给出使用逗号表达式展开参数包的一个例子:

template <class T>
void PrintArg(const T& t)
{
	cout << t << " ";
}
template <class ...Args>
void ShowList(Args... args)
{
	//列表初始化+逗号表达式
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}
int main()
{
	ShowList(1, 'x', 1.1, string("hello world"));
	ShowList(1, 2, 3, 4, 5);
	return 0;
}

下面我将给出其演化的过程:

前面我们学习到了可以使用{ }列表初始化来初始化数组等内置类型和自定义类型,那么我可不可以直接把参数包放到列表初始化呢?

template <class ...Args>
void ShowList(Args... args)
{
	//列表初始化
	int arr[] = { args... };
	cout << endl;
}

这里很明显是不可以的,C++只允许数组里面是同一种类型,但是模板的可变参数就意味着我参数包的类型并不统一,会出现一会是int,一会是char……。为了解决此问题,我们可以单独封装一层函数(PrintArg),此函数专门用于获得参数包的每个数据并输出,但是这又会出现一个问题,我得不到一个返回值放回数组里头,为了解决返回值的问题,又使用了逗号表达式来解决:

  • 逗号表达式会从左到右依次计算各个表达式,并且将最后一个表达式的值作为返回值进行返回。
  • 将逗号表达式的最后一个表达式设置为一个整型值,确保逗号表达式返回的是一个整型值。
  • 将处理参数包中参数的动作封装成一个函数,将该函数的调用作为逗号表达式的第一个表达式。

这里我们把逗号表达式的最后一个值设为0,此时我参数包里有几个参数,那么就有几个0,也就代表有几个值。调整后的代码如下:

template <class T>
void PrintArg(const T& t)
{
	cout << t << " ";
}
template <class ...Args>
void ShowList(Args... args)
{
	//列表初始化+逗号表达式
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}

注意:

  • 可变参数的省略号需要加在逗号表达式外面,表示需要将逗号表达式展开,如果将省略号加在args的后面,那么参数包将会被展开后全部传入PrintArg函数,代码中的{(PrintArg(args), 0)…}将会展开成{(PrintArg(arg1), 0), (PrintArg(arg2), 0), (PrintArg(arg3), 0), etc…}。

此时我们就会发现,就和一开始我们给出的代码一致了,这就是是用来逗号表达式的方式展开参数包。下面给出测试用例:

int main()
{
	ShowList(1);
	ShowList(1, 1.1);
	ShowList(1, 1.1, string("######"));
}

image-20230427194116554

当然,这里其实不用逗号表达式也可以,直接给PrintArg函数带上返回值即可完成逗号表达式的功能:

template <class T>
int PrintArg(const T& t)
{
	cout << t << " ";
	return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
	//列表初始化
	int arr[] = { PrintArg(args)... };
	cout << endl;
}

此时可以传入多种类型的参数了,但是不能不传参数,因为数组的大小不能为0,为了支持不传参数,我们需要单独写个无参的ShowList函数,就像无参版的终止函数那样:

//支持无参调用
void ShowList()
{
	cout << endl;
}
template <class T>
int PrintArg(const T& t)
{
	cout << t << " ";
	return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
	//列表初始化
	int arr[] = { PrintArg(args)... };
	cout << endl;
}

总结:

  • 这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, Printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
  • expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0),(printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

3.STL容器中的empalce相关接口函数

  • http://www.cplusplus.com/reference/vector/vector/emplace_back/
  • http://www.cplusplus.com/reference/list/list/emplace_back/
template <class... Args>
void emplace_back (Args&&... args);

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和

emplace系列接口的优势到底在哪里呢?

int main()
{
	std::list< std::pair<int, char> > mylist;
	// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
	// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
	mylist.emplace_back(10, 'a');
	mylist.emplace_back(20, 'b');
	mylist.emplace_back(make_pair(30, 'c'));
	mylist.push_back(make_pair(40, 'd'));
	mylist.push_back({ 50, 'e' });
	for (auto e : mylist)
		cout << e.first << ":" << e.second << endl;
	return 0;
}

int main()
{
	// 下面我们试一下带有拷贝构造和移动构造的bit::string,再试试呢
	// 我们会发现其实差别也不到,emplace_back是直接构造了,push_back
	// 是先构造,再移动构造,其实也还好。
	std::list< std::pair<int, cpp::string> > mylist;
	mylist.emplace_back(10, "sort");
	mylist.emplace_back(make_pair(20, "sort"));
	mylist.push_back(make_pair(30, "sort"));
	mylist.push_back({ 40, "sort" });
	return 0;
}

总结:

  • emplace系列接口最大的特点就是支持传入参数包,用这些参数包直接构造出对象,这样就能减少一次拷贝,这就是为什么有人说emplace系列接口更高效的原因。
  • 但emplace系列接口并不是在所有场景下都比原有的插入接口高效,如果传入的是左值对象或右值对象,那么emplace系列接口的效率其实和原有的插入接口的效率是一样的。
  • emplace系列接口真正高效的情况是传入参数包的时候,直接通过参数包构造出对象,避免了中途的一次拷贝。

其实这里对可变参数模版讲解的还不够深刻,具体可以看看大佬龙哥(2021dragon)的博文,个人觉得他总结的确实全面,且通俗易懂,下面附上链接:2021dragon -> 可变参数模板

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

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

相关文章

第64章 树型结构数据的前端渲染渲染显示示例

1 \src\views\TreeTestView.vue <template> <div class"wrap"> <!--注意&#xff1a;1、“回到顶部”组件及其回滚内容都必须包含到同1个div容器中。--> <!-- 2、div容器中必须有1个唯1性的样式类&#xff08;例如&#xff1a;wrap&#xff09…

【Kubenetes进阶】Helm3保姆级安装与Chart使用

之前在Centos 7_64位上实现了用kubespray-2.15.0完成kubenetes的部署&#xff0c;对于kubenetes&#xff0c;Helm无疑是一个良好的命令行下的客户端工具。Helm主要用于 Kubernetes 应用程序 Chart 的创建、打包、发布以及创建和管理本地和远程的 Chart 仓库。 需要对helm加深了…

安全狗云安全资源池为运营商行业云降本增效

随着政府大力促进数字化转型&#xff0c;网络安全作为数字化建设的安全基石&#xff0c;已然成为保障数字化过程中稳定运行的重要因素。 一 严峻挑战下用户亟需安全托管专业服务 1) 云计算虚拟化的存储、部署及运作模式&#xff0c;数据所有权与管理权分离等特点&#xff0c…

【iOS】—— KVO再学习

KVO 文章目录 KVOKVO概念KVO使用步骤注册KVO监听KVO监听实现移除KVO监听 KVO基本用法KVO传值禁止KVO的方法使用注意事项 KVO原理GSKVOInfoGSKVOPathInfoGSKVOObservation为什么要重写class方法呢&#xff1f; GSKVOReplacementGSKVOBaseGSKVOBase小结 源码实现移除观察者总结 K…

unity Shader实现半透明阴影

在shader中&#xff0c;要对移动端的兼容&#xff0c;还不想实现两套分开兼容的话&#xff0c; #pragma exclude_renderers gles gles3 glcore #pragma target 4.5这两句话一定要改掉&#xff0c;第一行代码直接剔除了gles的渲染&#xff0c;而恰恰大部分移动端都是用的gles&a…

第十七章 访问者模式

文章目录 前言一、访问者模式基本介绍二、访问者模式应用实例完整代码评测抽象类 Action成功评价 Success失败评价评价人抽象类男性女性数据结构&#xff0c;管理很多人评价Clint 测试添加 wait 选票clint 测试 三、访问者模式的注意事项和细节 前言 一、访问者模式基本介绍 二…

Vue2 脚手架下载及配置淘宝镜像--和ieda的配置和打开

目录 Vue2 脚手架下载及配置淘宝镜像 为什么需要 Vue Cli 脚手架? 环境配置&#xff0c;搭建项目 1. 搭建 Vue2 脚手架工程&#xff0c; 2.查看 3.冲突 4. 下载 5. 安装 6. 验证 7. 先删除以前的 cli 版本<不论是之前未下载或没有下载> 8. 安装淘宝镜像-cnpm…

20230427配置cv1826的buildroot在串口免登录的方法二

20230427配置cv1826的buildroot在串口免登录的方法二 2023/4/27 16:46 进度&#xff1a;可以拿掉密码&#xff0c;但是无法拿掉用户名&#xff01; 1、 Z:\buildroot1\buildroot\configs\cvitek_cv182x_defconfig BR2_TOOLCHAIN_EXTERNAL_GCC_6y BR2_TOOL…

编译一个魔兽世界开源服务端Windows需要安装什么环境

编译一个魔兽世界开源服务端Windows需要安装什么环境 大家好我是艾西&#xff0c;去年十月份左右wy和bx发布了在停服的公告。当时不少小伙伴都在担心如果停服了怎么办&#xff0c;魔兽这游戏伴随着我们渡过了太多的时光。但已经发生的事情我们只能顺其自然的等待GF的消息就好了…

Selenium基础篇之键盘操作(一)

文章目录 前言一、常用方法(上)二、小剧场2.1场景2.2代码2.2.1引入库2.2.2启动浏览器实例2.2.3访问C站首页2.2.4窗口最大化2.2.5获取输入框元素2.2.6向输入框输入文字2.2.7使用退格键删除最后一个字符2.2.8全选输入框文字2.2.9剪切输入框文字2.2.10粘贴文字到输入框2.2.11回车查…

经验分享 | 科研行业如何搭建RASP防护体系?

多年来&#xff0c;开源网安长期服务金融、政府、科技等大型企业&#xff0c;针对不同实际应用场景&#xff0c;打造出了成熟落地的解决方案&#xff0c;提高并完善了企业安全防护技术与管理体系。今天&#xff0c;我们就来了解开源网安是如何为科研行业搭建RASP防护体系的。 科…

“ ES6+ —— 让你的JavaScript代码从平凡到精彩 “

前期回顾 【提高代码可读性】—— 手握多个代码优化技巧、细数哪些惊艳一时的策略_0.活在风浪里的博客-CSDN博客代码优化对象策略https://blog.csdn.net/m0_57904695/article/details/128318224?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%…

GQCNN

Berkeley AUTOLAB’s GQCNN Package — GQCNN 1.1.0 documentation (berkeleyautomation.github.io) (3条消息) 机器人抓取&#xff08;六&#xff09;—— 抓取点检测&#xff08;抓取位姿估计&#xff09; gqcnn代码测试与解读_zxxRobot的博客-CSDN博客 GQ-CNN模型对生成数据…

C#,OpenCv开发指南(02)——OpenCvSharp编程入门与矩阵Mat的基础知识

在 Visual Studio 中很方便搭建与使用 OpenCV 的 C# 的开发环境&#xff0c;几乎不用键盘输入。 使用 C# 开发 OpenCV 可以直接成为工业软件产品&#xff0c;而不是实验室程序。世界上几乎所有的视频厂家都提供 C# OpenCV 开发接口。 C#&#xff0c;人工智能&#xff0c;深度学…

常见jvm调优操作详细记录

最近很多小伙伴跟我说&#xff0c;自己学了不少JVM的调优知识&#xff0c;但是在实际工作中却不知道何时对JVM进行调优。今天&#xff0c;我就为大家介绍几种JVM调优的场景。 cpu占用过高 cpu占用过高要分情况讨论&#xff0c;是不是业务上在搞活动&#xff0c;突然有大批的流…

SpringBoot整合ELK做日志(超完整)

SpringBoot整合ELK日志系统 SpringBoot整合ELK做日志环境准备安装包准备ELK安装包Java11安装包 软件安装安装java11安装ElasticSearch安装Kibana安装Logstash 编写SpringBoot项目初始化SpringBoot项目修改代码编写Controller SpringBoot整合ELK做日志 环境准备 提前准备一台C…

SpringBoot+Linux操作系统与项目部署(这一篇就够了|超详细)

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

华为联合openEuler发布全新NFS+协议,实现NAS存储性能与可靠性倍增

在openEuler开发者大会2023上&#xff0c;华为携手openEuler发布NFS协议&#xff0c;实现单客户端访问NAS存储可靠性提升3倍、性能提升6倍&#xff0c;助力NAS存储全面满足新型生产核心场景下苛刻要求。 在数字转型的新时代&#xff0c;非结构化数据价值持续提升。金融、大企业…

一种IPC通信机制Gdbus详解

一、DBus介绍 常规进程间通信有管道&#xff0c;消息队列&#xff0c;共享内存&#xff0c;以及socket等&#xff0c;每个都有优劣&#xff0c;这次我们介绍一种高阶的进程间通信方式DBus。 DBus通信是IPC通信机制的一种方式&#xff0c;本身是建立在socket机制之上&#xff0…

计算机网络——快速了解常见应用层协议体系

文章目录 〇、加密算法对称加密非对称加密 一、远程登录——TELNET、SSH1.Telnet2.SSH 二、文件传输——FTP、SFTP、FTPS1.FTP2.SFTP3.FTPSSSL工作原理 三、电子邮件——SMTP、POP、IMAP1.SMTP&#xff08;推送邮件&#xff09;2.POP&#xff08;接收邮件&#xff09;3.IMAP 四…