【C++】C++11新特性的讲解

news2024/11/16 10:15:03

新特性讲解第一篇~ 

文章目录

  • 前言
  • 一、较为重要的新特性
    • 1.统一的初始化列表
    • 2.decltype关键字
    • 3.右值引用+移动语义
  • 总结


前言

C++11 简介
2003 C++ 标准委员会曾经提交了一份技术勘误表 ( 简称 TC1) ,使得 C++03 这个名字已经取代了
C++98 称为 C++11 之前的最新 C++ 标准名称。不过由于 C++03(TC1) 主要是对 C++98 标准中的漏洞
进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为 C++98/03 标准。
C++0x C++11 C++ 标准 10 年磨一剑,第二个真正意义上的标准珊珊来迟。 相比于 C++98/03 C++11 则带来了数量可观的变化,其中包含了约 140 个新特性,以及对 C++03 标准中
600 个缺陷的修正,这使得 C++11 更像是从 C++98/03 中孕育出的一种新语言 。相比较而言,
C++11 能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更
强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个
重点去学习
注意:较为简单的如auto等就不再讲解

一、较为重要的新特性

1.统一的列表初始化

{}初始化相信大家应该并不陌生,比如int a[] = {1,2,3,4},而在c++11中,万物均可用{}进行初始化,并且还可以省略赋值符号。

int main()
{
	int x1 = 1;
	int x2{ 55 };
	return 0;
}


下面我们再演示一下自定义类型:

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

int main()
{
	Date d1(2023, 1, 9);
	Date d2{ 2024,5,1 };
	return 0;
}

 自定义类型也没有问题,下面我们再看看list,因为list和vector的意义不太一样:

为什么说意义不一样呢,因为我们刚刚的内置类型用{}初始化是调用构造函数,自定义类型也一样。那么vector和list里面的参数都是可变的,这是怎么支持的呢?这是因为c++11增加了std::initializer_list的类,下面我们看看:

 我们可以看到这个花括号的类型是一个initializer_list,下面我们看看这个可以修改吗:

 我们可以看到initializer_list指向的内容是不可以被修改的,因为initializer_list是存在常量区当中的。那么STL是如何支持用initializer_list初始化的呢?其实也很简单,就是增加一个支持用initializer_list初始化的构造函数,如下图所示:

 下面我们再看看其他初始化的用法:

 v3的初始化是先用里面的{}构造一个匿名对象,然后再调用initializer_list初始化。

2.decltype关键字

关键字 decltype 将变量的类型声明为表达式指定的类型。
可以用表达式的类型去定义变量,下面我们演示一下:
int main()
{
	const int x = 1;
	double y = 2.2;
	vector<decltype(x* y)> ret;
	return 0;
}

 这个关键字的作用就这么多我们就不再演示了。

3.右值引用和移动语义

传统的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,所以从现在开始我们
之前学习的引用就叫做左值引用。 无论左值引用还是右值引用,都是给对象取别名
什么是左值?什么是左值引用?
左值是一个表示数据的表达式 ( 如变量名或解引用的指针 ) 我们可以获取它的地址 + 可以对它赋
值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边 。定义时 const 修饰符后的左
值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值 ( 这个不能是左值引
用返回 ) 等等, 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址 。右值引用就是对右值的引用,给右值取别名。
下面我们用代码看看常见的右值有哪些:
int main()
{
	// 10  一个常量
	//  x + y 一个表达式
	// fmin(x,y) 一个函数返回值
	return 0;
}

下面我们先看一下左值引用可以引用右值吗:

 那么表达式呢?

同样不行,但是我们说过向函数的返回值这些都是临时变量具有常性,所以我们可以加上const:

 没错,我们的左值引用既可以引用左值也可以引用右值。下面我们用右值引用试试:

int main()
{
	int&& a1 = 10;
	double x = 10, y = 20;
	double&& ret = x + y;
	return 0;
}

 刚刚我们的左值引用既可以引用左值也可以引用右值,下面我们看看右值引用能否引用左值呢?

 很明显右值引用是无法引用左值的,在这里我们说一个小细节:右值引用可以给move后的左值取别名:

move是什么意思呢?move可以将一个值变成将亡值,比如上图中我们的a变量,这个变量的声明周期本来在这个main函数内,但是经过move后a的声明周期变成了200行这一行,这就是move的作用,也就是move后一定是右值。

下面我们先对比一下左值引用和右值引用,然后我们就进入右值引用+移动语义的学习。

左值引用与右值引用比较
左值引用总结:
1. 左值引用只能引用左值,不能引用右值。
2. 但是 const 左值引用既可引用左值,也可引用右值。
右值引用总结:
1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以 move 以后的左值。
右值引用使用场景和意义:

 首先我们可以看到左值引用和右值引用是可以构成重载的,下面我们调用一下看看:

void func(int& a)
{
	cout << "func(int& a)" << endl;
}
void func(int&& a)
{
	cout << "func(int&& a)" << endl;
}
int main()
{
	int x = 10, y = 20;
	func(x);
	func(x + y);
	return 0;
}

 可以看到编译器是可以正确识别左值和右值的,下面我们用string类做一下演示:

 我们可以看到库中的string类是支持右值的,下面我们讲讲这里支持右值的好处:

本来s1+s2的返回值会调用一次拷贝构造构造一个匿名对象,然后再用这个匿名对象调用拷贝构造来给ret(注意这里不是赋值,因为ret是一个新的对象,赋值只针对已经定义过的对象),所以这里耗费的资源是很大的,而有了右值引用+移动语义后这里就变成了直接将返回值和ret交换,也就是说ret直接拿到了s1+s2返回值的资源。下面我们用自己实现的string来试试:

namespace sxy
{
	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)
		{
			::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()
		{
			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;
		}
        string operator+(char ch)
		{
			string tmp(*this);
			tmp += ch;
			return tmp;
		}
		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}
int main()
{
	sxy::string s1("hello world");
	sxy::string ret1 = s1;
	sxy::string ret2 = (s1 + '!');
	return 0;
}

上面是我们自己实现的string,是没有实现右值引用版本的:

 首先构造一个s1,然后用s1拷贝构造ret1,这里调用一次拷贝构造。s1+!是右值,对于表达式首先返回值会调用一次拷贝构造产生一个匿名对象,然后再调用一次拷贝构造用这个匿名对象构造ret2。下面我们加入右值引用版本:

// 移动构造
		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;
		}

 我们再重新运行一下:

 首先ret1 = s1会调用一次拷贝构造,而有右值引用后本来ret2只需要移动构造就可以了,但是我们重载运算符+的时候用了拷贝构造:

 所以才会有如下现象,下面我们看看是如何转移资源的:

 我们可以看到刚开始ret2的地址是0xcccccc,然后调用运算符重载+,进入函数内部本来返回tmp的时候需要拷贝构造一个临时对象,但是对于右值这里调用移动构造直接将tmp和ret2做了交换,所以最后ret2的地址直接变成刚刚tmp的地址了。

下面我们看个更明显的:

int main()
{
	sxy::string s1("hello world");
	sxy::string ret1 = s1;
	sxy::string ret2 = move(s1);
	return 0;
}

 可以看到s1和ret2直接做了资源交换,所以经过move后一个变量就变成了将亡值,这个时候我们再使用s1这个变量就非法访问了,所以我们在用move的时候一定要注意,之前的那个值会变成将亡值不可以被使用。

有了上面这么多案列下面我们总结一下:左值引用直接减少拷贝,可以左值引用传参,也可以传引用返回,但是左值引用不能解决函数内的局部对象不能用引用返回的问题,而这样的问题就需要右值引用进行解决(比如杨辉三角,返回的是一个局部对象的二维数组,深拷贝一个二维数组的代价太大了,用右值引用就可以很好的解决这个问题 )。 

C++11以后,STL的所有容器都增加了移动构造,所以我们在平常使用的时候一定是能用右值就用。

下面这种场景会被转移资源吗?

int main()
{
	sxy::string s1("hello world");
	move(s1);
	sxy::string ret2 = s1;
	return 0;
}

 很明显并不会,move实际上是一个函数调用,是这个表达式是个右值,单独访问s1,s1还是右值这里要记住。

 而在C++11以后,STL所有的容器插入数据接口函数都增加了右值引用版本。

 对于链表的插入,普通插入s1需要先拷贝构造一个hello world,然后插入到链表中,而直接插入“hello hello”因为这是一个右值,所以可以直接调用移动构造,直接将这个匿名对象的资源转移到链表中。

 可以看到资源的转移。注意:匿名对象也是右值

下面我们再总结一下:左值引用减少拷贝,提高效率。右值引用也是减少拷贝,提高效率。但是他们的角度不同,左值引用是直接减少拷贝。右值引用是间接减少拷贝,识别出是左值还是右值,如果是右值,则不再深拷贝直接移动拷贝提高效率。

下面我们看一看完美转发:

首先我们说明一下:模板中的右值引用是万能引用,既能接收左值又能接收右值。

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);
	PerfectForward(std::move(b));
	return 0;
}

 下面这段程序可以演示出完美转发的问题,我们先运行看一下结果:

 全是左值引用,这是怎么回事呢?(注意:参数传递的时候右值的下一层会变成左值)首先10是右值,进入PF函数后调用Fun函数,而右值进入Fun函数就变成了左值,所以无论左值还是右值进入Fun函数后就变成了左值,这也就是全打印左值的原因,

 那么如何让他进入fun的时候还是右值呢,用forward完美转发即可,下面我们试一下:

 现在就解决了刚刚的问题,也就是说我们使用右值+移动语义的时候,为了让右值一直层层递归下去必须用完美转发。

下面我们用自己的链表来演示不用完美转发发生的问题:

namespace sxy
{
	template<class T>
	struct list_node
	{
		list_node(const T& x = T())
			:_data(x)
			, _next(nullptr)
			, _prev(nullptr)
		{

		}
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;
	};
	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* n)
			:_node(n)
		{

		}
		Ref operator*()
		{
			return _node->_data;
		}
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const self& it)
		{
			return _node != it._node;
		}
		bool operator==(const self& it)
		{
			return _node == it._node;
		}
	};
	template<class T>
	class list
	{
	public:
		typedef list_node<T> node;
		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;
		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}
		void empty_init()
		{
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;
		}
		list()
		{
			empty_init();
		}
		template<class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		list(const list<T>& ls)
		{
			empty_init();
			list<T> tmp(ls.begin(), ls.end());
			swap(tmp);
		}
		list<T>& operator=(list<T> ls)
		{
			swap(ls);
			return *this;
		}
		void swap(list<T>& ls)
		{
			std::swap(_head, ls._head);
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		void push_back(const T& x)
		{
			insert(end(), x);
		}
		void push_back(T&& x)
		{
			insert(end(), forward<T>(x));
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		void insert(iterator pos, const T& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;
			node* newnode = new node(x);
			newnode->_next = cur;
			cur->_prev = newnode;
			newnode->_prev = prev;
			prev->_next = newnode;
		}
		iterator erase(iterator pos)
		{
			assert(pos != end());
			node* prev = pos._node->_prev;
			node* tail = pos._node->_next;
			prev->_next = tail;
			tail->_prev = prev;
			delete pos._node;
			return iterator(tail);
		}
		void pop_front()
		{
			erase(begin());
		}
		void pop_back()
		{
			erase(_head->_prev);
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				//it = erase(it);
				erase(it++);
			}
		}
	private:
		node* _head;
	};
}

 上面是我们自己实现的list源代码,下面是测试代码:

int main()
{
	sxy::list<sxy::string> lt;
	sxy::string s1("hello world");
	lt.push_back(s1);
	lt.push_back("hello hello");
	return 0;
}

 我们可以看到,这里都用的深拷贝,这是因为我们自己的list没有实现右值版本,现在我们实现一下:

首先链表插入的时候需要判断是否为右值,所以我们先修改push_back:

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

 运行后确实进入了右值版本的push_back

 可以看到往下走进入insert的时候进入了左值版本,那么我们再给inser增加一个右值版本:

void insert(iterator pos, T&& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;
			node* newnode = new node(x);
			newnode->_next = cur;
			cur->_prev = newnode;
			newnode->_prev = prev;
			prev->_next = newnode;
		}

 可以看到即使我们实现了右值版本还是没进入,这就是我们刚刚讲的完美转发问题,刚进入的右值进入下一层变成左值了,所以我们现在转发一下:

 下面我们运行起来:

 这次成功进入右值版本:

但是在new新节点的时候进入构造函数还是左值版本的构造,所以我们再增加一个右值版本的节点构造:

list_node(T&& x = T())
			:_data(forward<T>(x))
			, _next(nullptr)
			, _prev(nullptr)
		{

		}

 这次我们运行起来:

 这次我们看到成功了,以上就是完美转发所引发的问题。

下面我们总结一下:

左值引用和右值引用都是给对象取别名,减少拷贝,左值引用解决了大多数场景问题,下面有些场景是左值引用没有办法解决的:

1.局部对象返回问题。

2.插入接口,对象拷贝问题。

而右值引用+移动语义解决了上面的问题:1.对于浅拷贝的类,移动构造就相当于拷贝构造,因为没有资源的转移。

2.深拷贝的类,这里就是移动构造,对于深拷贝的类,移动构造可以转移右值(将亡值)的资源,没有拷贝提高效率。

下面我们再看看移动赋值,移动赋值与移动构造一样:

这里我们将to_string函数的返回值赋值给s1,首先这个函数会调用移动构造拿到to_string中的返回值的资源然后再调用移动赋值直接将s1的资源和刚刚返回值的资源做交换,也就是说整体就直接交换了s1和to_string返回值的资源,如果是以前没有移动语义的话这段代码需要这几步:首先to_string函数的返回值调用一次拷贝构造,然后将这个拷贝出来的匿名对象赋值给s1的时候会调用第二次拷贝构造(注意:大多数赋值重载里实现的时候都用的拷贝构造)。

以上就是右值引用+移动语义的全部内容了。


总结

这一篇比较难的就是右值引用,要注意的是:右值引用给我们c++提高了很大的效率,左值+右值引用减少了很多的拷贝,下一篇文章的重点主要是可变参数模板和lambda函数。

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

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

相关文章

【游戏编程扯淡精粹】工作第三年总结

工作第三年总结 文章目录 工作第三年总结#1 做了什么自研路线Lua 脚本系统ToolX #2 职业发展如何做事技术中台化内卷的职业市场个人成长 #3 心态建设Owner vs 打工人 今年仍然是个人成长视角更多一些&#xff0c;额外新学到的重点是&#xff0c;借助团队力量 先介绍两个词&…

通过自由度比较迭代次数

( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点&#xff0c;AB训练集各由5张二值化的图片组成&#xff0c;让A中有7个1&#xff0c;B中全是0&#xff0c;让A的5行1的数量为1&#xff0c;1&#xff0c;1&#xff0c;2&#xff0c;2&#xff1b;让A的3列1的数量…

chatgpt赋能python:Python列表从后往前删除的方法及注意事项

Python列表从后往前删除的方法及注意事项 Python是一种功能强大而易于使用的编程语言。在Python中&#xff0c;列表是重要的数据类型之一&#xff0c;它可以存储任意类型的数据&#xff0c;例如整数、字符串、浮点数和对象等&#xff0c;而且列表数据可以动态添加或删除。在编…

numpy模块

目录 ❤ numpy简介 ❤ 为什么用numpy ❤ 创建numpy数组 ❤ numpy数组的常用属性 ❤ 获取numpy数组的行列数 ❤ 切割numpy数组 ❤ numpy数组元素替换 ❤ numpy数组的合并 ❤ 通过函数创建numpy数组 array arange linspace/logspace zeros/ones/eye/empty …

多功能电子听诊器(CMS-VESD)产品使用说明

Copyright reserved 子曰&#xff1a;桃李不言&#xff0c;下自成蹊。 Copyright reserved Ⅰ . 产品描述 : C M S − V E S D Ⅰ. 产品描述:^{CMS-VESD} Ⅰ.产品描述:CMS−VESD Ⅰ . 1 主要特点 : 如有疑问可留言沟通交流 Ⅰ.1 主要特点:^{如有疑问可留言沟通交流} Ⅰ.1主要特…

[python bezier贝塞尔曲线] 数值解法、德卡斯特里奥解法解法以及bezier库的使用demo

修改自这个老哥的&#xff0c;非常的nice,此处仅作为学习记录。 matplotlib3.7.0 可行 Note: 数值解法是真的快 先上图 import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D import numpy as np import math import timeclass Bezier:def __init__(se…

使用HTML5开发Kinect体感游戏

一、简介 我们要做的是怎样一款游戏&#xff1f; 在前不久成都TGC2016展会上&#xff0c;我们开发了一款《火影忍者手游》的体感游戏&#xff0c;主要模拟手游章节《九尾袭来 》&#xff0c;用户化身四代&#xff0c;与九尾进行对决&#xff0c;吸引了大量玩家参与。 表面上看…

Makerbase SimpleFOC ESP32 例程5 双电机闭环位置测试

Makerbase SimpleFOC ESP32 例程5 双电机闭环位置测试 第一部分 硬件介绍 1.1 硬件清单 序号品名数量1ESP32 FOC V1.0 主板12YT2804电机2312V电源适配器14USB 线156pin杜邦线2 注意&#xff1a;YT2804是改装的云台无刷电机,带有AS5600编码器&#xff0c;可实现360连续运转。…

香蕉派(Banana Pi) BPi-P2 Zero开源硬件物联网开发板评测

我们从制造商处收到的样品 BPi-P2 Zero 和 BPi-P2 Maker SBC 用于本次审查的体验非常好。这些都是基于多年H3/H2 SoC使用经验的高质量硬件实现。 优点 优良的价格和适用性稳定且经过验证的架构低散热供电电源WiFi / 蓝牙 kod 零模型完美平衡的 Maker 模型 Banana Pi BPi-P2 的…

设备树与pinctrl

设备树与pinctrl 原理&#xff1a;芯片内部有个内存控制器访问芯片的各个控制器单元&#xff08;如IIC、USB等&#xff09;&#xff0c; 阅读手册设置其相应功能即可。 设备树 是一种描述硬件的数据结构,由专门文件格式与语法结构。 pinctrl Pinctrl&#xff1a;Pin Contr…

vue-router(element侧导栏,子组件内容切换,不传参)使用的详细步骤

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

如何申请免费ChatGPT 2500刀初创金

近日OpenAI 推出了OpenAI for Startups项目&#xff0c;那么什么是Startups项目呢&#xff1a; 它是由全球知名的人工智能研究公司 OpenAI 推出的一个开放式的创业计划&#xff0c;旨在为初创公司提供一种新的激励机制和技术推广方式。 也就是说我们可以用自己账号申请&#x…

【软件工程题库】第四章 概要设计

&#x1f57a;作者&#xff1a; 迷茫的启明星 学习路线C语言从0到1C初阶数据结构从0到1 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

淬体归元,运营商资源域元数据管理

资源元数据是通信行业资源管理业务开展的基础性支撑要素&#xff0c;它定义了资源业务领域相关概念、关系和规则&#xff0c;即各种设施、缆线、设备、链路等网络资源的规格、属性、字典及相关存储模型等信息。高质量的元数据是提升业务效率、加强管理与分析能力的必要条件&…

多线程安全的案例展示与解决方案

一、概念 1. 什么是线程安全 当多个线程访问一个对象时&#xff0c;如果不用考虑这些线程在运行时环境下的调度和交替执行&#xff0c;也不需要进行额外的同步&#xff0c;或者在调用方进行任何其他的协调操作&#xff0c;调用这个对象的行为都可以获得正确的结果&#xff0c…

为什么HTTPS是安全的?

在谈论 HTTPS 协议之前&#xff0c;先来回顾一下 HTTP 协议的概念。 1. HTTP 协议 1.1 HTTP 协议介绍 HTTP 协议是一种基于文本的传输协议&#xff0c;它位于 OSI 网络模型中的应用层。 HTTP 协议是通过客户端和服务器的请求应答来进行通讯&#xff0c;目前协议由之前的 RFC…

Java程序设计入门教程--成员变量

成员的分类 实例成员 实例成员是属于对象的&#xff0c;即属于对象级别&#xff0c;包括实例成员属性&#xff08;也称为实例成员变量&#xff09;和实例成员方法&#xff0c;只有创建了对象之后才能访问实例成员属性和实例成员方法。 类成员 类成员属于类的&#xff0c;类成…

Stable Diffusion学习笔记

文章目录 参考资料△ 安装与环境配置○ 安装Python 人工智能真是厉害。。。 我也不能落后 虽然前面pytorch还没有学完&#xff0c;但是热点总是在变的嘛&#xff0c;现在大模型和生成式AI这么火&#xff0c;我也来蹭蹭热度。 就从学习怎么用AIGC工具生成 老婆 纸片人开始吧 …

app的动态导航栏及自定义图标的开发

效果展示 我的代码 <template><div class"nav-container"><!-- 动态底部导航栏 start--><div class"nav-content"><van-tabbar style"background-image: linear-gradient(135deg, #EE9AE5 10%, #5961F9 100%) !import…

防火墙之流量管理

防火墙流量管理 原理概述&#xff1a; 防火墙&#xff08;英语&#xff1a;Firewall&#xff09;技术是通过有机结合各类用于安全管理与筛选的软件和硬件设备&#xff0c;帮助计算机网络于其内、外网之间构建一道相对隔绝的保护屏障&#xff0c;以保护用户资料与信息安全性的一…