Java程序员面试宝典笔记记录-第4章Java基础部分(上)概括

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/YuYunTan/article/details/83020026

导言

  本次博文对何昊出版的《java程序员面试宝典》的第四章关于Java一部分基础知识(4.1-4.6)的概括笔记,删除其中部分代码,试题和一部分相对简单的内容题目。

相关题目

Java有哪些优点?

  面向对象,平台无关,内置类库多,提供web应用支持,安全性和健壮性,去掉C++难以理解和易混淆的特性(头文件、指针、结构、单元、计算符重载、虚拟基础类、多重继承等)

Java和C++的异同有哪些?

  都是面向对象,具有面向对象的诸多特性,具备可重用性。不同点:

  •   Java是解释性语言,运行过程是程序源码经过Java编译器编译成字节码由JVM解释运行。而C++编译型语言,源码经过编译和链接成可执行二进制文件。Java运行速度慢,但能跨平台执行。
  •   Java纯面向对象,所有代码(类或遍历等)必须在类中实现。Java不存在全局变量或全局函数。C++兼具面向过程,可定义全局变量或函数。
  •   Java没有指针概念。
  •   Java无多重继承,但引入接口,可以实现多接口。接口具备多态特性,通过实现多个接口与C++多重继承类似。
  •   C++需要开发人员管理对内存分配(申请和释放),Java有GC实现垃圾自动回收,无需显式管理内存分配。C++把释放资源放到析构函数,Java有finalize()方法,GC释放无用对象内存,首先调用该对象finalize()方法。
  •   Java不支持C++的运算符重载,预处理器【但提供import和预处理类似】,默认函数参数,goto语句,自动强制类型转换,结构和联合。
  •   Java平台无关,数据类型分配固定长度。C++不然。
  •   Java对注释文档内置支持,源码文件可以包含自己文档,利用单独程序提取文档信息并格式化为HTML。
  •   Java包含标准库,完成特定任务,而且简单易用,能大大缩短开发周期。

为什么需要public static void main(String []args)方法?

  该方法是Java程序入口方法,JVM运行程序会先查找main()方法。public是修饰符,表示任何类和对象都可以访问,static表明是静态方法,即方法中代码存储在静态存储区,类被加载后,就可以使用该方法而不需要通过实例化对象来访问,直接类名.main()直接访问,JVM启动便是按照上述方法签名(必须有public和static修饰,返还值为void,且方法参数是字符串数组)来查找方法入口,若查到则执行,否则报错。

main方法还有其他定义格式?

  

  • public和static没有先后顺序,可以static public void main(String []args)
  • 可以把main()方法定义为final
  • 也可以用synchronized修饰
  • 只要保证返还值void,有static public关键字,不能abstract关键字修饰。

同一个.java文件是否可以多个main()方法?

  只有与文件名相同的用public修饰的类中的main()方法才是程序入口

Java程序初始化顺序?

  
Java初始化遵循三原则(优先级依次递减)

  • 静态对象(变量)优先于非静态对象(变量)初始化。静态对象(变量)只初始化一次,非静态对象(变量)可多次初始化
  • 父类优先于子类初始化
  • 按照成员变量定义顺序进行初始化,即使变量定义散布在方法定义中,依旧在任何方法(包括构造函数)调用之前先初始化。
      
    Java初始化可以在不同代码块完成,执行顺序如下:

父类静态变量 \rightarrow 父类静态代码块 \rightarrow 子类静态变量 \rightarrow 子类静态代码块 \rightarrow 父类非静态变量 \rightarrow 父类非静态代码块 \rightarrow 父类构造函数 \rightarrow 子类非静态变量 \rightarrow 子类非静态代码块 \rightarrow 子类构造函数

Java作用域有哪些?

  作用域由花括号位置决定,决定了变量名可见性和生命周期。Java变量类型三种:成员变量、静态变量和局部变量。类的成员变量作用范围与类的实例化对象作用范围相同,当类被实例化,成员变量会在内存中分配空间并初始化,直到实例化对象生命周期结束,其生命周期才结束。

  被static修饰成员变量被称为静态变量或全局变量,但静态变量不依赖于特定实例而是所有实例。只要一个类被加载,JVM就会给类静态变量分配空间,可以类名和变量名来访问。局部变量作用域与可见性为它所在的花括号内。

四种可见性区别

作用域与可见性 当前类 同一Package 子类 其他package
public \checkmark \checkmark \checkmark \checkmark
private \checkmark × \times × \times × \times
protected \checkmark \checkmark \checkmark × \times
default \checkmark \checkmark × \times × \times

一个Java文件中是否可以定义多个类?

  可以定义多个类,只能一个类被public修饰且与文件名必须相同。若文件无Public,则文件名是随便一个类名字即可。

什么是构造函数?

  是特殊函数用于对象实例化初始化对象成员变量。

  具备特点:

  • 与类名相同无返还值;
  • 可有多个,若不提供构造函数则提供默认,若提供则不创建默认;
  • 可有0,1及1个以上参数;
  • 伴随new调用,不由程序编写者直接调用,经由系统调用。
  • 对象实例时调用仅一次;
  • 完成对象初始化构造;
  • 不能被继承不能被覆盖能重载;
  • 子类可通过super显式调用,父类不提供无参构造函数则子类需要显式调用,反之可以不显式。优先执行父类构造后子类构造;
  • 子父类均无定义构造函数,均生成默认无参构造,修饰符与当前类修饰符一致。

Java为什么某些接口无任何方法?

  接口不支持多继承,为克服单继承缺点引入接口概念。接口是抽象方法定义集合(可定义某些常量值),是特殊抽象类。只包含方法定义无方法实现,所有方法都是抽象。成员作用域都是public,常量值默认public static final修饰。类实现多个接口达到多继承的目的。
  某些接口不声明方法,这些接口被称为标志接口,充当标志作用表明属于一个特定类型。static不能修饰interface

Java的clone方法作用有哪些?

  new语句返还指针引用。基本数据类型按值传递(输入参数复制),其他是按引用传递(对象的一个引用)。对象除了在函数调用时是引用传递,在使用“=”赋值也是采用引用传递。

  Object类提供clone()方法,该方法返还object对象复制,该复制函数返还新对象而非引用。

  使用步骤:实现clone类要继承Cloneable接口,类中重写Object类的clone()方法,在clone方法调用super.clone()。super.clone会直接或间接调用Object类的clone()方法。把浅复制的引用指向原型对象新的克隆体。

  非基本类型的属性需要clone()方法才能完成深复制。

浅复制和深复制区别?

  • 浅复制:被复制对象所有变量与原对象具有相同值,对对象引用仍指向原对象。浅复制仅复制所考虑的对象,不复制所引用的对象。
  • 深复制:被复制对象所有变量与原对象具有相同值,引用其他对象的变量将指向被复制的新对象而非原有被引用的对象。

Java反射机制?

  反射是Java重要特性,允许程序运行自我检查,允许对内部成员操作。反射机制实现运行时对类装载,能增加程序灵活性,但使用不恰当会严重影响系统性能。

  反射功能:提供对象所属类,获取对象一个类所有成员变量和方法,运行时创建对象,运行时调用对象。运行时动态地创建类对象。

3种获取class类?

  • class.forName(“类的路径”);
  • 类名.class类名.class
  • 实例.getClass()实例.getClass()

java创建对象的方式有几种?

  • 通过new语句实例化一个对象
  • 通过反射机制创建对象
  • 实通过clone()方法创建一个对象
  • 通过反序列化的方式创建对象

java创建对象的方式有几种?

  package是抽象概念,宗旨是把.java文件(源文件)、.class文件(编译后的文件)以及其他resource文件(例如.xml、.avi文件、.mp3文件、.txt文件等)有条理地进行一个组织供使用。类似于Linux文件系统,有一个根,从根开始有目录和文件,然后目录中嵌套目录。
  package有两个作用

  • 提供多层命名空间,解决命名冲突。

  • 对类按功能分类,使项目组织更为清晰。对类按功能分类,使项目组织更为清晰。

    package的用法

  • 每个源文件开头加上"package packagename",然后源文件所在目录下创建一个新目录,名为packagename。

  • 用javac指令编译每个sourcename.java源文件,将生产的sourcename.classname 文件名复制到packagename目录。

  • 用java指令运行程序:java packagename.sourcename

如何实现类似C语言中函数指针功能?

  C语言函数指针重要功能是实现回调函数。回调函数指在某处注册,在某个需要的时候调用。

  回调函数用于截获信息、获取系统消息或处理异步事件。

  Java可以使用类和接口实现该效果。先定义一个接口,接口中声明要调用方法,接着实现该接口,用实现该类的对象作为参数传递调用程序,程度通过该参数调用指定函数实现回调功能。

  【接口相当于函数指针功能,按需传入实现该接口的自定义类】

面向对象与面向过程的区别?

  面向对象是软件开发方法主流之一,把数据和数据操作放在一起,作为一个相互依存的整体,即对象。对同类对象抽出共性,即类,类的大多数数据只能被本类方法处理。类通过一个简单外部接口与外界发生关系,对象与对象之间通过信息通信。程序流程由用户使用决定。

  面向过程是一种以事件为中心的开发方法,就是自顶向下顺序执行,逐步求精,其程序结构是按功能划分为若干模块,这些模块形成一个树状结构,各模块之间的关系也比较简单,在功能上相对独立。每个模块内部一般由顺序、选择和循环3种基本结构组成,其模块化实现的具体方法是使用子程序,而程序流程在写程序时依旧决定。

  两者不同处:

  1. 出发点不同。面向对象是用符合常规思维方式处理客观世界问题,强调把问题域要领直接映射到对象及对象间接口上。面向过程强调过程抽象与模块化,它以过程为中心构造或处理客观世界问题。
  2. 层次逻辑不同。面向对象以计算机逻辑模拟客观世界物理存在,以对象集合为处理问题基本单位,尽可能向客观世界靠拢,使得问题处理更为清晰直接,面向对象方法用类层次结构体现类间继承和发展。面向过程基本单位是清晰准确表达过程的模块,用模块的层次结构概括模块,用模块层次结构概括模块或模块间关系与功能,把客观世界问题抽象成计算机可处理过程。层次逻辑不同。面向对象以计算机逻辑模拟客观世界物理存在,以对象集合为处理问题基本单位,尽可能向客观世界靠拢,使得问题处理更为清晰直接,面向对象方法用类层次结构体现类间继承和发展。面向过程基本单位是清晰准确表达过程的模块,用模块的层次结构概括模块,用模块层次结构概括模块或模块间关系与功能,把客观世界问题抽象成计算机可处理过程。
  3. 数据处理方式与控制程序方式不同。面向对象方法将数据与对应代码封装成一个整体,原则上其他对象不能直接修改其数据。即对象的修改只能由自身成员函数完成,控制程序方式上是通过“事件驱动”来激活和运行程序。而面向过程方法是通过程序来处理数据,处理完毕后可显示处理结果,在控制程序方式上是按照设计调用或返还程序,不能自由导航,各模块之间存在控制与被控制,调用与被调用关系。数据处理方式与控制程序方式不同。面向对象方法将数据与对应代码封装成一个整体,原则上其他对象不能直接修改其数据。即对象的修改只能由自身成员函数完成,控制程序方式上是通过“事件驱动”来激活和运行程序。而面向过程方法是通过程序来处理数据,处理完毕后可显示处理结果,在控制程序方式上是按照设计调用或返还程序,不能自由导航,各模块之间存在控制与被控制,调用与被调用关系。
  4. 设计分析和编码转换方式不同。面向对象方法贯穿软件生命周期的分析、设计及编码中,是平滑过程了,从分析到设计再编码采用一致性模型表示,实现无法连接。面向过程强调分析、设计及编码之间按规则转换,贯穿软件生命周期分析、设计及编码,是有缝连接。设计分析和编码转换方式不同。面向对象方法贯穿软件生命周期的分析、设计及编码中,是平滑过程了,从分析到设计再编码采用一致性模型表示,实现无法连接。面向过程强调分析、设计及编码之间按规则转换,贯穿软件生命周期分析、设计及编码,是有缝连接。

面向对象特征有哪些?

  抽象、继承、封装和多态。
  

  1. 抽象:分过程抽象和数据抽象。忽略与当前目标无关的方面,充分注意与当前目标有关方面。
  2. 继承:连结类的层次模式-鼓励类重用,提供明确表示共性方法。继承:连结类的层次模式-鼓励类重用,提供明确表示共性方法。
  3. 封装:将客观事物抽象为类,由类自身对数据和方法进行保护与操作,对不可信信息隐藏。封装:将客观事物抽象为类,由类自身对数据和方法进行保护与操作,对不可信信息隐藏。
  4. 多态:允许不同类对象对同一信息做响应。参数化多态和包含多态。多态:允许不同类对象对同一信息做响应。参数化多态和包含多态。

继承与组合的区别?

  分别是两种代码复用方式。组合是在心累创建原有类对象,重复利用原有类。继承允许根据其他类实现定义一个类实现。

  组合与继承都允许在新类设置子对象,组合显式而继承隐式。组合整体类对应继承子类,组合局部类对应继承父类。

  选择原则:除非是“is a"关系,否则别使用继承。不要仅为实现多态而使用继承。

多态实现机制是什么?

  多态表现:方法重载,方法覆盖。

  重载指类中多个同名方法,方法参数不同,由编译时确定调用何种,是编译时多态。类中方法多态性。

  覆盖,是子类覆盖父类。基类引用可指向基类实例或其子类实例。接口引用可指向实现其的实例对象。程序调用时运行期间动态绑定(引用变量所指向具体实例对象方法,而非引用类型的实例对象方法),运行时多态。

  运行时多态是对于方法而非成员变量。成员变量值取父类还是子类取决于定义变量类型,编译期间确定。

重载与覆盖不同有哪些?

  都是多态性不同表现。重载体现类中的同名方法,覆盖体现子父类。

  重载按照方法不同参数区分(不同参数的个数,类型或顺序),不能由访问权限、返还值类型和抛出异常类型重载。

  覆盖:子类覆盖要和基类有相同函数名和参数。返还值与基类被覆盖方法相同。子类覆盖方法抛出异常与基类(或其子类)中被覆盖方法抛出异常一致。基类被覆盖方法不能是private否则子类只是定义方法并未覆盖。

  区别:覆盖是子父类间垂直关系,重载是同类方法见水平关系。覆盖由一个方法或只能由一对方法参成关系,重载是多方法之间。覆盖要求参数列表相同,重载相反。覆盖调用根据对象存储类型决定,重载根据调用时的实参和形参选择。

抽象类和接口不同有哪些?

  若类包含抽象方法,则该类为抽象类。接口是方法集合,所有方法没有方法体。

  两者都是支持抽象类定义的两种机制(抽象类表示实体,接口表示概念),二者相似有时可互换。

  区别:只包含一个抽象方法的类被声明抽象类,其声明抽象方法存在可不实现,但不能包含方法体。实现时,包含相同或更低访问级别(public-protected-private)。抽象类使用过程中不能实例化,但可创建对象时之指向具体子类实例。抽象类子类为父类所有抽象提供具体实现,否则也是抽象类。接口看似抽象类变体,所有方法都是抽象。实现接口间接达到多继承。成员变量都是static final类。

  相同点:都不能实例化。其子类只有实现其所有抽象方法才能被实例化。

  不同:接口只有定义,方法不能实现。抽象有定义和实现。接口需要实现而抽象类是继承。接口可以多实现,抽象只能单继承。接口强调has-a,抽象强调is-a。接口成员变量默认public static final,只有静态方法不能被修改的数据成员而且必须赋予初值,方法只能public abstract修饰。抽象类有自己数据成员变量,也有非抽象成员方法,成员变量默认default(本包可见)不能private,static,synchronized,navie等访问修饰符修饰,方法必须分号结尾而无花括号。功能需积累用抽象类,不需则接口。接口用于常用功能,便于日后维护或添加删除方法;抽象类充当公共角色,不适合对里面代码修改。

  接口可继承接口。抽象类可实现接口继承具体类包含静态main方法。

内部类有哪些?

  一个类定义另一个类内部,则该类为内部类,另一个为外部类。内部类可看成外部类的成员。顶层类,类定义不嵌套在其他类定义中。

  内部类分:静态内部类,成员内部类,局部内部类,匿名内部类。

  静态内部类指被声明static的内部类,不依赖外部类实例而被实例化,而通常内部类需要需要外部类实例化后才能实例化。静态内部类不能与外部类同名,不能访问外部类普通成员对象,只能访问外部类静态成员和静态方法。

  一个静态内部类去掉static关键字则为成员内部类,可自由引用外部类的属性和方法,无论这些属性和方法是静态的还是非静态的。

  但它与一个实例绑定在一起,不可以定义静态属性和方法,只有外部的类被实例化,这个内部类才能实例化。

  非静态内部类不能有静态成员。

  局部内部类是代码块中的类,作用域是其所在代码块,与局部变量用于不能被public,protected,private和static修饰,只能访问方法众定义为final的局部变量。

  匿名内部类是没有类名的内部类,不能使用关键帧class,extends,implement,没有构造函数,必须继承其他类或实现其他接口。其好处代码简洁。

  匿名内部类原则:无构造,不能定义静态成员,方法和类,不能是public,protected,private,static,只能创建匿名内部类的一个实例。一定是在new后面,必须继承父类或实现接口(一个)。匿名内部类是局部内部类有其所有限制。

如何获取父类类名?

this.getClass().getSuperClass().getName()

this与super区别?

  

  • this当前实例对象,用于区分对象成员变量与方法形参(同名)。
  • super可以用来访问父类方法或成员变量。防止子类具有同名覆盖。

final与finally和finalize的区别?

  
1)final声明属性,方法和类,分别表明属性不可变,方法不可覆盖和类不可被继承 final属性:被final修饰变量不可变,引用不可变和对象不可变。

  final修饰变量必须被初始化,其是引用不可变,是指指向的对象而不是对象的内容。初始化方式:定义初始化,初始化块中初始化,但不可在静态初始化块中初始化。静态final成员变量在静态初始化快中初始化,但不可在初始化块中初始化。在类构造器中初始化,但静态final变量则不行。
  

  • final方法:不允许子类重写但可使用。存在inline机制,可将方法主体插入调用处,而非方法调用,提高执行效率。
  • final参数:参数在函数内部不可更改。
  • final类:不可继承,方法均不可重写。final修饰的成员变量才不可改变。

    一个类不能既是abstract又是final。

  2)finally:try/catch结果一部分,表示最终必定执行,通常用于释放资源。

  3)finalize:Object类一个方法,垃圾回收器执行时会调用被回收对象的finalize方法,可以覆盖此方法实现对其他资源回收。例关闭文件。一旦垃圾回收准备释放对象占用空间,首先调用finalize()方法,并且下一次垃圾回收动作发生时才真正回收对象占用内存。

assert的作用?

  assert断言,是软件调试一种方法,提供代码中正确性检查机制。主要是对boolean表达式检查。若表达式为false则说明程序处在不正确状态,系统需要提供警告并退出程序。软件发布后,一般asset检查默认关闭。

  assert exp1:exp2。exp1是boolean表达式,exp2是基本类型或对象。

  应用范围:检查控制流;检查输入参数是否有效;检查函数结果是否有效;检查程序不变量。

  Java和C++的assert不同:前者实现功能,后者使用库函数;前者运行时开启,后者编译时开启。

static关键字的作用?

  为特定数据类型或对象分配单一存储空间,而与创建对象个数无关;实现某方法或属性与类而不是对象关联在一起,也就是,在不创建对象情况就可以通过类直接调用方法或使用类属性。

  使用情况:成员变量、成员方法、代码块和内部类

  (1)static成员变量:static静态变量属于类,在内存只有一个复制,只要类被加载就会被分配空间使用。方法体中不能定义static变量。不能在成员函数内部定义。

  (2)static成员方法:是类方法无需创建对象就可使用。static方法不能使用this和super。只能访问所在类静态成员变量和成员方法,无法访问非静态变量。

  重要用途实现单例模式。

  若static变量用private修饰,则表示该变量只在类静态代码块或类其他静态成员方法使用,但不能其他类通过类名直接引用。

  (3)static代码块:独立于成员变量和成员函数存在。不在任何一个方法体内,JVM加载类代码会执行static代码块,若有多个static代码块,JVM按照顺序执行。通常被用来初始化静态变量。只执行一次。

  (4)static内部类

  声明为static内部类,不依赖外部类实例对象而被实例化。静态内部类不能与外部类具有相同名字,不能访问外部类普通成员变量,只能访问外部类静态成员和静态方法(包括私有类型)

volatile的作用?

  用来修饰被不同线程访问和修改的变量。被其修饰的变量,会直接从内存提取,而不使用缓存,确保任何线程任何时候看到此变量值一致。

  volatile不能保证操作原子性,不能替代sychronized。会阻止编译器对代码优化,降低执行效率。除非迫不得已否则别用。

instanceof的作用?

  判断一个引用类型变量所指向的对象是否是一个类(接口、抽象类、父类)实例。

strictfp的作用?

  精确浮点,确保浮点运算准确性。若不指定,JVM执行浮点运算可能不精确,在不同平台或厂商虚拟机有不同结果产生难以难料错误。而一旦使用来声明一个类、接口或方法,在声明范围内,Java编译器及运行环境按照IEEE二进制浮点运算标准(IEEE 754)执行,声明范围内的浮点计算都是精确的,确保不同硬件平台具有一致运行结果。

什么是不可变类?

  意思指创建该类实例不允许修改其值,即某对象被创建,在其整个生命周期其成员变量不能被修改。

  所有包装类都是不可变类,String也是不可变类。

创建不可变类原则

  

  • 类中所有成员被private所修饰;
  • 类中没有写或修改成员变量方法;
  • 确保类中方法都不被子类覆盖。
  • 类成员不可变量,则成员初始化或使用get方法获取该成员变量,通过clone方法确保类不可变性。
  • 若有必要覆盖Object类的equals()方法和hashCode()方法。

不可变类,最好提供带参构造函数初始化成员变量。

    short a=128;
    byte b=(byte)a;
    // a=128,
    // b=-1;

a有两个字节0000000010000000,b一个字节取低字节10000000,所以是-1。

new String(“abc”)创建几个对象?

  一个或两个。若常量池中原来有"abc"则一个,否则两个。

==,equals,hashCoed的区别?

  == 判断对象的引用是否同一引用,基本数据是判断数值。

  equals没覆盖时和==一样。

  hashCode,Object原始实现是返还对象在内存中的地址转换成int型。x.euqals(y)=false,则hashcode可能不同。否则true,则hashcode一定相同。

Set元素不可重复,是用iiterator()方法区分

String,StringBuffer,StringBuilder,StringTokenizer的区别?

  Character是单字符操作,String字符串操作,不可变类。StringBuffer也是字符串操作,可变类。字符串经常需要修改使用StringBuffer。

  StringBuilder也可以被修饰的字符串,与StringBuffer类似都是字符串缓冲区。但前者不是线程安全。单线程用StringBuilder,多线程最好用StringBuffer。StringBuffer方法同步,任意特定示例上的操作都是串行顺序,该顺序与所涉及的每个线程进行的方法调用顺序一致。

   执行效率,StringBuilder > > StringBuffer > > String

  StringTokenizer用来分割字符串工具类。

try-catch-finally的部分注意

  try-finally或catch-finally都有return,finally的return覆盖它处的

  try语句块之前出现异常,finally块代码不会执行。try块中强制退出也不会执行,比如代码中有System.exit(0)

运行时异常和普通异常的区别?

  Error和Exception,共同父类:Throwable。

  Error程序运行期间非常严重错误,且不可恢复。属于JVM层次严重错误,该错误会异常终止。编译器不检查Error是否被处理,所以不推荐捕获。主要是运行时异常多由逻辑错误导致。

  Exception属于可恢复异常,编译器可以捕捉。分检查异常和运行异常。
  
(1)检查异常:继承Exception并且不是运行时异常的异常都是检查异常,例IO异常,SQL异常。发生在编译阶段,将可能出现的放入try中。
  
使用情况:异常发生不导致程序出错,处理后仍可继续。不依赖于外部条件,比如系统IO。

  
(2)运行时异常:编译器不强制捕获并处理。不处理但发生由JVM处理。常见有空指针异常,类型转换异常,数据越界异常,数据储存异常,缓冲区溢出异常,算术异常等。
  
注意:异常处理用到多态。若异常处理过程先捕获基类再捕获子类,则捕获子类代码永远不执行。所以应该先捕获子类再基类异常信息。

  尽可能早抛出异常,同时对捕获异常处理或从错误中恢复,或让程序继续执行。

  可以按需自定义继承自Exception的异常类。

  异常能处理则处理,不能则抛出。

结语

  博文对书中部分进行删除,留下感觉有点难度的,删掉简单的部分知识点,删除部分代码和试题部分,这是4.1-4.6部分。有兴趣的人可以去网上购买或者下载该书pdf。

猜你喜欢

转载自blog.csdn.net/YuYunTan/article/details/83020026