函数式编程快速入门

文章目录

  • 1. Lambda表达式
    • 1.1 基本格式
    • 1.2 省略规则
  • 2. Stream流
    • 2.1 快速入门
    • 2.2 常用操作
      • 2.2.1 创建流
      • 2.2.2 中间操作
        • filter
        • map
        • distinct
        • sorted
        • limit
        • skip
        • flatMap
      • 2.2.3 终结操作
        • forEach
        • count
        • max&min
        • collect
        • 查找与匹配
          • AnyMatch
          • allMatch
          • noneMatch
          • findAny
          • findFirst
        • reduce归并
    • 2.3 注意事项
  • 3 Optional
    • 3.1 使用
      • 3.1.1 创建对象
      • 3.1.2 安全消费值
      • 3.1.2 安全获取值
      • 3.1.3 过滤
      • 3.1.4 判断
      • 3.1.5 数据转换
  • 4 函数式接口
    • 4.1 概述
    • 4.2 常见函数式接口
    • 4.3 函数式接口常用默认方法
  • 5 方法引用
    • 5.1 引用类的静态方法
    • 5.2 引用对象的实例方法
    • 5.3 引用类的实例方法
    • 5.4 构造器引用
  • 6 高级用法
    • 6.1 基本数据类型优化
    • 6.2 并行流

面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象。

核心思想: 使用不可变值和函数,函数对一个值进行处理,映射成另一个值。

1. Lambda表达式

lambda表达式仅能放入如下代码:

  • 预定义使用了 @Functional 注释的函数式接口自带一个抽象函数的方法
  • SAM(Single Abstract Method 单个抽象方法)类型。

这些称为lambda表达式的目标类型,可以用作返回类型,或lambda目标代码的参数。

示例

若一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,可以传入lambda表达式。

类似的,如果一个方法接受声明于 java.util.function 包内的接口,例如 Predicate、Function、Consumer 或 Supplier,那么可以向其传lambda表达式。

1.1 基本格式

{params}->{code}

[示例]

使用匿名内部类创建线程

new Thread(new Runnable() {pubic void run() {// code}
}).start();

使用lambda表达式创建线程

new Thread(()->{// code
}).start();

[需求1] 现有方法定义如下,其中IntBinaryOperator是一个接口

public static int calculateNum(IntBinaryOperator operator) {int a = 1;int b = 2;return operator.applyAsInt(a, b);
}

IntBinaryOperator是一个函数式接口并且自带一个抽象方法,因此可以使用lambda表达式。

匿名内部类实现

int var = calculateNum(new IntBinaryOperator() {public int applyAsInt(int left, int right) {return left + right;}
});

lambda表达式实现

int var = calculateNum((int left, int right)->{return left + right;
});

继续优化

int var = calculateNum((left, right)->eft + right);

[需求2] 现有方法定义如下,IntPredicate是一个接口

public static void printNum(IntPredicate predicate) {int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};for (int i : arr) {if (predicate.test(i)) {System.out.println(i);}}
}

IntPredicate是一个函数式接口并且自带一个抽象方法,因此可以使用lambda表达式。

使用匿名内部类

printNum(new IntPredicate() {public boolean test(int value) {return value % 2 == 0;}
});

使用lambda表达式

printNum((int value)->{return value % 2 == 0;
});

[需求3] 现有方法定义如下

public static <R> R typeConver(Function<String, R> function) {String str="12345";R result = function.apply(str);return result;
}

使用匿名内部类

Integer res = typeConver(new Function<String, Integer> {public Integer apply(String s) {return Integer.valueOf(s);  }
});

使用lambda表达式

Integer res = typeConver((String s)->{return Integer.valueOf(s);  });

[需求4] 现有方法定义如下

public static void foreachArr(IntConsumer consumer) {int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};for (int i : arr) {consumer.accept(i);}
}

使用匿名内部类

 foreachArr(new IntConsumer() {public void accept(int value) {System.out.println(value);}});

使用lambda表达式

foreachArr((int value)->{System.out.println(value);
});

1.2 省略规则

  • 参数类型可以省略
  • 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
  • 方法只有一个参数时小括号可以省略

2. Stream流

Java8的Stream使用函数式编程模式,可以用来对集合或数组进行链状流式的操作

2.1 快速入门

[需求1] 现在需要打印所有年龄小于18的作家的名字,并且去重

使用Stream+匿名内部类

List<Author> authors = getAuthors();
authors.stream() //将集合转换成流.distinct().filter(new Predicate<Author>() {public boolean test(Author author) {return author.getAge()<18;}}).forEach(new Consumer<Author>() {public void accept(Author author) {System.out.println(author.getName);}});

使用Stream+lambda表达式

List<Author> authors = getAuthors();
authors.stream() // 将集合转换成流.distinct() // 去重.filter(author->author.getAge()<18) // 过滤.forEach(author- >System.out.println(author.getName())); // 遍历

2.2 常用操作

2.2.1 创建流

单列集合:集合对象.stream()

List<Author> authors = getAuthors();
Stream<Author>stream = authors.stream();

数组:Arrays.stream(数组)或使用Stream.of()创建

Integer[] arr = {1, 2, 3};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);

双列集合:转换成单列集合再创建

Map<String, Integer> map = new HashMap<>();
map.put("Cyan", 100);
map.put("Jack", 200);Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();

2.2.2 中间操作

filter

作用

对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中

匿名内部类写法

authors.stream()..filter(new Predicate<Author> {public boolean test(Author author) {return author.getName().length() > 1;}}).forEach(new Consumer<Author>() {public void accept(Author author) {System.out.println(author.getName());}});

lambda表达式写法

authors.stream().filter(author->author.getName().length>1).forEach(author->System.out.println(author.getName()));

map

作用

把流中的元素进行计算或转换

匿名内部类写法

authors.stream()..map(new Function<Author,String>() {public String apply(Author author) {return author.getName();}}).forEach(new Consumer<String>() {public void accept(String s) {System.out.println(s);}});

lambda表达式写法

authors.stream().map(author->author.getName()).forEach(s->System.out.println(s));

distinct

作用

流中去重[distinct是依赖Object的equals方法判断是否是相同对象的,要重写equals方法,lombok中使用@EqualsAndHashCode注解重写]

lambda表达式写法

authors.stream().distinct().forEach(author->System.out.println(author.getName()));

sorted

作用

对流中的元素进行排序

lambda表达式写法

1.调用无参sorted方法

重写Comparable接口

public class Author implements Comparable<Author> {...public int compare(Author o) {return this.getAge() - o.getAge();}
}
authors.stream().distinct().sort().forEach(author->System.out.println(author.getAge()));

调用空参的sorted()方法,需要流中元素实现Comparable接口

2.调用有参sorted方法

authors.stream().distinct().sort((o1,o2)->o1.getAge()-o2.getAge()).forEach(author->System.out.println(author.getAge()));

limit

设置流的最大长度,超出的部分将被抛弃

authors.stream().distinct().sorted().limit(2).forEach(author->System.out.println(author.getName()));

skip

跳过流中前n个元素,返回剩下的元素

authors.stream().distinct().sorted.skip(1).forEach(author->System.out.println(author.getName()));

flatMap

map只能只能把一个对象转换成另一个对象来作为流中的元素,而flatMap可以把一个对象转换成多个对象作为流中的元素。

public class Author implements Comparable<Author> {private Long id;private String name;private Integer age;private String intro;private List<Book> books;public int compare(Author o) {return this.getAge() - o.getAge();}
}
authors.stream().flatMap(new Function<Author, Stream<Book>>() {public Stream<?> apply(Author author) {return author.getBooks().stream();}}) .distinct().forEach(new Consumer<Book>() {public void accept(Book book) {System.out.println(book.getName());}});
authors.stream().flatMap(author->author.getBooks().stream()).distinct().forEach(book->System.out.println(book.getName()));

[需求] 打印所有数据的分类,对分类进行去重

authos.stream().flatMap(author->author.getName().stream()).distinct().flatMap(book->Arrays.stream(book.getCategory().split(","))).distinct().forEach(category->System.out.println(category));

2.2.3 终结操作

forEach

对流中的元素进行遍历操作,通过传入的参数去指定对遍历到的元素进行具体操作

authors.stream().map(author->author.getName()).distinct().forEach(name->System.out.print(name));

count

可以获取当前流中元素的个数

long cnt = authors.stream().flatMap(author->getBooks().stream()).distinct().count();

max&min

统计流中的最值

Optional<Integer> max=authors.stream().flatMap(author->author.Books()).map(book->book.getScore()).max((score1-score2)->score1-score2);
Optional<Integer> min=authors.stream().flatMap(author->author.Books()).map(book->book.getScore()).min((score1-score2)->score1-score2);

collect

将流转换成集合

Set

List<String> list = authors.stream().map(author->author.getName()).collect(Collectors.toList());

List

Set<Book> books = authors.stream().flatMap(author->author.getBooks.stream()),collect(Collectors.toSet());

Map

1.匿名内部类

authors.stream().collect(Collectors.toMap(new Function<Author,String>(){public String apply(Author author) {return author.getName();}}, new Function<Author,List<Book>>(){public List<Book> apply(Author author) {return author.getBooks();}}));

2.lambda表达式

authors.stream().distinct().collect(Collectors.toMap(author->author.getName(),author.getBooks()));

查找与匹配

AnyMatch

判断是否有任意符合匹配条件的元素,结果为boolean类型

authors.stream().AnyMatch(author->author.getAge()>29);
allMatch

判断是否都符合匹配条件,结果为boolean类型

authors.stream().allMatch(author->author.getAge()>29)
noneMatch

判断留着元素都不符合匹配条件的,结果为boolean类型

authors.stream().noneMatch(author->author.getAge()>29)
findAny

获取流中任意一个元素[保证不了是否为第一个元素]

Optional<Author> author = authors.stream().filter(author->author.getAge()>52).findAny();
author.ifPresent(author->System.out.print(author.getName()));
findFirst

获取流中第一个元素

Optional<Author> first = authors.stream().sorted((o1,o2)->o1.getAge()-o2.getAge()).findFirst();
first.ifPresent(author->System.out.print(author.getName()));

reduce归并

对流中的数据按照你制定的计算方式计算出一个结果,即把stream中的元素组合起来,传入一个初始值,按照计算方式依次拿流中的元素和在初始化值的基础上进行计算,计算结果再和后面元素计算

reduce单个参数的重载形式内部计算逻辑

boolean foundAny = false;
T result = null;
for (T element : this stream) {if(!foundAny) {foundAny = true;result = element;}else result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();

将流中第一个元素作为变量初始化值

reduce两个参数的重载形式内部计算逻辑

T result = identity;
for (T element : this stream)result = accumulator.apply(result, element)
return result;

其中,identity是通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是通过方法参数确定的

使用reduce对年龄求和

authors.stream().map(author->author.getAge()).reduce(0, new BinaryOperator<Integer>(){public Integer apply(Integer result, Integer element) {return result + element;}});
Integer sum = authors.stream().map(author->author.getAge()).reduce((result,element) ->result + element);

使用reduce求年龄最大值

authors.stream().map(author->author.getAge()).reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>(){public Integer apply(Integer result, Integer element) {return result < element ? element : result;}})
Integer max = authors.stream().map(author->author.getAge()).reduce(Integer.MIN_VALUE,(result,element)->result < element ? element : result);

使用reduce求年龄最小值

两个参数的重载实现

authors.stream().map(author->author.getAge()).reduce(Integer.MAX_VALUE, new BinaryOperator<Integer>() {public Integer apply(Integer result, Integer element) {return result > element ? element : result;}})
authors.stream().map(author->author.getAge()).reduce(Integer.MAX_VALUE,(result, element)->result > element ? element : result);

单个参数的重载实现

Optional<Integer> min = authors.stream().map(author->author.getAge()).reduce(new BinaryOperator<Integer>() {public Integer apply(Integer result, Integer element) {return result > element ? element : result;}});
min.ifPresent(age->System.out.println(age))

2.3 注意事项

  • 没有终结操作,中间操作是不会执行的
  • 一旦一个流对象经过终结操作后,该流不能再被使用
  • 在流中对数据进行操作,但不能影响到原来集合的元素

3 Optional

3.1 使用

3.1.1 创建对象

Optional类似包装类,将具体的数据封装Optional对象内部,然后使用Optional内部封装号的方法操作数据,从而避免空指针异常。

1.一般使用Optional的静态方法ofNullable把数据封装成Optional对象,无论传入的参数是否为null都不会出现问题

Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);

2.如果确定对象不是空可以使用Optional的静态方法of将数据封装成Optional对象

Optional<Author> authorOptional = Optional.of(author);

使用of传入的参数必须不为空

3.如果方法的返回值为Optional类型,经判断发现某次计算得到的返回值为null,需要把null封装成Optional对象返回,可以使用Optional的静态方法empty进行封装

Optional.empty();

3.1.2 安全消费值

如果获取到Optional对象需要使用数据,使用其ifPresent方法消费其中的值

该方法判断其内封装的数据是否为空,不空时执行具体的消费代码,具有安全性

Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);
authorOptional.ifPresent(author->System.out.println(author.getName()));

3.1.2 安全获取值

期望安全获取值,推荐使用Optional提供的以下方法:

  • orElseGet

    获取数据并且设置数据为空时的默认值,如果数据不为空就能获取到该数据,如果为空根据传入的参数创建对象作为默认返回值信息

    Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
    Author author = authorOptional.erElseGet(()->new Author());
    
  • orElseThrow

    获取数据,如果数据不为空就能获取到该数据,如果为空则根据传入的参数来创建异常抛出

    Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
    Author author = authorOptional.erElseGet((Supplier<Throwable>) ()-> new RuntimeException("author is null"));
    

3.1.3 过滤

使用filter方法进行数据过滤,原本有数据,但不符合要求,会变成无数据的Optional对象

Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.filter(author->author.getAge()>100).ifPresent(author->System.out.println(author.getName()));

3.1.4 判断

使用ifPresent方法进行是否存在数据的判断,如果为空返回值为false,如果不为空,返回值为true

Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
if (authorOptional.isPresent()) {System.out.println(authorOptional.get().getName());
}

3.1.5 数据转换

Option提供map方法对数据进行转换,并且转换得到的数据还是被Optional包装好的,保证使用安全

Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
Optional<List<Book>> books = authorOptional.map(author->author.getBooks());
books.ifPresent(new Consumer<List<Book>>() {public void accept(List<Book> books) {books.forEach(book->System.out.println(book.getName()));}
});

4 函数式接口

4.1 概述

仅含有一个抽象方法的接口称之为函数式接口,JDK中的函数式接口都加上@FunctionalInterface注解标识

4.2 常见函数式接口

  • Consumer消费接口

    根据其中抽象方法的参数列表和返回值类型,在方法中传入参数进行消费

    @FunctionalInterface
    public interface Consumer<T> {void accept(T t);...
    }
    
  • Function计算转换接口

    根据其中抽象方法的参数列表和返回值,在方法传入的参数计算或转换,将结果返回

    @FunctionalInterface
    public interface Function<T, R> {R apply(T t);...
    }
    
  • Predicate判断接口

    根据其中抽象方法的参数列表和返回值类型,在方法中对传入的参数条件判断,返回判断结果

    @FunctionalInterface
    public interface Predicate<T> {boolean test(T t);...
    }
    
  • Supplier生产型接口

    根据其中抽象方法的参数列表和返回值类型,在方法中创建对象,把创建好的对象返回

    @FunctionalInterface
    public interface Function<T> {T get();...
    }
    

    4.3 函数式接口常用默认方法

  • and

    在使用Predicate接口可能需要判断条件的拼接,而and方法相当于使用&&来拼接两个判断条件

    // 打作家中年龄大于17且姓名的长度大于1的作家
    authors.stream().filter(author->author.getAge()>17).and(new Predicate<Author>() {public void test() {return author.getName().length()>1;}}).forEach(author->System.out.print(author));
    
  • or

    在使用Predicate接口可能需要判断条件的拼接,而or方法相当于使用||拼接两个判断条件

    // 打印作家中年龄大于17或者姓名的长度小于2的作家
    authors.stream().filter(author->author.getAge()>17).or(author->author.getName().length()<2).forEach(author->author.getName());
    
  • negate

    Predicate接口中的方法,该方法相当于在判断添加!表示取反

5 方法引用

使用lambda表达式时,如果方法中只有一个方法的调用话(包括构造方法),可以使用方法引用进一步简化代码

基本格式

类名或者对象名::方法名

5.1 引用类的静态方法

基本语法

类名::方法名

使用前提

如果在重写方法时,方法体中只有一行代码,并且改代码调用某个类的静态方法,并且把重写的抽象方法中所有的参数都按照顺序传入静态方法中,此时可以引用类的静态方法

authors.stream().map(author->author.getAge()).map(age->String.valueOf(age));

如果重写的方法没有参数,调用的方法也是没有参数的也符合以上规则

优化代码

authors.stream().map(author->author.getAge()).map(String::valueOf);

5.2 引用对象的实例方法

基本语法

对象名::方法名

使用前提

如果在重写方法时,方法体中只有一行代码,并且该代码是调用某对象的成员方法,并且要把重写的抽象方法中所有的参数按照顺序传入该成员方法中,使用引用对象的实例方法

StringBuilder sb = new StringBuilder();
authors.stream().map(author->author.getName()).forEach(name->sb.append(name));

优化代码

StringBuilder sb = new StringBuilder();
authors.stream().map(author->author.getName()).forEach(sb::append);

5.3 引用类的实例方法

基本语法

类名::方法名

使用前提

如果在重写方法时,方法体中只有一行代码,并且该代码调用第一个参数的成员方法,并且把重写的抽象方法中剩余的所有参数都按照顺序传入到该成员方法中,可以使用类的实例方法。

subAuthorNam("Cyan",new UseString() {public String use(String str, int start, int len) {return str.substring(start, len);}
})

代码优化

subAuthorNam("Cyan", String::substring);

5.4 构造器引用

如果方法中的一行代码是构造器的话可以使用构造器引用

基本语法

类名::new

使用前提

如果在重写方法时,方法体中只有一行代码,并且该代码调用某类的构造器方法,并且把重写的抽象方法中所有的参数按照顺序传入构造器中,可以使用引用构造器

authors.stream().map(author->author.getName()).map(name->new StringBuilder(name)).map(sb->sb.append("Cyan").toString()).forEach(str->System.out.println(str));

优化代码

authors.stream().map(author->author.getName()).map(StringBuilder::new).map(sb->sb.append("Cyan").toString()).forEach(str->System.out.println(str));

6 高级用法

6.1 基本数据类型优化

我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的是整数小数,但是实际用的都是他们的包装类
所以为对大数据量时装箱拆箱的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。

mapToInt
mapToLong
mapToDouble
flatMapToInt
flatMapToDouble

6.2 并行流

使用parallel方法将串行流转换成并行流,也可以通过parallelStream直接获取并行流对象

Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
Integer sum = stream.parallel().peek(new Consumer<Integer>() {public void accept(Integer num) {System.out.println(num+Thread.currentThread().getName());}}).filter(num->num>5).reduce((result.ele)->result+ele).get();

[编程语言基础] 函数式编程相关推荐

  1. 函数式编程语言python-Python函数式编程

    在 Python 中使用函数式编程的最佳实践! 简 介 Python 是一种功能丰富的高级编程语言.它有通用的标准库,支持多种编程语言范式,还有许多内部的透明度.如果你愿意,还可以查看 Python ...

  2. 纯函数式编程语言_函数式编程正在接管具有纯视图的UI。

    纯函数式编程语言 by Bobby Schultz 由Bobby Schultz 函数式编程正在接管具有纯视图的 UI . (Functional Programming is taking over ...

  3. 【编程语言】函数式编程 Functional Programming

    函数式编程 1. 引言 "函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论. 它属于"结构化编程 ...

  4. python基础 — 函数式编程

    前言:这篇文章比较抽象,先整理记录下来,工作中慢慢领悟. 1.函数式编程定义 简单说,"函数式编程"是一种"编程范式"(programming paradigm ...

  5. Java基础函数式编程

    本篇博文目录: 前言 1.什么是函数式接口 2.函数式编程 (1) 使用Lambda表达式 (2) Lambda表达式的进一步简化 (3) Java内置函数式接口 3.方法引用 (1) 方法引用的简单 ...

  6. python基础函数式编程(十七)

    概念: 包括lambda函数的编程: # 高级函数变成 print(sum([1,2,3])) def test(x,f):return f(x) x = [1,2,3] print(test(x,s ...

  7. Go 函数式编程实战

    Go 函数式编程实战-目录 序 致谢 前言 关于本书 关于作者 函数式编程简介 1.1 函数概述 1.2 什么是函数式编程 1.3 函数式编程特性 1.3.1 函数是一等公民 1.3.2 纯函数 1. ...

  8. (二十三)Kotlin简单易学 基础语法-什么是函数式编程

    (二十三)Kotlin简单易学 基础语法-什么是函数式编程 什么是函数式编程 我们一直在学习面向对象编程范式,另一个较知名的编程范式是诞生于20世纪50年代,基于抽象数学的λ演算发展而来的函数编程,尽 ...

  9. [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念...

    本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程?   java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的函数 ...

最新文章

  1. UICollectionView的使用
  2. 【Python】疯狂的加速函数!
  3. html树状图右侧_如何在 Tableau 中创建控制图?
  4. Harbour.Space Scholarship Contest 2021-2022 E. Permutation Shift 思维 + 剪枝
  5. CVPR 2020丨更精准的视频目标检测:基于记忆增强的全局-局部整合网络的方法
  6. Layui表单账号注册校验密码是否一致
  7. 年夜饭之 --- 蒜蓉粉丝蒸扇贝
  8. 微服务架构系列文章、精品教程
  9. 自动柜员机属于微型计算机的一种,自动柜员机属于微型计算机的一种。(  )...
  10. JanusGraph部署方案
  11. 怎么通过当地时区计算格林尼治_地球运动之“时间计算”练习二(干货)
  12. vue3 中使用pinia报错 pinia.mjs:1696 Uncaught Error: []: getActivePinia was called with no active Pinia.
  13. IBM Cloud VPC网络与本地办公网络的互通
  14. VR教育虚拟现实技术应用的价值
  15. 2022-2028年中国滚动轴承行业市场发展规模及投资机会分析报告
  16. 基于B/S的城市公交查询系统的设计与实现(附:源码+论文+答辩PPT))
  17. 《会计学》账户与复式记账笔记的思维导图
  18. tomcat蛋疼的post字符串乱码问题
  19. Matlab中gray2ind函数用法
  20. 解决wpsoffice for linux 退出后在后台驻留进程问题,顺便解决启动弹窗“Failed to open……From=Qing”报错

热门文章

  1. Nmap扫描工具的使用
  2. Hadoop的概念及架构介绍
  3. 数据工程学建设思考与实践
  4. 爬取热榜2k图片,爬虫入门即可
  5. 对应分析(关联分析、R-Q型因子分析、处理分类变量的利器)原理介绍
  6. 374 猜数字大小
  7. STK与VC++联合编程实战(第四回:由TLE数据插入卫星对象)
  8. 用HTML CSS 实现简洁美观的时间线(历史年表)
  9. adc0804c语言程序,ADC0804芯片在模数转换电路中的应用研究
  10. android l字体,关于 Android 默认字体以及对比微软雅黑字体