<C++>STL->list

list的介绍

listimage-20240120113126755

  • list是一个类模板,第一个模板参数为存储数据类型;第二个模板参数为空间适配器
  • list是一个可以在常数时间内完成任意位置的插入和删除的顺序容器。
  • list容器是以双链表的形式实现的;双链表可以将其包含的每个元素存储在不同且不相关的存储位置。每个元素都有一个指向其前面元素的指针和一个指向其后面元素的指针,从而在内部保持排序。
  • 与其他基本标准序列容器(arrayvectordeque)相比,list在插入、提取和移动容器内任何位置的元素(已获得迭代器)时通常表现更好,因此在密集使用这些元素的算法(如排序算法)中也是如此。
  • 与其他序列容器相比,listforward_lists 的主要缺点是它们无法通过元素的位置直接访问元素;例如,要访问列表中的第六个元素,必须从已知位置(如开始或结束位置)迭代到该位置,而这需要花费这些位置之间距离的线性时间。此外,它们还需要消耗一些额外的内存来保存与每个元素相关的链接信息(这可能是由小尺寸元素组成的大列表的一个重要因素)。

list的使用

list的使用和vector类似,主要介绍listvector之间的不同点:

  • 时间复杂度:vector获取元素的时间复杂度为 O ( 1 ) O(1) O(1),尾插和尾删的时间复杂度为 O ( n ) O(n) O(n),其他位置插入和删除是 O ( n ) O(n) O(n);list获取元素的时间复杂度为 O ( n ) O(n) O(n),任意位置插入删除时间复杂度为 O ( 1 ) O(1) O(1)

  • 迭代器失效:vector在insert,erase之后,原本指向插入、删除后面所有元素的迭代器都会失效;list在insert,erase之后,只有指向插入或删除位置的迭代器才会失效

  • clear成员函数:vectorclear函数会将size清零,capacity保持不变;listclear函数会将size清零,只留下头节点

  • 迭代器的实现不同:

    • vector的迭代器底层就是原生指针。即typedef T* std::vector::iterator ,此后对迭代器进行解引用和++就相当于对指针进行解引用和++,可以访问容器的数据和遍历容器。
    • list的迭代器底层是指针,但不是原生指针,而是封装后的指针。在使用上我们需要解引用迭代器可以获得节点的数据,迭代器++可以遍历容器。list的数据结构是链表,链表各个节点的存储是不连续的,所以直接对指针进行解引用和++是不能实现获取链表节点的数据和遍历链表的。我们必须对指针进行运算符重载,但是内置类型不支持运算符重载,需要将指针封装成一个以该指针为成员变量的类,对类进行operator+operator*重载。
    • vector没有的成员函数:
    1. spliceimage-20240120213123046

      • 功能:将元素从 x 传输到容器中,并将它们插入到位置

      • 参数:
        position:容器内插入x元素的位置
        x:与*this具有相同类型元素的list
        i:x中的迭代器,只有单个数据会被传输
        first,last:指向容器x要被传输区间的头和尾部的下一个位置,及区间[first,last)会被传输到*thisposition位置。

      • 返回值:空

      • 注意:再调用函数后,原本指向元素的迭代器仍然指向该元素,但是迭代器可能指向的容器发生改变

      demo:

      // splicing lists
      #include <iostream>
      #include <list>
      
      int main ()
      {
        std::list<int> mylist1, mylist2;
        std::list<int>::iterator it;
      
        // set some initial values:
        for (int i=1; i<=4; ++i)
           mylist1.push_back(i);      // mylist1: 1 2 3 4
      
        for (int i=1; i<=3; ++i)
           mylist2.push_back(i*10);   // mylist2: 10 20 30
      
        it = mylist1.begin();
        ++it;                         // points to 2
      
        mylist1.splice (it, mylist2); // mylist1: 1 10 20 30 2 3 4
                                      // mylist2 (empty)
                                      // "it" still points to 2 (the 5th element)
                                                
        mylist2.splice (mylist2.begin(),mylist1, it);
                                      // mylist1: 1 10 20 30 3 4
                                      // mylist2: 2
                                      // "it" is now invalid.
        it = mylist1.begin();
        std::advance(it,3);           // "it" points now to 30
      
        mylist1.splice ( mylist1.begin(), mylist1, it, mylist1.end());
                                      // mylist1: 30 3 4 1 10 20
      
        std::cout << "mylist1 contains:";
        for (it=mylist1.begin(); it!=mylist1.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        std::cout << "mylist2 contains:";
        for (it=mylist2.begin(); it!=mylist2.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }// splicing lists
      #include <iostream>
      #include <list>
      
      int main ()
      {
        std::list<int> mylist1, mylist2;
        std::list<int>::iterator it;
      
        // set some initial values:
        for (int i=1; i<=4; ++i)
           mylist1.push_back(i);      // mylist1: 1 2 3 4
      
        for (int i=1; i<=3; ++i)
           mylist2.push_back(i*10);   // mylist2: 10 20 30
      
        it = mylist1.begin();
        ++it;                         // points to 2
      
        mylist1.splice (it, mylist2); // mylist1: 1 10 20 30 2 3 4
                                      // mylist2 (empty)
                                      // "it" still points to 2 (the 5th element)
                                                
        mylist2.splice (mylist2.begin(),mylist1, it);
                                      // mylist1: 1 10 20 30 3 4
                                      // mylist2: 2
                                      // "it" is now invalid.
        it = mylist1.begin();
        std::advance(it,3);           // "it" points now to 30
      
        mylist1.splice ( mylist1.begin(), mylist1, it, mylist1.end());
                                      // mylist1: 30 3 4 1 10 20
      
        std::cout << "mylist1 contains:";
        for (it=mylist1.begin(); it!=mylist1.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        std::cout << "mylist2 contains:";
        for (it=mylist2.begin(); it!=mylist2.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }
      

      Output:

      mylist1 contains: 30 3 4 1 10 20
      mylist2 contains: 2
      
    2. removeimage-20240120220101850

      • 功能:移除容器中所有值为val的数据,会调用移除数据对应的析构函数,同时会减少容器的size。
      • 参数:需要移除的元素,类型value_type是类模板T的重定义
      • 返回值:无
      • 注意:
        • remove函数是删除所有值为val的数据,erase删除迭代器pos处的位置。
        • 指向val数据处的迭代器会失效,其余的不会失效。

      demo:

      // remove from list
      #include <iostream>
      #include <list>
      
      int main ()
      {
        int myints[]= {17,89,7,14};
        std::list<int> mylist (myints,myints+4);
      
        mylist.remove(89);
      
        std::cout << "mylist contains:";
        for (std::list<int>::iterator it=mylist.begin(); it!=mylist.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }
      

      output:

      mylist contains: 17 7 14
      
    3. remove_if:删除满足条件的数据,条件需要通过仿函数传递, 等我们讲解仿函数后再来讲解。

    4. uniqueimage-20240120221434347

      • 功能:删除连续的重复元素,所有重复元素只保留第一个
      • 参数:
        第一个版本是严格删除所有值相等的连续元素。第二个版本我们可以根据传参自己定义“相同性”,通过仿函数实现。
      • 返回值:空
      • 注意:指向删除元素的迭代器会失效。

      demo:

      output:

    5. mergeimage-20240120222422941

      • 功能:通过将 x 的所有元素在各自有序位置传输到容器中(两个容器都应已排序),将 x 合并到列表中。
      • 参数:
        • x:与*this具有相同类型的list,函数调用结束后x会被修改
        • comp:仿函数参数,决定具体是如何比较的
      • 返回值:空
      • 注意:
        • 执行merge的前提是两个list都是有序的,list无序请使用std::list::splicex中的元素按照operator<或者comp定义的比较方法找到合适的位置进行插入,由此产生的等价元素顺序是稳定的。
        • 执行完后,xsize会变为0。
        • 执行该操作时,既不构造也不销毁任何元素。
        • 如果this==&x,则函数不会执行任何操作。
        • 调用函数之后,迭代器仍然指向之前的元素,但是指向的可能从一个容器变成另一个容器。

      demo:

      // list::merge
      #include <iostream>
      #include <list>
      
      // compare only integral part:
      bool mycomparison (double first, double second)
      { return ( int(first)<int(second) ); }
      
      int main ()
      {
        std::list<double> first, second;
      
        first.push_back (3.1);
        first.push_back (2.2);
        first.push_back (2.9);
      
        second.push_back (3.7);
        second.push_back (7.1);
        second.push_back (1.4);
      
        first.sort();
        second.sort();
      
        first.merge(second);
      
        // (second is now empty)
      
        second.push_back (2.1);
      
        first.merge(second,mycomparison);
      
        std::cout << "first contains:";
        for (std::list<double>::iterator it=first.begin(); it!=first.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }
      

      output:

      first contains: 1.4 2.2 2.9 2.1 3.1 3.7 7.1
      

      请注意,在第二次合并中,函数 mycomparison(仅比较整数部分)没有考虑 2.1 低于 2.2 或 2.9,因此它被插入到它们之后、3.1 之前。

    6. sortimage-20240120224158440

      • 功能:对列表中的元素进行排序,改变它们在容器中的位置。排序是通过使用 operator<(版本(1))或 comp(版本(2))来比较元素的算法进行的。这种比较应产生严格的元素弱排序(即一致的反式比较,不考虑其反射性)。
      • 参数:二元谓词,在取两个与列表中的值类型相同的值时,如果第一个参数在其定义的严格弱排序中位于第二个参数之前,则返回 true,否则返回 false。该谓词应是函数指针或函数对象。
      • 返回值:空
      • 注意:
        • 整个操作不涉及任何元素对象的构造、销毁或复制。元素在容器内移动。
        • 迭代器不会失效,还是只想之前的元素。

      demo:

      // list::sort
      #include <iostream>
      #include <list>
      #include <string>
      #include <cctype>
      
      // comparison, not case sensitive.
      bool compare_nocase (const std::string& first, const std::string& second)
      {
        unsigned int i=0;
        while ( (i<first.length()) && (i<second.length()) )
        {
          if (tolower(first[i])<tolower(second[i])) return true;
          else if (tolower(first[i])>tolower(second[i])) return false;
          ++i;
        }
        return ( first.length() < second.length() );
      }
      
      int main ()
      {
        std::list<std::string> mylist;
        std::list<std::string>::iterator it;
        mylist.push_back ("one");
        mylist.push_back ("two");
        mylist.push_back ("Three");
      
        mylist.sort();
      
        std::cout << "mylist contains:";
        for (it=mylist.begin(); it!=mylist.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        mylist.sort(compare_nocase);
      
        std::cout << "mylist contains:";
        for (it=mylist.begin(); it!=mylist.end(); ++it)
          std::cout << ' ' << *it;
        std::cout << '\n';
      
        return 0;
      }
      

      output:

      mylist contains: Three one two
      mylist contains: one Three two
      

      对于默认字符串,比较是严格的字符代码比较,其中所有大写字母都比所有小写字母小,在第一次排序操作中将所有以大写字母开头的字符串放在前面。

    7. reverse image-20240120224901029

      • 功能:逆置list中的数据。
      • 参数:空
      • 返回值:空
      • 注意:迭代器不会失效,让然指向之前的元素,但是位置可能发生改变

list的实现

  • 有关list的拷贝时,注意深拷贝问题!

  • 实现list的重头主要是迭代器的实现,其余的操作和双链表没有什么区别。下面直接直接看代码~

    /*list.h*/
    #define _CRT_SECURE_ 1
    #pragma once
    #include <iostream>
    #include <assert.h>
    #include <algorithm>
    #include <string>
    #include <list>
    using std::cout;
    using std::cin;
    using std::endl;
    using std::string;
    namespace myList
    {
    	/*定义节点类型*/
    	template<class T>
    	struct Node
    	{
    		T _val;
    		Node* _next;
    		Node* _prev;
    
    		Node(const T& val = T())
    			:_val(val),
    			_next(nullptr),
    			_prev(nullptr)
    		{}
    	};
    
    	/*定义list迭代器,我们想让list迭代器的++和*与普通指针操作不同
    	所以要将迭代器封装为一个以指向节点的指针为成员变量的类,对类进行运算符重载*/
    	template <class T, class Ref, class Ptr>
    	struct __list_iterator	//封装的迭代器类
    	{
    		typedef Node<T> Node;
    		typedef __list_iterator<T, Ref, Ptr> self;//简化迭代器的名称
    		/*成员变量---指向节点的指针*/
    		Node* _node;
    		/*成员函数*/
    		__list_iterator( Node* node)			  //单参数构造函数
    			:_node(node)				
    		{}
    		__list_iterator(const self& it)			  //拷贝构造函数
    		{
    			_node = it._node;
    		}
    		//重载后缀++								  //迭代器++相当于指向节点的指针指向链表中的下一个节点---_node=_node->next
    		self operator++(int)
    		{
    			self tmp(*this);
    			_node = _node->_next;
    			return tmp;
    		}
    		//重载前缀++
    		self& operator++()
    		{
    			_node = _node->_next;
    			return *this;
    		}
    		//重载后缀--
    		self operator--(int)
    		{
    			self tmp(*this);
    			_node = _node->_prev;
    			return tmp;
    		}
    		//重载前缀--
    		self& operator--()
    		{
    			_node = _node->_prev;
    			return *this;
    		}
    		//重载*
    		Ref operator*()							   //迭代器解引用相当于访问链表节点的数据与---_node->_val
    		{
    			return _node->_val;
    		}
    		//重载->
    		Ptr operator->()						   //list存储自定义类型时,重载->使得通过it->自定义成员可以直接访问自定义类型
    		{
    			return &_node->_val;
    		}
    		//重载==
    		bool operator==(const self& it)			   //2个迭代器指向链表的节点是否一样
    		{
    			return _node == it._node;
    		}
    		//重载!=
    		bool operator!=(const self& it)
    		{
    			return _node != it._node;
    		}
    
    	};
    	template<class T>
    	class list
    	{
    	private:
    		Node<T>* _head;							  //指向头节点
    	public:
    		typedef Node<T> Node;
    		//泛型编程
    		typedef __list_iterator<T, T&, T*> iterator;//普通迭代器的operator* operator->返回值所引用/指向的对象是可以修改的,返回普通引用/指针
    		typedef __list_iterator<T, const T&, const T*> const_iterator;//const迭代器的operator* operator->的返回值所引用/指向的对象不可以修改
    																	  //无法对*it或it->__进行写入,返回常引用/指向常量的指针
    
    		//下面直接返回指针,通过单参数构造函数的隐式类型转换,可以根据返回的指针构造一个对象,传值时在进行拷贝构造-->会优化为直接构造
    		iterator begin()
    		{
    			return _head->_next;//单参数构造函数的隐式类型转换
    		}
    		const_iterator begin() const
    		{
    			return _head->_next;//单参数构造函数的隐式类型转换
    		}
    		iterator end()
    		{
    			return _head;		//单参数构造函数的隐式类型转换
    		}
    		const_iterator end() const
    		{
    			return _head;		//单参数构造函数的隐式类型转换
    		}
    
    		//初始化一个带头双向循环链表
    		void empty_init()
    		{
    			_head = new Node;
    			_head->_prev = _head->_next = _head;
    		}
    		list()
    		{
    			empty_init();
    		}
    		list(int n, const T& val = T())					//fill构造函数
    		{
    			assert(n >= 0);
    			empty_init();
    			while (n--)
    			{
    				push_back(val);
    			}
    		}
    		template<class InputIterator>
    		list(InputIterator first, InputIterator last)  //range构造函数
    		{
    			empty_init();
    			while (first != last)
    			{
    				push_back(*first++);
    			}
    		}
    		//lt1(lt2)拷贝时注意深拷贝问题,使用push_back可以进行深拷贝
    		list(const list<T>& lt)
    		{
    			empty_init();
    			for (auto e : lt)
    			{
    				push_back(e);
    			}
    		}
    		/*修改类函数*/
    		void push_back(const T& val)
    		{
    			insert(end(), val);
    		}
    		void push_front(const T& val)
    		{
    			insert(begin(), val);
    		}
    		void pop_back()
    		{
    			erase(--end());
    		}
    		void pop_front()
    		{
    			erase(begin());
    		}
    		iterator insert(iterator pos, const T& val)//pos位置前插入元素
    		{
    			Node* newnode = new Node(val);
    			Node* cur = pos._node;
    			Node* prev = pos._node->_prev;
    			//连接节点
    			prev->_next = newnode;
    			newnode->_prev = prev;
    			newnode->_next = cur;
    			cur->_prev = newnode;
    			return pos;
    		}
    		iterator erase(iterator pos)
    		{
    			assert(pos != _head);//不能删除头节点
    			Node* prev = pos._node->_prev;
    			Node* next = pos._node->_next;
    			prev->_next = next;
    			next->_prev = prev;
    			delete pos._node;
    			return next;
    		}
    
    		void clear()//除头节点外所有节点全部删除
    		{
    			iterator it = begin();
    			while (it != end())
    			{
    				it = erase(it);
    			}
    		}
    		void swap(list<T>& lt)//交换两个list的值
    		{
    			std::swap(_head, lt._head);
    		}
    		//lt1=lt3
    		list<T>& operator=( list<T> lt)
    		{
    			swap(lt);
    			return *this;
    		}
    
    		size_t size()
    		{
    			size_t sz = 0;
    			iterator it = begin();
    			while (it != end())
    			{
    				sz++;
    			}
    			return sz;
    		}
    		~list()
    		{
    			clear();
    			delete _head;
    		}
    	};
    
    		//test1现在不是成员函数
    	void test1()
    	{
    		int arr[5] = { 1, 3 ,5, 7, 9 };
    		list<int> lt1(arr, arr + 5);
    		for (auto e : lt1)
    		{
    			cout << e << " ";
    		}
    		cout << endl;
    		list<int> lt2(3, 1);
    		for (auto e : lt2)
    		{
    			cout << e << " ";
    		}
    		cout << endl;
    		cout << endl;
    	}
    	void test2()
    	{
    		list<string> lt_str;
    		lt_str.push_back("hello");
    		lt_str.push_back("list");
    		lt_str.push_back("!!!");
    		list<string> lt_copy_str(lt_str);
    		for (auto e : lt_copy_str)
    		{
    			cout << e << endl;
    		}
    		lt_copy_str.pop_back();
    		lt_copy_str.pop_front();
    		for (auto e : lt_copy_str)
    		{
    			cout << e << endl;
    		}
    		lt_str = lt_copy_str;
    		for (auto e : lt_str)
    		{
    			cout << e << endl;
    		}
    	}
    }
    
    /*main.cpp*/
    #define _CRT_SECURE_NO_WARNINGS 1
    #include "List.h"
    //using namespace std;
    int main()
    {
            myList::test1();
    	myList::test2();
    	return 0;
    }
    

    output:image-20240121094627973

说明:list的模板参数有三个

  • 第一个参数class T决定list存储的数据类型。
  • 第二个参数class Ref决定operator*的返回值类型
    1. iterator的operator*返回值类型为可以修改的引用T&
    2. const_iterator的operator*返回值类型为不可修改的引用const T&
  • 第三个参数clss Ptr决定operator->的返回值类型
    1. iterator的operator*返回值类型为指向对象可修改的指针T*
    2. const_iterator的operator->返回值类型为指向不可修改的对象的指针const T*
struct AA
{
    int a;
    int b;
};

重载operator->后,假若list中存放的是自定义类型AA,可以通过it->a1访问自定义类型中的成员image-20240121100158968

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

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

相关文章

Java开发工具:IntelliJ IDEA 2023 for Mac中文激活

IntelliJ IDEA 2023是一款由JetBrains开发的强大的集成开发环境&#xff08;IDE&#xff09;软件&#xff0c;适用于多个编程语言。它旨在提高开发人员的生产力和代码质量。 软件下载&#xff1a;Java开发工具&#xff1a;IntelliJ IDEA 2023 for Mac中文激活 IntelliJ IDEA 20…

Docker入门学习

1、docker简介 1.1什么是docker Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目&#xff0c;它是基于 dotCloud 公司多年云服务技术的一次革新&#xff0c;并于 2013 年 3 月以 Apache 2.0 授权协议开源&#xff0c;主要项目代码在 GitH…

Maven工程继承和聚合关系

1. Maven工程继承关系 1.1 继承概念 Maven 继承是指在 Maven 的项目中&#xff0c;让一个项目从另一个项目中继承配置信息的机制。继承可以让我们在多个项目中共享同一配置信息&#xff0c;简化项目的管理和维护工作。 1.2 继承作用 在父工程中统一管理项目中的依赖信息。 …

Kafka(三)【Broker 存储】

目录 前言 Kafka Broker 1、工作流程 1.1、Zookeeper 存储的 Kafka 信息 1.2、Kafka Broker 的总体工作流程 1.3、Broke 重要参数 2、Kafka 副本 2.1、副本基本信息 2.2、Keader 选举流程 2.3、Leader 和 Follower 的故障处理细节 Follower 故障 Leader 故障&#x…

GAMMA处理数据(五)

1、差分干涉 命令&#xff1a;SLC_diff_int 2、相干性估计 命令&#xff1a;cc_ad 3、地形相位去除 因为这个错误&#xff1a;浪费了大把时间&#xff0c;到处百度&#xff0c;bing&#xff0c;怀疑是脑子糊涂了&#xff0c;我的参数输入错误了&#xff0c;命令叫输入par文件…

智能合约:Web3的商业合作新模式

随着区块链技术的发展&#xff0c;智能合约在Web3时代崭露头角&#xff0c;成为商业合作中的全新模式。这一技术不仅重新定义了商业合作的方式&#xff0c;还为各行各业带来了更加高效、透明和安全的商务交往。本文将深入探讨智能合约在Web3时代的崭新商业合作模式&#xff0c;…

【一站解决您的问题】mac 利用命令升级nodejs、npm、安装Nodejs的多版本管理器n、nodejs下载地址

一&#xff1a;下载nodejs 官网地址&#xff0c;点击下载稳定版 https://nodejs.org/en 如果官网下载特别慢&#xff0c;可以点击这个地址下载 点击这里 https://nodejs.cn/download/current/ 安装完成后&#xff0c;就包含了nodejs 和 npm。此时您的版本就是下载安装的版本…

Redis 面试题 | 05.精选Redis高频面试题

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

STM32WLE5JC 低功耗模式

低功耗模式 该器件支持多种功耗模式&#xff0c;以实现低功耗、短启动时间、可用外设和可用唤醒源之间的最佳折衷。 默认情况下&#xff0c;在系统或上电复位后&#xff0c;微控制器处于运行模式&#xff0c;范围1&#xff1a; 休眠模式&#xff1a;CPU时钟关闭&#xff0c;…

使用PSIM软件生成DSP28335流水灯程序

最近在学习DSP28335芯片&#xff0c;然后在使用PSIM仿真软件时发现这个仿真软件也支持28335芯片&#xff0c;于是就想学习下如何在PSIM软件中使用DSP28335芯片。在PSIM自带的官方示例中有使用DSP28335芯片的相关例子。 工程下载链接 https://download.csdn.net/download/qq_20…

mockjs使用(2)

mockjs使用&#xff08;1&#xff09; 4、Mock 4.1 Mock.mock() 根据数据模版生成模拟数据 Mock.mock( rurl?, rtype?, template|function(options) )问号代表该参数不必填 4.1.1 各参数及其默认值 rurl: 不必填。表示需要拦截的URL&#xff0c;可以使URL字符串或URL正…

QT quick基础:组件Canvas

参考《QT quick 核心编程》 使用qml画图。以下面的代码段为例&#xff0c;记录画图方法。 一、基本用法 Canvas {// 画布id:canvas;width: parent.width; // 画布宽度height: parent.height;// 画布高度onPaint: {var ctx canvas.getContext("2d"); // 使用画布类…

Scala基础知识

scala 1、scala简介 ​ scala是运行在JVM上的多范式编程语言&#xff0c;同时支持面向对象和面向函数式编程。 2、scala解释器 要启动scala解释器&#xff0c;只需要以下几步&#xff1a; 按住windows键 r输入scala即可 在scala命令提示窗口中执行:quit&#xff0c;即可退…

深度学习笔记(九)——tf模型导出保存、模型加载、常用模型导出tflite、权重量化、模型部署

文中程序以Tensorflow-2.6.0为例 部分概念包含笔者个人理解&#xff0c;如有遗漏或错误&#xff0c;欢迎评论或私信指正。 本篇博客主要是工具性介绍&#xff0c;可能由于软件版本问题导致的部分内容无法使用。 首先介绍tflite: TensorFlow Lite 是一组工具&#xff0c;可帮助开…

Java21 + SpringBoot3集成easy-captcha实现验证码显示和登录校验

文章目录 前言相关技术简介easy-captcha 实现步骤引入maven依赖定义实体类定义登录服务类定义登录控制器前端登录页面实现测试和验证 总结附录使用Session缓存验证码前端登录页面实现代码 前言 近日心血来潮想做一个开源项目&#xff0c;目标是做一款可以适配多端、功能完备的…

Android.mk和Android.bp的区别和转换详解

Android.mk和Android.bp的区别和转换详解 文章目录 Android.mk和Android.bp的区别和转换详解一、前言二、Android.mk和Android.bp的联系三、Android.mk和Android.bp的区别1、语法&#xff1a;2、灵活性&#xff1a;3、版本兼容性&#xff1a;4、向后兼容性&#xff1a;5、编译区…

鸿蒙开发笔记(二十三):图形展示 Image,Shape,Canvas

1. Image 在应用中显示图片需要使用Image组件实现&#xff0c;Image支持多种图片格式&#xff0c;包括png、jpg、bmp、svg和gif&#xff0c;具体用法请参考Image组件。 Image通过调用接口来创建&#xff0c;接口调用形式如下&#xff1a; Image(src: string | Resource | me…

力扣第92题——反转链表 II(C语言题解)

题目描述 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff1…

精品基于Uniapp+springboot智慧农业环境监测App

《[含文档PPT源码等]精品基于Uniappspringboot智慧农业环境监测App》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;Java 后台框架&#xff1a;springboot、ssm …

Android双指缩放ScaleGestureDetector检测放大因子大图移动到双指中心点ImageView区域中心,Kotlin

Android双指缩放ScaleGestureDetector检测放大因子大图移动到双指中心点ImageView区域中心&#xff0c;Kotlin 在 Android双击图片放大移动图中双击点到ImageView区域中心&#xff0c;Kotlin-CSDN博客 基础上&#xff0c;这次使用ScaleGestureDetector检测两根手指的缩放动作&a…