【C++】STL学习——list模拟实现

目录

  • list介绍
  • list结构介绍
  • 节点类的实现
  • 迭代器的实现
    • 构造函数
    • ++运算符重载
    • --运算符重载
    • ==运算符重载
    • !=运算符重载
    • *运算符重载
    • ->运算符重载
  • const迭代器的实现
  • 多参数模板迭代器
  • list函数接口总览
  • 默认成员函数
    • 构造函数1
    • 构造函数2
    • 构造函数3
  • 析构函数
    • 拷贝构造函数
    • 赋值重载函数
  • 迭代器
    • begin()和end()
  • 修改相关函数
    • insert
    • push_back和push_front
    • erase
    • pop_back和pop_front
    • empty
    • size
    • swap
    • clear
  • 访问相关函数
    • front和back
  • 模板的拓展应用

list介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器并且该容器可以前后双向迭代
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向
    其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高
    效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率
    更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list
    的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间
    开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这
    可能是一个重要的因素)

list学习参考文档

list的底层是带头双向循环链表,在学习数据结构时也使用C语言对带头双向循环链表进行过模拟实现——
双向链表的模拟实现;得益于其优秀的结构设计,在数据的插入,删除方面有着极高的效率。

带头双向循环链表

但在STL中的list更让人叫绝的是其迭代器是设计。

  • 迭代器的主要作用就是让算法能够不用关心底层数据结构,对外提供一个统一的访问方式,即使不了解容器的底层是什么,也能通过迭代器对容器进行访问。
  • 迭代器其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* ,而今天学习的list的迭代器则是一个封装过的类。

list结构介绍

list得益于其结构,实现增删等功能比前面实现的vector,string更加简单。list更值得我们学习的是其迭代器的的实现。而且迭代期也是我们访问list的主要手段,所以优先实现迭代器。

list的实现需要有三个类:节点类,迭代器类,list类

节点类的实现

节点作为链表组成的基本单位,其成员有:

  • 存储数据的_data
  • 连接前一节点的前驱指针_prev
  • 连接后一节点的后继指针_next

节点类只需要实现默认构造函数即可。负责将成员初始化。

	template<class T>struct list_node{T _data;//数据list_node<T>* _prev;//前驱指针list_node<T>* _next;//后继指针list_node(const T& x = T()):_data(x),_prev(nullptr),_next(nullptr){}};

迭代器的实现

先实现非const版的迭代器;
迭代器的意义就是:

让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。

而list的底层已不再是连续的空间,而是分散的节点,无法再使用[]加下标访问,也不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作。究其原因就是vector,string的底层空间是连续的,其迭代器就是对应的指针,天然就支持++,–这样的操作。

list的结点指针的行为不满足迭代器定义,那么我们可以对这个结点指针进行封装对结点指针的各种operator运算符操作进行重载,使其符合迭代器的行为。如:当你使用list当中的迭代器进行自增操作时,实际上执行了p = p->next语句,只是你不知道而已,我们通过类的封装屏蔽了其细节,符合迭代器方法的使用。
总结:
用一个类封装迭代器去模拟指针的行为,该类的成员函数为运算符重载函数(模拟指针行为)。

	template<class T>struct __list_iterator//用一个类封装迭代器去模拟指针的行为,该类的成员函数为运算符重载函数(模拟指针行为){typedef list_node<T> Node;typedef __list_iterator<T> self;Node* _node;//节点__list_iterator(Node* node):_node(node){}self& operator++();self& operator--();self operator++(int);self operator--(int);bool operator==(const self& it);//it为迭代器,_node为节点bool operator!=(const self& it);T& operator*();T* operator->();};

该类中只有一个成员,那就是节点。类名太长了,使用typedef__list_const_iterator 重命名为self

构造函数

对于封装的迭代器类,我们只需要实现一个构造函数即可,只需要将获取的链表的节点用来构造迭代器中的节点即可。

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

对于我们模拟实现的迭代器,需要明确我们的目的:我们实现的迭代器是为了帮我们去遍历,访问我们的链表。这也是为什么我们不需要实现拷贝构造和赋值重载函数进行深拷贝的原因,我们使用编译器默认生成的浅拷贝的拷贝构造和赋值重载函数就能达到我们的目的,使用深拷贝反而达不到我们但目的。

++运算符重载

对于前置++,使用原则是:先++,再使用;所以直接将当前节点往后走一位,再返回当前节点即可。引用返回效率更好。

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

后置++使用原则为:先使用,再++,所以需要返回++之前的值,所以先用一个临时对象保存该值,再让节点往后走,最后返回临时对象。不能引用返回,tmp为临时对象,函数结束就销毁,不能使用引用返回。

	self operator++(int){Node* tmp(_node);_node = _node->_next;return tmp;}

–运算符重载

前置–:先–,再使用:让节点往前走一位,再返回。

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

后置–:先使用,再–;逻辑与后置++一致。

		self operator--(int){Node* tmp(_node);//_node = _node->_prev;return tmp;}

==运算符重载

重载节点与迭代期是否相等。分清_node为节点,而it为迭代期,类型不同;对比的是节点_node和迭代器的成员:节点_node。

        bool operator==(const self& it)//it为迭代器,_node为节点{return _node == it._node;}

!=运算符重载

原理与上述==相同,只不过现在是!=。

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

*运算符重载

使用解引用操作符时,是想得到该位置的数据内容。因此,我们直接返回当前结点指针所存储数据即可,但是这里使用引用返回,因为解引用是支持对数据进行修改的。

		T& operator*(){return _node->_data;//返回引用}

->运算符重载

在某些场景下,我们需要使用->来访问数据如指针,或对象类型为自定义类型时。
->操作符会先对指针进行解引用,然后访问其指向对象的成员。所以我们只需要返回数据的地址即可完成任务。

		T* operator->(){return &_node->_data;//返回其地址}

注意:
对于->的使用:当数据的类型为自定义类型时,使用一个->就能取到成员对象实际是编译器优化的结果,完整的应为it1.operator->()->对象:先调用operator->()获取对象的地址,再使用->获取对象。
->
好了,以上就是非const版迭代器所需的重载运算符。那么const迭代器如何实现呢,在现有基础上加个const吗?

const迭代器的实现

如果你认为const迭代器是在非const迭代器上加const,那么我们先回顾一下const对指针的相关限制

	int a = 10;int b = 20;const int* pa1 = &a;//对指针所指向的内容进行限制*pa1 = 30;//err,指向的内容不能变。pa1 = &b;//可以改变指针变量的指向。int* const pa2 = &a;//对指针变量进行限制*pa2 = 30;//所指向内容可以改变。pa2 = &b;//err,指针变量不能修改。

const迭代器的目的:
list的const迭代器是允许++,–这样的访问操作的,其需要保证的是const对象所指向的内容不能被修改,也就是不能通过运算符重载operator* ()和operator-> ()来改变数据。这么一看不就是类似const int* pa1 = &a;在*pa1前加上const修饰。但是你别忘了,这些都是对原生指针的而言的,而list的迭代器是我们封装的一个类无论你在哪里加const,都会导致迭代器被const修饰而无法修改,从而连最基本的访问也无法执行了。
在这里插入图片描述
所以list的const迭代器是不可能在非const迭代器变来的,它必须是一个重新的类

const迭代器:

	template<class T>struct __list_const_iterator//用一个类封装迭代器去模拟指针的行为,该类的成员函数为运算符重载函数(模拟指针行为){typedef list_node<T> Node;typedef __list_const_iterator<T> self;Node* _node;__list_const_iterator(Node* node):_node(node){}self& operator++();self& operator--();self operator++(int);self operator--(int);bool operator==(const self& it);//it为迭代器,_node为节点bool operator!=(const self& it);const T& operator*();const T* operator->();};

仔细一看两个迭代器,虽说是两个不同的类,但是内容高度重合,因为const迭代器不能通过运算符重载operator*()和operator->()来改变数据,所以我们只需要对这两个函数的返回值加const限制即可。逻辑也是一致的。

		const T& operator*(){return _node->_data;//返回引用}const T* operator->(){return &_node->_data;//返回其地址}

多参数模板迭代器

对比const迭代器和非const迭代器,你会发现只有operator*()和operator->()处多了一个const,就因为这两个const而重写了一份一模一样的代码,这不是为了吃醋而包饺子了嘛。为了解决这种重复代码的问题,C++拿出了模板来应对。

const迭代器和非const迭代器的代码中有不同的只有两处:T&与const T&,T与const T;再加上原本的T,我们将模板参数设为三个。template<class T,class Ref,class Ptr>T为数据类型,Ref为T&或const T&,Ptr为T*或const T*,再将operator*(),operator->()的类型更为Ref和Ptr即可在使用时根据所需要的迭代器类型来实例化所需的代码。

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++();self& operator--();self operator++(int);self operator--(int);bool operator==(const self& it);bool operator!=(const self& it);Ref operator*()const;//返回数据的引用Ptr operator->()const;//返回数据的地址};

在list类当中,只需要使用对应的迭代器,模板参数的Ref就会替换成const T&或者T&Ptr就会替换为const T*或者T*这样就能使用同一份类模板实例化出两种迭代器

	template<class T>class list{typedef list_node<T> Node;//构造时以下操作多,设为私有不公开void list_init()//开好头节点{_head = new Node;_head->_next = _head;_head->_prev = _head;}public:typedef __list_iterator<T,const T&,const T*> const_iterator;//使用那个迭代器就实例化哪一个迭代器typedef __list_iterator<T,T&,T*> iterator;};

好了,这就是迭代器的模拟实现,接下来进入list的模拟实现。

list函数接口总览

list类的结构十分简单,只有一个节点类;还记得我们list的底层是带头双向循环链表吗,所以这里的成员_head为头节点,不存储有效数据

	template<class T>class list{typedef list_node<T> Node;Node* _head;//头节点//构造时以下操作多,设为私有不公开void list_init()//开好头节点{_head = new Node;_head->_next = _head;_head->_prev = _head;}public:typedef __list_iterator<T,const T&,const T*> const_iterator;//使用那个迭代器就实例化哪一个迭代器typedef __list_iterator<T,T&,T*> iterator;//typedef __list_iterator<T> iterator;//typedef __list_const_iterator<T> const_iterator;list();list(initializer_list<T> il);template <class InputIterator>list(InputIterator first, InputIterator last);list(const list& x);list& operator= (const list& x);~list();iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;iterator insert(iterator position, const T& val);void push_back(const T& val);void push_front(const T& val);iterator erase(iterator position);void pop_back();void pop_front();bool empty() const;size_t size() const;void swap(list& lt);T& front();const T& front() const;T& back();const T& back() const;};
  • 注意,模拟实现的节点类,迭代器类,lsit类为了不和库的发生冲突,全都放在自己的命名空间里。

默认成员函数

我们实现了三个构造函数:

  • 第一个为默认构造
  • 第二个为迭代器区间构造
  • 第三个为使用initializer_list来构造。

构造函数开头节点的操作都一致,所以将这份代码装一起,并设为私有。

		//构造时以下操作多,设为私有不公开void list_init()//开好头节点{_head = new Node;_head->_next = _head;_head->_prev = _head;}

构造函数1

默认构造,开好头结点即可。

		list(){list_init();//开好头节点}

构造函数2

使用迭代器区间构造,开好头节点后,将传入区间的元素一个一个push_back进容器当中。

		template <class InputIterator>list(InputIterator first, InputIterator last){list_init();while (first != last){push_back(*first);first++;}}

构造函数3

使用initializer_list来构造,这种方法是在C++11提出的,使用方法如下:
直接将所要用添加的元素放在花括号里。

	list<int>lt6 = { 1,2,3,4,5,6 };list<int>lt7({ 1,2,3,4,5,6 });

这里的initializer_list是一个类模板,支持迭代器的使用。
initializer_list
initializer_list支持迭代器,所以使用范围for直接将initializer_list中的数据push_back进容器

		list(initializer_list<T> il){list_init();for (const auto& e : il){push_back(e);}}

析构函数

我们一共实现了三个类,但前两个都没有实现析构函数,那是因为前两个都没有实现深拷贝的拷贝构造和赋值重载函数;而且就像迭代器类,你总不能访问完就把我的节点给释放了吧。所以我们只需要在进行了深拷贝的list中写析构函数。
实现也简单,使用clear先将存储有效数据的节点都释放,最后再将头节点释放

  • clear是不清除头结点的。
		~list(){clear();delete _head;_head->_next = _head->_prev = nullptr;}

拷贝构造函数

有了之前vector,string的经验,现在实现拷贝构造也应该信手拈来了:开好头节点,然后使用范围for直接将数据push_back进容器即可。

list(const list<T>& lt){list_init();for (const auto& e : lt){push_back(e);}}

赋值重载函数

使用现代写法,利用不用引用传参的形参会在传参时调用拷贝构造,构造一个一模一样的list对象,然后调用swap函数将原容器与该list对象进行交换即可

		list& operator= (list<T> lt){swap(lt);return *this;}

迭代器

迭代器的使用区间为:左闭右开,所以开始位置应该是第一位有效数据——即头结点的下一位;而结尾则应该是不存储有效数据的头节点

  • 返回类型为迭代器,但返回的是节点,这里其实发生了隐式类型转化,编译器在返回时会用该节点构造一个迭代器。
  • 也可以使用匿名构造,直接构造一个迭代器类型。

begin()和end()

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

非const迭代器为const对象所调用,防止对容器内容进行修改

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

修改相关函数

insert

使用迭代器在指定位置插入值val:首先使用val创建一个新的节点,紧接着让pos前后位置的前驱或后继指针建立新的双向关系。具体如下:
insert

		iterator insert(iterator position, const T& val){assert(position._node);//防止空指针Node* node = new Node(val);Node* pos = position._node;//position为迭代器类型,需要获其节点node->_prev = pos->_prev;node->_next = pos;pos->_prev->_next = node;pos->_prev = node;return node;}
  • list中进行插入时是不会导致list的迭代器失效的

push_back和push_front

调用insert,在头或尾插入val;

		void push_back(const T& val){insert(end(), val);}void push_front(const T& val){insert(begin(), val);}

erase

删除指定位置的数据:需要对传入的迭代器进行合法性审查,防止空指针,而且不能删除头节点;然后删除位置前后的节点重新建立双向关系;删除指定位置节点时记得先保存下一节点,用作返回。
erase

		iterator erase(iterator position){assert(position._node);//防止空指针assert(position != end());//不能释放头节点assert(!empty());//非空才能删Node* pos = position._node;//节点pos->_next->_prev = pos->_prev;pos->_prev->_next = pos->_next;iterator ret = pos->_next;delete pos;//new的时节点,所以delete节点return ret;}

erase会有迭代器失效问题,使用前记得更新获取迭代器位置。

pop_back和pop_front

同样调用erase完成任务,pop_front删除头节点的下一位,pop_back删除头节点的前一位——即最后一位。

  • 尾删是删除存储有效数据的节点,end返回的是头节点。
		void pop_back(){erase(--end());}void pop_front(){erase(begin());}

empty

检查链表是否为空:对于空的链表而言,只剩下一个头节点,此时_head的_prev和_next都指向_head,因此可以利用这一点来判断链表是否为空。

empty

		bool empty() const{return _head->_next == _head;}

size

获取容器中数据个数:用size来统计个数,再利用范围for遍历链表。

		size_t size() const{size_t size = 0;for (auto e : *this){size++;}return size;}

swap

交换链表:交换两个链表的头节点即可。

		void swap(list& lt){std::swap(_head, lt._head);}

clear

清空链表:除头节点外,清空剩余全部节点;借助erase,删除节点。

  • erase会返回删除节点的下一个节点,所以不需要++,只需要接受其返回值即可。
		void clear(){auto it = begin();while (it != end()){it = erase(it);//erase返回下一个节点}}

访问相关函数

front和back

访问一头一尾的数据,直接使用*迭代器的操作访问一头一尾的操作。

T& front(){return *begin();}T& back(){return *(--end());}

当然少不了const版,因为const对象调用front和back函数后所得到的数据不能被修改。

        const T& front() const{return *begin();}const T& back() const{return *(--end());}

至此,list的模拟实现基本完成了。

模板的拓展应用

如果我们想要在类外实现一个list的打印函数:利用迭代器可以轻松完成。

	void print_list(list<int>& lt){list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;}

但这样写只是针对list的类型为int,换做其他类型就不行了,所以我们可以套上模板,支持所有list的打印。
注意:
由于我们套上了模板,这样的话在编译时list的类型是不确定的,所以迭代器也不能确认,所以在list<T>::iterator it = lt.begin();处会报错,无法通过编译。此时可以加上前缀typename作为标记,让编译器先通过编译,等模板实例化后再取。

	template<class T>void print_list(list<T>& lt){   //由于list<T>还未实例化,所以T的类型不确定,编译不通过。//此时在迭代器前加上typename,作为标记,让编译器编译成功,等实例化后再来确定迭代器具体类型typename list<T>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;}

除此之外,还能实现一个针对所有容器都能打印的函数吗?
我们此时直接将具体的类作为模板参数,传入的是哪种容器就去取该容器的迭代器,从而进行打印。

template<class Container>//格局打开,直接将具体的类作为模板参数void print_containers(Container& con){typename Container::iterator it = con.begin();while (it != con.end()){cout << *it << " ";it++;}cout << endl;}

本篇篇幅有点长,主要对list的迭代器进行介绍及模拟实现,对于不是原生指针的迭代器,其const的迭代器使用了多参数模板进行实现,一份代码实例化两种迭代器,这也是本篇博客的难点及精华,对于模板的学习,需要在日后的实践中不断学习。出错的地方还请指出。Thank~

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开放式系统互连(OSI)模型的实际意义

0 前言 开放式系统互连&#xff08;OSI&#xff0c;Open Systems Interconnection&#xff09;模型&#xff0c;由国际标准化组织&#xff08;ISO&#xff09;在1984年提出&#xff0c;目的是为了促进不同厂商生产的网络设备之间的互操作性。 定义了一种在层之间进行协议实现…

【C++】STL容器详解【下】

目录 一、list容器 1.1 list基本概念 1.2 lsit构造函数 1.3 list数据元素插入和删除操作 1.4 list大小操作 1.5 list赋值操作 1.6 list数据的存取 1.7 list反转排序 二、set/multiset容器 2.1 set/multiset基本概念 2.2 set构造函数 2.3 set赋值操作 2.4 set大小操…

数据库的操作:SQL语言的介绍

一.前言 SQL是一种结构化查询语言。关系型数据库中进行操作的标准语言。 二.特点 ①对大小写不敏感 例如&#xff1a;select与Select是一样的 ②结尾要使用分号 没有分号认为还没结束; 三.分类 ①DDL&#xff1a;数据定义语言&#xff08;数据库对象的操作&#xff08;结…

| Origin绘图 |瀑布图的绘制(保姆级教程)

&#x1f411; | Origin绘图 |瀑布图的绘制&#x1f411; 文章目录 &#x1f411; | Origin绘图 |瀑布图的绘制&#x1f411;前言瀑布图简介瀑布图绘制数据导入坐标轴刻度调节调整画布大小添加颜色及设置线条为曲线坐标轴标签调节网格调节 总结 前言 感觉好久没出过关于Origin…

MyBatis-MappedStatement什么时候生成?QueryWrapper如何做到动态生成了SQL?

通过XML配置的MappedStatement 这部分MappedStatement主要是由MybatisXMLMapperBuilder进行解析&#xff0c;核心逻辑如下&#xff1a; 通过注解配置的MappedStatement 核心逻辑就在这个里面了&#xff1a; 继承BaseMapper的MappedStatement 我们看看这个类&#xff0c;里…

FreeRTOS学习笔记—③RTOS内存管理篇(待更新完善)

二、RTOS的核心功能 RTOS的核心功能块主要分为任务管理、内核管理、时间管理以及通信管理4部分&#xff0c;框架图如下所示&#xff1a; &#xff08;1&#xff09;任务管理&#xff1a;负责管理和调度任务的执行&#xff0c;确保系统中的任务能够按照预期运行。 &#xff08;…

了解开源消息代理RabbitMQ

1.RabbitMQ 是什么&#xff1f; RabbitMQ是一个消息代理:它接受并转发消息。你可以把它想象成邮局:当你把要寄的邮件放进邮箱时&#xff0c;你可以确定邮递员最终会把邮件送到收件人那里。在这个比喻中&#xff0c;RabbitMQ是一个邮筒、一个邮局和一个邮递员。RabbitMQ和邮局之…

【kubernetes】配置管理中心Configmap运用

一&#xff0c;介绍 Configmap&#xff08;简写 cm&#xff09;是k8s中的资源对象&#xff0c;用于保存非机密性的配置的&#xff0c;数据可以用key/value键值对的形式保存&#xff0c;也可通过文件的形式保存。 【局限性】&#xff1a;在ConfigMap不是用来保存大量数据的&am…

(计算机网络)运输层

一.运输层的作用 运输层&#xff1a;负责将数据统一的交给网络层 实质&#xff1a;进程在通信 TCP&#xff08;有反馈&#xff09;UDP&#xff08;无反馈&#xff09; 二.复用和分用 三. TCP和UDP的特点和区别 进程号--不是固定的 端口号固定--mysql--3306 端口--通信的终点 …

【深度学习】softmax 回归的从零开始实现与简洁实现

前言 小时候听过一个小孩练琴的故事&#xff0c;老师让他先弹最简单的第一小节&#xff0c;小孩练了两天后弹不出。接着&#xff0c;老师让他直接去练更难的第二小节&#xff0c;小孩练习了几天后还是弹不出&#xff0c;开始感觉到挫败和烦躁了。 小孩以为老师之后会让他从简…

科技信贷业务怎么寻找客户?

在科技信贷业务领域&#xff0c;寻找客户的痛点主要集中在以下几个方面&#xff1a; 1.风险评估难题&#xff1a;科技型企业尤其是初创企业&#xff0c;往往缺乏足够的历史数据和抵押物&#xff0c;这使得金融机构在评估其信用风险时面临较大挑战。由于科技企业的研发周期长、…

C语言小游戏--贪吃蛇实现

C语言小游戏--贪吃蛇实现 1.游戏实现背景2.Win32 API介绍2.1什么是Win32 API2.2控制台程序(Console)2.3控制台屏幕的坐标COORD2.4GetStdHandle2.4.1函数语法2.4.2函数的使用 2.5GetConsoleCursorInfo2.5.1函数语法2.5.2函数的使用 2.6CONSOLE_CURSOR_INFO2.6.1结构体结构2.6.2结…

【数据库】MySQL聚合统计

目录 1.聚合函数 案例1&#xff1a; 统计班级共有多少同学 案例2&#xff1a;统计本次考试的数学成绩分数个数 案例3&#xff1a;统计数学成绩总分 案例4&#xff1a;统计平均总分 案例5&#xff1a;返回英语最高分 案例6&#xff1a;返回 > 70 分以上的数学最低分 2.分…

通信工程学习:什么是SSB单边带调制、VSB残留边带调制、DSB抑制载波双边带调制

SSB单边带调制、VSB残留边带调制、DSB抑制载波双边带调制 SSB单边带调制、VSB残留边带调制、DSB抑制载波双边带调制是三种不同的调制方式&#xff0c;它们在通信系统中各有其独特的应用和特点。以下是对这三种调制方式的详细解释&#xff1a; 一、SSB单边带调制 1、SSB单边带…

Android Framework(四)WMS-窗口显示流程——窗口创建与添加

文章目录 流程概览涉及模块流程概览 应用端——window创建&#xff1a;Activity::attach创建window流程setWindowManager&#xff0c;getWindowManagerDecorView 应用端——window的显示流程&#xff1a;Activity::onResumeViewRootImpl::setViewmWindowSession 是什么mWindow是…

ThinkPHP5 5-rce远程代码执行漏洞复现

启动容器 docker-compose up -d 查看端口 docker ps 端口为:8080,访问网站&#xff0c;搭建成功 漏洞复现 &#xff08;1&#xff09;输出关于 PHP 配置的信息 &#xff08;2&#xff09;将php代码写入文件 接着访问shell.php 由于存在过滤&#xff0c;需要用到base64加密来使…

SprinBoot+Vue图书馆预约与占座微信小程序的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue3.6 uniapp代码 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平…

用了虚拟机后,本机摄像头打不开了(联想电脑thinkpad)

虚拟机有摄像头&#xff0c;我断开了连接&#xff0c;现在本机的摄像头打开就是一个锁 我先把虚拟机的摄像头关了 然后把本机的vm usb关闭了 WinR&#xff09;&#xff0c;输入services.msc&#xff0c;找到VMware USB Arbitration Service&#xff0c;确保其状态为“关闭 然后…

【Day09-IO-字符流其它流】

IO流 IO流-字符流 字节流&#xff1a;适合复制文件等&#xff0c;不适合读写文本文件 字符流&#xff1a;适合读写文本文件内容 FileReader&#xff08;文件字符输入流&#xff09; 作用&#xff1a;以内存为基准&#xff0c;可以把文件中的数据以字符的形式读入到内存中来。 …

【Qt】窗口移动和大小改变事件

窗口移动和大小改变事件 moveEvent窗口移动时触发的事件resizeEvent窗口大小改变时触发的事件 例子&#xff1a;测试移动窗口和改变窗口事件 代码展示 #include "widget.h" #include "ui_widget.h"#include <QDebug> #include <QMoveEvent> …