c++修炼之路之C++11

news2024/9/21 18:31:02

目录

一:使用列表初始化

二:decltype和nullptr

三:右值引用和移动语义 

四:新的类功能 

五:可变参数模板 

 六:lambda表达式

七:包装器 

1.function包装器

 2.bind包装器

接下来的日子会顺顺利利,万事胜意,生活明朗-----------林辞忧 

一:使用列表初始化

1.统一使用{}初始化(对于内置类型和自定义类型)

在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定

struct Point
{
	int _x;
	int _y;
};
int main()
{
	int array1[] = { 1, 2, 3, 4, 5 };
	int array2[5] = { 0 };
	Point p = { 1, 2 };
	return 0;
}

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自
定义的类型
,使用初始化列表时,可添加等号(=),也可不添加 

struct Point
{
	int _x;
	int _y;
};

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

//c++11支持一切使用列表初始化
int main()
{
	int x1 = { 1 };
	int array1[] = { 1, 2, 3, 4, 5 };
	Point p = { 1, 2 };

	Date d1={ 2022, 1, 2 };//多参数的构造函数的隐式类型转换
	const Date& d2 = { 2024,8,31 };


	//c++11支持可以不加=
	int x2{1};
	int array2[]{ 1, 2, 3, 4, 5 };
	Point p1{ 1, 2 };
	Date d3{ 2022, 1, 2 };
}

2.std::initializer_list(对于容器的多值构造)

 对于vector,list等容器支持多个值来构造初始化

对于这里的Date类是多参数的构造函数的隐式类型转换,必须跟对应构造函数的参数个数匹配,而对于这里的vector,list等容器则是使用了initializer_list的构造函数

 对于这里的initializer_list则是c++11新引入的一个类型,查阅官方文档介绍得

 c++11在这里把列表的值识别为一个initializer_list的对象,每个值的类型都是initializer_list

在底层方面

对于上述的多值构造,实际上是用迭代器遍历这个initializer_list,将每个值插入到vector等容器中或者使用对应的迭代器区间来构造

对于这里的列表初始化和 initializer_list构造结合起来使用的优秀例子为

二:decltype和nullptr

1.关键字decltype将变量的类型声明为表达式指定的类型

int i = 1;
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();
cout << typeid(i).name() << endl;
auto k = i;

vector<decltype(it)> v;
decltype(it) it1;

对于typeid().name()来获取的类型是以字符串形式获取到的,获取到的这个类型不能用来创建变量或者使用的,此时我们就可以使用auto来创建变量

但auto不能做参数,这是就得用decltype(变量)来推导对象的类型,这个类型是可以用来做模板实参或者再定义对象

2.nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针

三:右值引用和移动语义 

1.左值,左值引用,右值,右值引用

  // 以下的p、b、c、*p都是左值
	// 左值:可以取地址
	int* p = new int(0);
	int b = 1;
	const int c = b;
	*p = 10;
	string s("111111");
	s[0];
	cout << &c << endl;
	cout << &s[0] << endl;

	// 左值引用给左值取别名
	int& r1 = b;
	int*& r2 = p;
	int& r3 = *p;
	string& r4 = s;


	// 右值:不能取地址
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值,常量临时对象,匿名对象
	10;
	x + y;
	fmin(x, y);
	string("11111");

	//cout << &10 << endl;
	
	// 右值引用给右值取别名
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x,y);
	string&& rr4 = string("11111");


	///
	//左值引用引用给右值取别名:不能直接引用,但是const 左值引用可以
	const int& rx1 = 10;
	const double& rx2 = x + y;
	const double& rx3 = fmin(x, y);
	const string& rx4 = string("11111");
	//适用场景
	// void push(const T& x);
	vector<string> v;
	string s1("1111");
	v.push_back(s1);
	v.push_back(string("1111"));
	v.push_back("1111");


	//右值引用引用给左值取别名:不能直接引用,但是move(左值)以后右值引用可以引用
	int&& rrx1 = move(b);
	int*&& rrx2 = move(p);
	int&& rrx3 = move(*p);
	string&& rrx4 = move(s);

这里实际在底层看来是没有左右值区分的,都是用指针来实现的,move的本质也就相当于强制类型转换

2.右值引用使用场景和意义

引用的意义为减少拷贝,在这里左值引用解决的场景:引用传参/引用传返回值

但当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回,这里就会有多次拷贝

因此提出使用右值引用的移动构造/赋值(移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己)来解决上述问题

但现在的编译器会做很大的优化处理,会影响观察到的结果,接下来就用一个实例来观察的

举例如下

namespace mjw
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		typedef const char* const_iterator;
		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			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)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		// s2(s1)
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			reserve(s._capacity);
			for (auto ch : s)
			{
				push_back(ch);
			}
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷贝" << endl;
			if (this != &s)
			{
				_str[0] = '\0';
				_size = 0;

				reserve(s._capacity);
				for (auto ch : s)
				{
					push_back(ch);
				}
			}

			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];
				if (_str)
				{
					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
	};

	mjw::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		mjw::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;
	}
}

int main()
{
	mjw::string s1;
	s1 = mjw::to_string(1234);

	return 0;
}

 

string的移动构造和移动赋值

// 移动构造
// 临时创建的对象,不能取地址,用完就要消亡
// 深拷贝的类,移动构造才有意义
string(string&& s)
{
	cout << "string(string&& s) -- 移动拷贝" << endl;
	swap(s);
}


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

	swap(s);
	return *this;
}

在vs2022

这种编译器的最新优化下,上述过程还会优化的更深入,直接优化为构造 

 

这里会想如果在这样优化的环境下,是不是移动构造/赋值就会意义,其实不然,就像下面的场景下是无法直接优化为构造的

对于右值引用的移动语义还有一些应用场景

对于大对象的传值返回,拷贝代价极大,就比如这里的杨辉三角

 

对于一些容器的插入接口也增加了右值引用的版本 

3.关于右值引用本身的属性是左值的探究

问题引入:

在引入前面所写list文件(可从前面博客中寻找)后,在list.h中实现移动构造和移动赋值后

会发现这里的打印仍然是拷贝构造,移动构造并没有触发,造成这样的原因是

这里的右值引用x本身的属性还是左值,因此接下来还是会匹配到拷贝构造的

那么这里为啥要这样规定呢,其实主要是为了解决这个问题

如果这里swap(s),s的属性是右值的话,与swap函数的参数类型是不能匹配的,况且swap函数的参数类型只能写string&,const string&和string&& 的对象是不能修改的

因此为了解决上述问题可以使用move来解决的

4.完美转发

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
	// 模版实例化是右值引用,右值引用属性会退化成左值,转换成右值属性再传参给Fun
	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;
}

对于这里的万能引用中Fun传参,是不能区分左右值的,都会被统一处理为左值,为了解决这个问题就可以使用完美转发

这时就会保持它的原有属性,不会发生右值退化为左值的情况

四:新的类功能 

1.移动构造和移动赋值

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	//~Person() {}
private:
	mjw::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

如果这里没有显示写析构,拷贝构造,赋值的话,编译器就会自动生成移动构造和移动赋值

 

如果实现了其中的任意一个的话,就不会自动生成 

 2.强制生成默认函数的关键字default和禁止生成默认函数的关键字delete

在c++98中,当不想成员函数被拷贝时,就可以将成员函数只声明,不定义,放到private中;在c++11之后就采用delete关键字来解决

五:可变参数模板 

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

 1.对函数形参参数包sizeof得参数包的参数个数

2.使用递归函数方式展开参数包 

在这里如果要展开参数包的话,或许我们会首先想到这样的方法

但这样是不支持的,因为可变参数模板是在编译时解析的,而这样的方式是在运行时获取和解析的,所以不支持

这里就要使用在编译时递归推导解析参数

void Print()
{
	cout << endl;
}

template <class T, class ...Args>
void Print(T&& x, Args&&... args)
{
	cout << x << " ";
	Print(args...);
}

// 编译时递归推导解析参数
template <class ...Args>
void ShowList(Args&&... args)
{
	Print(args...);
}
int main()
{
	ShowList();
	ShowList(1);
	ShowList(1, "xxxxx");
	ShowList(1, "xxxxx", 2.2);

	return 0;
}

对于这里的原理就是,比如是有三个参数的情况下,是将有三个参数的参数包传给args,再在编译时将参数包传给Print函数,就将第一个参数传给x然后解析出来,再将其余两个参数的参数包传给Print函数的第二个参数,再递归调用自己,依次解析参数包中的内容,直至为空,此时就调用Print()函数,完成整个参数包的解析

这里在编译器实例化的时候,其实是实例化为不同的函数,然后再分别调用,如下

3.使用逗号表达式展开参数包

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

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

// 编译推演生成下面的函数
//void ShowList(int x, char y, std::string z)
//{
//	int arr[] = { PrintArg(x),PrintArg(y),PrintArg(z) };
//	cout << endl;
//}

int main()
{
	//ShowList(1);
	//ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));

	return 0;
}

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数组的过程中就将参数包展开了

也可以使用这样的方式

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

	cout << endl;
}

//void ShowList(int x, char y, std::string z)
//{
//	int arr[] = { (cout<<(x)<<" ", 0), (cout << (y) << " ", 0), (cout << (z) << " ", 0) };
//
//	cout << endl;
//}

int main()
{
	ShowList(1, 'A', std::string("sort"));

	return 0;
}

4.emplace_back接口与push_back等插入接口的区别

.template <class... Args>
void emplace_back (Args&&... args); 

这里对于普通的左值还是右值的插入,这两个区别不大

这里的可变参数模板是针对多参数的,与push_back()不同的地方为 

往下传的过程中,就会直接构造对象进行插入操作,而push_back是先构造,再拷贝/移动构造

5.在list中模拟实现emplace_back版本

 六:lambda表达式

1.实例引入

#include<algorithm>
struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
	//...

	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

struct Compare1
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};

struct Compare2
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };

	sort(v.begin(), v.end(), Compare1());
	sort(v.begin(), v.end(), Compare2());
	return 0;
}

在实际的排序实例当中,一般都是对结构体按照某个成员为标准的排序,但这样的话,就需要写很多的仿函数来参与不同的排序,但当仿函数

的函数名是不明确的时候,这时还得查看源代码,于是为了清晰明了,减少仿函数,就引入了lambda表达式

2. lambda表达式语法

3.简单的lambda表达式 

实现加法

打印多行内容 

lambda表达式的一些省略

4.关于捕获列表

实现一个交换函数

此时在函数体内部是无法直接使用a,b的,而要使用的话,就要使用捕捉列表的

要想改变外面捕捉的值,就得使用引用捕捉

其余几种捕捉方式

int x = 0;
int main()
{
	// 只能用当前lambda局部域和捕捉的对象和全局对象

	int a = 0, b = 1, c = 2, d = 3;
	// 所有值传值捕捉
	auto func1 = [=]
	{
		int ret = a + b + c + d + x;
		return ret;
	};
	
	// 所有值传引用捕捉
	auto func2 = [&]
	{
		a++;
		b++;
		c++;
		d++;
		int ret = a + b + c + d;
		return ret;
	};

	// 混合捕捉
	auto func3 = [&a, b]
	{
		a++;
		// b++;
		int ret = a + b;
		return ret;
	};

	// 混合捕捉
	// 所有值以引用方式捕捉,d用传值捕捉
	auto func4 = [&, d]
	{
		a++;
		b++;
		c++;
		//d++;

		int ret = a + b + c + d;
	};

	auto func5 = [=, &d]() mutable
	{
		a++;
		b++;
		c++;
		d++;
		int ret = a + b + c + d;
	};

	return 0;
}

对于最初的排序问题,就可以使用lambda表达式添加排序方案 

// 价格升序
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool
	{
		return g1._price < g2._price;
	});

sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool
	{
		return g1._price > g2._price;
	});

sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool
	{
		return g1._evaluate < g2._evaluate;
	});

sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool
	{
		return g1._evaluate > g2._evaluate;
	});	

5.函数对象与lambda表达式 ->探究lambda的底层实现

class Rate
{
public:
	Rate(double rate) : _rate(rate)
	{}

	double operator()(double money, int year)
	{
		return money * _rate * year;
	}
private:
	double _rate;
};

int main()
{
	// 函数对象
	double rate = 0.015;
	Rate r1(rate);
	cout << r1(10000, 2) << endl;

	// lambda
	auto r2 = [rate](double monty, int year)->double
	{
		return monty * rate * year;
	};
	cout << r2(10000, 2) << endl;

	int x = 1, y = 2;
	auto r3 = [=](double monty, int year)->double
	{
		return monty * rate * year;
	};
	cout << r3(10000, 2) << endl;
	return 0;
}

 从使用方式上来看,函数对象与lambda表达式完全一样,函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到

运行代码转到底层汇编代码得

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

对于我们上层看到的是lambda表达式是一个匿名函数对象,但实际在底层是有函数名的,构成为lambda_uuid

七:包装器 

1.function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器

1.function包装器的意义

ret = func(x);
// 上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能
是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下

因此提出了用包装器来解决问题,提供一种统一的方式来访问

std::function在头文件<functional> 

模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参 

2.包装实例1

#include<functional>
int f(int a, int b)
{
	return a + b;
}

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b)
	{
		return a + b;
	}
};

int main()
{
	// 包装可调用对象
	function<int(int, int)> f1 = f;
	function<int(int, int)> f2 = Functor();
	function<int(int, int)> f3 = [](int a, int b) {return a + b; };

	cout << f1(1, 1) << endl;
	cout << f2(1, 1) << endl;
	cout << f3(1, 1) << endl;

	// 包装静态成员函数
	function<int(int, int)> f4 = &Plus::plusi;
	cout << f4(1, 1) << endl;

	// 包装非静态成员函数(注意有隐含的this指针):方式1
	function<double(Plus*, double, double)> f5 = &Plus::plusd;
	Plus pd;
	cout << f5(&pd, 1.1, 1.1) << endl;

	//方式2
	function<double(Plus, double, double)> f6 = &Plus::plusd;
	cout << f6(pd, 1.1, 1.1) << endl;
	cout << f6(Plus(), 1.1, 1.1) << endl;

	return 0;
}

 实例2

 2.bind包装器

 std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可
调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。使用std::bind函数还可以实现参数顺序调整等操作

1.函数原型

 2.实例演示

#include<functional>

using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

int Sub(int a, int b)
{
	return (a - b) * 10;
}

int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}

int main()
{
	// bind 本质返回的一个仿函数对象
	// _1代表第一个实参
	// _2代表第二个实参
	// ...

	auto sub1 = bind(Sub, _1, _2);
	cout << sub1(10, 5) << endl;

	// 调整参数顺序(不常用)
	auto sub2 = bind(Sub, _2, _1);
	cout << sub2(10, 5) << endl;

	// 调整参数个数 (常用)
	auto sub3 = bind(Sub, 100, _1);
	cout << sub3(5) << endl;

	auto sub4 = bind(Sub, _1, 100);
	cout << sub4(5) << endl;

	// 分别绑死第123个参数
	auto sub5 = bind(SubX, 100, _1, _2);
	cout << sub5(5, 1) << endl;

	auto sub6 = bind(SubX, _1, 100, _2);
	cout << sub6(5, 1) << endl;

	auto sub7 = bind(SubX, _1, _2, 100);
	cout << sub7(5, 1) << endl;

    return 0;
}

图示分析:

这里的bind包装器主要解决的是下面的这个例子 

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{

	function<double(Plus, double, double)> f6 = &Plus::plusd;
	Plus pd;
	cout << f6(pd, 1.1, 1.1) << endl;
	cout << f6(Plus(), 1.1, 1.1) << endl;

	// bind一般用于,绑死一些固定参数
	function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
	cout << f7(1.1, 1.1) << endl;
    return 0;
}

其余实例:

//auto func1 = [](double rate, double monty, int year)->double {return monty * rate * year;};
auto func1 = [](double rate, double monty, int year)->double {
	double ret = monty;
	for (int i = 0; i < year; i++)
	{
		ret += ret * rate;
	}

	return ret - monty;
	};

function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);

cout << func3_1_5(1000000) << endl;
cout << func5_1_5(1000000) << endl;
cout << func10_2_5(1000000) << endl;
cout << func20_3_5(1000000) << endl;

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

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

相关文章

《深度学习》OpenCV 图像轮廓检测、轮廓处理及代码演示

目录 一、图像轮廓检测 1、边缘检测和轮廓检测 2、常用的图像轮廓检测方法包括&#xff1a; 1&#xff09;基于梯度的方法 2&#xff09;基于边缘检测器的方法 3&#xff09;基于阈值的方法 3、查找轮廓的函数 4、轮廓的绘制 5、轮廓特征 1&#xff09;轮廓面积 2&a…

呵,老板不过如此,SQL还是得看我

2018年7月&#xff0c;大三暑假进行时&#xff0c;时间过得飞快&#xff0c;我到这边实习都已经一个月了。 我在没工作之前&#xff0c;我老是觉得生产项目的代码跟我平时自学练的会有很大的区别。 以为生产项目代码啥的都会规范很多&#xff0c;比如在接口上会做很多安全性的…

自己开发完整项目一、登录功能-05(动态权限控制)

一、上节回顾 在上一节中&#xff0c;我们介绍了如何通过数据库查询用户的权限&#xff0c;并对方法级别的接口使用注解的方式进行权限控制&#xff0c;之后通过用户携带的tocken进行解析权限&#xff0c;判断是否可以访问。 具体步骤&#xff1a; 1.在查询用户信息的时候将用户…

神经网络中激活函数介绍、优缺点分析

本文主要介绍神经网络中的常用的激活函数 激活函数是神经网络中用于引入非线性模型&#xff0c;提升模型泛化能力的函数 非线性激活函数至关重要&#xff0c;它可以让神经网络学习复杂特征、提供模型复杂度 1、激活函数定义 激活函数是神经网络模型中的一种非线性函数&#xf…

教学能力知识

第一章课程理论知识 一、课程理念 二、课程目标 1.核心素养 2.课程总目标 三、教学建议 四、教学环节 第二章教学实施 第一节导入新课类 二.导入方法 第二节教学方法类 教学方法的选择依据 第三节教法实施原则类 设计意图 第四节设计意图类 1.教学目标 2.教学重难点 3.教学…

【安当产品应用案例100集】014-使用安当TDE实现达梦数据库实例文件的透明加密存储

随着数据安全重要性的不断提升&#xff0c;数据库文件的落盘加密已成为数据保护的一项基本要求。达梦数据库作为一款高性能的国产数据库管理系统&#xff0c;为用户提供了一种高效、安全的数据存储解决方案。本文将详细介绍如何利用安当KSP密钥管理平台及TDE透明加密组件来实现…

[数据集][目标检测]灭火器检测数据集VOC+YOLO格式3255张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3255 标注数量(xml文件个数)&#xff1a;3255 标注数量(txt文件个数)&#xff1a;3255 标注…

c++多线程下崩溃一例分析 ACTIONABLE_HEAP_CORRUPTION heap failure block not busy DOUBLE

之前的三个代码接口使用了同一把锁&#xff0c;共享资源的访问是有序执行的没有问题。最近改成各个接口使用单独的锁&#xff0c;结果漏掉了共享资源的保护&#xff0c;于是出现了崩溃。最近与这个崩溃做斗争并定位找到的原因&#xff0c;成功复现了。这里总结下&#xff0c;后…

[YM]课设-C#-WebApi-Vue-员工管理系统 (六)前后端交互

Http状态码&#xff1a; 终于也是到了前端 上文提到http状态码 这里详细说一下 1xx 表示临时响应并需要请求者继续执行操作 2xx 成功&#xff0c;操作被成功接收并处理 3xx 表示要完成请求&#xff0c;需要进一步操作。 通常&#xff0c;这些状态代码用来重定向 4…

LiveQing视频点播流媒体RTMP推流服务用户手册-分屏展示:单分屏、四分屏、九分屏、十六分屏、轮巡播放、分组管理、记录加载

LiveQing视频点播流媒体RTMP推流服务用户手册-分屏展示:单分屏、四分屏、九分屏、十六分屏、轮巡播放、分组管理、记录加载 1、分屏展示1.1、分组管理1.1.1、新建分组1.1.2、选择资源1.1.3、编辑分组1.1.4、删除资源 1.2、多分屏1.2.1、选择资源1.2.2、单分屏1.2.3、四分屏1.2.…

【多模态大模型】的正确打开方式——图片

早期痛点 识别图片中的物体&#xff0c;早期可以使用Yolo 但是缺点也很明显&#xff1a; 训练时间长成本高泛华性能差通用识别领域覆盖有限 优点&#xff1a; 特殊领域识别 大模型出现 大模型出现后&#xff0c;一些大模型对接了图片识别相关的模型&#xff0c;实现了图片…

利用衍射进行材料分析--Muad

软件介绍 MAUD是一款免费软件&#xff0c;使用组合 Rietveld 方法分析衍射数据。其功能不仅限于衍射&#xff0c;还包括荧光和反射率。 它可以分析来自 X 射线源以及中子、TOF 和 TEM 电子的数据。相含量和晶体结构、微观结构特征&#xff08;如尺寸和应变&#xff09;、晶体…

沉浸式体验亚马逊云科技上私有化部署零一万物AI大模型

小李哥将继续带大家沉浸式体验亚马逊云科技上的国产AI大模型。最近亚马逊云科技的机器学习模型管理平台Amazon SageMaker JumpStart 上线了由零一万物提供的基础模型 Yi-1.5 6B/9B/34B&#xff0c;这也是首批登陆中国区 Amazon SageMaker JumpStart 的中文基础模型&#xff0c;…

【多线程】并发编程wait和sleep的区别

notyfy、notifyAll、wait的使用&#xff1a;sleep/wait/notify/notifyAll分别有什么作用 背景&#xff1a;之前的博客讲解到了notify的使用&#xff0c;那并发编程的时候&#xff0c;到底该用 sleep还是notify呢&#xff1f;本篇我们来一起梳理一下区别 所属类与方法类型 wait…

vscode+django开发后端快速测试接口(轻量版,免postman安装)

目录 背景 步骤 安装插件 编写测试文件 示例一&#xff1a;get接口类型 示例二&#xff1a;post接口类型 示例三&#xff1a;delete接口类型 如何运行test.http测试文件 背景 在最近工作中涉及到使用Django框架开发后端&#xff0c;写完接口后&#xff0c;不可避免需要…

php法律事务综合管理系统Java律师事务所业务流程管理平台python法律服务与案件管理系统(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

Java八股文总结一

Java基础 一、JDK、JRE、JVM之间的关系&#xff1f; 1、JDK(Java Development Kit):Java开发工具包&#xff0c;提供给Java程序员使用&#xff0c;包含了JRE&#xff0c;同时还包含了编译器javac与自带的调试工具Jconsole、jstack等。 2、JRE(Java Runtime Environment):Ja…

蔡司小乐圆镜片:自由环面与微柱镜排布助力兼顾舒适与效果

从学习到休闲娱乐&#xff0c;孩子们的日常生活已与电子设备密不可分&#xff0c;视力面临日益严峻的挑战。为了让孩子拥有全视野清晰视觉体验的同时&#xff0c;更有效管理孩子的近视发展&#xff0c;让孩子佩戴蔡司小乐圆镜片&#xff0c;也成为不少家长的首选。 数据统计&am…

opencv图像形态学(边缘检测算法实例)

引言 图像形态学是一种基于数学形态学的图像处理技术&#xff0c;它主要用于分析和修改图像的形状和结构。在OpenCV中&#xff0c;图像形态学操作通过一系列的数学运算来实现&#xff0c;如腐蚀、膨胀、开运算、闭运算等。这些操作在图像处理、计算机视觉和模式识别等领域有着…

Python自动化必会技能-Excel文件读取

01 重点 在自动化测试过程中&#xff0c;经常需要使用excel文件来存储测试用例&#xff0c;那么在表格内设计好了测试用例数据后&#xff0c;如何通过自动化读取呢&#xff1f;此时就需要测试小姐姐动手写“代码”了~ 本文主要介绍通过python来读取表格数据。Python读取表格的…