参考:

关于泛型的一些重要知识点

泛型由来:早期Java版本(1.4及之前)如果要代指某个泛化类对象,只能使用Object,这样写出来的代码需要增加强转,而且缺少类型检查,代码缺少健壮性。在1.5之后,Java引入了泛型的概念,提供了一套抽象的类型表示方法。

简单来说,泛型是JDK1.5中出现的安全机制。
好处:将运行时期的ClassCastException问题转到了编译时期,避免了强制转换的麻烦。
什么时候用:当操作的引用数据类型不确定的时候,就使用<>,将要操作的引用数据类型传入即可。
其实<>就是一个用于接收具体引用数据类型的参数范围。
在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型。

泛型技术是给编译器使用的技术,用于编译时期。确保了类型的安全。
运行时,会将泛型去掉,生成的class文件中是不带泛型的,这个称为泛型的擦除。
为什么擦除呢?因为为了兼容运行的类加载器。
泛型的补偿:在运行时,通过获取元素的类型进行转换动作。这样就不用再手动强制转换了。

泛型的通配符【?】未知类型。
泛型的限定:
  • 【? extends E】接收E类型或者E的子类型对象。上限。一般存储对象的时候用。比如 添加元素 addAll。
  • 【? super E】接收E类型或者E的父类型对象。下限。一般取出对象的时候用。比如比较器。

利用泛型,我们可以:
  • 1、表示多个可变类型之间的相互关系:HashMap<T,S>表示类型T与S的映射,HashMap<T, S extends T>表示T的子类与T的映射关系。
  • 2、细化类的能力:ArrayList<T> 可以容纳任何指定类型T的数据,当T代指人,则是人的有序列表,当T代指杯子,则是杯子的有序列表,所有对象个体可以共用相同的操作行为。
  • 3、复杂类型被细分成更多类型:List<People>和List<Cup>是两种不同的类型,这意味着List<People> listP = new ArrayList<Cup>()是不可编译的。这种检查基于编译时而非运行时,所以说是不可编译并非不可运行,因为运行时ArrayList不保留Cup信息。另外要注意,即使People继承自Object,List<Object> listO = new ArrayList<People>()也是不可编译的,应理解为两种不同类型。因为listO可以容纳任意类型,而实例化的People列表只能接收People实例,这会破坏数据类型完整性。

泛型的基本概念

泛型的定义:泛型是JDK 1.5的一项新特性,它的本质是参数化类型 ParameterizedType,即带有类型参数的类型。也就是说所操作的数据类型被指定为一个参数,在用到的时候再指定具体的类型。如:List<T>、Map<Integer, String>、List<? extends Number>。
public interface java.lang.reflect.ParameterizedType extends Type

GenericDeclaration接口是声明类型变量的所有实体的公共接口,也就是说,只有实现了该接口才能在对应的实体上声明类型变量。这些实体目前只有三个:Class、Construstor、Method。当这种参数化类型用在类、接口和方法的创建中时,分别称为泛型类、泛型接口和泛型方法。
注意:因为直接实现子类没有Field类,所以在属性上面不能定义类型变量。
public interface java.lang.reflect.GenericDeclaration
所有已知实现类:Class、Constructor、Method

泛型思想早在C++语言的模板(Templates)中就开始生根发芽,在Java语言处于还没有出现泛型的版本时,只能通过 "Object是所有类型的父类" 和 "类型强制转换" 两个特点的配合来实现类型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一个Object对象,由于Java语言里面所有的类型都继承于java.lang.Object,那Object转型为任何对象成都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多ClassCastException的风险就会被转嫁到程序运行期之中。

泛型技术在C#和Java之中的使用方式看似相同,但实现上却有着根本性的分歧,C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,比如 List<int> 与 List<String> 就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为真实泛型。

Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原始类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<int> 与 ArrayList<String> 就是同一个类型。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。

使用泛型机制编写的程序代码要比那些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类来说尤其有用。
泛型程序设计(Generic Programming)意味着编写的代码可以被很多不同类型的对象所重用。

实例分析

在JDK1.5之前,Java泛型程序设计是用继承来实现的。因为Object类是所用类的基类,所以只需要维持一个Object类型的引用即可。就比如ArrayList只维护一个Object引用的数组:
public class ArrayList{
public Object get(int i){......}
public void add(Object o){......}
......
private Object[] elementData;
}
这样会有两个问题:
  • 没有错误检查,可以向数组列表中添加任何类的对象
  • 在取元素的时候,需要进行强制类型转换
这样,很容易发生错误,比如:
/**jdk1.5之前的写法,容易出问题*/
ArrayList arrayList1=new ArrayList();
arrayList1.add(1);
arrayList1.add(1L);
arrayList1.add("asa"); int i=(Integer) arrayList1.get(1);//因为不知道取出来的值的类型,类型转换的时候容易出错
这里的第二个元素是一个长整型,而你以为是整形,所以在强转的时候发生了错误。

所以。在JDK1.5之后,加入了泛型来解决类似的问题。例如在ArrayList中使用泛型:
/** jdk1.5之后加入泛型*/
ArrayList<String> arrayList2=new ArrayList<String>(); //限定数组列表中的类型
//arrayList2.add(1); //因为限定了类型,所以不能添加整形
//arrayList2.add(1L);//因为限定了类型,所以不能添加整长形
arrayList2.add("asa");//只能添加字符串
String str=arrayList2.get(0);//因为知道取出来的值的类型,所以不需要进行强制类型转换

还要明白的是,泛型特性是向前兼容的。尽管 JDK 5.0 的标准类库中的许多类,比如集合框架,都已经泛型化了,但是使用集合类的现有代码(没有加泛型的代码)可以继续不加修改地在 JDK 1.5 中工作。

泛型的使用

泛型的参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。下面看看具体是如何定义的。

泛型类:类名后面

泛型类就是在声明类时,定义了一个或多个类型变量的类。
泛型类中定义的类型变量的作用范围为当前泛型类中。
泛型类中定义的类型变量用于,在多个方法签名间实施类型约束。例如,当创建一个 Map<K, V> 类型的对象时,您就在方法之间宣称一个类型约束,您 put() 的值将与 get() 返回的值的类型相同。
public class HashMap<K,V> {
public V put(K key, V value) {...}
public V get(Object key) {...}
...
}

定义一个泛型类十分简单,只需要在类名后面加上<>,再在里面加上类型参数:
public class Pair<T> {
private T value; public Pair(T value) {
this.value = value;
} public T getValue() {
return value;
} public void setValue(T value) {
this.value = value;
}
}
现在我们就可以使用这个泛型类了:
public static void main(String[] args) throws ClassNotFoundException {
Pair<String> pair = new Pair<String>("Hello");//注意,"="号左边和右边都要使用<>指定泛型的实际类型
String str = pair.getValue();
pair.setValue("World");
}
泛型类可以有多个类型变量,例如:
class Pair<T, S, P, U, E> { }
注意:类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。需要时还可以用临近的字母U和S表示“任意类型”。

泛型接口

泛型接口和泛型类差不多:
interface Show<T,U>{
void show(T t,U u);
}
实现类
public class ShowTest implements Show<String, Date> {
@Override
public void show(String t, Date u) {
System.out.println(t + " " + u.getTime());
} }
测试一下:
Show<String, Date> show = new ShowTest();
show.show("包青天", new Date());

泛型方法:返回值之前

泛型方法就是在声明方法时,定义了一个或多个类型变量的方法。
泛型方法中定义的类型变量的作用范围为当前泛型方法中。
泛型方法中定义的类型变量用于,在该方法的多个参数之间,或在该方法的参数与返回值之间,宣称一个类型约束。
class Person<S> {
public <W> void show(W w) {//这里的【W】完全等价于Object
if (w != null) System.out.println(w.toString());
} public static <Y> void staticShow(Y y) {
if (y != null) System.out.println(y.toString());
//静态方法不能访问在类声明上定义的类型变量
//S s;//错误提示:Cannot make a static reference to the non-static type S
}
}

泛型变量的类型限定

对于上面定义的泛型变量,因为在编译之前,也就是我们还在定义这个泛型方法的时候,我们并不知道这个泛型类型 T 到底是什么类型,所以,只能默认T为原始类型Object,所以它只能调用来自于Object的那几个方法。
如果我们想限定类型的范围,比如必须是某个类的子类,或者某个接口的实现类,这时可以使用类型限定对类型变量T设置限定(bound)来实现。

类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过要注意下面几点:
  • 无限定的泛型变量等价于Object(白哥添加)
  • 不管该限定是类还是接口,统一都使用关键字 extends
  • 可以使用 & 符号给出多个限定
  • 如果限定既有接口也有类,那么类必须只有一个,并且放在首位置
比如:
public static <T extends Comparable> T get(T t1,T t2)  //继承或实现都用extends
public static <T extends Comparable & Serializable> T get(T t1,T t2) //使用 & 符号给出多个限定
public static <T extends Object & Comparable & Serializable> T get(T t1,T t2) //继承的类Object必须放在首位

通配符?的使用

通配符有三种:
  • 无限定通配符  形式<?>
  • 上边界限定通配符 形式< ? extends Number>
  • 下边界限定通配符    形式< ? super Number>

1、泛型中的?通配符
如果定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,如果这样写
public static void main(String[] args) throws Exception {
List<Integer> listInteger = new ArrayList<Integer>();
printCollection(listInteger);//报错 The method printCollection(Collection<Object>) in the type Test is not applicable for the arguments (List<Integer>)
} public static void printCollection(Collection<Object> collection) {
for (Object obj : collection) {
System.out.println(obj);
}
}
语句printCollection(listInteger);报错,这是因为泛型的参数是不考虑继承关系的,就直接报错。
这就得用?通配符
public static void printCollection(Collection<?> collection) {...}
在方法 printCollection 中不能出现与参数类型有关的方法,比如:
collection.add(new Object());//The method add(capture#1-of ?) in the type Collection<capture#1-of ?> is not applicable for the arguments (Object)
因为程序调用这个方法的时候传入的参数不知道是什么类型的。
但是可以调用与参数类型无关的方法比如 collection.size();
总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量的主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。

2、?通配符的扩展:界定通配符的上边界
List<? extends S> x = new ArrayList<T>();
类型S指定一个数据类型,那么类型T就只能是类型S或者是类型S的子类
List<? extends Number> x = new ArrayList<Integer>();//正确
List<? extends Number> y = new ArrayList<Object>();//错误 Type mismatch: cannot convert from ArrayList<Object> to List<? extends Number>

3、?通配符的扩展:界定通配符的下边界
List<? super S> x = new ArrayList<T>();
类型S指定一个数据类型,那么类型T就只能是类型S或者是类型S的父类
List<? super Number> y = new ArrayList<Object>();//正确
List<? super Number> x = new ArrayList<Integer>();//错误 Type mismatch: cannot convert from ArrayList<Integer> to List<? super Number>
提示:限定通配符总是包括自己

类型擦除

前面已经说了,Java的泛型是伪泛型。为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉。正确理解泛型概念的首要前提是理解类型擦出(type erasure)。

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

如在代码中定义的List<object>和List<String>等类型,在编译后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。

可以通过两个简单的例子,来证明java泛型的类型擦除。
案例一:
ArrayList<String> list1 = new ArrayList<String>();
ArrayList<Integer> list2 = new ArrayList<Integer>();
System.out.println((list1.getClass() == list2.getClass()) + " " + (list1.getClass() == ArrayList.class));//true true
在这个例子中,我们定义了两个ArrayList集合,不过一个是ArrayList<String>泛型类型,只能存储字符串。一个是ArrayList<Integer>泛型类型,只能存储整形。最后,我们通过两个ArrayList对象的getClass方法获取它们的类的信息,最后发现两者相等,且等于ArrayList.class。说明泛型类型String和Integer都被擦除掉了,只剩下了原始类型。

案例二:
List<Integer> list = new ArrayList<Integer>();
list.add(10086);
Method method = list.getClass().getMethod("add", Object.class);
//运行时利用反射机制调用集合的add方法,跳过编译时的泛型检查
method.invoke(list, "虽然集合中对元素限定的泛型是Integer,但是也能通过反射把字符串添加到集合中");
Object object = list.get(1);
System.out.println(object.getClass().getSimpleName() + " " + (object.getClass() == String.class));//String true
try {
System.out.println(((Object) list.get(1)).getClass());//class java.lang.String
System.out.println(list.get(1).getClass());//如果不指定list.get(1)的类型,则会默认将其强制转换为集合上指定的泛型类型
} catch (Exception e) {
e.printStackTrace();//java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
}
因为泛型只在编译的时候起作用,在运行的时候,你得ArrayList已经不受泛型的控制了,也就是说跟已经没有泛型限定的ArrayList没有任何区别了。而反射直接获得了add方法的字节码,跳过编译层在运行时直接添加,这样就骗过了编译。

类型擦除后保留的原始类型

在上面,两次提到了原始类型,什么是原始类型?原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除(crased),并使用其限定类型(无限定的变量用Object)替换。

例如:
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Pair<T>的原始类型为:
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
因为在Pair<T>中,T是一个无限定的类型变量,所以用Object替换。其结果就是一个普通的类,如同泛型加入java编程语言之前已经实现的那样。在程序中可以包含不同类型的Pair,如Pair<String>或Pair<Integer>,但是,擦除类型后它们就成为原始的Pair类型了,原始类型都是Object。
从上面的那个例2中,我们也可以明白ArrayList<Integer>被擦除类型后,原始类型也变成了Object,所以通过反射我们就可以存储字符串了。

如果类型变量有限定,那么原始类型就用第一个边界的类型变量来替换。
比如Pair这样声明:
public class Pair<T extends Comparable& Serializable> { ... } 
那么原始类型就是Comparable
如果Pair这样声明
public class Pair<T extends Serializable & Comparable> 
那么原始类型就用Serializable替换,而编译器在必要的时要向 Comparable 插入强制类型转换。为了提高效率,应该将标签接口(即没有方法的接口)放在边界限定列表的末尾。


要区分原始类型和泛型变量的类型
在调用泛型方法的时候,可以指定泛型,也可以不指定泛型。
  • 在不指定泛型的时候,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级,直到Object。
  • 在指定泛型的时候,该方法中的几种类型必须是该泛型实例类型或者其子类。
public class Test {
public static void main(String[] args) {
/**不指定泛型的时候,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级,直到Object*/
int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型
Number f = Test.add(1, 1.2);//这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为Number
Object o = Test.add(1, "asd");//这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为Object /**指定泛型的时候,该方法中的几种类型必须是该泛型实例类型或者其子类*/
int a = Test.<Integer> add(1, 2);//指定了Integer,所以只能为Integer类型或者其子类
//int b = Test.<Integer> add(1, 2.2);//编译错误,指定了Integer,不能为Float
Number c = Test.<Number> add(1, 2.2); //指定为Number,所以可以为Integer和Float
} public static <T> T add(T x, T y) {
return y;
}
}
其实在泛型类中,不指定泛型的时候也差不多,只不过这个时候的泛型类型为Object,就比如ArrayList中,如果不指定泛型,那么这个ArrayList中可以放任意类型的对象。

附加:GenericDeclaration 接口

public interface java.lang.reflect.GenericDeclaration
所有已知实现类:Class、Constructor、Method
声明类型变量的所有实体的公共接口。

可以声明类型变量的实体的公共接口,也就是说,只有实现了该接口才能在对应的实体上声明(定义)类型变量,这些实体目前只有三个:Class、Construstor、Method。
注意:因为直接实现子类没有Field类,所以属性上面不能定义类型变量。

方法
  • TypeVariable<?>[]  getTypeParameters() 返回声明顺序的 TypeVariable 对象的数组,这些对象表示由此 GenericDeclaration 对象表示的一般声明声明的类型变量。
    • 返回:表示由此一般声明声明的类型变量的 TypeVariable 对象的数组
    • 如果底层的一般声明未声明任何类型变量,则返回一个 0 长度的数组。
public static <T extends Person, U> void main(String[] args) throws Exception {
Method method = Test.class.getMethod("main", String[].class);
TypeVariable<?>[] tvs = method.getTypeParameters();//返回声明顺序的 TypeVariable 对象的数组
System.out.println("声明的类型变量有:" + Arrays.toString(tvs));//[T, U] for (int i = 0; i < tvs.length; i++) {
GenericDeclaration gd = tvs[i].getGenericDeclaration();
System.out.println("【GenericDeclaration】" + gd);//public static void com.bqt.Test.main(java.lang.String[]) throws java.lang.Exception
System.out.println(gd.getTypeParameters()[i] == tvs[i]);//true。 GenericDeclaration和TypeVariable两者相互持有对方的引用 System.out.println(tvs[i] + " " + tvs[i].getName() + " " + Arrays.toString(tvs[i].getBounds()));//T T [class com.bqt.Person] 和 U U [class java.lang.Object]
}
}
2017-9-4

【泛型】Generic 参数化类型 类型转换的更多相关文章

  1. 泛型 Generic 类型擦除引起的问题及解决方法

    参考:http://blog.csdn.net/lonelyroamer/article/details/7868820#comments 因为种种原因,Java不能实现真正的泛型,只能使用类型擦除来 ...

  2. Java之集合初探(二)Iterator(迭代器),collections,打包/解包(装箱拆箱),泛型(Generic),comparable接口

    Iterator(迭代器) 所有实现了Collection接口的容器都有一个iterator方法, 用来返回一个实现了Iterator接口的对象 Iterator对象称作迭代器, 用来方便的实现对容器 ...

  3. C#泛型(Generic)

    一.什么是泛型 泛型(Generic)是C#语言2.0.通用语言运行时(CLR)2.0..NET Framework2.0推出来的新特性. 泛型为.NET框架引入类型参数(Type Parameter ...

  4. Java - 泛型 ( Generic )

    Java - 泛型 ( Generic )     > 泛型的特点         > 解决元素存储的安全性问题         > 解决获取数据元素时,需要类型强转的问题     ...

  5. 谈一谈从 Delphi 2009 之后就支援的重要功能 – 泛型 (Generic)

    前言 在C++的语言基础当中,除了物件导向.事件驱动的概念之外,模版设计(Template)也是非常重要的一环.然而,C++的开发人员能够善用模版设计的并不多.模版设计这个好物,一般还有一个名称,就是 ...

  6. JAVA中的泛型(Generic)

    Java泛型(Generic)简介 泛型是jdk1.5版本以后推出来的,表示类型参数化,让java能更具有动态性一些,让类型能变成参数传递. 要我自己感觉的话,泛型本身没啥用,跟反射在一起用,就体现出 ...

  7. Dephi泛型generic的应用

    Dephi泛型generic的应用   泛型在C++, C#中已有广泛应用,Delphi自2009版本也引入泛型,典型的应用如TList,TDictionary.如果你熟悉C#,其用法十分类似. 比如 ...

  8. Java基础之Comparable接口, Collections类,Iterator接口,泛型(Generic)

    一.Comparable接口, Collections类 List的常用算法: sort(List); 排序,如果需要对自定义的类进行排序, 那就必须要让其实现Comparable接口, 实现比较两个 ...

  9. Java自学-集合框架 泛型Generic

    ArrayList上使用泛型 步骤 1 : 泛型 Generic 不指定泛型的容器,可以存放任何类型的元素 指定了泛型的容器,只能存放指定类型的元素以及其子类 package property; pu ...

随机推荐

  1. java实现两台电脑间TCP协议文件传输

    记录下之前所做的客户端向服务端发送文件的小项目,总结下学习到的一些方法与思路. 注:本文参考自<黑马程序员>视频. 首先明确需求,在同一局域网下的机器人A想给喜欢了很久的机器人B发送情书, ...

  2. React Native之网页组件WebView的使用与通信

    在实际开发中,我们通常会嵌入一些html页面,官方为我们提供了一个非常好用的网页组件WebView,通过这个组件我们可以通过传入一个url或者是传入一段html 一. WebView的基本属性方法介绍 ...

  3. C++运算符重载 模板友元 new delete ++ = +=

    今天的重载是基于C++ 类模板的,如果需要非类模板的重载的朋友可以把类模板拿掉,同样可以参考,谢谢. 一.类模板中的友元重载 本人喜好类声明与类成员实现分开写的代码风格,如若您喜欢将类成员函数的实现写 ...

  4. 删除或修改eclipse中svn的账号密码

    由于eclipse没有自带的管理svn账号的功能,我也没有找到相关的插件,要是有朋友知道的话也可以跟我说下哦!以下是关于自己手动去删除eclipse 软件的 svn账号,以便切换项目的时候去更换svn ...

  5. python opencv3 基于ORB的特征检测和 BF暴力匹配 knn匹配 flann匹配

    git:https://github.com/linyi0604/Computer-Vision bf暴力匹配: # coding:utf-8 import cv2 """ ...

  6. 80X86指令总结

    一.数据传送指令 指令名称 汇编语句格式 功能 影响标志位 传送move data mov opd, ops (ops) → opd:分为主存储器.通用寄存器.段寄存器,不可同时使用主存储器,类型要匹 ...

  7. Python环境右键定制

    有时候,我们需要将py打包成exe.需要将ui转换成py.需要将py转换成pyc等等,命令行操作起来有点繁琐.所以做了这个教程: 1. py打包成exe 先安装cx_freeze,参照教程:http: ...

  8. .NET面试宝典-高级2

    http://blog.csdn.net/shanyongxu/article/category/6023593 对于 Web 性能优化,您有哪些了解和经验吗? 1.前端优化 (1)减少 HTTP 请 ...

  9. Android 手机 无线 ADB

    要用网络调试Android需要设备已经获取root权限 如果手机没有命令行工具,请先在手机端安装终端模拟器,然后在终端输入: $su #stop adbd #setprop service.adb.t ...

  10. HDU4548+筛素数

    先筛出素数,再筛出美素数. 简单题. /* 筛素数 */ #include<stdio.h> #include<string.h> #include<stdlib.h&g ...