Java clone() 浅克隆与深度克隆

 

1. Cloneable接口介绍

今天编写一个用到Cloneable接口的程序时才注意到原来clone()方法提供的是浅层拷贝,而不是我先前认为的深层拷贝。
class A implements Cloneable{
int a=1;
ArrayList b=new ArrayList();
public A getClone(){
return (A) this.clone();
}
}
对于这样的一个类,如果调用它的getClone()方法进行复制的话,成员变量a会被复制,但是成员变量b不会像预想的那样会复制一个ArrayList(),复制的对象与原先的对象中的b都会指向相同的一个ArrayList()。因此如果需要做深层拷贝需要添加额外的代码。
class A implements Cloneable{
int a=1;
ArrayList b=new ArrayList();
public A getClone(){
A temp=(A) this.clone();
temp.b=new ArrayList();
for(int i=0;i<b.size();i++){
temp.b.add(b.get(i));
}
return temp;
}
}

原始对象中的字符串和数组不受到克隆对象设置值的影响(会创建一个独立的),而类对象变量则会受到了克隆对象的影响。如list、map........ ;static修饰的也将是以引用的形式克隆

2.Java clone() 浅克隆与深度克隆

分类:Java基础解惑集2009-02-28 14:462330人阅读评论(4)收藏举报

以下文字转自:桔子园http://www.blogjava.net/orangelizq/archive/2007/10/17/153573.html

现在Clone已经不是一个新鲜词语了,伴随着“多莉”的产生这个词语确实很“火”过一阵子,在java中也有这么一个概念,它可以让我们很方便的“制造”出一个对象的副本来,下面来具体看看java中的Clone机制是如何工作的?
1. Clone&Copy
假设现在有一个Employee对象,Employee tobby =new Employee(“CMTobby”,5000),通
常我们会有这样的赋值Employee cindyelf=tobby,这个时候只是简单了copy了一下reference,cindyelf和tobby都指向内存中同一个object,这样cindyelf或者tobby的一个操作都可能影响到对方。打个比方,如果我们通过cindyelf.raiseSalary()方法改变了salary域的值,那么tobby通过getSalary()方法得到的就是修改之后的salary域的值,显然这不是我们愿意看到的。我们希望得到tobby的一个精确拷贝,同时两者互不影响,这时候我们就可以使用Clone来满足我们的需求。Employee cindy=tobby.clone(),这时会生成一个新的Employee对象,并且和tobby具有相同的属性值和方法。
2. Shallow Clone&Deep Clone
Clone是如何完成的呢?Object在对某个对象实施Clone时对其是一无所知的,它仅仅是简单地执行域对域的copy,这就是ShallowClone这样,问题就来了咯,以Employee为例,它里面有一个域hireDay不是基本型别的变量,而是一个reference变量,经过Clone之后就会产生一个新的Date型别的reference,它和原始对象中对应的域指向同一个Date对象,这样克隆类就和原始类共享了一部分信息,而这样显然是不利的,过程下图所示:

这个时候我们就需要进行deep Clone了,对那些非基本型别的域进行特殊的处理,例如本例中的hireDay。我们可以重新定义Clone方法,对hireDay做特殊处理,如下代码所示:

[java] view plaincopyprint?

1.  class Employee implements Cloneable

2.   

3.  {

4.  public Object clone() throws CloneNotSupportedException

5.  {

6.  Employee cloned = (Employee) super.clone();

7.  cloned.hireDay = (Date) hireDay.clone()

8.  return cloned;

9.  }

10.  }

3. Clone()方法的保护机制

在Object中Clone()是被申明为protected的,这样做是有一定的道理的,以Employee

类为例,通过申明为protected,就可以保证只有Employee类里面才能“克隆”Employee对象,原理可以参考我前面关于public、protected、private的学习笔记。

4. Clone()方法的使用

Clone()方法的使用比较简单,注意如下几点即可:

a. 什么时候使用shallow Clone,什么时候使用deep Clone,这个主要看具体对象的域是什么性质的,基本型别还是referencevariable

b. 调用Clone()方法的对象所属的类(Class)必须implementsClonable接口,否则在调用Clone方法的时候会抛出CloneNotSupportedException

 

 

3.Cloneable接口分析

分类:Java基础2004-09-07 14:034923人阅读评论(2)收藏举报

Cloneable接口是一个标记接口,也就是没有任何内容,定义如下:
package java.lang;
pubilc interface Cloneable{
}
这里分析一下这个接口的用法
java
clone的含义(或者说是目标)
假设x是一个非空对象,应该有:
x.clone()!=x
true,就是说他们不是同一个对象.
x.clone().getClass()==x.getClass()
true,他们是同一个类型Class.
x.equals(x.clone())
true,逻辑上应该相当.

clone方法是在Object种定义的,而且是protected型的,只有实现了这个接口,

可以在该类的实例上调用clone方法,否则会抛出CloneNotSupportException.
Object
中默认的实现是一个浅拷贝,也就是表面拷贝,如果需要实现深层次拷贝

的话,必须对类中可变域生成新的实例.
pubilc class Unsupported{
public Object clone(){
Object obj;
try {
obj=super.clone();
}
catch (CloneNotSupportedException ex) {
ex.printStackTrace(); //Exception was thrown
}
return obj;//
返回的是null
}
}
加上implements Cloneable 就可以了.
可不可以不实现这个接口,但是覆盖Clone方法.
pubilc class Unnormal{
public Object clone(){
return new Unnormal();
}
}
这样肯定没有问题的,不过已经和java中的clone机制没有关系了.
下面举一个例子说明浅拷贝和深拷贝:
public class ShallowCopy implements Cloneable{
private Date begin;
public Date getBegin(){return this.begin;}
public void setBegin(Date d){this.begin=d;}
public Object clone(){
Object obj=null;
try
{
obj=super.clone();
}
catch (CloneNotSupportedException ex) {
ex.printStackTrace();
}
return obj;
}
}
public class DeepCopy implements Cloneable{
private Date begin;
public Date getBegin(){return this.begin;}
public void setBegin(Date d){this.begin=d;}
public Object clone(){
DeepCopy obj=null;
try
{
obj=(DeepCopy)super.clone();
}
catch (CloneNotSupportedException ex) {
ex.printStackTrace();
}
obj.setBegin((Date)this.getBegin().clone());
return obj;
}
}

 

4.如何实现深度克隆

在java中,对于非原型数据,想实现拷贝的话必须要使用深度克隆,这样克隆出来的对象才会与源对象相互独立。

深度克隆一般有两种方法:

一是使用Objectcopy方法以及Cloneable接口;

二是使用序列化与反序列化。

方法一:使用Object的copy方法以及Cloneable接口

// deep clone

public class HomeAddress implementsCloneable {

   private String homeAddr;

   private String homePhone;

   // ...setter & getter

   public Object clone() throws CloneNotSupportedException {

       return super.clone();

    }

}

 

 

public class Customer implements Cloneable{

   private String customerNm;

   private HomeAddress homeAddr;

   //..setter and getter

   public Object clone() throws CloneNotSupportedException {

        Customer customer = (Customer)super.clone();

       customer.homeAddr = (HomeAddress) homeAddr.clone();

       return customer;

    }

}

 

方法二:使用序列化与反序列化

// serialize

public class HomeAddress implementsSerializable {

   //...

}

public class Customer implementsSerializable {

   public Object clone() throws CloneNotSupportedException {

       Object obj = null;

       ByteArrayOutputStream bos = null;

       ObjectOutputStream oos = null;

       ByteArrayInputStream bis = null;

       ObjectInputStream ois = null;

       try {

           bos = new ByteArrayOutputStream();

           oos = new ObjectOutputStream(bos);

           oos.writeObject(this);

           bis = new ByteArrayInputStream(bos.toByteArray());

           ois = newObjectInputStream(bis);

           obj = ois.readObject();

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           // ... close streams

       }

       return obj;

    }

}

 

 

5.通过实现CLONEABLE接口和覆盖CLONE()方法实现深度克隆,以及如何通过BYTEARRAYIOSTREAM实现克隆

某个类要想实现拷贝的功能,就必须实现Cloneable接口,并覆盖Objectclone()方法,才能真正实现克隆。

浅拷贝,一种默认的实现,Teacher类中的clone方法的实现就是浅拷贝。

Student
类的clone方法就是深拷贝。注意super.clone返回的对象实际上是被类的对象,可以放心强制转换,至于为什么,我也不知道,估计得读虚拟机规范,从Object源码看也不到什么,因为是protect native Object clone();

importjava.util.*;
public classTestClone{
publicstatic void main(String[] args)
{
Teachert = new Teacher("Name",22);
Teacherm = (Teacher)t.clone();
System.out.println(m);

Studentstu = new Student();
StudentdeepcloneSTU = (Student)stu.clone();
stu.courses.put(newInteger(1),"Math");
deepcloneSTU.courses.put(newInteger(100),"Java");
disp(stu.courses);
disp(deepcloneSTU.courses);

}

staticvoid disp(HashMap h)
{
SetkeySet = h.keySet();
Iteratorit = keySet.iterator();
while(it.hasNext())
{
System.out.println(h.get(it.next()));
}
}
}

class Teacherimplements Cloneable{
Stringname;
intage;
Teacher(Stringname,int age)
{
this.name= name;
this.age= age;
}

publicObject clone()
{
try{
returnsuper.clone();
}catch(CloneNotSupportedException e)
{
thrownew Error("This should never happen!");
}
}

publicString toString()
{
returnname + " " + age;
}
}

class Studentimplements Cloneable{
HashMapcourses = new HashMap();
Student(){}

publicObject clone()
{
try{
Studentstu = (Student)super.clone();
stu.courses= (HashMap)courses.clone();
returnstu;
}catch(CloneNotSupportedException e)
{
thrownew Error("This should never happen!");
}
}
}



如何通过对象串行化的方式来做进行拷贝工作呢?

import java.util.*;
importjava.io.*;
public classTestClone{
publicstatic void main(String[] args) throws Exception
{
BMWmycar = new BMW();
ByteArrayOutputStreammemoryOutputStream = new ByteArrayOutputStream();
ObjectOutputStreamserializer = new ObjectOutputStream(memoryOutputStream);
serializer.writeObject(mycar);
serializer.flush();

ByteArrayInputStreammemoryInputStream = new ByteArrayInputStream(memoryOutputStream.toByteArray());
ObjectInputStreamdeserializer = new ObjectInputStream(memoryInputStream);
BMWmycopycar = (BMW)deserializer.readObject();

mycar.add(newString("NB"));
mycopycar.add(newString("NBNB"));
System.out.println(mycar.hashCode());
System.out.println(mycopycar.hashCode());
System.out.println(mycar.equals(mycopycar));
}
}

classBMW implements Serializable
{
privateint wheels;
privateString model;
privateArrayList forTest;

BMW()
{
wheels= 4;
model= "530i";
forTest= new ArrayList();
}

publicvoid add(Object o)
{
forTest.add(o);
}

publicString toString()
{
return"WHEEL:" + wheels + "MODEL:" + model + forTest.toString();
}

publicint hashCode()
{
returnwheels + model.hashCode() + forTest.hashCode();
}

publicboolean equals(Object o)
{
if(o== this)
returntrue;
if(o== null)
returnfalse;
if(!(oinstanceof BMW))
returnfalse;
BMWbmw = (BMW)o;
if(bmw.wheels== wheels&&bmw.model.equals(model)&&bmw.forTest.equals(forTest))
returntrue;
elsereturn false;
}
}


记住,如果覆盖了equals方法,应该也覆盖hashCode(),因为如果两个对象相等也就是equals()返回true,那么这两个对象应该有相同的hashCode

http://www.blogjava.net/lhulcn618/archive/2006/01/08/27157.html

今天看了一上午关于clone()cloneable interface 的文章,我推荐一篇供大家参考学习。

蓝色为我的翻译,有不当之处,大家多多包涵!

clone()and the Cloneable Interface in Java

......The clone( ) methodgenerates a duplicate copy of the object on which it is called. Only classesthat implement theCloneable interface can be cloned.

...clone()产生了一个调用它的对象的复制;只有实现了Cloneable接口的类才可以被复制。

The Cloneable interfacedefines no members. It is used to indicate that a class allows a bitwise copyof an object (that is, a clone ) to be made. If you try to call clone( ) ona class that does not implement Cloneable , aCloneNotSupportedExceptionis thrown. When a clone is made, the constructor for the objectbeing cloned is notcalled. A clone is simply an exact copy of the original.

Cloneable 接口没有定义任何成员。它用来指明一个类可以逐位复制一个对象。如果你试图对一个没有实现cloneable接口的类调用clone()方法,一个CloneNotSupportedException就会抛出。在复制时,被复制的对象的构造器并没有被调用。复制对象就是原来对象的拷贝。

Cloning is a potentially dangerousaction, because it can cause unintended side effects. For example, if theobject being cloned contains a reference variable called obRef, thenwhen the clone is made, obRef in the clone will refer to the sameobject as does obRef in the original. If the clone makes a change to thecontents of the object referred to by obRef, then it will be changed for theoriginal object, too. Here is another example. If an object opens an I/O streamand is then cloned, two objects will be capable of operating on the samestream. Further, if one of these objects closes the stream, the other objectmight still attempt to write to it, causing an error.

复制是一种存在潜在危险的行为,因为它会引起一些意想不到的负作用。例如,如果被复制的对象包含一个名为 obRef 引用变量,在复制时,复制对象的 obRef原来对象的 obRef 都会指向同一个对象。如果复制对象对 obRef 指向的对象的内容做出一些改变,对于原来对象来说,也就相当于它也被改变了。还有另一个例子,如果一个操作I/O流的对象被复制了,这两个对象都能对同一I/O流进行操作。进一步说,如果它们两个中的一个关闭了I/O流,而另一个对象可能试图对I/O流进行写操作,这就会引起错误。

Because cloning can cause problems, clone( ) isdeclared as protected inside Object . This means that it musteither be called from within a method defined by the class that implements Cloneable, or it must be explicitly overridden by that class so that it is public. Let'slook at an example of each approach.

因为复制可以引起许多问题,clone()object类中被声明为protected.这意味着,它要么在一个实现了cloneable接口的类中的某一方法里被调用,要么在明确的在那个类中的被重写,且被声明为public的。下面,我们来看一下每一种方法。

The following program implements Cloneable anddefines the method cloneTest( ) , which calls clone( ) inObject :

// Demonstrate the clone() method.
class TestClone implements Cloneable {
int a;
double b;
// This method calls Object's clone().
TestClone cloneTest() {
try {
// call clone in Object.
return (TestClone) super.clone();
} catch(CloneNotSupportedException e) {
System.out.println("Cloning not allowed.");
return this;
}
}
}

class CloneDemo {
public static void main(String args[]) {
TestClone x1 = new TestClone();
TestClone x2;
x1.a = 10;
x1.b = 20.98;
x2 = x1.cloneTest(); // clone x1
System.out.println("x1: " + x1.a + " " + x1.b);
System.out.println("x2: " + x2.a + " " + x2.b);
}
}

Here, the method cloneTest( )calls clone( ) in Object andreturns the result. Notice that the object returned by clone( )mustbe cast into its appropriate type (TestClone ). The following exampleoverrides clone( ) so that it can be called from code outside of itsclass. To do this, its access specifier must be public , asshown here:

// Override the clone() method.
class TestClone implements Cloneable {
int a;
double b;
// clone() is now overridden and is public.
public Object clone() {
try {
// call clone in Object.
return super.clone();
} catch(CloneNotSupportedException e) {
System.out.println("Cloning not allowed.");
return this;
}
}
}

class CloneDemo2 {
public static void main(String args[]) {
TestClone x1 = new TestClone();
TestClone x2;
x1.a = 10;
x1.b = 20.98;
// here, clone() is called directly.
x2 = (TestClone) x1.clone();
System.out.println("x1: " + x1.a + " " + x1.b);
System.out.println("x2: " + x2.a + " " + x2.b);
}
}

The side effects caused by cloning aresometimes difficult to see at first. It is easy to think that a class is safefor cloning when it actually is not. In general, you should not implement Cloneable for anyclass without good reason.

以上两个程序,运行一下,便于理解。

以下在补充两点:

1 It is what is known as a 'marker'interface . A marker interface has no methodsor fields and serves only to identify the semantics of thatinterface. Another example is Serializable .

2object.clone()可能产生a shallow copy()也可能产生a deep copy.

 

 

 

C#浅拷贝和深拷贝

浅拷贝就比如像引用类型,而深拷贝就比如值类型。

浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。举个例子,一个人一开始叫张三,后来改名叫李四了,可是还是同一个人,不管是张三缺胳膊少腿还是李四缺胳膊少腿,都是这个人倒霉。

深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。比较典型的就是Value(值)对象,如预定义类型Int32Double,以及结构(struct),枚举(Enum)等。



C#中有两种类型变量,一种是值类型变量,一种是引用类型变量。对于前者,copy是属于全盘复制;而对于后者,一般的copy只是浅copy,相当于只传递一个引用指针一样。因此对于后者进行真正copy的时候,也是最费事的,具体的说,必须为其实现ICloneable接口中提供的Clone方法。

浅拷贝(影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用.
深拷贝(深度克隆):不紧复制对象的基本类,同时也复制原对象中的对象.就是说完全是新对象产生的.

拷贝和深拷贝之间的区别:浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。如果改变目标对象中引用型字段的值他将反映在原是对象中,也就是说原始对象中对应的字段也会发生变化。深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一个新的和原是对象中对应字段相同(内容相同)的字段,也就是说这个引用和原是对象的引用是不同的,我们在改变新对象中的这个字段的时候是不会影响到原始对象中对应字段的内容。所以对于原型模式也有不同的两种处理方法:对象的浅拷贝和深拷贝。

MemberwiseClone
方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。深拷贝,即实现ICloneable接口.ICloneable可用于深拷贝和浅拷贝。
这些都是概念,但是需要我们理解,下面介绍实例:

using System;
using System.Collections.Generic;
using System.Text;

namespace WindowsApplication1
{
publicclass ClassA:ICloneable
{
publicint Value = 0;
publicobject Clone()
{
returnthis.MemberwiseClone();
}
}

publicclass ClassB:ICloneable
{
public ClassA Member= newClassA();
publicobject Clone()
{
//浅复制
//return this.MemberwiseClone();

//深复制
ClassBobj= new ClassB();
//obj.Member=(ClassA)Member.Clone();
return obj;
}
}

publicclass class4
{
publicstaticvoid Main()
{
ClassB sb = new ClassB();
sb.Member.Value = 15;

ClassB nb = (ClassB)sb.Clone();
nb.Member.Value = 6;
Console.Write(sb.Member.Value.ToString()+"---"+ nb.Member.Value.ToString());
ClassA nnb =new ClassA();
nnb.Value =111;
ClassA bbn =new ClassA();
bbn = (ClassA)nnb.Clone();
bbn.Value =222;
Console.Write(bbn.Value);
Console.Read();

}
}
}

其中.MemberwiseClone()在上面已经介绍过了,通过实例可以清楚看到浅拷贝与深拷贝的区别
再来个:

using System;
using System.Collections.Generic;
using System.Text;

namespace WindowsApplication1
{
class Program
{
publicclass Sex
{
privatestring _PSex;
publicstring PSex
{
set
{
_PSex = value;
}
get
{
return _PSex;
}
}

}

publicclass Person : ICloneable
{

private Sex _pSex = new Sex();
publicint aa = 1213;

publicstring pSex
{
set
{
_pSex.PSex = value;
}
get
{
return _pSex.PSex;
}
}
privatestring _PName;
publicstring PName
{
set
{
this._PName = value;
}
get
{
returnthis._PName;
}
}

publicvoid ShowPersonInfo()
{
Console.WriteLine("-------------------------");
Console.WriteLine("Name:{0}Sex:{1}", _PName, this.pSex);
Console.WriteLine("-------------------------");
Console.WriteLine(this.aa);
}
//浅拷贝
publicobject Clone()
{
returnthis.MemberwiseClone();
}
//深拷贝
publicobject DeepClone()
{
Person newP = new Person();
newP.PName = this._PName;
newP.pSex = this.pSex;
return newP;
}

}

staticvoid Main(string[] args)
{
Console.WriteLine("原对象:");
Person p = new Person();
p.PName = "JackLee";
p.pSex = "";

p.ShowPersonInfo();


//浅拷贝
Personcopy = (Person)p.Clone();
//深拷贝
Persondcopy = (Person)p.DeepClone();


Console.WriteLine("修改后的原对象:");
p.PName = "JackZhao";
p.pSex = "";
p.aa = 1111;
p.ShowPersonInfo();


Console.WriteLine("修改后的浅拷贝对象:");
copy.ShowPersonInfo();
Console.WriteLine("修改后的深拷贝对象:");
dcopy.ShowPersonInfo();

Console.WriteLine("直接拷贝对象:");
Person PP = p;
PP.ShowPersonInfo();

Console.ReadLine();


}

}
}



接下来介绍一下数组的拷贝:
首先数组的直接拷贝也就是复制,不用多说看例子:

int [] numbers ={ 2, 3,4, 5};

int [] numbersCopy = numbers;

numbersCopy[2] =0;

Console.Write(numbers[2]);

Console.Write(numbersCopy[2]);

结果就是

0

0

道理很简单,数组的复制也就是引用传递,指向的是同一个地址,这不是我们介绍的重点

接下来

看一下概念

数组是引用类型,所以将一个数组变量赋予另一个数组变量,就会得到两个指向同一数组的变量。而复制数组,会使数组实现ICloneable接口。这个接口定义的Clone()方法会创建数组的浅副本。

如果数组的元素是值类型,就会复制所有的值,如果数组包含引用类型,则不复制元素,而只复制引用,

除了使用Clone()方法之外,还可以使用Array.Copy()方法创建浅副本。但Clone()方法和Copy()方法有一个重要区别:Clone()方法会创建一个新数组,而Copy()方法只是传送了阶数相同、有足够元素空间的已有数组。

提示:

如果需要包含引用类型的数组的深副本,就必须迭代数组,创建新对象。

看一下例子:

using System;
using System.Collections.Generic;
using System.Text;

namespace WindowsApplication1
{
class Class2
{
publicstaticvoid Main()
{
int[] numbers ={ 2, 3,4, 5 };

int[] numbersCopy =newint[5];

numbers.CopyTo(numbersCopy,0);

numbersCopy[2] =0;

int[] numbers1 ={ 2, 3,4, 5 };

int[] numbersClone1 = (int[])numbers1.Clone();

numbersClone1[2] =0;

Console.Write(numbers[2] +"---"+ numbersCopy[2]);

Console.Read();


}
}
}

我这里介绍的主要是数组的clonecopyto的用法,两者都不会改变其中的值,与上面我们的复制有很大的区别

因为数组里面是值类型,所以他们不存在引用,而

class class1
{
publicstring aa ="aa";
}


class class4
{
publicstaticvoid Main()
{

class1 aaa =new class1();
class1[] array3 =new class1[1];
class1[] array4 =new class1[1];
array3[0] = aaa;
//Array.Copy(array3,0, array4, 0,1);
array3.CopyTo(array4,0);
//array4=(class1[])array3.Clone();
array3[0].aa="bb";

Console.Write(array4[0].aa);
Console.Read();
}
}

结果是两者都改变了值,最后输出都是bb,那时因为两者都是浅拷贝,区别上面已经介绍了,所以

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值