【C++】Essential c++第五章学习笔记

Essential c++ 第五章

这一章,主要介绍了怎么进行各个类的高级管理

1. 概述

  第四章主要讲了一个类都有什么内容,第五章主要介绍如果出现多个有关系的类,应该如果写程序,如果更加高效的管理类。面向对象的编程中,最关键的有两点,就是继承和多态。继承是将所有类共有的东西抽象出来,放到上一层,然后下一层次放个性的东西,使得各个类之间层次分明。多态是提高类活力的方法,能够将一个函数,根据各个类的需要,进行特化,通过同一个函数,却能够使得每一个类都有自己的实现方法。

2. 继承

2.1 基类和派生类

  当我们定义两个类的时候,两个类有互相包含的关系,我们就可以使用继承的方法来构造这些类。比如我们有一个类叫做人,有另外一个类叫做学生,学生是属于人的,必定含有人的所有共性,但是学生又有自己的特性,并不是所有的人都有学生的特性。这种关系就是继承的关系。
  更加共性的那个类被叫做父类也叫做基类,具有自己特性的那个类,叫做子类或者叫做派生类。子类具有父类的所有成员,并且可以定义自己的成员变量和成员函数。
  下面来写一下继承的例子。继承通过:表示继承关系

//比如我们定义了一个类叫book,所有的书都有书名和作者
#include<iostream>
using namespace std;
class book
{
public:
    string _author;
    string _name;

};

//我们定义了一种类别的书叫做audiobook,audiobook是book类的子类,通过继承符号:表示二者的关系
//有了继承关系之后,audiobook就默认包括了book类的书名和作者两个成员变量,同时又定义了自己的成员变量,讲述人。
class audiobook :public book  //这里使用public对父类进行修饰,是一种访问权限
{
public:
    string _narrator;
};

int main()
{
    //我们可以看到,虽然audiobook中并没有直接定义_name和_author,但是因为父类有,所以仍然可以使用,这是他继承来的成员
    audiobook abk;
    abk._author = "herry";
    abk._name = "king";
        abk._narrator = "ross";

    return 0;
}

2.2 类的访问权限

  其实,子类并不能够访问父类的全部成员,这要看子类对父类的访问权限是怎么样的。访问权限有public/protected/private三个级别

class book
{
public:
    string _author;
    string _name;
protected:
    string _ID;
private:
    int _a;
};

  就像上面定义的这个样子。public的类表示,我们在程序中定义了一个对象以后,可以通过这个对象直接访问类中的这个成员变量;protected表示,我们在main函数中是不能通过实例对象访问这个变量的,但是可以在类本身和他继承的类中使用这个对象;private表示,这个成员变量只有类本身可以访问,他的子类也不能访问

class audiobook:book
{
    void f()
    {
        //这句话可以的,因为继承的类可以访问父类public和protected级别的成员
        cout << _ID << endl ; 
        
        //这句话是不可以的,因为子类不能访问父类private级别的变量
        //cout <<_a << endl ;
    }
}

int main()
{
    book bk;
    //这句话是可以的,因为实例对象可以访问public级别的成员函数或者成员变量
    cout<<bk._author;
    
    //这两句话是不可以的,因为实例对象不可以访问private和protected级别的成员函数和成员变量
   // cout<<bk._ID;
    //cout<<bk._a;
}

2.3 父类和子类的关系

2.3.1 构造和析构

  下面我们通过一个简单的程序来看一下,继承的过程到底是一个怎么样的过程。我们定义这样的两个类

#include<iostream>
using namespace std;
class LibMat
{
public:
    LibMat()
    {
        cout<<"这是libmat的构造函数"<<endl;
    }
    ~LibMat()
    {
        cout<<"这是libmant的析构函数"<<endl;
    }
};
class book: public LibMat
{
public:
    book()
    {
        cout<<"这是book的构造函数"<<endl;
    }
    ~book()
    {
        cout<<"这是book的析构函数"<<endl;
    }
};

int main()
{
   { //小括号表示生存域,出了括号,book的实例对象bk就不存在了
   
            book bk;
   }
    
    return 0;
}

  运行这段代码之后,我们发现显示父类LibMat构造,然后是book构造;析构的顺序相反,book先析构,LibMat再析构。于是我们就大概理解了继承是个怎么样的过程。每次构造子类的时候,先把父类有的东西复制到子类,因此会调用父类的构造函数。然后再回产生子类自己的东西,于是子类的构造函数被调用。析构的时候,先移去子类自己的东西,再清理父类的东西。

2.3.2 初始化

  使用类一般需要对这些类进行初始化,那么当子类使用父类的成员变量的时候,如何完成初始化这个过程呢?我们可以使用父类的构造函数完成初始化过程。


#include<iostream>
using namespace std;

//定义了父类
class book
{
public:
    book(string author, string name) :_author(author), _name(name)
    {

    }
protected:
    string _author;
    string _name;

};

//定义了子类,使用父类的构造函数完成子类的构造
class audiobook :public book
{
public:
    audiobook(string author, string name, string narrator) :book(author, name), _narrator(narrator)
    {

    }

protected:
    string _narrator;
};

int main()
{
    audiobook abk("me", "mybook", "he");
    return 0;
}
2.3.3 同名函数

  这里要说明的一点是,如果父类和子类具有同名的函数,他们之间不会构成重载的关系,子类的函数会把父类的函数给掩盖掉



#include<iostream>
using namespace std;
class book
{
public:
    book(string author, string name) :_author(author), _name(name)
    {

    }
    void f(int a)
    {
        cout << "book" << endl;
    }
protected:
    string _author;
    string _name;

};


class audiobook :public book
{
public:
    audiobook(string author, string name, string narrator) :book(author, name), _narrator(narrator)
    {

    }
    void f()
    {
        cout << "audiobook" << endl;
    }
protected:
    string _narrator;
};

int main()
{
    audiobook abk("me", "mybook", "he");
    abk.f();
    //abk.f(1); 这个函数并不能够被识别。所以父类和子类的同名函数不构成重载关系,而是被隐藏了
    return 0;
}

2.4 关于继承的一个小建议

  如果说,我们把一些类的共有特征抽象成了一个基类,我们不需要创建基类的实例对象,但是我们在基类里面放置了一些重要的成员。这个时候,我们可以把基类的构造函数定义为protected的。这么做的话,用户就不能使用构造函数定义这个抽象类,同时它的子类可以通过构造函数来定义这个抽象类,往里面给重要成员变量赋值。

  比如我们把所有的书抽象为LibMat,然后发现所有的书都有作者和名字,我们可以把这两个成员函数放到最抽象的libMat类中,然后我们不希望用户定义这个类,就把他的构造函数定义为protected的,只能通过他的子类来调用构造函数并赋值。比如book类。

3. 多态

3.1 造型

  造型简单来说,就是把父类当子类用,把子类当父类用。比如把子类的指针指向父类,叫做向上造型;把父类的指针指向子类,叫做向下造型。向下造型是安全的,因为父类中所有的成员子类都有,不存在丢失东西的问题;而向上造型就是不安全的了,因为父类不能调用子类的成员。

#include<iostream>
using namespace std;
class book
{
public:
    book(string author, string name) :_author(author), _name(name)
    {

    }
    void f()
    {
        cout << "book" << endl;
    }
protected:
    string _author;
    string _name;

};


class audiobook :public book
{
public:
    audiobook(string author, string name, string narrator) :book(author, name), _narrator(narrator)
    {

    }
    void f()
    {
        cout << "audiobook" << endl;
    }
protected:
    string _narrator;
};

int main()
{
    audiobook abk("me", "mybook", "he");

//这个过程就叫做向上造型,意思就是把子类当成父类使用
        book* bk = &abk;
    bk->f();
}

3.2 静态绑定

  通过3.1的函数运行实例,我们发现,当我们用向上造型的方法,把子类当做父类使用,并且调用了一个父类和子类都有的函数,其运行结果并不是子类的函数,而是使用了父类的函数。
  为什么会这样呢?这是因为,类中的函数在程序编译的时候就以及确定好了要使用哪个了,哪个类使用哪个函数是被提前绑定好了的,所以,即使把子类的指针交给父类,仍不能够改变父类所绑定的成员函数,所以在调用函数f的时候,运行的是父类的函数。这种成员函数的运行机制,就叫做静态绑定。

3.3 虚函数与动态绑定

  既然有静态绑定,必然有一种机制叫做动态绑定。我们要先引入一个概念,叫虚函数。虚函数是对函数使用关键词virtual进行定义。虚函数的运行机制是,在一个类定义的时候,在类的最前端,产生一个指针,这个指针用来表示类中的虚函数具体是指向哪个内存空间,这个区域也叫做虚函数表。在程序编译的时候,并没有指定类中函数调用的时候要调用哪一个,而是程序运行的时候根据虚函数表的指向才确定。这是一种动态绑定的机制。基于动态绑定之后,我们再运行3.1的程序,这个时候使用向上造型的方法,把子类的指针交给父类的时候,实际上也把子类的虚函数表给复制了过来,所以程序再运行的时候,同名函数就调用的是子类的了。


#include<iostream>
using namespace std;
class book
{
public:
    book(string author, string name) :_author(author), _name(name)
    {

    }
  virtual  void f()
    {
        cout << "book" << endl;
    }
    virtual ~book(){}
protected:
    string _author;
    string _name;

};


class audiobook :public book
{
public:
    audiobook(string author, string name, string narrator) :book(author, name), _narrator(narrator)
    {

    }
   virtual  void f()
    {
        cout << "audiobook" << endl;
    }
    virtual ~audiobook(){}
protected:
    string _narrator;
};

int main()
{
    audiobook abk("me", "mybook", "he");


        book* bk = &abk;
    bk->f();
}

  所有类中的虚函数都应该具有相同的函数名和函数声明,但是具体函数定义可以不相同。每个子类中都应该具有相对应的虚函数,否则运行的时候没有办法找到对应的运行函数。

3.4 构造函数和析构函数与虚函数

  普通函数的虚函数我们已经看到了,那么构造函数和析构函数是否存在虚函数呢?
  对于构造函数而言,由于函数定义的时候必须要进行初始化,所以这个调用的函数必须在编译的时候就明确好了,因此构造函数是不允许做成虚函数的,事实上,大部分编译器也认为这是个错误的。
  而对于析构函数来说,就必须要使用虚函数了,因为有时候你使用的一些资源是来自子类的,比如申请的内存空间等等,必须要用子类的析构函数才能释放这些资源,这个时候如果析构函数不是虚函数,只能调用父类的析构函数,很可能会出现资源无法释放的问题。

3.5 多态

  知道了各种基础知识以后,我们其实也就对多态这个名词比较了解了。多态就是向上构造和动态绑定的组合。这样使得我们定义函数的时候,只要写一个基类的指针就行了,所有的派生类都可以通过这个基类的指针被送入函数,同时由于动态绑定,我们可以让那些指针去调用派生类的函数,最大程度上简化了类的设计。

3.6 静态函数与虚函数

  由于静态函数是在程序运行之前被定义在全局变量区的,虚函数是在运行之后才确定的,这两类函数有着本质的区别,因此静态函数是不能够被定义为虚函数的。

#include<iostream>
using namespace std;



class LibMat
{
public:
	LibMat()
	{
		cout << "这里是libmat的构造函数!" << endl;
	}

	virtual void print()
	{
		cout << "我是libmat" << endl;
	}
	virtual ~LibMat()
	{
		cout << "这里是libmat的析构函数!" << endl;
		cout << endl;
	}
	
private:
};

class book: public LibMat
{
public:
	book(string title, string author) :_title(title), _author(author)
	{
		cout << "这里是book的构造函数!" << endl;
	}
	virtual void print()
	{
		cout << "我是book" << endl;
		cout << "我的title是" << _title << endl;
		cout << "我的author是" << _author << endl;
	}
	virtual ~book()
	{
		cout << "这里是book的析构函数!" << endl;
		cout << endl;
	}
	string& title(){ return _title; }
	string& author(){ return _author; }
protected:
	string _title;
	string _author;
};

class audiobook :public book
{
public:
	
	//配合注解2,派生类构造函数要给父类赋值,如果要用父类定义的对象的话
	audiobook(string title, string author, string narrator) :book(title, author), _narrator(narrator)
	{
		cout << "这里是audiobook的构造函数!" << endl;
	}

	virtual void print()
	{
		cout << "我是audiobook" << endl;
		cout << "我的title是" << _title << endl;
		cout << "我的author是" << _author << endl;
		cout << "我的narrator是" << _narrator << endl;
	}

	virtual ~audiobook()
	{
		cout << "这里是audiobook的析构函数!" << endl;
		cout << endl;
	}

	string& narrator() { return _narrator; }
protected:
	string _narrator;
	
};
void print(LibMat& lib)
{
	lib.print();
}
int main()
{
	{
		book bk("my book", "me");
		print(bk);
		
	}

	{
		audiobook abk("my audiobook", "me", "he");
		abk.author() = "he";
		abk.narrator() = "me";
		print(abk);

	}
	return 0;
}

//注解1:这个程序演示了继承和多态的使用方法。基类为libmat,book是libmat的派生类,audiobook是book的派生类


//注解2:从里面看出,使用继承,必须要给他的基类进行赋值,使用初始化列表就行。同时父类的所有对象,都可以被
//派生类使用,只要父类的对象是public或者protected的

//注解3:从外部函数print的实现情况来看,虚函数是一种动态绑定,只要运行时候才会去判断自己要调用哪个函数

//注解4:如果类中有虚函数,析构函数必须也是虚函数,否则派生类多的一些对象没有相对应的析构函数去析构

4.如何定义一个类

  • 确定哪些操作是共有的,构成基类
  • 确定哪些操作是特有的,定义为虚函数
  • 根据需要对成员变量和成员函数设定访问级别

5.什么情况下虚函数会变成静态调用

  在有些时候,虚函数也会变成静态调用。可能是我们刻意为之,也可能是错误,下面就谈一下虚函数哪些情况下会变成静态调用

5.1 使用 class scope

  要调用的虚函数前加入class scope,可以明确的表示你要调用哪个函数,通过这种方式以后,这个虚函数就变成确定的了,就变成静态绑定的了。一般应用场景是,你定义的这个虚函数需要能够特化基类的虚函数,同时这个类中还有其他函数要调用这个虚函数,于是就在其他函数调用虚函数的地方,给他加一个class scope,举例如下:

#include<iostream>
using namespace std;



class LibMat
{
public:
	LibMat()
	{
		cout << "这里是libmat的构造函数!" << endl;
	}

	virtual void print()
	{
		cout << "我是libmat" << endl;
	}
	virtual ~LibMat()
	{
		cout << "这里是libmat的析构函数!" << endl;
		
	}
	void P()
	{
		print();
	}

	void Q()
	{
		LibMat::print();
	}

};

class book : public LibMat
{
public:
	book(string title, string author) :_title(title), _author(author)
	{
		cout << "这里是book的构造函数!" << endl;
	}
	virtual void print()
	{
		cout << "我是book" << endl;
		cout << "我的title是" << _title << endl;
		cout << "我的author是" << _author << endl;
	}
	virtual ~book()
	{
		cout << "这里是book的析构函数!" << endl;
		
	}
	string& title() { return _title; }
	string& author() { return _author; }
protected:
	string _title;
	string _author;
};

class audiobook :public book
{
public:

	audiobook(string title, string author, string narrator) :book(title, author), _narrator(narrator)
	{
		cout << "这里是audiobook的构造函数!" << endl;
	}

	virtual void print()
	{
		cout << "我是audiobook" << endl;
		cout << "我的title是" << _title << endl;
		cout << "我的author是" << _author << endl;
		cout << "我的narrator是" << _narrator << endl;
	}

	virtual ~audiobook()
	{
		cout << "这里是audiobook的析构函数!" << endl;
		
	}

	string& narrator() { return _narrator; }
protected:
	string _narrator;

};

int main()
{
	

	
		audiobook abk("my audiobook", "me", "he");
		
		abk.P();
		cout << endl;
		abk.Q();
		cout << endl;

	
	return 0;
}

//注解1:有时候,我们可能会在类中的其他函数中调用虚函数,但是我们不希望这个虚函数是运行时候才确定是哪个,
//因为我们只想调用自己这个类的函数,而不希望他动态绑定到其他地方。所以前面要使用class scope符号,来明确
//调用哪个函数,这相当于隐藏了虚函数的运行机制。比如函数P,就调用的是audiobook的print,而Q调用的是libmat的print


5.2 虚函数不完全匹配

5.2.1 一个典型的错误

  如果两个虚函数不完全匹配,是不能够形成多态的效果的,最典型的就是把函数定义为const,但是另外一个没有定义为const,这样虽然编译能过,程序能够正常运算,但是运行结果却是静态绑定的结构

#include<iostream>
using namespace std;

class num_sequence
{
public:
	virtual const char* what_am_i()const
	{
		return "num_sequence";
	}
};

class Fibnoacci: public num_sequence
{
public:
	virtual const char* what_am_i() 
	{
		return "Fibnoacci";
	}
};

int main()
{
	num_sequence p;
	Fibnoacci q;
	num_sequence& pp = q;
	cout << pp.what_am_i() << endl;;

	return 0;
}



5.2.2 特例

  一种虚函数不完全匹配,但是仍然是动态匹配结果的例子是,函数返回值是类本身。这样仍然是动态绑定的效果,可以看做是一种特例

#include<iostream>
using namespace std;

class num_sequence
{
public:
	virtual num_sequence& f()
	{
		cout << "num_sequence" << endl;
		return *this;
	}

};

class Fibonacci :public num_sequence
{
public:
	virtual Fibonacci & f()
	{
		cout << "Fibonacci" << endl;
		return *this;
	}
};

int main()
{
	Fibonacci b;
	num_sequence p;

	num_sequence* pp = &b;
	pp->f();

	return 0;
}




5.3 没有通过索引和指针的方式进行访问

  多态构成的核心条件就是,子类把指针交给父类,所以,如果在写函数的参数列表时,把类定义了不是地址类型的,那么自然也就无法构成多态,结果也就是一种静态绑定的结果了。

#include<iostream>
using namespace std;



class LibMat
{
public:
	LibMat()
	{
		cout << "这里是libmat的构造函数!" << endl;
	}

	virtual void print()
	{
		cout << "我是libmat" << endl;
	}
	virtual ~LibMat()
	{
		cout << "这里是libmat的析构函数!" << endl;
		cout << endl;
	}

private:
};

class book : public LibMat
{
public:
	book(string title, string author) :_title(title), _author(author)
	{
		cout << "这里是book的构造函数!" << endl;
	}
	virtual void print()
	{
		cout << "我是book" << endl;
		cout << "我的title是" << _title << endl;
		cout << "我的author是" << _author << endl;
	}
	virtual ~book()
	{
		cout << "这里是book的析构函数!" << endl;
		cout << endl;
	}
	string& title() { return _title; }
	string& author() { return _author; }
protected:
	string _title;
	string _author;
};

class audiobook :public book
{
public:

	
	audiobook(string title, string author, string narrator) :book(title, author), _narrator(narrator)
	{
		cout << "这里是audiobook的构造函数!" << endl;
	}

	virtual void print()
	{
		cout << "我是audiobook" << endl;
		cout << "我的title是" << _title << endl;
		cout << "我的author是" << _author << endl;
		cout << "我的narrator是" << _narrator << endl;
	}

	virtual ~audiobook()
	{
		cout << "这里是audiobook的析构函数!" << endl;
		cout << endl;
	}

	string& narrator() { return _narrator; }
protected:
	string _narrator;

};
void print(LibMat lib)  //函数传递参数没有使用引用或者指针的方式,即使传入book,也不调用book的虚函数
{
	lib.print();
}
int main()
{
	{
		book bk("my book", "me");
		print(bk);

	}

	
	return 0;
}

6. 多态类的类型转换

6.1 鉴定多态类

  c++中有一个函数,能够鉴别程序运行过程中,当前的多态类绑定到了哪个类中,需要头文件#include

  依旧使用前面的libMat,book和audiobook三个类,只是更新一个函数print

void print(LibMat &lib)  
{
	lib.print();
	cout << typeid(lib).name() << endl; //鉴别当前的多态类与谁绑定
	cout << (typeid(lib) == typeid(book) )<< endl;//同时这个多态类还支持比较运算符
	
}

6.2 静态转换

  我们可以使用多态类调用其他类中的成员函数,但是必须要把多态类进行类型转换才能调用。如果我们不进行类型转换,即使编译器知道它就是那个类型,也是不能运行的。比如


book bk("me","mybook");

LibMat *lib = &bk;

//lib->book::author();
//这句话是不能调用的,即使我们知道确实当前多态类指代的就是book,不经过类型转换,我们仍然不能使用book的成员函数

  静态类型转换static_cast有些不安全,因为他是无条件的转换,即使转换条件不成了,所以我们必须要判断一下,当前的多态类指代是谁

//使用静态类型转换,为了确保安全,要先保证该多态是否是我们要的那个样子
		if (typeid(*lb) == typeid(book))
		{
			book* b = static_cast<book*>(lb);
			cout << b->book::author() << endl;//不进行类型转换直接用会报错的
		}

6.3 动态转换

  动态类型转换dynamic_cast只有符号条件才能转换,只能从宽类型往窄类型转换,所以不需要判断当前多态类指代是谁,直接用即可

//动态类型转换,只有能转,才会转
		if (book *b1 = dynamic_cast<book*>(lb))
		{
			cout << "b1" << endl;
			cout << b1->book::author() << endl;//不进行类型转换直接用会报错的
		}

6.4 完整代码

  多态类型转换的代码贴在这里,虽然目前并不是很清楚,对多态类做类型转换有什么意义。

#include<iostream>
#include<typeinfo>
using namespace std;



class LibMat
{
public:
	LibMat()
	{
		cout << "这里是libmat的构造函数!" << endl;
	}

	virtual void print()
	{
		cout << "我是libmat" << endl;
	}
	virtual ~LibMat()
	{
		cout << "这里是libmat的析构函数!" << endl;
		cout << endl;
	}

private:
};

class book : public LibMat
{
public:
	book(string title, string author) :_title(title), _author(author)
	{
		cout << "这里是book的构造函数!" << endl;
	}
	virtual void print()
	{
		cout << "我是book" << endl;
		cout << "我的title是" << _title << endl;
		cout << "我的author是" << _author << endl;
	}
	virtual ~book()
	{
		cout << "这里是book的析构函数!" << endl;
		cout << endl;
	}
	string& title() { return _title; }
	string& author() { return _author; }
protected:
	string _title;
	string _author;
};

class audiobook :public book
{
public:

	//配合注解2,派生类构造函数要给父类赋值,如果要用父类定义的对象的话
	audiobook(string title, string author, string narrator) :book(title, author), _narrator(narrator)
	{
		cout << "这里是audiobook的构造函数!" << endl;
	}

	virtual void print()
	{
		cout << "我是audiobook" << endl;
		cout << "我的title是" << _title << endl;
		cout << "我的author是" << _author << endl;
		cout << "我的narrator是" << _narrator << endl;
	}

	virtual ~audiobook()
	{
		cout << "这里是audiobook的析构函数!" << endl;
		cout << endl;
	}

	string& narrator() { return _narrator; }

protected:
	string _narrator;

};
void print(LibMat& lib)  //函数传递参数没有使用引用或者指针的方式,即使传入book,也不调用book的虚函数
{
	lib.print();
	cout << typeid(lib).name() << endl;
	cout << (typeid(lib) == typeid(book)) << endl;

}
int main()
{
		LibMat libmat;
		book bk("my book", "me");
		
		audiobook abk("my audiobook", "me", "he");
		

		//LibMat* lb = &libmat;  //这句话两个函数都不会执行,因为窄类型变不成宽类型
		//LibMat* lb = &bk;  //这句话两个函数都会执行
		LibMat* lb = &abk; //这句话,动态类型转换可以,静态类型转换不可以,动态类型转换能把宽类型变成窄类型
		
		//使用静态类型转换,为了确保安全,要先保证该多态是否是我们要的那个样子
		if (typeid(*lb) == typeid(book))
		{
			book* b = static_cast<book*>(lb);
			cout << b->book::author() << endl;//不进行类型转换直接用会报错的
		}

	//动态类型转换,只有能转,才会转
		if (book *b1 = dynamic_cast<book*>(lb))
		{
			cout << "b1" << endl;
			cout << b1->book::author() << endl;//不进行类型转换直接用会报错的
		}




	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值