【C++进阶之路】C++11(中)

news2024/10/7 14:27:07

一、可变参数模板

1.基本概念

 想要了解C语言的可变参数列表的原理可见:可变参数列表

 这个跟C语言的可变参数列表有一定的关系,常用的printf与scanf的参数就包含可变参数列表。

 那么可变参数模板是什么呢?举个例子便一目了然。

template<class...Arg>
void ShowList(Arg... arg)
{
	//...
}
int main()
{
	ShowList();
	ShowList(1, 2, 3);
	ShowList('a', 'b', 'c');
	return 0;
}

 这就是可变参数模板,可以传多个参数,这里的Arg称为模板的可变参数包,这里的arg称为形参的可变参数包(包括0-N个参数)。

但是这里有一个问题即如何用这个参数包呢?

  1. 递归展开
void _ShowList()
{
	cout<< endl;
}

template<class T,class...Arg>
void _ShowList(T val, Arg... arg)
{
	cout << val << " ";
	_ShowList(arg...);
}

template<class...Arg>
void ShowList( Arg... arg)
{
	_ShowList(arg...);
}
int main()
{
	ShowList();
	ShowList(1, 2, 3);
	ShowList('a', 'b', 'c');
	return 0;
}
  • 编译器以类似递归的方式(调用的不是同一个函数,是来自一个模板实例化出的函数)自动推导出参数包的类型然后进行打印。

运行结果:
在这里插入图片描述

  1. 逗号表达式
template<class T>
int PrintArg(T val)
{
	cout << val << " ";
	return 0;
}

void ShowList()
{
	cout << endl;
}

template<class... Arg>
void ShowList(Arg... arg)
{
	int arr[] = { PrintArg(arg)... };
	//处理成类似与int arr[] = { PrintArg(arg), PrintArg(arg),PrintArg(arg)};这样的形式。
	//这是编译链接阶段处理的结果。
	cout << endl;
}

int main()
{
	ShowList();
	ShowList(1, 2, 3);
	ShowList('a', 'b', 'c');
	return 0;
}

运行结果:
在这里插入图片描述

2.应用场景

1.提升传参的灵活性

例:

class Date
{
public:
	Date(int year = 0, int month = 0, int day = 0)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
public:
	int _year;
	int _month;
	int _day;
};

template<class ...Args>
Date* CreatDate(Args... args)
{
	Date* ret = new Date(args...);

	return ret;
}

int main()
{
	Date* date1 = CreatDate(1);
	Date* date2 = CreatDate(1,2);
	Date* date3 = CreatDate(1,2,3);
	Date* date4 = CreatDate(Date(1,2,3));
	return 0;
}

  • 不仅可以调用构造函数,也可调用默认的拷贝构造函数,进而提高了灵活性。

2.接口——emplace_back

参数:
在这里插入图片描述

  • 说明:这里在可变参数模板的基础上添加了万能引用。
int main()
{
	list<pair<int, MY_STL::string>>lt;
	//先调用pair的构造函数再调用右值的移动拷贝构造。
	lt.push_back(make_pair(1, "张三"));
	cout << endl;
	lt.push_back({ 2, "李四" });

	cout << endl << endl;
	
	//作为参数包(引用)传递下去,直接调用list的构造函数进行构造
	lt.emplace_back(3, "王五");
	cout << endl;

	lt.emplace_back(make_pair(4, "赵六"));//这里编译器进行了优化,也是直接调用构造函数进行初始化。

	return 0;
}
  • 可见push_back虽然多调用了两次构造但好在是移动构造消耗不大,emplace_back直接将参数包传下去,直接调用构造函数进行初始化,总而言之与push_back有区别但性能上相差不大。

  • 举例 : list的emplace_back

//list里面的实现接口
	template <class... Args>
	void push_back(Args&&... args)
	{
		insert(end(), args...);
	}
	template <class... Args>
	void emplace_back(Args&&... args)
	{
		push_back(args...);
	}
	template <class... Args>
	void insert(iterator pos, Args&&... args)
	{
		Node* newnode = new Node(args...);
		Node* cur = pos._node;
		Node* prev = cur->_prev;
	
		newnode->_next = cur;
		newnode->_prev = prev;
	
		cur->_prev = newnode;
		prev->_next = newnode;
		_size++;
	}
//Node的实现接口
	template<class ...Args>
	list_node(Args&& ...arg)
		:_val(arg...)
		, _next(nullptr)
		, _prev(nullptr)
	{}
//实现逻辑——不断传参数包,将参数包传给构造函数进行初始化。

二、包装器

1. 简单实现

 如何用外界控制某段执行逻辑?

  • C语言阶段我们可以通过传函数指针
  • C++阶段我们可以通过传仿函数或者lambda(底层是仿函数)

 这三者有没有什么共同的特性呢?就是可以像函数一样进行使用。如果实现的功能一样那么参数和返回值是一样的。

 根据这两个性质,我们可以将这三者抽象成一个模板,以实现减法举例:

template<class T1,class T2,class T3>
double fun(T1 f, const T2& x, const T3& y)
{
	return f(x, y);
}
double sub(int x, int y)
{
	return x - y;
}
struct Sub
{
	double operator()(int x, int y)
	{
		return x - y;
	}
};
int main()
{
	auto x = [](int x, int y)->double {return x - y;};//lambda表达式。
	cout << fun(x, 1, 2) << endl;
	cout << fun(sub, 4, 2) << endl;
	Sub s;
	cout << fun(s, 9, 2) << endl;
	return 0;
}

 这样的一个模板就将这三者进行泛化,都可以通过模板实例化使用,也就是实现了包装的作用,当然这只是最简单的用法,下面我们就进入库里面的包装器的使用。

2. 基本语法与使用

在这里插入图片描述

重点:class function<Ret()Args...>;
1. Ret 返回值
2. Args... 函数参数

简单使用:

double sub(int x, int y)
{
	return x - y;
}
struct Sub
{
	double operator()(int x, int y)
	{
		return x - y;
	}
};
int main()
{
	auto x = [](int x, int y)->double {return x - y;};
	function<double(int, int)> f1 = x;
	function<double(int, int)> f2 = s;
	function<double(int, int)> f3 = sub;
	cout << f1(2 , 3) << endl;
	cout << f2(2, 9) << endl;
	cout << f3(9, 9) << endl;
	return 0;
}

更为实际的使用:
比如:逆波兰表达式的求值

class Solution {
public:
    int evalRPN(vector<string>& tokens) 
    {
        map<string,function<int(int,int)>> s = {
            {"+",[](int x,int y){return x + y;}},
            {"-",[](int x,int y){return x - y;}},
            {"*",[](int x,int y){return x * y;}},
            {"/",[](int x,int y){return x / y;}}
        };
        stack<int> st;
        int size = tokens.size();
        for(int i = 0; i < size; i++)
        {
            if(tokens[i] == "+" || tokens[i] == "-"
            || tokens[i] == "*" || tokens[i] == "/")
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop(); 
                st.push(s[tokens[i]](left,right));
            }
            else
            {
                st.push(stoi(tokens[i]));
            }
        }
        return st.top();
    }
};

 仔细品味这段代码,就会发现包装器的好用之处,其原因在于直接将一类功能类似的函数进行泛化,可以通过具体的反应进行直接调用,还有我们所写的一类代码统一的出现在一个地方这样便于维护

三、bind

 bind也是函数的封装,类似与包装器,不过更详细的是对参数的个数和传参顺序的封装,下面我们来看看如何具体使用。

1.基本语法

在这里插入图片描述

  • 将参数1(函数指针/ 仿函数/lambda),剩余函数参数传进去即可,具体要进行分类进讨论。

2.常见用法

1.函数指针

  • 改变参数顺序
double sub(int x, int y)
{
	return x - y;
}
int main()
{
	function<double(int, int)> f1 = bind(sub, placeholders::_1,\
	  placeholders::_2);
	cout << f1(1, 2) << endl;
	
	function<double(int, int)> f2 = bind(sub, placeholders::_2,\
	 placeholders::_1);
	cout << f2(1, 2) << endl;
	return 0;
}

逻辑图解:
在这里插入图片描述

  • 改变参数个数
double sub1(int x, double rate, int y)
{
	return (x - y) * rate;
}
int main()
{
	function<double(int, int)> ff1 = bind(sub1, placeholders::_1,\
	 1.5, placeholders::_2);
	 //在定义期间固定参数,类似与缺省值的效果。
	cout << ff1(2, 1) << endl;

	auto ff2 = bind(sub1, placeholders::_1, 1, placeholders::_2);
	cout << ff2(3, 1) << endl;
}

逻辑图解:
在这里插入图片描述

  • 说明:lambda与函数指针的用法相同。

2.仿函数

struct SubType
{
	static double sub(int x, int y)//没有this指针,不用进行传参
	{
		return x - y;
	}
	double ssub(int x, int y)
	{
		return x - y;
	}
};
int main()
{
	function<double(int, int)> pf1 = bind(SubType::sub,\
	 placeholders::_1, placeholders::_2);
	//sub为static函数,没有this指针,只需指定作用域即可。

	SubType st;		//语法规定普通成员函数传参需加&
	auto pf3 = bind(&SubType::ssub, &st, placeholders::_1,\
	 placeholders::_2);
	//ssub为普通成员函数,有this指针,因此需要指定作用域外加传this指针。

	auto pf2 = bind(&SubType::ssub, SubType(), placeholders::_1,\
	 placeholders::_2);
									//也可用对象进行调用。
	return 0;
}

 总之,bind可以改变参数的个数和顺序,改变参数的个数可以固定参数值和减少传参改变参数顺序,可以更加符合函数使用者的习惯(比如在strcpy一般第一个参数是dest,第二个参数为source,如果实现接口的程序员将参数写反了,我们可以通过包装更加符合我们的代码编写习惯。) 。

四、智能指针

 为了解决异常的执行流乱跳而导致内存泄漏的问题:

void func()
{
	//假设new int 可能会抛出bad_alloc的异常。
	int* ptr1 = new int(1);
	//当运行到此处ptr1出错时,我们只需抛出异常即可

	int* ptr2 = new int(2);
	//当运行到此处出ptr2错时,我们需要释放ptr1,然后抛出异常。

	int* ptr3 = new int(3);
	//当运行到此处ptr3出错时,我们需要释放ptr2,然后向外层抛出异常。
	
	delete ptr1;
	delete ptr2;
	delete ptr3;
}

 第一种方式我们可以通过try catch进行解决:

void func1()
{
	//假设new int 也可能会抛出异常。
	//根据异常的捕获与处理,我们可以这样解决。
	try
	{
		int* ptr1 = new int(1);
		//...
		try
		{
			int* ptr2 = new int(2);
			//...
			try
			{
				int* ptr3 = new int(3);
				//...
				delete ptr3;
			}
			catch (...)
			{
				//当运行到此处ptr3出错时,我们需要释放ptr2,然后向外层抛出异常。
				delete ptr2;
				throw;
			}
			delete ptr2;
		}
		catch (...)
		{
			//当运行到此处出ptr2错时,我们需要释放ptr1,然后抛出异常。
			delete ptr1;
			throw;
		}
		delete ptr1;
	}
	catch (...)
	{
		//当运行到此处ptr1出错时,我们只需抛出异常即可
		throw;
	}
}
int main()
{
	try
	{
		func1();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		//...未知异常;
	}
	return 0;
}
  • 很明显,每开辟一次我们需要进行try catch进行手动对异常进行捕获,很麻烦,那有没有简单的方式呢?很明显是有的。

  • RAII思想,资源获取即初始化,也就是用局部对象对资源进行管理,出作用域时会自动调用析构函数对资源进行释放。

就像这样:

template<class T>
class smart_ptr
{
public:
	smart_ptr(T* ptr)
		:_ptr(ptr)
	{}
	~smart_ptr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};
void func()
{
	//假设new int 可能会抛出bad_alloc的异常。
	smart_ptr<int> ptr1 (new int(1));
	//当运行到此处ptr1出错时,我们只需抛出异常即可

	smart_ptr<int> ptr2(new int(2));
	//当运行到此处ptr2出错时,出作用域自动释放ptr1,然后抛出异常。

	smart_ptr<int> ptr3(new int(3));
	//当运行到此处ptr3出错时,出作用域自动释放ptr1,ptr2,然后向外层抛出异常。
}

 这样就简单的使用了RAII思想解决问题,但是如果是赋值问题与拷贝该如何解决呢?在使用默认的成员函数的情况下,可能会出现内存泄漏和重复释放的风险。下面就让我们看看库里面是如何实现的吧!

1.auto_ptr

1.1基本使用

class A
{
public:
	A(int a)
		:_a(a)
	{}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a = 0;
};
int main()
{
	auto_ptr<A> ap1(new A(1));
	auto_ptr<A> ap2(new A(2));
	auto_ptr<A> ap3(ap1);//资源管理权的转移。
	ap1 = ap2;//调用析构函数先释放ap1的资源,再将ap2的资源转移给ap1。
	return 0;
}

监视逻辑:从ap3的初始化开始。

在这里插入图片描述

  • 赋值与拷贝的实现方式是对资源的管理权进行转移。

1.2基本实现

namespace MY_SMART_PTR
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}
		~auto_ptr()
		{
			if (_ptr)
				delete _ptr;
		}
		//既然是指针就得像指针一样进行访问。
		T& operator *()
		{
			return *_ptr;
		}
		T* operator ->()
		{
			return _ptr;
		}
		auto_ptr( auto_ptr<T>& ptr)
			:_ptr(ptr._ptr)
		{
			ptr._ptr = nullptr;
		}
		auto_ptr<T>& operator = (auto_ptr& ptr)
		{
			if(_ptr)
				delete _ptr;
			_ptr = ptr._ptr;
			ptr._ptr = nullptr;

			return *this;
		}
	private:
		T* _ptr;
	};
}

2.unique_ptr

1.1基本使用

  • 对资源的使用权进行转移。
class A
{
public:
	A(int a)
		:_a(a)
	{}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a = 0;
};
int main()
{
	unique_ptr<A> up1(new A(1));
	unique_ptr<A> up2(new A(2));
	unique_ptr<A> up3(new A(3));

	up1 = up2;//直接禁掉了。
	unique_ptr<A> up3(up1);//也是被删除了
	return 0;
}

会直接进行报错:
在这里插入图片描述

1.2基本实现

  • 不可进行共享资源
namespace MY_SMART_PTR
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
				delete _ptr;
		}
		T& operator *()
		{
			return *_ptr;
		}
		T* operator ->()
		{
			return _ptr;
		}
		//两种方式:
		//1.C++98之前构造函数私有+声明进行实现。
		//2.C++11沿用之前的default与纯虚函数 == 0的写法,设为delete进行删除。
		unique_ptr(const unique_ptr<T>& ptr) = delete;
		unique_ptr<T>& operator = (unique_ptr<T>& ptr) = delete;

	private:
		T* _ptr;
	};
}

3.shared_ptr

1.1基本使用

  • 可以进行共享资源。
class A
{
public:
	A(int a)
		:_a(a)
	{}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a = 0;
};
int main()
{
	shared_ptr<A> sp1(new A(1));
	shared_ptr<A> sp2(new A(2));

	shared_ptr<A> sp3 = sp1;//采取引用计数sp3与sp1共享资源,析构采取引用计数。
	sp1 = sp2;//实现需注意与自身的赋值
	return 0;
}

实现图解:
在这里插入图片描述

  • 当对象进行拷贝构造时,将对象进行浅拷贝,并进行引用计数。
  • 当对象进行赋值时,对被赋值的对象的引用计数进行检查,对引用计数减减,如果引用计数为0。则进行资源的清理。

1.2基本实现

  • 开辟一个count的资源对指向资源的对象进行计数。
namespace MY_SMART_PTR
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr )
			:_ptr(ptr)
			,_pcount(new int(1))
		{}
		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}
		}
		T& operator *()
		{
			return *_ptr;
		}
		T* operator ->()
		{
			return _ptr;
		}
		shared_ptr(shared_ptr<T>& ptr)
			:_ptr(ptr._ptr)
			,_pcount(ptr._pcount)
		{
			if(_pcount)
				(*_pcount)++;
		}
		shared_ptr<T>& operator = (shared_ptr<T>& ptr)
		{
			//判断是否相等,包括资源指向空的情况
			if (ptr._pcount == _pcount)
				return *this;
			//*this的_pcount的内容进行减减,判断是否为0
			if (_pcount && --(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}
			//对ptr指向的pcount加加
			_ptr = ptr._ptr;
			_pcount = ptr._pcount;
			if(_pcount)
				++(*_pcount);
			return *this;
		}
		T* get()const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
	};
}

shared_ptr看起来是完美的,那么有没有什么缺陷呢?

思考下面一段代码:

struct Node
{
public:
	~Node()
	{
		cout << "~Node()" << endl;
	}
	shared_ptr<Node> _prev;
	shared_ptr<Node> _next;
};
int main()
{
	//循环引用——关键与难点
	//画图分析
	shared_ptr<Node> sp1(new Node);
	shared_ptr<Node> sp2(new Node);

	sp1->_next = sp2;
	sp2->_prev = sp1;
	return 0;
}
  • 运行分析程序的结果:

在这里插入图片描述
 运行结果看似正确,其实隐含了内存泄漏,因为sp1和sp2的资源没有被释放,这是为什么呢?

我们画图进行分析:
在这里插入图片描述

  • 关键在于对象析构时sp1和sp2分别只调用了一次析构函数,使引用计数减为1,最后对资源没有进行释放,那么如何解决这个问题呢?就引出了weak_ptr。
  • 这种现象我们称之为循环引用。

4.weak_ptr

1.1基本使用

{
public:
	~Node()
	{
		cout << "~Node()" << endl;
	}
	weak_ptr<Node> _prev;
	weak_ptr<Node> _next;
};
int main()
{
	//循环引用——关键与难点
	//画图分析
	shared_ptr<Node> sp1(new Node);
	shared_ptr<Node> sp2(new Node);

	sp1->_next = sp2;
	sp2->_prev = sp1;
 	return 0;
}
  • 其实现原理也很简单,就是weak_ptr中没有引用计数,且不参与资源的析构。

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

  • 在析构时调用两次即可对资源进行释放。

1.2基本实现

  • 实现shared_ptr转weak_ptr的赋值与拷贝构造。
  • 没有引用计数且不需要写析构函数。
namespace MY_SMART_PTR
{

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
		{}
		T& operator *()
		{
			return *_ptr;
		}
		T* operator ->()
		{
			return _ptr;
		}
		weak_ptr(shared_ptr<T>& ptr)
			:_ptr(ptr.get())
		{}
		weak_ptr<T>& operator = (shared_ptr<T>& ptr)
		{
			_ptr = ptr.get();
			return *this;
		}
	private:
		T* _ptr = nullptr;
	};
}
  • 1.weak_ptr不是RAII的智能指针,是专门用来解决循环引用的问题。
    2.weak_ptr不增加引用计数,只参与资源使用,不参与资源的释放。

总结

 今天的分享就到这里了,如果有所帮助,不妨点个赞鼓励一下,我们下篇文章再见~

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

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

相关文章

Qt Quick读取本地文件并显示成表格

&#x1f680;作者&#xff1a;CAccept &#x1f382;专栏&#xff1a;Qt Quick 文章目录 &#x1f34e;C代码部分实现&#x1f680;C类注册到QML中&#x1f382;QML部分实现&#x1f330;小知识点⭐C与QML进行交互⭐将运行路径进行传递保证程序的稳定性⭐QML中定义信号其默认…

【Proteus仿真】【STM32单片机】智能加湿器设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用LCD1602液晶、按键、蜂鸣器、DHT11温湿度传感器、水位传感器、PCF8591 ADC、继电器、加湿装置等。 主要功能&#xff1a; 系统运行后&#xff0…

从零开始:深入理解Kubernetes架构及安装过程

K8s环境搭建 文章目录 K8s环境搭建集群类型安装方式环境规划克隆三台虚拟机系统环境配置集群搭建初始化集群&#xff08;仅在master节点&#xff09;配置环境变量&#xff08;仅在master节点&#xff09;工作节点加入集群&#xff08;knode1节点及knode2节点&#xff09;安装ca…

吃鸡高手秘籍大揭秘,享受顶级游戏干货!

大家好&#xff01;作为吃鸡行家&#xff0c;今天我将揭示一些与众不同、足够吸引力的内容&#xff0c;帮助您提高游戏战斗力并分享顶级游戏作战干货。 首先&#xff0c;让我们推荐一些绝地求生作图工具。这些工具可以帮助您在游戏中更好地制作作战图和规划策略&#xff0c;让您…

基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持本地图片上传与回显的功能实现(二)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 排除路径&#xff0c;增加avatar图片 # security配置 security:# 排除路径excludes:# 静态资源- /*.html…

Python+Tkinter 图形化界面基础篇:集成数据库

PythonTkinter 图形化界面基础篇&#xff1a;集成数据库 引言为什么选择 SQLite 数据库&#xff1f;集成 SQLite 数据库的步骤示例&#xff1a;创建一个任务管理应用程序步骤1&#xff1a;导入必要的模块步骤2&#xff1a;创建主窗口和数据库连接步骤3&#xff1a;创建数据库表…

Spring源码解析—— AOP代理的生成

本文已经收录到大彬精心整理的大厂面试手册&#xff0c;包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题&#xff0c;非常实用&#xff0c;有小伙伴靠着这份手册拿…

(1)(1.3) 匿名航空电子设备DroneCAN激光雷达接口

文章目录 前言 1 设置参数 2 参数说明 前言 Avionics Anonymous DroneCAN 激光雷达接口是一个微型接口(Avionics Anonymous DroneCAN LIDAR Interface)&#xff0c;适用于几种常见的激光测距仪(several common laser rangefinders)&#xff0c;可通过 DroneCAN 连接到 Pixha…

混淆技术研究笔记(五)混淆后如何反篡改?

有了上一节的基础工具后&#xff0c;接下来要考虑如何反篡改。 本文采用的是对混淆后的代码&#xff0c;针对某些关键包的字节码数据计算md5值&#xff0c;对所有类计算完成后对md5值进行排序&#xff0c;排序后拼接字符串再次计算md5值&#xff0c;最后通过私钥对md5进行RSA对…

Linux之open和fopen的比较

1、fopen 是ANSIC标准中的C库函数&#xff0c;open是系统调用 2、fopen提供了IO缓存功能&#xff0c;而open没有&#xff0c;所以fopen速度要比open快 3、fopen具有良好的移植性&#xff0c;而open 是依赖于特定的环境 4、fopen返回一个FILE 结构体指针&#xff0c;而open 返…

MES管理系统如何解决电子企业的生产痛点

随着电子行业的快速发展&#xff0c;企业面临着越来越多的生产和管理挑战。其中&#xff0c;物料编码管理困难、产品设计工作繁重、客户需求多样化 以及产品设计变更管理困难等问题尤为突出。为了解决这些问题&#xff0c;许多电子企业开始引入MES管理系统解决方案&#xff0c;…

如何实现响应式网页设计?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

做直播或短视频 其实有几个精准粉丝就可以很快变现

随着短视频 和 直播 快速发展 人流量巨大 只要是个东西 只要你能豁得出去 都能卖出去 把精准流量引流到自己的私域里面 可以组建团队 一起发展 自己喝点汤就好 有一起做CSDN和其他短视频项目的 可以左上方私信留言 我怕说多了 审核不过去

Svelte生命周期(加整体概述)

目录 前言 一、编译阶段 1. 导入语句 2. 组件声明 3. 模板部分 4. CSS样式 二、运行时阶段 三、生命周期函数 1. onMount 2. beforeUpdate 与 afterUpdate 3. onDestroy 4. setContext 与 getContext 6. hasContext 7. getAllContexts 前言 Svelte是一种现代的Ja…

冠军代言|媒介易:释放品牌潜力,实力助力,助您势如破竹!

在竞争激烈的市场中&#xff0c;品牌需要不断创新&#xff0c;找到吸引目标客户的方法。而与体育冠军合作&#xff0c;通过冠军代言&#xff0c;已经成为了众多企业提高品牌知名度、树立形象、吸引消费者目光的重要策略之一。在这个领域&#xff0c;媒介易以其实力加冕&#xf…

如何正确高效使用墨西哥专线?

在当今全球化的物流行业中&#xff0c;跨境运输服务已经成为许多企业拓展国际市场的重要手段。然而&#xff0c;由于各国法律法规、文化差异以及运输环节的复杂性&#xff0c;企业在进行跨境运输时可能会遇到诸多挑战。为了解决这些问题&#xff0c;一些专业的物流公司推出了“…

浅谈电能质量监测装置在某半导体公司的应用

摘 要&#xff1a;半导体生产制造业在国民经济中起着举足轻重的作用&#xff0c;相关企业的规模也越来越大。其供配电系统稳定、可靠的运维不仅是其安全生产的基本保证&#xff0c;还关系到产品质量和生产的顺利进行。而半导体行业中大部分工艺设备对电能质量比较敏感&#xff…

KEIL5添加沁恒的ch55x芯片(其他非arm和stm32芯片也可使用类似的方法)

准备工作 参考&#xff1a;https://www.iotword.com/8615.html 已经安装好keil5的软件环境 烧录工具下载 沁恒烧录工具地址&#xff0c;下载安装后如下图 操作步骤 打开从沁恒官网下载安装好的WHCISPTOOL软件 安装下图中的操作方式完成对安装软件keil5中的配置文件的生…

京东商品列表数据接口,关键词搜索京东商品数据接口

在网页抓取方面&#xff0c;可以使用 Python、Java 等编程语言编写程序&#xff0c;通过模拟 HTTP 请求&#xff0c;获取京东网站上的商品页面。在数据提取方面&#xff0c;可以使用正则表达式、XPath 等方式从 HTML 代码中提取出有用的信息。值得注意的是&#xff0c;京东网站…

Camtasia Studio2024最新版本正式更新上线!

Camtasia Studio2024是一款专门录制屏幕动作的工具&#xff0c;它能在任何颜色模式下轻松地记录 屏幕动作&#xff0c;包括影像、音效、鼠标移动轨迹、解说声音等等&#xff0c;简单实用的视频录制软件,游戏的精彩画面,网络视频,屏幕录制可以让您录制屏幕所有内容视频录制支持3…