使用类

使用类

运算符重载

格式如下:

operatorop(argument_list)

例如operator+()这将重载“+”运算符。如果A、B、C都是类D的对象就可以这样用:A=B+c。其中,运算符左侧的对象是调用对象,右侧的则是作为参数被传递的对象。
重载限制:

  1. 重载后的运算符必须至少有一个操作数是用户定义的类型
  2. 使用运算符时不能违反运算符原来的句法规则,同样,不能修改运算符的优先级
  3. 不能创建新运算符
  4. 不能重载某些运算符
  5. 某些运算符只能通过成员函数重载的运算符

类的自动转换和强制类型转换

例1
  • 自动转换 int i=3.3 最终i会自动转换成3
  • 强制类型转换 int i=int(3.3) i最终也为3.3
    对于类来说也是如此, 将类放到与int,double相同的高度上,规则大体相同。
例2

假设Stonewt类有一个构造函数Stonewt(double lbs)//可加explicit关闭隐式转换

Stonewt example//声明一个对象
example=1.1//自动(隐式)转换
example=Stonewt(1.1)//强制(显式)转换

现在我们将类对象转换成特定的数据类型,那么,反过来呢?
我们需要转换函数
注意:

  1. 转换函数必须是类方法
  2. 转换函数不能指定返回类型
  3. 转换函数不能有参数
例3
class{
    .
    .
    .
    .
    .
Public:
    operator int() const;//转换函数//可在前面加上关键字explicit来关闭隐式转换
    .
    .
    .
};
Stonewt::operator int() const
{
    Return int (pound+0.5)
}

类和动态内存分配

class{
    Staic int num_strings;//静态储存类
    .
    .
    .
    .
}

它有一个特点,无论创建了多少个对象,程序都只创建一个静态类变量副本,也就是说所有的类对象共用一个静态成员。它不能在类中声明,因为这样会分配内存而类不可,不好在头文件中声明因为这会导致多次声明,最好在表示方法的文件中声明。
Such as:int stringbad::num_strings=0;
很少为人所知的是,编译器会帮我们生成许多自动生成的成员函数,从而造成一些意料之外的结果。如:复制构造函数
复制构造函数用于将一个对象复制到新创建的对象中,它的原型通常如下:
Class_name(const Class_name &);
为什么隐式复制构造函数在一些情况下会出问题?
首先, 对于一些静态储存类,复制构造函数的执行不会对它造成改变,而有的时候正是需要
它的改变,这时候就会出问题。其次,一些类成员使用的是new初始化的,指向数据的指针,而不是数据本身。所以当对对象使用delete操作时,新构造出的函数很容易就会受到牵连。
下面给出一个复制构造函数的例子:

StringBad::StringBad(const StringBad &st)
{
    Num_strings++;
    Len=st.len;
    Str=new char [len+1];
    Std::strcpy(str,st.str);
    Cout<<num_strings<<":\""<<str
    <<"\"object created\n";
}

有关默认的赋值运算符的问题

与复制构造函数类似,当存在两个已经初始化的对象,我们想将一个对象的值赋给另一个时,默认的赋值运算符就会起作用,其作用机理与复制构造函数相同,产生的问题也大致相同,解决的方法也相似,下面提供赋值运算符(进行深度复制)定义。
e.g:

StringBad & StringBad::operator=(const StringBad &st)
{
    If(this==&st)
        Return *this;
    Delete [] str;
    Len=st.len;
    Str=new char[len+1];
    Std::strcpy(str,st.str);
    Return *this;
}

比较成员函数

e.g:

Bool operator<(const String &st1,const String &st2)
{
    if(std::strcmp(st1.str,st2.str)<0)
        return true;
    else
        return false;
}

使用中括号表示法访问字符

总的说来就是重载[]运算符,为了确保不修改数据,可以用Const char & string::operator[](int i) const的形式来声明函数。

静态类成员函数

我们可以将成员函数声明为静态的static int Howmany(){ return num_strings;}。这样做的后果是,不能通过对象来调用此函数,此函数也只能使用静态数据成员。
使用方法:String::Howmany();其中String为类名,Howmany为函数名

使用指向对象的指针

ps:cpp经常使用指向对象的指针
e.g:
string * favorite = new String(sayings[choice]);

再谈定位new运算符

可以用定位new运算符声明对象
Such as:pc1=new (buffer) JustTesting;
但要注意的是,如果是在同一块内存块上声明对象,不要使它们相互重叠而造成错误。还有就是对于用定位new运算符创建的对象要显示的调用析构函数
e.g:pc3->~justtesting();//justtesting为pc3所指向的类的名称。
还需注意的是晚创建的应该先删除

Chapter 13 && Chapter 14

几种继承的方法

  • 公有继承(is-a关系):
    eg:
class A:{
    ........
};

如何继承?

Class B:public A
{
    ......
};

即可,当然其中构造函数有些许的不同。
稍特殊情况:多态公有继承
即方法的行为取决于调用该方法的对象
两种方法:

  1. 在派生类中重新定义基类的方法
  2. 使用虚方法
  • 私有继承
    基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们
    e.g
Class student:private std::string,private std::valarray<double>
{
public:
    .............
};
  • 保护继承
    保护继承是私有继承的变体。保护继承在列出基类时使用关键字protected:
    e.g:
Class student:protected std::string,
Protected std::valarray<double>
{
    ........
};

使用保护继承时,基类的公有成员都将成为派生类的保护成员。

关于公有继承的具体细节

  1. 派生类构造函数必须使用基类构造函数
  2. 基类指针可以在不进行显示类型转换的情况下指向派生类对象;基类引用可以在不进行显示
    类型转换的情况下引用派生类对象,反之,派生类指针不能指向基类对象。但基类指针或引用
    只能用于调用基类方法。
  3. 关于多态公有继承的问题:在派生类中声明了一个基类中已经存在的方法,如show()函
    数,在show()函数中又调用基类的show()函数,这很自然,可以减少工作量,要记得使
    用作用域解析运算符,不然会是一个无穷尽的递归函数。
  4. 虚析构函数的重要性
    若派生类中有动态分配内存的一些操作,就需要基类有虚析构函数,这样可以确保程序首先调
    用派生类中的析构函数,再调用基类的析构函数,使内存可以正常的释放。
  5. 关于重新定义将隐藏方法的问题
    e.g:
Class Dwelling
{
public:
    Virtual void showperks(int a) const;
    ...
};
Class Hovel:public Dwelling
{
public:
    Virtual void showperks() const;
    ....
};

下面的showperks函数将隐藏上面的,这是很自然的。但是有时候很容易出现问题。当你想
调用上面的某个函数时,你就发现不能调用了。所以,如果要保证不出现此类问题,最好的方
法是把上一个类的虚方法的所有重载的版本在这个类中也全部写一遍。

访问控制:protected

我们已经用过public和private来控制对类成员的访问,其实还存在一种访问类别,那就是protected。protected与private较为相似。对于外部世界来说(相对的),它们都是不可访问的。但是派生类的成员可以直接访问protected中的成员。
警告:最好对类数据成员采用私有访问控制,不要使用保护访问控制;同时通过基类方法使派
生类能够访问基类数据。

抽象基类

抽象基类也是为了表示一种特定的关系而存在着的。比如我们有篮球和足球这两种球,它们可以独立成两个类别,但它们有很多共性,那么我们就可以使用一个叫做‘球’的类来作为抽象基类,再通过球类来派生出篮球类和足球类。
e.g

class AcctABC
{
private:
    std::string fullName;
    long acctNum;
    double balance;
protected:
    struct Formatting
    {
        std::ios_base::fmtflags flag;
        std::streamsize pr;
    };
    const std::string &FullName() const { return fullName; }
    long AcctNum() const { return acctNum; }
    Formatting SetFormat() const;
    void Restore(Formatting &f) const;
    public:
    AcctABC(const std::string &s = "Nullbody", long an = -1, double bal = 0.0);
    void Deposit(double amt);
    virtual void Withdraw(double amt) = 0;
    double Balance() const { return balance; };
    virtual void ViewAcct() const = 0;
    virtual ~AcctABC() {}
};
class Brass :public AcctABC
{
public:
    Brass(const std::string &s = "Nullbody", long an = -1,
    double bal = 0.0) :AcctABC(s, an, bal) {}
    virtual void Withdraw(double amt);
    virtual void ViewAcct() const;
    virtual ~Brass() {}
};
class BrassPlus : public AcctABC
{
private:
    double maxLoan;
    double rate;
    double owesBank;
public:
    BrassPlus(const std::string &s = "Nullbody", long an = -1,
    double bal = 0.0, double ml = 500,
    double r = 0.10);
    BrassPlus(const Brass &ba, double ml = 500, double r = 0.1);
    virtual void ViewAcct() const;
    virtual void Withdraw(double amt);
    void ResetMax(double m) { maxLoan = m; }
    void ResetRate(double r) { rate = r; };
    void ResetOwes() { owesBank = 0; }
};

其中AcctABC是抽象类,Brass和BrassPlus是具体类。AcctABC包含Brass和BrassPlus类共有的所有方法和数据成员,而那些在BrassPlus类和Brass类中的行为不同的方法应被声明为虚函数。至少应有一个虚函数是纯虚函数,这样才能使AcctABC成为抽象类。抽象基类的特点,抽象基类的作用相对于公有继承的基类来说作用有点单调,它只能提供一个垫板,本身作为类并不能声明对象。并且它一定要有纯虚函数的存在,它的纯虚函数可以定义也可以不定义,如果它的派生类没有定义纯虚函数,那么它也成为了抽象基类,知道有的类定义了这些函数,那么它就成为了实体类,可以声明对象。

继承和动态分配内存的细节

  1. 派生类不使用new
    这种方式的复杂度可以说是最低了(如果有更低的,原谅我的孤陋寡闻),不需要进行一些所谓的额外的操作。
  2. 派生类使用new
    在这种情况下,必须为派生类定义显示析构函数、复制构造函数和赋值运算符。派生类析构函数自动调用基类的析构函数,故其自身的职责是清除自身调用的内存。派生类的复制构造函数必须调用基类的复制构造函数来处理基类的数据。对于显示赋值运算符也是如此。
    如何使用基类的友元函数?
    解决方法是使用强制类型转换
    示例代码块:
std::ostream&operator<<(std::ostream &os,const hasDMA & hs)
{
    os<<(const baseDMA &) hs;
    os<<“Style:<<hs.style <<endl;
    Return os;
}

如上例所示,想要使用的基类的重载<<运算符的友元函数,只要加上(const baseDMA &)即可。

包含、组合或层次化

使用这样的类成员,本身是另一个类的对象
e.g:

Classs student:
{
    private:
    String name;
    Valarray<double> scores;
    ...
};

使用构造函数时,对于对象成员的处理:使用初始化列表,调用对象名即可而不是对象的类名。假定scores是一个对象成员,要调用该成员的方法,只需scores.function()即可。

私有继承

使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味这基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
使用构造函数:
e.g:

Student(const char *str, const double * pd,int n)
:std::string(str),ArrayDb(pd,n){}

使用私有继承时将使用类名和作用域解析运算符来调用方法。
访问基类对象(强制类型转换)

Const string &student::name() const
{
    Return (const string &) *this;
}

访问基类的友元函数
用类名显示地限定函数名不适合友元函数,这是因为友元不属于类。然而,可以通过显示地转换为基类来调用正确的函数。
e.g:

Ostream &operator<<(ostream &os, const student &stu)
{
    os<<“scores for<<(const string &)stu<<:\n“;
    .....
}

保护继承

保护继承是私有继承的变体。使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。当从派生类中派生出另一个类时,私有继承和保护继承之间的主要区别便呈现出来了。使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的公有方法在派生类中将变成私有方法;使用保护继承时,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。
使用using来重新定义访问权限
e.g
Using std::valarray<double>::min;
这使得valarray<double>::min可用,就像它们是student的公有方法一样

多重继承的麻烦

比如说有一个worker的基类,从其中派生出singer类和waiter类,至此,整个代码是没有问题的,singer和waiter都获得了worker的一些数据成员,它们统称为work。当我们从singer和waiter中派生出一个singerwaiter类时,问题就出现了。首先,singerwaiter将有两个有关于worker的数据,那么,进行赋值时要把值赋给谁呢?cpp解决了这个问题,引入了虚基类(virtual base class)
e.g:

Class singer: virtual public Worker {...};
Class wiater: public virtual worker {...};

虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。特别地使用构造函数

singerwaiter(const worker &wk, int p=0, int v=singer::other)
:worker(wk),waiter(wk,p),singer(wk,v){}

worker是虚基类,则singerwriter使用构造函数时要显式的使用worker的构造函数。还有就是方法的问题,如果singer和waiter都使用了show()方法,那么singerwaiter要使用哪个show()方法呢?
可以用作用域解析运算符来澄清编者的意图
Newwhire.singer::show();
还有一种方法可以解决这个问题
e.g

void Worker::Data() const
{
    cout << "Name: " << fullname << "\n";
    cout << "Employee ID: " << id << "\n";
}
void Waiter::Data() const
{
    cout << "Panache rating: " << panache << "\n";
}
void Singer::Data() const
{
    cout << "Vocal range: " << pv[voice] << "\n";
}
void SingingWaiter::Data() const
{
    Singer::Data();
    Waiter::Data();
}
void SingingWaiter::Show() const
{
    cout << "Category: singing waiter\n";
    Worker::Data();
    Data();
}

这样,singingwaiter的show函数就能完美地完成任务了。但要记得将上述基类的data函数
设置为受保护的,这样可以放置不被当前的类对象调用。

类模板

e.g

template <class Type>
class Stack
{
private:
    enum { MAX = 10 };
    Type items[MAX];
    int top;
    public:
    Stack();
    bool isempty();
    bool isfull();
    bool push(const Type &item);
    bool pop(Type &item);
};
template <class Type>
Stack<Type>::Stack()
{
    top = 0;
}
template <class Type>
bool Stack<Type>::isempty()
{
    return top == 0;
}
template <class Type>
bool Stack<Type>::isfull()
{
    return top == MAX;
}
template <class Type>
bool Stack<Type>::push(const Type &item)
{
    if (top < MAX)
    {
        items[top++] = item;
        return true;
    }
    else
        return false;
}
template <class Type>
bool Stack<Type>::pop(Type &item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false;
}

上述提供了类模板的一个例子,类模板函数最好与类在同一个文件中。
使用类模板的例子:

Stack<int> kernels;
Stack<string> colonels;

如果要使用指针和涉及动态内存分配的东西,可能需要重新设计一下类模板。我们在上面已经可以看到template <class Typename>的存在了,实际上,类模板可以使用的不仅仅时Typename这一个参数,比如,它可以是template<classs typename,int n>,n可以称为时表达式参数。表达式参数有一些限制。表达式参数可以是整型、枚举、引用或指针。另外,模板代码不能修改参数的值,也不能使用参数的地址。
模板多功能性
e.g

Array< Stack<int> > asi;
Array< Array<int,5>, 10> twodee;

可以使用多个类型参数
e.g
Template <class T1,class T2>
可以为类型参数提供默认值
Template <class T1,class T2= int>
模板具体化

  1. 隐式实例化
    不做额外的操作
  2. 显示实例化
    声明必须位于模板定义所在的名称空间中
    E.g
    Template class ArrayTP<string, 100>;
    在这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。
  3. 显示具体化
    提供一个为具体类型定义的模板
    e.g
Template <> class SortedArray<const char char*>
{

}

这样就可以专门地为该种数据类型设计函数。
4. 部分具体化
即部分限制模板的通用性
e.g
Template <class T1> class Pair<T1,int> {};

  • 成员模板
    一个模板类将另一个模板类和模板函数作为其成员
    将模板用作参数
    e.g
    Template <template <typename T> class Thing>
  • 模板类和友元
  1. 非模板友元
    都可以使用,在引入变量时要指定模板类的具体的参数类型。
  2. 模板类的约束模板友元函数
    在类定义的前面声明每个模板函数
Template <typename T> void counts();
Template <typename T> void report(T &);

然后,在函数中再次将模板声明为友元。这些语句根据类模板参数的类型声明具体化。

Template <typename TT>
Class HasFriendT
{
    ....
    Friend void counts<TT>();
    Friend void report<>(HasFriendT<TT> &)
};

在写函数的时候不用再指定具体的参数类型了。
3.模板类的非约束模板友元函数
可以使用多个不同的类具体化

Template <typename T>
Class ManyFriend
{
    ....
    Template <typename C,typename D> friend void show2(C &,D &);
};

模板别名
可以使用typedef为模板具体化指定别名:

Typedef std::array<double,12> arrd;
Typedef std::array<int,12> arri;

当然,也可以这么做:

template<typename T>
using arrtype=std::array<T,12>;

定义好以后,就可以这样使用了:

Arrtype<double> gallons;
Arrtype<int> days;

注意:Typedef const char * pc1;Using pc1=const char *;是等价的

Chapter 15 友元,异常和其他

友元

  1. 友元类
    所谓的友元类,就是在类头部把另一个类声明为有友元关系的类。这样,另一个类就可以在自己类的作用域中访问原始类的私有成员和保护成员。
    e.g:
class Tv
{
public:
    Friend class Remote; //Remote can access Tv private parts
};
  1. 友元成员函数
    作用与友元类相同,只是起作用的范围缩小了。
    e.g:
Class Tv:
{
    Friend void Remote::set_chan(Tv &t, int c);
};
  1. 前向声明
    前向声明的出现是为了满足特定的需求的。比如说,remote中的一个方法被声明为Tv的一个友元成员函数后,那么在构建这两个函数的过程中,remote中的方法提到了Tv类,意味这Tv类必须放在remote类的前面,但Tv类中
    remote成员函数意味着它必须先知道remote的定义,这样它才能知道remote是一个类,这样就有了一种矛盾的关系,即它们交叉引用了,为了拯救它们,前向声明出现了。
    e.g:
Class Tv;
Class remote:{...};
Class Tv:{...};

可不可以是

Class remote;
Class Tv:{...};
Class remote: {...};

呢?答案是否定的,因为,这样做对于Tv类来说它能看到的信息是remote是一个类,但是没有得到友元成员函数的定义,所以它就很迷惑了。所以就是不可以的。

  • 其他友元关系
  1. 让两个类成为彼此的友元
    作用是共享它们的数据,当然可以完成更宽泛的操作了。
  2. 共同的友元
    可以将一个友元函数作为两个类的友元函数。

嵌套类

有的时候,为了方便,我们可以将一个类声明在另一个类中,这个类其实就相当于一种自定义的数据结构,而且这种类的作用域依实现而不同,是一种很好的封装的方法。
e.g:

Class example1:
{
    class example2:
    {

    };
};

异常

异常的出现是为了更好地检测出代码中潜在的问题,但异常也使代码变得更复杂了,可谓有一利有一弊吧。
系统有两种方式处理错误,这里简单的介绍一下

  1. abort()。其典型实现是向标准错误流(即cerr使用的错误流)发送消息abnormal program termination(程序异常中止),然后中止程序。
  2. 返回错误码。可以用一个函数检验,如果输入错误,则返回false,让用户可以重新输入。
  3. 我们自己设计的异常机制。一共有三个模块,try块,throw块和catch块。try块执行预计会发生错误的代码,期间对于不同的错误使用不同的throw代码,跳转到相应的catch块。throw块和catch块具有参数匹配的特性。
    1. 异常规范。异常规范是在C98提出来的一项特性,但实践证明它并不好用,C11保留了它,但以后可能会
      被删除。它的功能就是让编译器添加执行运行阶段检查的代码,检查是否违反了异常规范。另外,有一个关键字noexcept,它告诉编译器函数不会引发异常。
    2. 栈解退。所谓的栈解退就是程序在按一定的顺序执行时,当它检测到异常以后,会反方向寻找解决问题
      的catch方法,不管有多远,期间该调用析构函数的调用,该清理内存的清理,不会受到任何
      影响。
    3. 其他异常特性。引发异常时编译器总是创建一个临时拷贝,即使异常规范和catch块中指定的是引用。
      但指定引用也有它的好处,比如我们指定一个基类的引用,这样它就可以作用用基类和派生类等的多个类了。
      如果不知道将会引发哪些异常,可以这样写。catch(...) {...}这样它就可以捕获任何异常。
  4. exception类。就是cpp提供了许多的类,它们的为检测不同的错误而生,我们可以使用它们,但实现的方
    法因人而异。
    异常何时会迷失方向:
  • 有的时候,可能没有捕获到异常。这样,不会导致程序立刻异常中止。相反,程序将首先调用函数terminate()。在默认情况下,terminate()调用abort()函数。当然,我们也可以指定要调用的函数。
    e.g:
#include <exception>
Using namespace std;
Void myQuit()
{
    cout<<“Terminating due to uncaught excepiton\n“;
    exit(5);
}
Set_terminate(myQuit);
  • 原则上,异常规范应包含函数调用的其他函数引发的异常。但有时并没有,这个时候就需要进行补救。这个时候就需要进行补救方法就是在异常规范中加入一个默认的异常。使其它可能引发的异常都指向这个异常。
  • 有关异常的注意事项
    有的时候动态分配了内存,在它被释放之前就引发了异常,导致了内存的泄露,这是可以解决的,比如在catch块中加入相应处理的代码, 但这会使得代码变复杂,可能还会引发许多其他的问题。

RTTI

RTTI是运行阶段类型识别(Runtime Type Identification)的简称。
cpp有三个支持RTTI的元素

  • 如果可能的话,dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指
    针;否则,该运算符返回0——空指针。
  • 空指针typeid运算符返回一个指出对象的类型的值
  • type_info结构储存了有关特定类型的值

1.dynamic_cast运算符
它是最常用的RTTI组件。它不能回答“指针指向的是哪类对象“这样的问题,但能够回答”是否可以安全地将对象的地址赋给特定类型的指针“这样的问题。
e.g:

Class Grand :{...};
Class superb:public grand {...};
Class magnificent:public superb{...};
Grand *pg=new grand;
Superb *pm=dynamic_cast<supurb *>(pg);

这提出了一个问题 ,指针pg的类型是否可被安全地转换为superb*?如果可以,运算符将返回对象的地址,否则返回一个空指针。也可以将这个运算符用于引用啦,但是没有一个指示错误的空指针,所以只能用try块和catch块在检测了。
2. typeid运算符和type_info类
Typeid运算符使得能够确定两个对象是否为同种类型。它与sizeof有些相像,可以接受两种参
数。

  • 类名
  • 结果为对象的表达式
    返回一个对type_info对象的引用
    e.g typeid(Magnificent) == typeid(*pg)
    如果类型为真则为true,否则为false。若pg为空指针,则会引发bag_typeid异常。

类型转换运算符

四个类型转换运算符

  • dynamic_cast
  • const_cast
  • static_cast
  • reinterpret_cast
    Dynamic_const之前已经介绍过了,这里不再赘述。Const_cast用于执行只有一种用途的类型转换,即改变值为const或volatile,其语法与dynamic_const相同。就是去const或volatile性。
    Static_const
    Static_cast <type-name> (expression)
    仅当type_name可被隐式转换为expression所属的类型或expression可被隐式转换为type_name所属的类型时,上述转换才是合法的。Reinterpret_const是所有转换中最危险的一种。能把指针转化为数,具体是怎么作用的书上没有具体的描述。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值