c++第八章多态性

目录

一、多态性描述

(一)多态的类型

(二)多态的实现

(三)运算符重载

1、运算符重载的规则

2、运算符重载为函数成员

3、运算符重载为非成员函数

三、虚函数

(一)一般的虚函数

(二)虚析构函数

四、纯虚函数与抽象类

(一)纯虚函数

(二)抽象类

五、深度探讨

(一)多态类型和非多态类型

(二)运算时类型转换

 1、使用static_cast实现基类指针显性转换为派生类指针

2、用dynamic_cast执行基类向派生类的转换

3、用typeid获取运行时的关键字

(三)虚函数动态绑定的实现原理


一、多态性描述

*      同样的消息被不同类型的对象接收时,导致的不同的行为,消息指的是对类的成员函数的调用,不同的行为指的是不同的实现,也就是调用了不同的函数
 

(一)多态的类型

*                  面向对象的多态性可以分为四类:重载多态(专用多态)、强制多态(专用多态)、包含多态(通用多态)和参数多态(通用多态)
*                   普通函数和类的成员函数的重载都属于重载多态
*                   强制多态:将一个变元的类型加以改变,以符合一个函数或者操作之间的要求
*                   包含多态:类族中定义不同类中的同名函数的多态行为,主要是通过虚函数来实现,
*                   参数多态和模板类相关联,在使用时必须赋予实际的类型才可以

(二)多态的实现

*                    多态从实现的角度来讲划分为两种,编译时的多态和运行时的多态,前者是在编译过程中确定了同名操作得到具体操作对象,而后者是在程序运行过程中才动态的确定操作所针对的对象
*                    这种确定操作的具体对象的过程就是绑定,绑定指的是计算机程序自身彼此关联的过程,把标识符和存储地址联系在一起的过程,也就是把一条消息和一个对象的方法相结合的过程,按照绑定及逆行的阶段不同分为静态绑定和动态绑定
*                    绑定工作在编译连接阶段完成的称为静态绑定
*                    程序执行之前完成绑定,确定某个标识符到底要调用那一段程序代码
*                    绑定工作在程序运行阶段完成的称为动态绑定
*                    在编译连接过程中无法解决的绑定问题,等程序运行后再来确定
*                    包含多态操作对象的确定就是通过动态绑定完成的

(三)运算符重载

*                     运算符重载就是对已有的运算符赋予多重含义,使同一个运算符对于不同类型的数据具有不同的行为
*                     实质就是函数重载,首先将指定的运算表达式转换为对运算符函数的调用,将运算对象转换为运算符函数的实参,再根据参数的类型来确定需要调用哪一个函数
 

1、运算符重载的规则

*                      (1)C++运算符处理少数的几个之外,全部都可以重载,而且只能重载c++中已经有的运算符
*                      (2)重载之后运算符的优先级和结合性质都不会发生改变
*                      (3)运算符重载是针对新类型数据的实际需求,对原有运算符进行适当的改造
*                           一般来说,重载功能应该与原有功能相似,不能改变原有运算符的操作对象的个数,同时至少有一个操作对象是自定义类型
*                        运算符的重载有两种,重载为类的非静态成员和重载为非成员函数,
*                             返回类型  operator   运算符(形参表)
*                             {          函数体           }
*                         返回值类型:指定了重载运算符的返回值类型,运算结果的类型
*                         operator:是定义运算符重载函数的关键字
*                         形参表:给出重载运算符需要的参数和类型
*                          重载为非成员函数时,有时候计算涉及到类的私有成员,这时候可以将运算符重载函数设置为友元函数
*                          当运算符重载为类的函数成员时,函数的参数个数比原有的操作数个数要少一个,当重载为非成员函数时第一个操作数会被调用成函数调用的目的对象,因此无需出现再参数表中,函数体可以直接访问第一个操作数的成员
*                          当重载不是类的函数成员时,所有操作数必须显性的通过参数传递
*                          运算符重载的优点就是可以改变现有的运算符的操作方式

2、运算符重载为函数成员

*                         可以自由的访问本类的数据,总是通过类的对象来访问重载的运算符,
*                         如果是双目运算符,左操作数是对象本身的数据(this指针所指的数据),有操作数通过运算符重载函数,如果是单目运算符,操作数由对象的this指针指出
*                         对于双目运算符B,如果要重载为类的函数成员,使之能够实现表达式  op1  B  op2,其中op1作为A类的对象,应该把B重载成A的成员函数,该函数只有一个形参就是op2类型的参数
*                         对于单目运算符U,如果要重载为类的成员函数,U op,op为A的对象,运算符U应该重载为A的成员函数,没有形参
*                         前置单目运算符重载正常写,后置运算符重载加一个参数类型(int),与前置运算符函数相区别
 

//运算符重载为函数成员
//双目运算符重载
class jisuan
{
public:
	jisuan(double a=1.0, double b=1.0) :x(a), y(b) {}
	jisuan operator +( jisuan c2);//重载声明
	jisuan operator -( jisuan c2);
	void show();
private:
	double x;
	double y;
};
jisuan jisuan:: operator +( jisuan c2)
{
	return jisuan((x + c2.x), (y + c2.y));//创建一个临时无名对象作为返回值
}//+重载定义
jisuan jisuan:: operator -( jisuan c2)
{
	return jisuan ((x - c2.x), (y - c2.y));//创建一个临时无名对象作为返回值
}//-重载定义

void jisuan::show()
{
	cout << x << y;
}
int main()
{
	jisuan c1(1.0, 2.0), c2(2.0, 3.0);
	jisuan c3;
	c3=c1 + c2;
	c3.show();
	return 0;
}
//单目运算符重载
//对于this指针所指向的对象进行操作
class clock1
{
public:
	//clock1() { hour = 1; minute = 1; second = 1; }
	clock1(int a=1 , int b=1 , int c=1 );
	clock1& operator++();//引用B
	clock1 operator++(int);
	//clock1 operator ++();
	void show();
private:
	int hour;
	int minute;
	int second;
};
clock1::clock1(int a , int b, int c ) 
{

	if ( a <= 12 && b <= 60 &&  c <= 60&&a>=0&&b>=0&&c>=0)
	{
		hour = a;
		minute = b;
		second = c;
	}
	else
		cout << "时间输错了" << endl;
}
//通过引用直接改变对象本身的值,最后通过this指针进行返回
clock1& clock1::operator++()
{
	second++;
	if (second = 60)
	{
		second -= 60;
		minute++;
		if (minute = 60)
		{
			minute -= 60;
			hour++;
			if (hour = 12)
			{
				hour -= 12;
			}
		}

	}
	return *this;
}
clock1 clock1::operator ++(int)
{
	clock1 old = *this;
	++(*this);
	return old;
}
/*
//用一个新创建的对象去接收改变后的值,但是之前的对象本身并没有发生改变
clock1 clock1 ::operator++()
{
	
	int q = second;
	int p = minute;
	int h = hour;
	q++;
	if (q >= 60)
	{
		q -=60;
		p++;
		if (p >= 60)
		{
			p -=60;
			h++;
			if(h>=12)
			{
				h -= 12;
			}
		}
		
	}
	return clock1(h, p, q);
}
*/
void clock1::show()
{
	cout << hour << "," << minute << "," << second << endl;
}
int main()
{
	clock1 A;
	clock1 B(11,59,59);
	clock1 C(13, 89, 89);
	C.show();
	A.show();
	B.show();
	(++B).show();//++1先自增再赋值
	(A++).show();//i++先赋值再自增
	return 0;
}

3、运算符重载为非成员函数

*                         运算时所需要的操作数都是通过函数的参数列表来实现的,在形参列表中,从左到右的顺序就是运算符操作的顺序,如果需要访问运算符参数对象的私有成员,可以将函数声明为函数的友元类(前面加friend关键字)
*                         对于双目运算符,要实现 p1 B p2,  p1\p2中只要有一个具有自定义类型,就可以将B重载为非成员函数,函数形参为p1,p2
*                         operatoe B(p1,p2)//相当于函数调用
*                         对于单目运算符
*                         operator B(p)
*                         前置运算符,其中p为自定义的类型,运算符就可以重载为非成员函数运算符,operator B(p)
*                         后置运算符,形参有两个,一个是p,一个是int类型参数,目的是为了区别前置运算符和后置运算符
 

//以非成员函数形式重载加减法
class jisuan
{
public:
	jisuan(double a = 1.0, double b = 1.0) :x(a), y(b) {}
	friend jisuan operator +(jisuan &c1,jisuan &c2);//重载声明
	friend jisuan operator -(jisuan &c1,jisuan& c2);
	void show();
private:
	double x;
	double y;
};
jisuan operator +(jisuan &c1,jisuan &c2)
{
	return jisuan((c1.x + c2.x), (c1.y + c2.y));//创建一个临时无名对象作为返回值
}//+重载定义
jisuan operator -(jisuan &c1,jisuan & c2)
{
	return jisuan((c1.x - c2.x), (c1.y - c2.y));//创建一个临时无名对象作为返回值
}//-重载定义

void jisuan::show()
{
	cout << x << y;
}
int main()
{
	jisuan c1(1.0, 2.0), c2(2.0, 3.0);
	jisuan c3;
	c3 = c1 + c2;
	c3.show();
	return 0;
}

三、虚函数

*                     虚函数是动态绑定的基础,虚函数必须是非静态函数的成员,虚函数经过派生之后,在类族中就可以实现运行过程中的多态
*                      可以用派生类的对象来代替基类对象,如果用基类类型的指针指向派生类对象,就可以通过这个指针来访问该对象,访问到的只是从基类继承来的同名对象
*                      如果需要通过基类的指针指向派生类的对象,并访问到某一个与基类同名的对象,那么首先就要在基类中将这个同名函数说明为虚函数,这样通过基类的指针,就可以是属于不同派生类的对象产生不同的行为
*                      从而实现运行过程的多态

(一)一般的虚函数

*                           virtual 函数类型 函数名(形参表);
*                            使用关键字virtual来限制成员函数
*                            虚基类一般语法形式:
*                            class 派生类名::virtual  继承方式  基类名
*                           虚函数声明只能出现在类定义的函数原型的声明中,而不能在成员函数实现时
*                           运行过程中的多态需要满足3个条件
*                           1、类之间满足赋值兼容规则
*                           2、声明虚函数
*                           3、由成员函数来调用或者通过指针、引用来访问虚函数,如果是使用对象名来访问虚函数,则绑定在编译过程中就可以进行(静态绑定)而不是在运行过程中进行
*                            虚函数一般不声明为内联函数,因为对于虚函数的调用需要动态绑定,而对于内联函数的处理是静态的,所以虚函数一般 不能以内联函数来处理,但是将函数声明为内联函数也不会引起错误
*                           当派生类没有显性的给出修函数声明,会根据以下条件自行判断派生类函数成员是不是虚函数
*                            1、该函数是否与基类的虚函数具有相同的名称
*                            2、是否与基类的虚函数有相同的参数个数以及相同的对应参数类型
*                            3、该函数是否有与基类的虚函数相同的返回值或者满足赋值兼容规则的指针、引用型返回值
*                             当基类构造函数调用虚函数时,不会调用派生类的虚函数
*                             只有虚函数是动态绑定,如果派生类需要修改基类的行为,就应该在基类中将相应的函数声明为虚函数
*                             虽然虚函数是动态绑定,但是默认形参值是静态绑定,通过一个派生类对象的指针,可以访问基类的虚函数,但是默认形参值却只能来自基类的定义
 

(二)虚析构函数

*                            virtual~类名();
*                            如果一个类的析构函数是虚函数,那么由他派生而来的所有子类的析构函数也都是虚函数,析构函数设置为虚函数之后,在指针引用时可以动态绑定
*                             实现运行的多态,保证基类类型指针就能够调用适当的析构函数针对不同的对象进行清理工作
*                            如果有可能通过基类指针调用对象的构造函数,就需要让基类的析构函数称为虚函数,否则
*                            如果不设置为虚函数,进行调用时,只能调用基类的析构函数,设置为虚函数则都能调用
 

//虚函数
class A
{
public:
	virtual void show();//没有定义虚函数,A类型的指针只能访问A类的函数
	                    //定义虚函数之后,通过把A类型的指针指向派生类的对象来访问派生类的函数
};
void A::show()
{
	cout << "是A" << endl;
}
class B:public A
{
public:
	 void show();//自动判断是不是虚函数
};
void B :: show()
{
	cout << "是B" << endl;
}
class C :public B
{
public:
	void show();
};
void fun(A* p)
{
	p->show();
}
void C :: show()
{
	cout << "是C" << endl;
}
int main()
{
	A a1;
	B b1;
	C c1;
	//fun(&a1);
	fun(&b1);
	fun(&c1);
	return 0;
}

四、纯虚函数与抽象类

*              抽象类是一种特殊的类,它为一个类族提供了统一的操作页面,抽象类是为了抽象和设计的目的而建立的,建立抽象类就是为了通过它多态的使用其中的成员函数
*              抽象类处于类层次的上层,一个抽象类自身无法实例化,我们没有办法定义一个抽象类的对象,只能通过继承机制,生成抽象类的非抽象派生类,然后再实例化
*              抽象类是带有纯虚函数的类

(一)纯虚函数

*                        纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作功能,要求各派生类根据实际需要给出各自的定义
*                        纯虚函数的声明:
*                             virtual 函数类型  函数名(参数列表)=0;
*                        和一般虚函数的声明方式在于最后=0,声明为虚函数之后,基类中就可以不用给出函数的实现部分,纯虚函数的定义由派生类给出
*                        基类仍然允许给出纯虚函数的实体,但是必须由派生类覆盖,否则无法实体化
*                        如果将析构函数声明为纯虚函数,必须给出它的函数体,因为派生类的析构函数执行完后要调用基类的纯虚函数
*                        纯虚函数不同于函数体为空的虚函数:
*                        纯虚函数没有函数体,所在的类是抽象体不能实体化,函数体为空的虚函数,所在类可以实例化
*                        共同特点:都可以派生出新的类,在新的类中给出虚函数新的实现,而且这种新的实现是可以具有多态特征的、
 

(二)抽象类

*                        带有纯虚函数的类是抽象类,抽象类的主要作用是通过它为一个类群建立一个公共接口,使他们能够发挥多态
*                        抽象类声明的是一个类族派生类的共同接口,而接口的完整实现,即虚函数的函数体,要由派生类自己定义
*                        抽象类不能实例化,但是抽象类可以定义一个抽象类的指针或者引用,用过指针和引用,就可以指向并访问派生类的对象
*                        进而访问派生类的成员

class A
{
public:
	virtual void show() = 0;//纯虚函数,在基类中声明,在派生类定义,如果在基类声明,派生类必须覆盖
};
class B:public A 
{
public:
	void show()//因为他们与虚函数有相同的名称、参数列表和返回值,所以不用显性的声明为虚函数,当它的派生类调用时,自动试别为虚函数
	{
		cout << "是B" << endl;
	}
};
class C :public B
{
public:
	void show()
	{
		cout << "是C" << endl;
	}
};
void fun(A& a)//用基类指针指派生类对象
{
	a.show();
}
int main()
{
	B b;
	C c;
	fun(b);
	fun(c);
}

五、深度探讨

(一)多态类型和非多态类型

*                        C++的类分为两类,多态类型和非多态类型,多态类型指的是有虚函数的类型,非多态类指的是其他所有类型
*                        区别:
*                        1、二者具有不一样的语言特性
*                        2、由于设计过程中,有关这两种类型的原则和理念有所不同
*                        多态类型:基类的指针可以指向派生类的对象,如果基类是多态类型,那么可以通过指针调用基类的虚函数,实际操作是由派生类决定的,派生类只是继承了基类的接口,不必继承基类中虚函数的实现
*                        设计多态类型的重要原则,把多态类型的析构函数设置为虚函数
*                        因为使用delete时会调用对象所属类的析构函数,如果不设置为虚函数,会发生不确定的行为
*                        非多态类型:对于非多态类型,将他的指针指向派生类的对象,但是通过指针的操作,只能是基类本身的操作,派生类不仅继承了基类的接口,还继承了基类的实现
*                        并且由于基类不具有虚析构函数,对他进行派生时,删除派生类的堆对象,只能通过派生类的指针,通过基类指针删除,会调用基类的构造函数引起不确定性问题
*                        删除一个对象,会因为指向它的指针类型不同而不同,因此对于非多态的公有继承应该慎重,而没有太大的必要
*                        非多态类型的行为完全是静态的不如多态类型灵活,但是,由于每一个成员函数的实现都是静态绑定,函数的指向效率更高,并且不需要额外的空间保存实现动态绑定的信息
*                        如果一个函数的实现非常明确,不需要任何特殊处理,不希望派生类提供特殊实现,就应该把他声明为非虚函数
*                        如果一个类的所有函数都具有这个特点就将他称为非多态类型的类
*                        将一个类设计为非多态类型,一般不希望对他进行公有继承,如果需要其他类对他进行公有继承,应该将他设计为多态类型,至少为它声明一个虚析构函数
 

(二)运算时类型转换

* 、                   基类指针可以指向派生类对象,通过虚函数实现对于派生类中从基类继承下来的函数的调用,但是对于在派生类中新定义的成员函数,无法进行调用
*                      解决办法:

 1、使用static_cast实现基类指针显性转换为派生类指针

*                        A*p=static_cast<B*>(p); 
*                        不太安全的转换,只能在指针指向的对象类型明确的情况下执行,若对象类型与转换的目的类型不兼容(对象类型不是转换的目的类型及其派生类),程序不会发生不确定的行为

2、用dynamic_cast执行基类向派生类的转换

*                        将基类的指针显性的准换为派生类的指针
*                        它和static_cast不同的地方在于,它的转换不是无条件的转换,在准换之前,它会检查转换的指针所指的对象是否与转换的目的类型兼容,如果类型兼容才能发生,才能得到派生类指针
*                        否则:a、如果执行的是指针类型的转换,就会得到空指针
*                              b、如果是引用类型的准换,就会抛出异常
*                       另外,类型转换前必须是多态类型的指针,或引用,而不能是指向非多态类型的指针或引用,因俄日c++只为多态运行时保存用于类型识别 的信息,这从另一个方面说明,非多态类型为什么不宜被公有继承
*                       原始类型为多态类型的指针,目的类型除了是派生类的指针之外,还可以是void指针
*                         dynamic_cast<void*>(p)
*                        将p指针转换为它所指对象的实际类型指针,再将指针转换为void指针,就是得到p所指向对象的首地址
*                        (在多继承存在的情况下,基类指针存储的地址未必是对象的首地址)
*                        多继承时,父类指针指向子类对象时,父类指向的不是子类开始地址,而是子类中对应父类的结构的类对象的基地址,所以,当多个父类指向同一个子类时,父类的指针值其实是不一样的。
*                        基类的对象和基类的指针都不能转换为派生类的指针和对象
*                        基类到派生类的转换: 
                         基类对象到派生类对象之间的转换是不存在的,编译器时错误
                         因为基类对象只能是基类对象,它不包含也不能包含派生类的成员,如果允许基类到派生类转换,那么就会试图使用派生类对象访问不存在的成员。 
                         基类对象的引用或指针到派生类对象的引用或指针也是不存在的,不被允许的,编译时错误,原因同上

//dynamic_cast用法实例

class A
{
public:
	virtual void show()
	{
		cout << "是A" << endl;
	}
	virtual~ A() {};
};
class B :public A
{
public:
	virtual void show()
	{
		cout << "是B" << endl;
	}
	

};
class C :public B
{
public:
	void show()
	{
		cout << "是C" << endl;
	}
};
void fun(A * a)
{
	a->show();
	B* b = dynamic_cast<B*>(a);
	if (b != 0)
	{
		b->show();//是B
	}
	else if (b == 0)
	{
		cout << "空" << endl;
	}

}
int main()
{
	
	A a;
	fun(&a);//基类的指针不能转换为派生类的指针,基类的对象也不能转换为派生类的对象
	B b;
	fun(&b);//本身是派生类,传参时变成了基类指针,可以转回
	C c;
	fun(&c);//本身是派生类,传参时变成了间接基类的指针,可以转换为基类指针,相当于,间接基类指针转换为派生类的指针
}

3、用typeid获取运行时的关键字

*                        typied是c++的一个关键字,用它可以获得一个类型的星官信息,它有两种表达形式
*                        typied(表达式)
*                        typied(类型说明符)
*                        typied(5+3);//typied作用于一个表达式,从而获取这个表达式的信息
                         typied(int);//typied作用于一个类型说明符
*                        const char*name()const;/用来获取类型的名称

//typied的使用
class A
{
public:
	virtual~A() {}
 };
class B :public A
{
};
void fun(A* a)
{
	const type_info& name1 = typeid(a);
	cout << name1.name() << endl;
	const type_info& name2 = typeid(a);
	//cout << name2.name() << endl;
}
int main()
{
	A a1;
	//fun(&a1);
	B b;
	fun(&b);

	return 0;
}

(三)虚函数动态绑定的实现原理

*                        通过指针和引用调用一个虚函数,实际被调用的函数到运行时才能确定
*                        动态绑定的关键时在运行时决定被调用的函数
*                        一个函数指针可以被赋予不同的函数的入口地址,如果通过函数指针去调用函数,实际被调用的函数一般到运行时才能确定,因此通过函数指针去调用函数也是一种动态绑定,一种由源程序进行显示控制的动态绑定,而虚函数的绑定,一些控制细节被隐藏起来了由编译器自动处理了
*                        一种最直接的处理方式,在每个对象中,除了存储数据成员之外,还有为每一个虚函数设置一个函数指针,分别存放这些虚函数对用的代码的入口,由于派生类也要继承这些虚函数的接口,因此保留这些指针,而派生类引入新的数据成员和函数,置于从基类继承下来的数据成员和函数函数指针之后
*                        在各个类的构造函数中,为每一个函数指针初始化,使得基类对象中的函数指针指向为基类定义的函数,派生类对象中的函数指针指向派生类覆盖的函数,通过指针和引用进行函数的调用,先读取相应的函数指针,再 通过函数指针调用相应的函数
*                        虚表:
*                        但是这种调用有一个弊端,占用的额外空间太大,实际上都是在对象中保存大量的重复信息,类中有一个成员函数就要占用一个指针的大小、
*                        同一个类的不同对象保存的保存的函数指针都是一样的,,分别指向成员函数的入口,因此这些指针可以只保存一份,构成一个表,称为虚表(virtual table),每一个对象不再保存函数指针,而是只保存一个指向函数虚表的指针(虚表指针vptr),这样每一个多态的对象只需要占用一个指针的额外空间,虽然虚表本身还要占用空间,但是每一个多态类型只有一个虚表,不会因为对象的增多而有所增加
*                        每一个类对应一个虚表,虚表的内容时由编译器安排的,派生类的虚表中,基类声明的虚函数对应的指针放在最前面,派生类新增的虚函数的指针放在后面,这样虚函数的指针在基类虚表和派生类虚表中具有相同的位置
*                        多态类型的对象中都有一个指向当前类型的虚表指针,该指针在构造函数中赋值,当通过基类的指针或引用调用虚表条目该条目中存放的指针读出后,就可以获得相应的被调用的函数的入口地址,然后是哦你好该虚函数,虚函数的动态绑定就这样完成了;
*                        当指向一个类的构造函数的时候,首先执行基类的构造函数,因此构造一个派生类对象,该对象的虚表指针会首先指向基类的虚表,,只有基类的构造函数执行完,虚表指针才会指向派生类的虚表
*                        这就是基类构造函数调用虚函数时不会调用派生类的虚函数的原因
*                        构造一个类的一般顺序:
*                        1、如果该类有直接或者间接的虚基类,则先执行虚基类的构造函数
*                        2、如果该类有其他基类,按照他们在继承中声明的顺序,分别执行他们的构造函数
*                        3、按照在类的定义中出现的顺序,对派生类中的新增成员及逆行初始化,对于类类型的成员对象,如果出现在构造函数初始化列表中,,执行其构造函数,没出现执行默认构造函数
*                        对于基本类型的成员对象,如果出现在构造函数的初始化列表,则使用其中的值为其赋初值,否则什么都不做
*                        4、执行构造函数的函数体
*                        实际上,一个虚表中存放的不仅是虚函数的指针,用于支持运行时类型的识别的对象的运行时的类别信息也需通过虚表来访问
*                        只有多态类型有虚表,因此只有多态类型支持运行时类型试别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值