Java学习总结——类和对象

玖——面向对象设计

类( class )和对象( object )是面向对象程序设计最重要的概念。要深入了解 Java 程序语言,一定要树立面向对象程序设计的观念。

一、理解面向对象程序设计

面向对象程序( Object-oriented programming,OOP )设计是继面向过程又一具有里程碑意义的编程思想,是现实世界模型的自然延伸。

1.结构化程序设计简介

结构化程序设计概念最早由 E.W.Dijikstra 在1965年提出的,是软件发展的一个重要的里程碑。它的主要观点是采用自顶向下、逐步求精及模块化的程序设计方法,使用三种基本控制结构构造程序,任何程序都可由顺序、选择、循环这三种基本控制结构来构造。

结构化程序设计主要强调的是程序的易读性。在该程序设计思想的指导下,编程基本是通过写不同目的的函数/过程来实现,故又称 “ 面向过程编程( Procedure Oriented Programming,POP )”。面向过程开发方式是对计算机底层结构的一层抽象,它把程序的内容分为数据和操纵数据的操纵两个部分。这种编程方式的核心问题是数据结构和算法的开发和优化。

结构化程序设计方法可以用一句话概括:

程序 = 算法 + 数据结构

这里的 “ 算法 ” 可以用顺序、选择、循环这三种基本控制结构来实现。

这里的 “ 数据结构 ” 是指数据以及其相应的存取方式。数据结构用来描述问题,算法用来解决问题。

2.面向对象程序设计简介

面向对象的思想主要是基于抽象数据类型( Abstract Data Type,ADT ),在结构化编程过程中,人们发现把某种数据结构和专用于操纵它的各个操作以某种模块化方式绑定到一起会非常方便,做到 “ 特定数据对应特定处理方法 ”,使用这种方式进行编程时数据结构的接口是固定的。如果对抽象数据类型进一步抽象,就会发现把这种数据类型的实例当做一个具体的东西、事物、对象,就可以引发人们对编程过程中怎样看待所处理的问题的一次大的改变。

抽象数据类型方法虽然也有一定的抽象能力,但其核心仍然是数据结构和算法。而面向对象方法直接把所有事物都当做独立的对象,处理问题过程中所思考的不再主要是怎样用数据结构来描述问题,而是直接考虑重现问题中各个对象之间的关系。可以说,面向对象革命的最重要价值就在于改变了人们看到和处理问题的方式。

我们可以将现实世界中的任何实体都看做是对象,现实世界中的对象均有属性和行为。类似的,映射到计算机程序上,属性则表示对象的数据,行为表示对象的方法( 其作用是处理数据或同外界交互 )。现实世界中的任何实体都可归属于某类事物,任何对象都是某一类事物的实例。所以在面向对象的程序设计中一个类可以实例化多个相同类型的对象。面向对象编程达到了软件工程的三个主要目标:重用性、灵活性和扩展性。

3.面向对象程序设计的基本特征

面向对象的程序设计的 3 个主要特征:封装性、继承性、多态性。

封装性( encapsulation ):封装是一种信息隐蔽技术,它体现于类的说明,是对象的重要特性。封装把数据和加工该数据的方法( 函数 )打包成为一个整体,以实现独立性很强的模块,使得用户只能见到对象的外特性( 对象能接收到哪些消息,具有哪些处理能力 ),而对象的内特性( 保存内部状态的私有数据和实现加工能力的算法 )对用户是隐蔽的。封装的目的在于把对象的设计者和对象的使用者分开,使用者不必知晓其行为实现的细节,只须用设计者提供的消息来访问该对象。

继承性:继承性是子类共享其父类数据和方法的机制。它由类的派生功能体现。一个类直接继承其他类的全部描述,同时可修改和扩充。继承具有传递性。继承分为单继承( 一个子类有一个父类 )和多重继承( 一个类有多个父类 )。类的对象是各自封闭的,如果没继承性机制,则类的对象中的数据、方法就会出现大量重复。继承不仅支持系统的可重用性,而且还促进系统的可扩充性。

多态性:对象根据所接收的信息而做出动作。同一消息被不同的对象接收时可产生完全不同的行动,这种现象称为多态性。利用多态性用户可发送一个通用的信息,而将所有的实现细节都留给接收消息的对象自行决定,就是统一消息可调用不同的方法。多态性的实现受到继承性的支持,利用类继承的层次关系,把具有通用功能的协议存放在类层次中尽可能高的地方,而将实现这一功能的不同方法置于较低层次,这样,在这些低层次上生成的对象就能给通用消息以不同的相应。在 OOPL 中可通过在派生类中重定义基类函数( 定义为重载函数或虚函数 )来实现多态性。

综上所述,在面向对象方法中,对象和传递消息分别表现事物及事物间相互联系的概念。类和继承是适应人们一般思维方式的描述范式。方法是允许作用于该类对象上的各种操作。这种对象、类、消息和方法的程序设计范式的基本点在于对象的封装性和类的继承性。通过封装能将对象的定义和对象的实现分开,通过集成能体现类与类之间的关系,以及由此实现动态联编和实体的多态性,从而构成了面向对象的基本特征。

4.面向对象编程和面向过程编程的比较

面向对象编程和面向过程编程既有区别也有联系。

(1)两种编程范式之间的区别

在面向对象编程出现以前,面向过程颇受程序人员的青睐,因为面向过程编程采用的是 “ 自顶而下,步步求精 ” 的编程思想,人们更易于理解这种思想。将程序一步一步的分解,并按照一定的顺序执行。这就是面向过程程序设计,以过程为中心,以算法为驱动。

程序 = 算法 + 数据结构

但是面向过程程序设计的不足之处在于,面向过程的程序上一步和下一步环环相扣,如果需求发生变化那么代码的改动会很大,这样很不利于软件的后期维护和扩展。

而面向对象程序设计的出现就可以很好的解决这一问题,它的设计思想是:

程序 = 对象 + 消息传递

用户首先自定义的数据结构——“ 类 ”,然后用该类型下的 “ 对象 ” 组装程序。对象之间通过 “ 消息 ” 进行通信。每个对象包括数据以及对数据的处理,每个对象都像是一个小型的 “ 机器 ”。面向对象设计使程序更容易扩展,也更加符合现实世界的模型。

面向对象程序设计有其优点,自然也带来了 “ 副作用 ”——执行效率要低于面向过程程序设计。所以要进行科学计算和要求高效率的程序中,面向过程设计要好于面向对象设计。而且面向对象程序的复杂度要高于面向过程的程序,如果程序比较小,面向过程要比面向对象更加清晰。

更为具体来说,为解决某个任务,面向过程程序设计首先强调的 “ 该怎么做(How to do?)” 这里的 “ How ” 对应的解决方案就形成一个个功能块——function( 函数 ),而面向对象程序设计首先考虑的是 “ 该让谁来做(Who to do?)”,这里的 “ Who ” 就是对象,这些对象完成某项任务的能力就构成一个 method( 方法 ),最后一些列具备一定的方法的对象 “ 合力 ” 能把任务完成。

(2)两种编程范式之间的联系

面向对象实在面向过程的基础上发展而来的,只是添加了它独有的一些特性。面向对象程序中的对象就是由数据和方法构成,所以完整的面向对象概念应该是,

对象 = 数据 + 方法

更进一步的可以描述为,

程序 = 对象 + 消息传递 = ( 数据 + 方法 )+ 消息传递

二、面向对象的基本概念

1.类

将具有相同属性及相同行为的一组对象称为类( class )。广义地讲,具有共同性质的事务的集合就称为类。在面向对象程序设计中,类是一个独立的单位,它有一个类名,其内部包括成员变量,用于描述对象的属性;还包括类的成员方法,用于描述对象的行为。在 Java 程序设计中,类被认为是一种抽象的数据类型,这种数据类型不但包括数据,还包括方法,这大大地扩充了数据类型的概念。

类是一个抽象的概念,要利用类的方式来解决问题,必须用类创建一个实例化的对象,然后通过对象去访问类的成员变量,去调用类的成员方法来实现程序的功能。

一个类可创建多个类对象,它们具有相同的属性模式,但可以具有不同的属性值。Java 程序为每一个对象都开辟了内存空间,以便保存各自的属性值。

2.对象

对象( object )是类的实例化后的产物。对象的特征分为静态特征和动态特征两种。静态特征指对象的外观、性质、属性等。动态特征指对象具有的功能、行为等。客观事物是错综复杂的,但人们总是从某一目的除法,运用抽象分析的能力,从众多的特征中抽取最具代表性、最能反映对象本质的若干特征加以详细研究。

人们将对象的静态特征抽象为属性,用数据来描述,在 Java 语言中称之为变量,将对象的动态特征抽象为行为,用一组代码来表示,完成对数据的操作,在 Java 语言中称之为方法( method )。一个对象由一组属性和一系列对属性进行操作的方法构成。

在现实世界中,所有的事物都可视为对象,对象时客观世界里的实体。而在 Java 里,“ 一切皆为对象 ”,它是一门面向对象的编程语言,面向对象( Object-Oriented )的核心就是对象。

3.类和对象的关系

面向对象的编程思想力图使在计算机语言中对事物的描述与现实世界中该事物的本来面目尽可能地一致,类和对象就是面向对象方法的核心概念。类是对某一类事物的描述,是抽象的、概念上的定义。对象是实际存在的该类事物的个体,因而也称做实例( instance )。所以面向对象程序设计的重点是类的设计,而不是对象的设计。

一个类按同种方法产生出来的多个对象,其最开始的状态都是一样的,但是修改其中一个对象的时候,其他的对象是不会受到影响的。

三、类的声明与定义

1.类的声明

在使用类之前,必须先声明它,然后才可以声明变量,并创建对象。

类声明的语法如下:

        [标识符] class 类名称
        {
            //类的成员变量
            //类的方法
        }

声明类使用的是 class 关键字。声明一个类时,在 class 关键字后面加上类的名称,这样就创建了一个类,然后在类的里面定义成员变量的方法。

其中的标识符可以是 public、private、protected 或者完全省略这个修饰符,类名称只要是一个合法的标识符即可,但从程序的可读性方面来看,类名称最好是由一个或多个有意义的单词连缀而成,每个单词首字母大写,单词间不要使用其他分隔符。

类的标识符可以是访问控制符。Java 提供了一系列的访问控制符来设置基于类( class )、变量( variable )、方法( method )及构造方法( constructor )等不同等级的访问权限。

Java 的访问权限主要有 4 类:

(1)默认模式( default )。在默认模式下,不需为某个类、方法等不加任何访问修饰符。这类方是声明的方法和类,只允许在同一个包( package )内是可访问的。

(2)private( 私有 )。这是 Java 语言中对访问权限控制最严格的修饰符。如果一个方法、变量和构造方法被声明为 “ 私有 ” 访问,那么它仅能在当前声明它的类内部访问。类和接口( interface )的访问方式是不能被声明为私有的。

(3)public( 公有 )。这是 Java 语言中访问权限控制最宽松的修饰符。如果一个类、方法、构造方法和接口等被声明 “ 公有 ”访问,那么它不仅可以被跨类访问,而且允许挎包访问。如果需要访问其他包里的公有成员,则需要事先导入( import )那个包含所需公有类、变量和方法等的那个包。

(4)protected( 保护 )。介于 public 和 private 之间的一种访问修饰符。如果一个变量、方法和构造方法在父类中被声明为 “ 保护 ” 访问类型,只要被类本身的方法及子类访问,即使子类在不同的包中也可以访问。类和接口( interface )的访问方式是不能声明为保护类型的。

类的标识符除了上述的 4 个访问控制符,还可以是 final。关键字 “ final ” 有 “ 无法改变的 ” 或者 “ 终态的 ” 含义。一个类一旦被声明为 final,那这个 final 类不能被继承,因此 final 类的成员方法没有机会被覆盖,默认情况下类都是 default 的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会再被扩展,那么就设计为 final 类。

2.类的定义

在声明一个类后,还需要对类进行定义。

定义的语法如下:

        class 类名称
        {
            数据类型 属性;    //零到多个属性
            类名称(参数,…)    //零到多个构造方法
            {
  
            }
            返回值的数据类型 方法名称(参数1,参数2……)    //零到多个方法
            {
                程序语句;
                return 表达式;
            }
        }

对于一个类定义而言,构造方法( constructor,又称构造器或构造函数 )、属性和方法是三种最常见的成员,它们都可以定义零个或多个。如果三种成员都只定义零个,那实际上是定义了一个空类,那就失去了定义类的意义了。

类中各个成员之间,定义的先后顺序没有任何影响。各成员可相互调用,但值得注意的是,static 修饰的成员不能访问没有 static 修饰的成员。

属性用于定义该类或该类的实例所包含的各种数据。方法则用于定义类中的行为特征或功能实现( 即对数据的各种操作 )。构造方法是一种特殊的方法,专用于构造该类的实例( 如实例的初始化、分配实例内存空间等 ),Java 语言通过 new 关键字来调用构造方法,从而返回该类的实例。

定义一个类后,就可以创建类的实例了,创建类实例通过 new 关键字完成。

注意:在类中,如果没有显式定义构造方法,Java 编译器会提供一个默认的无参构造方法。

四、类的属性

类的基本组成部分包括属性和方法。

事实上,经常使用的 System 是系统类( class ),out 是标准输出对象 ( object ),而 println() 是对象 out 中的一个方法( method )。这句话的完整含义就是调用系统类 System 中的标准输出对象 out 中的方法 println()。

类的属性也称为字段或成员变量,不过习惯上将它成为属性。

1.属性的定义

类的属性是变量。定义属性的语法如下:

        [修饰符] 属性类型 属性名 [ = 默认值]

属性语法格式的详细说明如下:

(1)修饰符:修饰符可省略,也可是访问控制符 public、protected、private 及 static、final,其中前三个访问控制符只能使用其中之一,static 和 final 则可组合起来修饰属性。

(2)属性类型:属性类型可以是 Java 允许的任何数据类型,包括基本类型 ( int、float 等)和引用类型(类、数组、接口等)。

(3)属性名:从语法角度来说,属性名则只要是一个合法的标识符即可。但如果从程序可读性角度来看,属性名应该由一个或多个有意义的单词( 或能见名知意的简写 )连缀而成,推荐的风格是第一个单词应以小写字母作为开头,后面的单词则用大写字母开头,其他字母全部小写,单词间不实用其他分隔符。如:String studentCode。

(4)默认值:定义属性还可以定义一个可选的默认值。

提示:属性是一种比较符合汉语习惯的说法,在 Java 的官方文献中,属性被称为 Field,因此有些书籍也把 “ 属性 ” 翻译为 “ 字段 ” 或 “ 域 ”,它们本质上是相同的。

2.属性的使用注意

在实际的应用中,并不建议在类中定义属性时实施初始化。

但值得注意的是,被 static 修饰的变量成为类变量( class‘s variables ),它们被类的实例所共享。也就是说,某一个类的实例改变了这个静态值,其他这个类的实例也会受到影响。而成员变量( member variable )则是没有被 static 修饰的变量,为实例私有,也就是说,每个类的实例都有一份自己专属的成员变量,只有当前实例才可更改它们的值。

static 是一个特殊的关键字,其在英文中直译就是静态的意思。它不仅用于修饰属性( 变量 ),成员,还可用于修饰类中的方法。被 static 修饰的方法,同样表明它是属于这个类共有的,而不是属于该类的单个实例,通常把 static 修饰的方法也称为类方法。

五、对象的声明与使用

在实际中单单有类是不够的的,类提供的只是一个模板,必须依照它创建出对象之后才可以使用。

1.对象的声明

下面定义了由类产生对象的基本形式。

        类名 对象名 = new 类名();

创建属于某类的对象,需要通过下面两个步骤实现:

(1)声明指向 “ 由类所创建的对象 ” 的变量。

(2)利用 new 创建新的对象,并指派给先前所创建的变量。

举例来说,如果要创建 Person 类的对象,可用下列语句实现。

        Person p1;    //先声明一个Person类的对象p1
        p1 = new Person();    //利用 new 关键字实例化Person的对象p1

当然也可以用下面的这种形式来声明变量。

        Person p = new Person();    //声明Person对象并直接实例化此对象

提示:对象只有在实例化之后才能被使用,而实例化对象的关键字就是 new。

对象实例化的过程如下:

(1)在栈内存中建立指向新对象的引用变量p1

(2)在堆内存中分配用于存储 Person 对象 p1 所需的内存空间

(3)把堆内存空间的参考地址赋给 p1

对象的 “ 引用 ” 本质上就是一个对象在堆内存的地址,所不同的是,在 Java 中,用户无法像 C/C++ 那样直接操作这个地址。

本质上,“ new Person() ” 就是使用 new 关键字,来调用构造方法 Person() ,创建一个真实的对象,并把这个对象在 “ 堆内存 ” 中的占据的内存首地址赋予 p1 ,这时 p1 才能成为一个实例化的对象。

这里做个比对来说明 “ 栈内存 ” 和 “ 堆内存 ” 的区别。在医院中,为了迎接一个新生命的诞生,护士会先在自己的登记本上留下一行位置,来记录婴儿床的编号,一旦婴儿诞生后,就会将其安置在育婴房内的某个婴儿床上。然后护士就在登记本上记录下婴儿床编号,这个编号不那么好记,就给这个编号取个好记的名称,例如 p1,那么这个 p1( 本质上就为婴儿床编号 )就是这个婴儿 “ 对象 ” 的引用,找到这个引用,就能很方便找到育婴房里的婴儿。这里,护士的登记表就好比是 “ 栈内存 ”,它由护士管理,无需婴儿父母费心。而育婴房就好比是 “ 堆内存 ”,它由婴儿父母显式申请( 使用 new 操作 )才能有床位,但一旦使用完毕,会由一个专门的护工( 编译器 )来清理回收这个床位——在 Java 中,有专门的内存垃圾回收( Garbage Collection,GC )机制来负责回收不再使用的内存。

2.对象的使用

如果要访问对象里的某个成员变量或方法,可以通过下面的语法来实现。

        对象名称.属性名    //访问属性
        对象名称.方法名()    //访问方法

例如,想要访问 Person 类中的 name 和 age 属性,可用如下方法来访问。

        p1.name;    //访问Person类中的name属性
        p1.age;    //访问Person类中的age属性

对于对象属性和方法点操作符 “ . ”,可以直接读成 “ 的 ”,例如,p1.name = '张三',可以读成 “ p1的 name 被赋值为张三 ”。

这样读是有原因的:点操作符 “ . ” 对应的英文为 “ dot [dɔt] ”,通常 “ t ” 的发音弱化而读成 “ [dɔ] ” ,而 “ [dɔ] ” 的发音很接近汉语 “ 的 ” 的发音 “ [de] ”。此外,“ 的 ” 在含以上也有 “ 所属 ” 关系。因此将点操作符 “ . ” 读成 “ 的 ”,音和意皆有内涵。

3.匿名对象

匿名对象是指没有名字的对象。实际上,对于一个对象实例化的操作来讲,对象真正有用的部分是在堆内存里面,而栈内存只是保存了一个对象的引用名称( 严格来讲是对象在堆内存的地址 ),所以所谓的匿名对象就是指,只开辟了对内存空间,而没有栈内存指向的对象。

匿名对象有如下两个特点:

(1)匿名对象是没有被其他对象所引用,即没有栈内存指向。

(2)由于匿名对象没有栈内存指向,所以其只能使用一次,之后就变成无法找寻的垃圾对象,故此会被垃圾回收器收回。

4.对象的比较

有两种方式可用于对象间的比较:

(1)利用 “ == ” 运算符;

(2)利用 equale() 方法。

“ == ” 运算符用于比较两个对象的内存地址值( 引用值 )是否相等,equals() 方法用于比较两个对象的内容是否一致。

需要重复的是,“ == ” 运算符用于比较两个对象的内存地址值( 即所谓的引用值 )的,而 equals() 方法用于比较对象内容的。

5.对象数组的使用

我们可以把类理解为用户自定义的数据类型,它和基本数据类型( 如 int、float 等 )具有相同的地位。同样的,对象也可以用数组来存放,可通过下面两个步骤来实现。

(1)声明以类为数据类型的数组变量,并用 new 分配内存空间给数组。

(2)用 new 产生新的对象,并分配内存空间给它。

例如,要创建 3 个 Person 类型的数组元素,语法如下:

        Person p[];    //声明 Person类类型的数组变量
        p = new Person[3];    //用new分配内存空间

创建好数组元素之后,便可把数组元素指向由 Person 类所定义的对象。

        p[0] = new Person();
        p[1] = new Person();
        p[2] = new Person();

当然也可以写成如下形式。

        Person p[] = new Person[3];    //创建对象数组元素,并分配内存空间

当然,也可以利用 for 循环来完成对象数组内的初始化操作,此方式属于动态初始化。

        for(int i=0; i<p.lenght;i++)
        {
            p[i] = new Person();
        }

或者也可以采用静态方式来初始化对象数组,如下所示。

        Person p[] = { new Person(),new Person(),new Person()};

当创建一个对象后,Java 虚拟机(JVM)就会给这个对象分配一个自身的引用——this。由于 this 是和对象本身相关联的,所以 this 只能在类中的非静态方法中使用。静态属性及静态方法属于类,它们与具体的对象无关,所以静态属性及静态方法是没有 this 的。同一个类顶一下的不同对象,每个对象都有自己的 this,虽然都叫 this,但指向的对象不同。这好比一个班里的众多同学来做自我介绍:“ 我叫XXX ”,虽然说的都是 “ 我 ”,但每个 “ 我 ” 指向的对象是不同的。

六、本文注意事项

1.栈内存和对内存的区别

在 Java 中,栈( stack )是由编译器自动分配和释放的一块内存区域,主要用于存放一些基本类型( 如 int、float 等 )的变量、指令代码、常量及对象句柄( 也就是对象的引用地址 )。

栈内存的操作方式类似于数据结构中的栈( 仅在表尾进行插入或删除操作的线性表 )。栈的优势在于,它的存取速度比较快,仅次于寄存器,栈中的数据还可以共享。其缺点表现在,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

堆( heap )是一个程序运行动态分配的内存区域,在 Java 中,构建对象时所需要的内存从堆中分配。这些对象通过 new 指令 “ 显式 ” 建立,放弃分配方式类似于数据结构中的链表。堆内存在使用完毕后,是由垃圾回收( Garbage Collection,GC )器 “ 隐式 ” 回收的。在这一点上,是和 C/C++ 是有显著不同的,在 C/C++ 中,堆内存的分配和回收都是显式的,均由用户负责,如果用户申请了堆内存,而在使用后忘记释放,则会产生 “ 内存溢出 ” 的问题——可用内存存在,而其他用户却无法使用。

堆的优势是在于动态地分配内存大小,可以 “ 按需分配 ”,其生存期也不必事先告诉编译器,在使用完毕后,Java 的垃圾收集器会自动收走这些不再使用的内存块。其缺点为,由于要在运行时才动态分配内存,相比于栈内存,它的存取速度较慢。

由于栈内存比较小,如果栈内存不慎耗尽,就会产生著名的堆栈溢出( stack overflow )问题,这能导致整个运行中的程序崩溃( crash )。由于这个问题的普通性,全球 IT最受欢迎的技术问答网站也取名为 Stack Overflow( http://stackoverflow.com/)。

类似的,如果堆内存使用不当也会产生问题,典型的问题就是内存碎片( fragmentation ),当回收堆内存时,可能会导致一些小块的但不连续的内存存在。当用户申请一块较大的堆内存,虽然可用的小块内存总和足够大,本可以满足申请所需,但是由于它们不连续,导致申请失败。这些不可用的非连续的小块内存就是所谓的内存碎片。

2.面向对象编程与面向过程编程的感性认知

表面上看来面向过程代码 POP 依然占据优势,比较简洁,但是 POP 代码在代码维护中,“ 牵一发而动全身 ”,过程中有一点错误都会让前期的代码受到牵连——无法正确运行。

而面向对象代码 OOP,虽然代码过程看起来复杂一点,但是如果前期的代码可以正确相应,那么在新版本维护过程中,即使出现了错误( 不管是逻辑上的还是语法上的 ),那这些程序上的错误就可控,很方便维护。

如果代码很短,面向对象编程的模式优势并不明显,落后的软件生产方式无法满足循序增长的计算机软件需求,从而导致软件开发与维护过程中出现一系列严重问题的现象。这就是所谓的软件危机。但相比于面向过程编程,面向对象编程还有更多的优点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值