zoukankan      html  css  js  c++  java
  • 7.3 Set集合


    下图展示Collection接口与其子类的继承树关系:

    Set集合类似一个罐子,程序可以依次把多个对象“丢进”Set集合,而且Set集合通常不能记住这些元素的添加顺序。Set集合与Collection基本相同,没有提供额外的方法,事实上Set就是Collection,只是行为略有不同(Set集合不允许包含重复元素)。

    一、HashSet类

    HashSet是Set接口的典型实现,大多数时候使用Set集合就是使用的这个实现类。HashSet按Hash算法存储集合中的元素,因此具有很好的存取和查找功能。
    HashSet具有以下特点:
    (1)不能保证元素的排列顺序,顺序可能与添加顺序不同。
    (2)HashSet是不同步的,如果多个线程同时访问一个HashSet,假设有两个或两个以上的线程同时修改HashSet集合时,则必须通过代码来保证其同步。
    (3)集合值可以为null。
    当向一个HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来获得该对象的hashcode值,然后根据hashcode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals()比较返回true,但他们的HashCode()返回值不一样,HashSet会把它们存放在不同位置。,依然可以添加成功。HashSet判断两个对象是否相等的标准:两个对象通过equals()比较返回true,且它们的hashCode值相同
    下面提供了三个类A,B,C,它们分别重写了equals()和hashCode()两个方法的一个或两个,通过该程序可以明白HashSet判断集合元素相同的标准。

    import java.util.HashSet;
    class A
    {
    	@Override
    	public boolean equals(Object obj)
    	{
    		return true;//类A的equals()方法总是返回true
    	}
    }
    //类B的hashCode()方法总是返回1
    class B
    {
    	@Override
    	public int hashCode()
    	{
    		return 1;
    	}
    }
    //类C的hashCode()总是返回2,equals()方法总是返回true
    class C
    {
    	public int hashCode()
    	{
    		return 2;
    	}
    	public boolean equals(Object obj)
    	{
    		return true;
    	}
    }
    public class HashSetTest
    {
    	public static void main(String[] args)
    	{
    		var books=new HashSet();
    		//分别向集合中添加两个A对象、两个B对象、两个C对象
    		books.add(new A());
    		books.add(new A());
    		books.add(new B());
    		books.add(new B());
    		books.add(new C());
    		books.add(new C());
    		System.out.println(books);
    	}
    }
    ---------- 运行Java捕获输出窗 ----------
    [B@1, B@1, C@2, A@2a84aee7, A@8efb846]
    
    输出完成 (耗时 0 秒) - 正常终止
    

    上面程序中,只有5个元素,这是因为集合把两个C对象当成一个对象。虽然两个B对象的hashCode值相同,但是通过equals()方法放回false,HashSet仍然将它们看成两个不同的对象。
    HashSet集合访问集合元素通过元素的hashCode值来快速定位,如果HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降。因此想把某个对象保存在HashSet集合中,尽量保证两个对象通过equals()返回true时,它们的hashCode()方法返回值也相同。
    HashSet中每个能存储元素的“槽位”(slot)通常称为“桶”(bucket),如果有多个元素的hashCode值相同,但他们通过equals()方法比较返回flase,就需要在一个桶里放多个元素,将会导致性能降低。
    下面给出重写hashCode()的原则:
    ★当程序运行过程中,同一个对象多次调用hashCode()方法返回相同值。
    ★当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法返回相同的值。
    ★对象中用于比较标准实例变量,都应该用于计算hashCode值。
    重写hashCode()方法的一般步骤:
    1、把每个有意义的实例变量(即每个参与equals()方法比较的标准的实例变量)计算出一个int类型的hashCode()值。

    实例变量类型 计算方式 实例变量类型 计算方式
    boolean hashCode=(f?0:1) float hashCode=Float.floatToIntBits(f)
    整数类型(byte、short、int、char) hashCode=(int)f double long l=Double.doubleToIntBits(f);hashCode=(int)l^(l>>>32)
    long hashCode=(int)(f^(f>>>32)) 引用类型 hashCode=f.hashCode()

    2、用第一步计算出来的多个hashCode值组合计算出一个hashCode值返回。
    return f1.hashCode()+(int)f2;
    为了避免偶然相等(两个对象的f1,f2实例变量不相等,但他们的hashCode值计算相等),可以通过为个实例变量的hashCode值乘以一个任意的质数再相加。
    return f1.hashCode()*19+(int)f2*31;
    向HashSet中添加一个可变对象后,后面程序修改了该对象的实例变量,则可能导致他与集合中的其他元素相同(即两个对象通过equals()方法返回true,两个对象的hashCodez值也相同),这就导致了HashSet中可能包含两个相同的元素。

    import java.util.HashSet;
    class R
    {
    	int count;
    	public R(int count)
    	{
    		this.count=count;
    	}
    	@Override
    	public String toString()
    	{
    		return "R[count:"+count+"]";
    	}
    	@Override
    	public boolean equals(Object obj)
    	{
    		if(this==obj)
    			return true;
    		else if(obj!=null&&obj.getClass()==R.class)
    		{
    			var r=(R) obj;
    			return this.count==r.count;
    		}
    		else 
    			return false;
    	}
    	@Override
    	public int hashCode()
    	{
    		return this.count;
    	}
    }
    public class HashSetTest2
    {
    	public static void main(String[] args)
    	{
    		var hs=new HashSet();
    		hs.add(new R(5));
    		hs.add(new R(-3));
    		hs.add(new R(9));
    		hs.add(new R(-2));
    		//打印出HashSet集合,集合元素没有重复
    		System.out.println(hs);//输出[R[count:-2], R[count:-3], R[count:5], R[count:9]]
    		System.out.println(hs.add(new R(9)));//输出false
    		System.out.println(hs);//集合元素没有改变[R[count:-2], R[count:-3], R[count:5], R[count:9]]
    
    		//取出第一个元素:
    		var it=hs.iterator();
    		//取出第一个元素
    		var first=(R)it.next();
    		//为第一个元素的count实例变量赋值
    		first.count=-3;
    		//再次输出HashSet集合,集合元素有重复元素
    		System.out.println(hs);//[R[count:-3], R[count:-3], R[count:5], R[count:9]]
    		//删除count为-3的R对象
    		hs.remove(new R(-3));//只删除第一个符合条件的
    		System.out.println(hs);//[R[count:-3], R[count:5], R[count:9]]
    		System.out.println(hs.contains(new R(-3)));//false
    		System.out.println(hs.contains(new R(-2)));//false
    		System.out.println(hs.contains(new R(5)));//true
    	}
    }
    

    上面过程图解:

    二、LinkedHashSet类

    LinkedHashSet是HashSet的子类,LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是以插入顺序保存的。也就是说遍历LinkedHashSet集合的元素时,LinkedHashSet将会按照元素的添加顺序来访问集合里的元素。
    LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但访问Set的全部元素将有很好的性能,因为它以链表来维护内部的顺序。

    import java.util.LinkedHashSet;
    public class LinkedHashSetTest
    {
    	public static void main(String[] args)
    	{
    		var books=new LinkedHashSet();
    		books.add("指环王1");
    		books.add("指环王2");
    		books.add("指环王3");
    		books.add("指环王4");
    		//1、使用Lambda表达式遍历集合
    		books.forEach(obj->System.out.println("迭代元素:"+obj));
    		//2、使用Iterater遍历结合元素
    		var it=books.iterator();
    		while(it.hasNext())
    		{
    			System.out.print(it.next()+",");
    		}
    		System.out.println();
    		//3、使用lambda表达式遍历iterator
    		var it1=books.iterator();
    		it1.forEachRemaining(obj->System.out.println("迭代元素:"+obj));
    		//4、使用foreach循环遍历集合元素
    		for(var book:books)
    		{
    			System.out.println("迭代元素:"+book);
    		}
    
    	}
    }
    

    上面输出LinkedHashSet集合的元素时,总是与添加顺序一致。虽然LinkedHashSet通过链表记住了集合元素的添加顺序,但它依然是HashSet,因此依然不允许集合元素重复。

    三、TreeSet类

    TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。与HashSet集合相比,TreeSet还提供了几个额外的方法:
    ★Comparator comparator():如果TreeSet采用了定制顺序,该方法返回定制排序所用的Comparator;如果TreeSet采用自然排序,则返回null。
    ★Object first():返回集合中的第一个元素。
    ★Object last():返回集合中最后一个元素。
    ★Object lower(Object e):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素不需要是TreeSet集合里的元素)。
    ★Object higher(Object e):返回集合中位于指定元素之后的元素(即大于指定元素的最小元素,参考元素不需要是TreeSet集合里的元素)。
    ★SortedSet subSet(Object fromElement,Object toElement):返回此Set子集合,范围从fromElement(包含)到toElement(不包含)。
    ★SortedSet headSet(Object toElement):返回此Set的子集,由小于toElement的元素组成。
    ★SortedSet tailSet(Object fromElement):返回此Set的子集,由大于或等于fromElement的元素组成。
    测试TreeSet的方法:

    import java.util.TreeSet;
    public class TreeSetTest
    {
    	public static void main(String[] args)
    	{
    		var nums=new TreeSet();
    		//向TreeSet中添加四个Integer对象
    		nums.add(5);
    		nums.add(2);
    		nums.add(10);
    		nums.add(-9);
    		//输出集合元素,看到集合的元素已经处于排序状态
    		System.out.println(nums);//[-9, 2, 5, 10]
    		//输出集合中第一个元素
    		System.out.println(nums.first());
    		//输出集合中最后一个元素
    		System.out.println(nums.last());
    		//返回小于4的子集,不包含4
    		System.out.println(nums.headSet(4));
    		//返回大于或等于5的子集
    		System.out.println(nums.tailSet(5));
    		//返回大于等于-3、小于4的子集
    		System.out.println(nums.subSet(-3,4));
    	}
    }
    ---------- 运行Java捕获输出窗 ----------
    [-9, 2, 5, 10]
    -9
    10
    [-9, 2]
    [5, 10]
    [2]
    
    输出完成 (耗时 0 秒) - 正常终止
    

    TreeSet采用红白树的数据结构来存储集合元素,TreeSet支持两种排序方式:自然排序和定制排序。

    1、自然排序

    TreeSet会调用集合元素的compareTo(Object obj)来比较两个元素之间的大小关系,然后将集合元素升序排列,这种方式就是自然排序。
    compareTo(Object obj)方法来自于Comparable接口,该方法返回一个整数值。当两个对象相等时,obj1.compare(obj2)返回0;如果obj1>obj2,则obj1.compare(obj2)返回正整数;如果obj1<obj2,obj1.compare(obj2)返回负整数。
    Java常用的一些类已经实现了Comparable接口,并提供了比较大小的标准,下面是实现了Comparable接口的常用类:
    ★BigDecimal、BigInteger以及所有的包装类:按它们对应的数值大小进行比较。
    ★Character:按字符的Unicode值进行比较。
    ★Boolean:true对应的包装类实例大于false对应的包装类实例。
    ★String:依次比较字符串中每个字符的Unicode值
    ★Date、Time:后面的时间、日期比前面的时间、日期大。
    如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。例如:

    import java.util.TreeSet;
    class Err
    {
    }
    public class TreeSetErrorTest
    {
    	public static void main(String[] args)
    	{
    		var ts=new TreeSet();
    		//向TreeSet集合中添加Err对象
    		ts.add(new Err());
    	}
    }
    Exception in thread "main" java.lang.ClassCastException: class Err cannot be cast to class java.lang.Comparable (Err is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
    

    大部分类在实现compareTo(Object obj)方法时,都需要将比较对象obj强制转换为相同类型,因为只有相同类型的两个实例才会比较大小。当试图把一个对象添加到TreeSet集合时,TreeSet会调用该对象的compareTo(Object obj)方法与集合中的其他元素进行比较——这就要求集合中的其他元素与该元素是同一类中的实例。

    import java.util.TreeSet;
    class Err
    {
    }
    public class TreeSetErrorTest
    {
    	public static void main(String[] args)
    	{
    		var ts=new TreeSet();
    		//向TreeSet集合中添加Err对象
    		ts.add(new Err());
    	}
    }
    Exception in thread "main" java.lang.ClassCastException: class Err cannot be cast to class java.lang.Comparable (Err is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
    

    当向程序试图向TreeSet添加Err对象,在自然排序时,集合元素都必须实现Comparable接口,否则将会引发运行时异常:ClassCastException。——因此,TreeSet要求自然排序的集合元素都必须实现该接口。
    大部分类在实现compareTo(Object obj)时,都需要将比较对象转换为相同类型,因为只有相同类型的两个实例才会比较大小。当试图把一个对象添加到TreeSet集合时,TreeSet会调用该对象的compareTo(Object obj)方法与集合中的其他元素进行比较——这就要求集合的其他元素与该元素是同一个元素。也就是说向TreeSet添加的应该是同一个类的对象,否则将会引发ClassCastException异常。如下程序所示:

    import java.util.Date;
    import java.util.TreeSet;
    public class TreeSetErrorTest2
    {
    	public static void main(String[] args)
    	{
    		var ts=new TreeSet();
    		ts.add(new String("Java疯狂讲义"));
    		ts.add(new Date());
    	}
    }
    Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Date (java.lang.String and java.util.Date are in module java.base of loader 'bootstrap')
    

    当把一个对象加入TreeSet集合中时,TreeSet会调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置。如果两个对象的compareTo(Object obj)方法比较相等,新对象将无法添加到TreeSet集合中。对于TreeSet集合判断两个对象是否相等的标准是:两个对象通过compareTo(Object obj)方法比较是否返回0——如果通过compareTo(Object obj)方法比较返回0,TreeSet则会认为它们相等,否则认为不相等。

    import java.util.TreeSet;
    class Z implements Comparable
    {
    	int age;
    	public Z(int age)
    	{
    		this.age=age;
    	}
    	//重写equlas()方法,总是返回true
    	public boolean equals(Object obj)
    	{
    		return true;
    	}
    	//重写Comparable接口的compareTo()方法,总是返回1
    	public int compareTo(Object obj)
    	{
    		return 1;
    	}
    }
    public class TreeSetTest3
    {
    	public static void main(String[] args)
    	{
    		var set=new TreeSet();
    		var z1=new Z(6);
    		set.add(z1);
    		//第二次添加同一个元素,输出true,表明添加成功
    		System.out.println(set.add(z1));//true
    		System.out.println(set);//[Z@30f39991, Z@30f39991]
    		//修改set集合第一个元素的age变量
    		((Z)set.first()).age=9;
    		//输出set集合的最后一个元素的age变量,也将看到9
    		System.out.println(((Z)(set.last())).age);//9
    	}
    }
    

    TreeSet以及Z对象在内存中的存储示意图:

    **在重写equals()和compareTo()方法时,要注意一个问题:当需要把一个对象放入TreeSet中,重写该对象对应类的equals()方法时,应该保证与compareTo()方法有一致的结果。其规则是:如果两个对象通过equals()方法返回true时,两个对象通过compareTo(Object obj)方法比较应返回0.
    如果向TreeSet中添加一个可变对象后,并且后面的程序修改了该可变对象的实例变量,浙江导致它与其他对象的大小顺序也发生了改变,但TreeSet不会再次调整它们的顺序,甚至可能导致TreeSet中保存的这两个对象通过compareTo(Object obj)方法比较返回0.下面程序演示这种情况:

    import java.util.TreeSet;
    class R implements Comparable
    {
    	int count;
    	public R(int count)
    	{
    		this.count=count;
    	}
    	public String toString()
    	{
    		return "R[count="+count+"]";
    	}
    	//重写equals()方法,根据count来判断是否相等
    	public boolean equals(Object obj)
    	{
    		if(this==obj)
    			return true;
    		else if(obj!=null&&obj.getClass()==R.class)
    		{
    			var r=(R)obj;
    			return r.count==this.count;
    		}
    		else
    			return false;
    	}
    	//重写compareTo()方法,根据count来比较大小
    	public int compareTo(Object obj)
    	{
    		var r=(R)obj;
    		return this.count>r.count?1:this.count<r.count?-1:0;
    	}
    }
    public class TreeSetTestFinal
    {
    	public static void main(String[] args)
    	{
    		var ts=new TreeSet();
    		ts.add(new R(5));
    		ts.add(new R(-3));
    		ts.add(new R(9));
    		ts.add(new R(-2));
    		//打印TreeSet集合,集合元素时有序排列的
    		System.out.println(ts);//[R[count=-3], R[count=-2], R[count=5], R[count=9]]
    		//对第一个元素赋值
    		var first=(R)ts.first();
    		first.count=20;
    		//取出最后一个元素赋值,与第二个元素的count相同
    		var last=(R)ts.last();
    		last.count=-2;
    		//再次输出TreeSet的元素是无需排列的,且含有重复元素
    		System.out.println(ts);//[R[count=20], R[count=-2], R[count=5], R[count=-2]]
    		//删除实例变量给改变的元素,将会失败
    		System.out.println(ts.remove(new R(-2)));//false
    		System.out.println(ts);//[R[count=20], R[count=-2], R[count=5], R[count=-2]]
    		//删除实例变量没有被改变的元素,删除成功
    		System.out.println(ts.remove(new R(5)));//true
    		System.out.println(ts);//[R[count=20], R[count=-2], R[count=-2]]
    		//执行上面代码后,TreeSet中的元素重新索引(不是重新排序),接下来就可以删除TreeSet集合中的所有元素啦
    		System.out.println(ts.remove(new R(-2)));
    		System.out.println(ts);
    	}
    }
    

    一旦改变了TreeSet集合里可变元素的实例变量,当再次试图删除该对象时,TreeSet也会删除失败(甚至是集合中原有的、实例变量没有被修改的但与修改后的元素相等的元素也无法删除),所以上面程序中ts.remove(new R(-2))没有删除任何元素;当程序执行System.out.println(ts.remove(new R(5))),可以看到删除了count为5的R对象,这表明TreeSet可以删除没有被修改的实例变量,且不与其他被修改的实例变量的对象重复的元素

    2、定制排序

    TreeSet自然排序方式是根据集合元素的大小,将它们以升序排列。如果要实现定制排序,则可以通过Comparator接口的帮助。该接口里包含一个int compare(T o1,T o2)方法,该方法用于比较o1,o2的大小,如果o1>o2,返回正整数;如果o1==o2,返回0;如果o1<o2,返回负整数。如果要实现定制排序,则需要创建TreeSet集合对象时,提供一个comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。由于Comparator是一个函数式接口,可使用Lambda表达式来代替Comparator对象。

    
    import java.util.*;
    
    class M
    {
    	int age;
    	public M(int age)
    	{
    		this.age = age;
    	}
    	public String toString()
    	{
    		return "M [age:" + age + "]";
    	}
    }
    public class TreeSetTest4
    {
    	public static void main(String[] args)
    	{
    		// 此处Lambda表达式的目标类型是Comparator
    		var ts = new TreeSet((o1, o2) ->
    		{
    			var m1 = (M) o1;
    			var m2 = (M) o2;
    			// 根据M对象的age属性来决定大小,age越大,M对象反而越小
    			return m1.age > m2.age ? -1
    				: m1.age < m2.age ? 1 : 0;
    		});
    		ts.add(new M(5));
    		ts.add(new M(-3));
    		ts.add(new M(9));
    		System.out.println(ts);
    	}
    }
    输出结果:
    [M [age:9], M [age:5], M [age:-3]]
    

    上面程序传入new TreeSet()构造器中的参数就是Comparator的Lambda表达式,它负责ts集合的排序。

    四、EnumSet类

    EnumSet是一个专门为枚举类设计的集合类,EnumSet中所有元素都必须指定枚举类型的枚举值,该枚举值在创建EnumSet时显式或隐式地指定。EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类中定义顺序来决定集合元素的排列。
    EnumSet在内部以位向量的形式存储,这种存储方式非常紧凑、高效,因此EnumSet占用内存很小,而且效率很高,尤其时批量操作。
    EnumSet没有暴露任何构造器来创建该类的实例,程序应通过它的类方法来创建EnumSet对象。EnumSet提供常用类方法来创建EnumSet对象:
    ★EnumSet allOf(class elementType):创建一个包含指定枚举类里所有枚举值的EnumSet集合。
    ★EnumSet complementOf(EnumSet s):创建一个其元素和指定EnumSet里元素类型相同的EnumSet集合,新的EnumSet集合包含原EnumSet所不包含的、此枚举剩下的枚举值(即新EnumSet和原EnumSet集合的集合元素加起来就是该美剧类的所有枚举值)。
    ★EnumSet copyOf(Collection c):使用一个普通集合来创建EnumSet集合。
    ★EnumSet copyOf(EnumSet s):创建一个与指定EnumSet具有相同元素、相同集合元素的EnumSet集合。
    ★EnumSet noneOf(class elementType):创建一个元素类型为指定枚举类型的空EnumSet。
    ★EnumSet of(E first,E...rest):创建一个包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举类。
    ★EnumSet range(E from,E to):创建一个包含from枚举值到to枚举值范围内的所有枚举值的EnumSet集合。
    下面示范如何使用EnumSet保存枚举类的多个枚举值:

    import java.util.EnumSet;
    enum Season
    {
    	SPRING,SUMMER,FALL,WINTER;
    }
    public class EnumSetTest
    {
    	public static void main(String[] args)
    	{
    		//创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值
    		var es1=EnumSet.allOf(Season.class);
    		System.out.println(es1);//[SPRING, SUMMER, FALL, WINTER]
    		
    		//创建一个元素为Season类枚举值的空EnumSet
    		var es2=EnumSet.noneOf(Season.class);
    		System.out.println(es2);//[]
    		es2.add(Season.SPRING);
    		es2.add(Season.SUMMER);
    		System.out.println(es2);//[SPRING, SUMMER]
    
    		//以指定枚举值创建EnumSet集合
    		var es3=EnumSet.of(Season.SUMMER,Season.WINTER);
    		System.out.println(es3);//[SUMMER, WINTER]
    		var es4=EnumSet.range(Season.SUMMER,Season.WINTER);
    		System.out.println(es4);//[SUMMER, FALL, WINTER]
    
    		//es4+es4=Season枚举类的全部枚举值
    		var es5=EnumSet.complementOf(es4);
    		System.out.println(es5);//[SPRING]
    
    	}
    }
    

    复制另一个EnumSet集合中的所有元素来创建新的EnumSet集合,或者复制另一个Collection集合中的所有元素来创建新的EnumSet集合。当复制Collection集合中的所有元素来创建新的EnumSet集合时,要求Collection集合中所有元素都是同一个枚举类的枚举值。

    import java.util.HashSet;
    import java.util.EnumSet;
    
    public class  EnumSetTest2
    {
    	public static void main(String[] args) 
    	{
    		var c=new HashSet();
    		c.clear();//清除集合中的所有元素
    		c.add(Season.SPRING);
    		c.add(Season.SUMMER);
    		//复制Collection集合中的所有元素来创建EnumSet集合
    		var e=EnumSet.copyOf(c);
    		System.out.println(e);//[SPRING, SUMMER]
    		//下面代码将报错,因为c集合的元素不全部是枚举值。
    		c.add("疯狂Java讲义");
    		//e=EnumSet.copyOf(c);
    		//Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Enum.
    	}
    }
    
    

    总结:

  • 相关阅读:
    2019年8月7日 封装 7夕快乐
    2019年8月4日 多态 苗苗苗苗苗
    2019年7月31日 选课系统
    字符串类型的输入输出
    getline()函数的使用
    出栈序列统计
    因式分解
    质数
    多项式求和
    回文数字
  • 原文地址:https://www.cnblogs.com/weststar/p/12494850.html
Copyright © 2011-2022 走看看