C++11|完美转化 新的类功能 可变参数模板

目录

 一、完美转发

1.1模板中的&&万能引用

1.2完美转发

1.3完美转发实际中的引用场景

二、新的类功能

2.1移动构造和移动赋值规则详解

2.2类成员变量初始化和强制生成默认函数(default)

2.3禁止生成默认函数的关键字(delete)

三、可变参数模板

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

3.2逗号表达式展开参数包 

3.3可变参数模板使用场景(emplace优势分析)


 一、完美转发

在上一篇章中,学习了右值引用,我们知道右值引用基本上只能接收右值,对于左值加上move函数才能接收,但是这一章节重点不是在这里,而是当&&用在模板当中就不再代表右值引用了,而是万能引用 ,其既能接收左值又能接收右值。同时,改万能引用又存在一定的缺陷,不管该万能引用变量接收的是左值还是右值,要使用该引用变量,其都会被当成左值,就失去了原来的含义了。如下代码:

1.1模板中的&&万能引用

#include <iostream>
using namespace std;

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);//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;
}

输出结果:

那么为了在传递过程中能够保持原来的属性,就可以使用完美转发。

1.2完美转发

std::forward完美转发在传参的过程中保留对象原生类型属性 

 那么对于上述代码,只需对需要保持原属性的变量进行完美转发,其他代码不变,如下:

Fun(forward<T>(t));//完美转发,保持t的原生属性

 输出结果:

1.3完美转发实际中的引用场景

采用完美转发,可以保持引用变量拥有原来的属性,若是右值,当要进行拷贝时,就可以调用移动构造,来减少拷贝构造。例如:

#include <iostream>
using namespace std;


template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};
template<class T>
class List
{
	typedef ListNode<T> Node;
public:
	List()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	void PushBack(T&& x)
	{
		//Insert(_head, x);
		Insert(_head, std::forward<T>(x));//保持x原来的属性
	}
	void PushFront(T&& x)
	{
		//Insert(_head->_next, x);
		Insert(_head->_next, std::forward<T>(x));//保持x原来的属性
	}
	void Insert(Node* pos, T&& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = std::forward<T>(x); // 插入的时候也能够保持原来的属性,就可以调用移动构造,减少深拷贝
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
	void Insert(Node* pos, const T& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = x; // 关键位置
		// prev newnode pos
		prev->_next = newnode;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};
int main()
{
	List<string> lt;
	lt.PushBack("1111");
	lt.PushFront("2222");
	return 0;
}

二、新的类功能

2.1移动构造和移动赋值规则详解

上一篇章,我们知道c++11中增加了两个默认成员函数,移动构造函数和移动赋值运算符重载函数。相应的对于这两个函数也围绕着一定的规则。

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

为什么条件会如此苛刻?

思考一下,会发现,这并不是凭空这么严格,移动构造、移动赋值针对的是深拷贝,若是自己实现了移动构造、移动赋值,从推理来说,这是解决深拷贝,就没必要去实现拷贝构造、拷贝赋值,编译器也没必要去提供。反过来,若没有实现移动构造、移动赋值,说明是一些浅拷贝,若也没有特定的意向去指定浅拷贝怎么实现去向(析构、拷贝构造、赋值重载),那么可以由编译器默认生成的移动构造来实现浅拷贝。 同理,对于自定义类型也一样。

通过一段代码来展示上述的理论:

#include <iostream>
using namespace std;


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

	//提供了移动构造,那么编译器不会自动提供拷贝构造和拷贝赋值,所以得自己提供拷贝构造和拷贝赋值
	Student(Student&& s)
		:_name(move(s._name))
		,_age(s._age)
	{}

	// Copy constructor
	Student(const Student& s)
		: _name(s._name)
		, _age(s._age)
	{}

	// Copy assignment operator
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			_name = s._name;
			_age = s._age;
		}
		return *this;
	}
private:
	string _name;
	int _age;
};
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
		,_stu("张三",13)
	{}
	
private:
	string _name;
	int _age;
	Student _stu;//自定义类型成员
};
int main()
{
	Person s1;
	Person s2 = s1;//对于Person类,没有移动构造,析构,拷贝构造,赋值重载。将s1拷贝给s2,对于内置类型就就是按成员逐字节拷贝,对于自定义(_stu)
	//,由于s1是左值,会去调用它的拷贝构造

	//同理,move(s1)是右值,由于自定义类型中实现了移动构造,就去调用移动构造,否则调用拷贝构造
	Person s3 = move(s1);

	//同理,move(s2)是右值,由于自定义类型中未实现移动赋值,就去调用拷贝赋值重载
	Person s4;
	s4 = move(s2);
	return 0;
}

 调试监口:

上述结果还需小伙伴自己动手观察现象才能更加清楚的明白其中的道理。

2.2类成员变量初始化和强制生成默认函数(default)

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

我们知道default可以强制生成默认构造函数,对于提供了拷贝构造,编译器就不会提供移动构造了,如果要生成移动构造,那么对此就可以使用default

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

	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
		, _stu(p._stu)
	{}
	Person& operator=(const Person& p)
	{
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
			_stu = p._stu;
		}
	}
	Person(Person&& p) = default;//强制生成默认移动构造,相应的,编译器就不会默认生成拷贝构造,赋值重载,而main函数自己写的代码中又要用到这两个构造函数,那么就需要自己提供拷贝构造、赋值重载
private:
	string _name;
	int _age;
	Student _stu;
};

2.3禁止生成默认函数的关键字(delete)

在c++98要想限制某些默认函数的生成,可以将该函数设置为private,并且只声明不定义,对于其他人想要调用就会报错。在c++11中,只需在声明加上=delete即可,告诉编译器不生成对应的默认函数,称该函数被删除。

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
		, _stu("张三", 13)
	{}
	Person(const Person& p) = delete;
private:
	string _name;
	int _age;
	Student _stu;
};

对于新的功能还有override和final,这里就不在多说了,都在多态章节中讲过了。 

override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。其加在函数重写后面

final:修饰虚函数,表示该虚函数不能再被重写。这就隔绝了函数重写可能带来的错误。

三、可变参数模板

c++11的新特性可变参数模板是一个可以接收多个参数的模板,其中参数类型是...Args,用该参数类型定义一个参数包的方式为Args ...args,把带省略号的参数叫做模板参数包,它里面包含了0到N个模板参数。对于可变参数模板特性,这里只能学点皮毛,要想深入得另外循序渐进。

定义形式:

template <class ...Args>

void 函数名(Args... args){}

//其中,中间的省略号是连着的,但该串省略号可以不与旁边的字符串连着,只要在两者之间即可,例如:class  ...   Args           Args   ...   args

对于可以包含这么多个模板参数到底是什么意思呢?又该如何获取里头的参数?

参数包的意思是可以通过外面传递多个参数给这个参数包,当要获取里头的参数时,就可以通过展开参数包来获取,虽然这是获取参数的方式,但同时也是难点,对于展开参数包,这里有两种方式。

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

#include <iostream>
using namespace std;

template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}
// 递归展开参数包
template <class T, class  ...Args>
void ShowList(T value, Args... args)//参数value表示第1个参数,后面的参数包是从第2个参数开始(更符合规范)
{
	cout << value << " ";
	ShowList(args...);//若参数大于1个,就递归该多参数函数,若为1就调用上面单参函数。每次递归会取到当前参数交给value,直到剩一个参数会调用单参数函数
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

输出结果:

3.2逗号表达式展开参数包 

#include <iostream>
using namespace std;

template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}
// 逗号表达式展开参数包
template <class  ...Args>
void ShowList( Args... args)
{
	((cout << args << " "), ...);// 该逗号表达式是一个折叠表达式,它会依次展开参数包 args,(cout << args2 << " ",cout << args3 << " ",...)
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

输出结果:

其中折叠表达式需要用到/std:c++17。那么如何设置?

点击项目,打开属性,到此页面,找到c/c++->语言->c++语言标准(默认是c++14标准)->选择c++17标准,应用确定即可

3.3可变参数模板使用场景(emplace优势分析)

对于可变参数模板,用到的就是STL容器的emplace系列接口,之前不提,是因为不了解,那么现在有了基础,可以尝试来理解。例如:

emplace系列借口也是插入接口,支持可变参数,还有万能引用。那么相对insert,emplace有什么优势,在用法上有什么区别吗?

我们拿出自己的list类并进行修改,添加对应的右值版本,主要是节点、插入的添加,相应的左值需要通过move变右值来满足参数传递,以及通过forward进行完美转发维持原来的属性。结合自己的string类,来观察emplace现象。

list.h:

//list.h
#pragma once
#include <iostream>
#include <vector>
using namespace std;


namespace bit
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		list_node(const T& x = T())
			:_data(x)
			, _next(nullptr)
			, _prev(nullptr)
		{}

		//右值引用版本
		list_node(T&& x)
			:_data(move(x))
			, _next(nullptr)
			, _prev(nullptr)
		{}

		//参数包版本
		template <class... Args>
		list_node(Args&&... args)
			: _data(args...)
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};

	// T T& T*
	// T cosnt T& const T*
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		Node* _node;

		__list_iterator(Node* node)
			:_node(node)
		{}

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;

			return tmp;
		}

		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;

			return tmp;
		}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const self& s)
		{
			return _node != s._node;
		}

		bool operator==(const self& s)
		{
			return _node == s._node;
		}
	};


	

	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

		//typedef __list_const_iterator<T> const_iterator;

		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}

		iterator begin()
		{
			//return iterator(_head->_next);
			return _head->_next;
		}

		iterator end()
		{
			//return iterator(_head->_next);
			return _head;
		}

		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;

			_size = 0;
		}

		list()
		{
			empty_init();
		}

		// lt2(lt1)
		list(const list<T>& lt)
		{
			empty_init();
			for (auto e : lt)
			{
				push_back(e);
			}
		}



		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		// lt3 = lt1
		list<int>& operator=(list<int> lt)
		{
			swap(lt);

			return *this;
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		void push_back(const T& x)
		{
			insert(end(), x);
		}

		void push_back(T&& x)
		{
			insert(end(), forward<T>(x));
		}

		template <class... Args>
		void emplace_back(Args&&... args)
		{
			Node* newnode = new Node(args...);
			// 链接节点。。。
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void pop_front()
		{
			erase(begin());
		}

		void pop_back()
		{
			erase(--end());
		}

		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(x);

			Node* prev = cur->_prev;

			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;

			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;

			return iterator(newnode);
		}

		iterator insert(iterator pos, T&& x)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(forward<T>(x));

			Node* prev = cur->_prev;

			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;

			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;

			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			delete cur;
			prev->_next = next;
			next->_prev = prev;

			--_size;

			return iterator(next);
		}

		size_t size()
		{
			return _size;
		}

	private:
		Node* _head;
		size_t _size;
	};	
}

test.cpp: 

//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)


#include "list.h"

#include <assert.h>

namespace bit
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str) -- 构造" << endl;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str);
			swap(tmp);
		}

		// 移动构造
		string(string&& s)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;

			swap(s);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷贝" << endl;
			/*string tmp(s);
			swap(tmp);*/
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);

				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s)-- 移动赋值" << endl;

			swap(s);
			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0; // 不包含最后做标识的\0
	};

	bit::string to_string(int x)
	{
		bit::string ret;
		while (x)
		{
			int val = x % 10;
			x /= 10;
			ret += ('0' + val);
		}

		reverse(ret.begin(), ret.end());

		return ret;
	}
}



int main()
{
	bit::list<bit::string> lt;//深拷贝
	bit::string s1("hello world");
	cout << endl;
	lt.push_back(s1);//深拷贝


	lt.push_back(bit::to_string(1234));//移动构造+移动构造
	cout << endl;
	lt.push_back("1122");//移动构造


	return 0;
}

 输出结果:

由上述结果结合代码分析,验证了代码的正确性,接下来进行emplace的实验

int main()
{
	bit::list<bit::string> lt;//深拷贝
	cout << endl;


	lt.push_back("xxxx");//构造+移动构造

	lt.emplace_back("1122");//直接构造


	return 0;
}

 输出结果:

将string中的构造的注释给打开,由结果分析,对于传递单个参数而言,emplace_back是直接将字符串给构造了,并没有进行移动构造。这是为何,再来看看传递多参数。

int main()
{
	bit::list<pair<bit::string,int>> lt;//深拷贝
	cout << endl;

	lt.push_back(make_pair("111", 1));

	lt.emplace_back("20", 2);

	return 0;
}

输出结果:

 由结果分析,emplace_back依然是直接构造,因为emplace_back传递的是参数包,会一层一层往下传,最后直接调用data的构造,如图:

 通过以上的实验,可以发现,emplace对比insert仅仅减少了一个移动构造,而移动构造的时间复杂度并不多高,所以emplace确实比insert好一点,但是并不是说好到哪里去,当然这是深拷贝的情况,但是对于浅拷贝就不一样了,当浅拷贝比较大时,对于insert来说时间复杂度较高,而对于emplace直接构造才会有更大的优势。

end~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TCP和IP数据包结构

一、问题引入 一般我们在谈上网速度的时候&#xff0c;专业上用带宽来描述&#xff0c;其实无论说网速或者带宽都是不准确的&#xff0c;呵呵。比如&#xff1a;1兆&#xff0c;512K……有些在学校的学生&#xff0c;也许会有疑问&#xff0c;明明我的业务是1M&#xff0c;为…

某yi逆向sign值

1. 定位 url: aHR0cHM6Ly93d3cuaXFpeWkuY29tL3ZfMTlycjRyZ3AxZy5odG1s打开网址&#xff0c;找到sign值所在位置 应该是32位大写md5加密&#xff0c;可以看到console中加载出来很多东西&#xff0c;往下翻到base_info所在位置 点进去main.js&#xff0c;打上断点 2. 调试 刷…

项目基础知识

1.JDBC编程和MySQL数据库 数据库的连接&#xff08;以前写qq项目时的代码&#xff09; package com.wu.Util; import java.sql.*; public class JDBCUtil {private static JDBCUtil jdbcUtil null;private JDBCUtil() {}public static JDBCUtil getJdbcUtil() {if (jdbcUtil…

基于Echarts进行图表组件的封装

什么是Echarts 是一个使用js实现的开源可视库&#xff0c;提供了多种图表&#xff0c;但是当我们在项目中进行使用的时候可能就是需要进行一系列的相关配置如&#xff1a; 标题&#xff0c;类型&#xff0c;x轴&#xff0c;y轴等&#xff0c;当我们使用较为频繁的时候就容易导…

昇思25天学习打卡营第16天 | DCGAN生成漫画头像

这两天把minspore配置到我的电脑上了&#xff0c;然后运行就没什么问题了✨&#x1f60a; 今天学这个DCGAN生成漫画头像&#xff0c;我超级感兴趣的嘞&#x1f984;&#x1f970; GAN基础原理 这部分原理介绍参考GAN图像生成。 DCGAN原理 DCGAN&#xff08;深度卷积对抗生成…

一本超简单能用Python实现办公自动化的神书!让我轻松摆脱办公烦恼!

《超简单&#xff1a;用Python让Excel飞起来》 这本书旨在通过Python与Excel的“强强联手”&#xff0c;为办公人员提供一套高效的数据处理方案。书中还介绍了如何在Excel中调用Python代码&#xff0c;进一步拓宽了办公自动化的应用范围。 全书共9章。第1~3章主要讲解Python编…

【数据结构】06.栈队列

一、栈 1.1栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out)的原则。 压栈&#…

JAVA 对象存储OSS工具类(腾讯云)

对象存储OSS工具类 import com.qcloud.cos.COSClient; import com.qcloud.cos.ClientConfig; import com.qcloud.cos.auth.BasicCOSCredentials; import com.qcloud.cos.auth.COSCredentials; import com.qcloud.cos.model.ObjectMetadata; import com.qcloud.cos.model.PutObj…

洗地机品牌哪个最好用?硬核推荐五大实力爆款洗地机

在这个忙碌的时代&#xff0c;家就是我们放松的港湾&#xff0c;但要保持它的清洁与舒适常常很不容易。每天拖着疲惫的身体回家&#xff0c;还要面对地板上那些难缠的灰尘、污渍&#xff0c;真是非常让人头疼。不过&#xff0c;洗地机的出现就像是给家务清洁装上了智能引擎&…

idea中maven全局配置

配置了就不需要每次创建项目都来设置maven仓库了。 1.先把项目全关了 2. 进入全局设置 3.设置maven的仓库就可以了

一篇文章带你完全理解C语言数组

文章目录 1.一维数组的创建和初始化数组的创建1.2数组的初始化1.3 一维数组的使用1.4一维数组在内存中的存储 2.二维数组的创建和初始化2.1二维数组的创建2.2 二维数组的初始化2.3 二维数组的使用2.4 二维数组在内存中的存储 3.数组越界4.数组作为函数参数4.1 冒泡排序函数的错…

从零开始开发美颜SDK:打造属于平台的主播美颜工具

本篇文章&#xff0c;小编将从零开始&#xff0c;介绍如何打造一款属于平台的主播美颜工具。 一、需求分析 首先&#xff0c;明确开发美颜SDK的需求是至关重要的。当前市场上&#xff0c;美颜工具的功能主要包括&#xff1a; 1.实时美颜&#xff1a;磨皮、美白、瘦脸等基础功…

Static关键字的用法详解

Static关键字的用法详解 1、Static修饰内部类2、Static修饰方法3、Static修饰变量4、Static修饰代码块5、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java编程语言中&#xff0c;static是一个关键字&#xff0c;它可以用于多种上…

项目机会:4万平:智能仓,AGV,穿梭车,AMR,WMS,提升机,机器人……

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 如下为近期国内智能仓储物流相关项目的公开信息线索&#xff0c;这些项目具体信息会发布到知识星球&#xff0c;请感兴趣的球友先人一步到知识星球【智能仓储物流技术研习社】自行下载…

时钟系统框图(时钟树)解析

时钟系统框图&#xff08;时钟树&#xff09;解析 文章目录 时钟系统框图&#xff08;时钟树&#xff09;解析1、时钟树2、 4个时钟源&#xff1a;$HSI、HSE、LSI、LSE$3、PLL锁相环倍频输出4、系统时钟的来源5、Enable CSS&#xff08;时钟监视系统&#xff09;6、几个重要的时…

微信开发者工具使用

1.下载微信开发者工具 https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html 2.下载小程序项目代码 3.用微信开发者工具导入项目代码 4.npm安装依赖 5.构建 6.修改测试环境 7.清除缓存 观察切换test后&#xff0c;登录时是否test字样提醒&#xff0c;若…

使用Python+OpenCV实现姿态估计--20240705

姿态估计使用Opencv+Mediapipe来时实现 什么是Mediapipe? Mediapipe是主要用于构建多模式音频,视频或任何时间序列数据的框架。借助MediaPipe框架,可以构建令人印象深刻的ML管道,例如TensorFlow,TFLite等推理模型以及媒体处理功能。 安装命令: pip install mediapipe如果…

大模型提示词工程和落地思考

本文是一篇内部的个人分享&#xff08;已无敏感信息&#xff09; &#xff0c;目的是增加产品、开发同学对 LLM 的理解&#xff0c;以降低沟通中的阻力&#xff0c;更好推进落地。 以下经脱敏后的原文: 大模型并不神奇 很多人听到’大模型’这个词可能会觉得很神秘&#xff…

centos7固定ip

1.查看虚拟网络配置 2.修改网卡配置文件 [jiajinglocalhost ~]$ su - Password: Last login: Thu Jul 4 19:06:16 PDT 2024 on pts/0 [rootlocalhost ~]# vim /etc/sysconfig/network-scripts/ifcfg-ens33 TYPE"Ethernet" PROXY_METHOD"none" BROWSER_ON…

倘若你的的B端系统如此漂亮,还担心拿不出手吗,尤其是面对客户

如果你的B端系统设计如此漂亮&#xff0c;那么通常来说&#xff0c;你不太需要担心在客户那里拿不出手。一个漂亮和易用的设计可以提升用户体验&#xff0c;增加客户对系统的满意度。 然而&#xff0c;还是有一些因素需要考虑&#xff0c;以确保你的B端系统在客户那里能够得到良…