STL<vector>用法汇总

使用场合:

vector算是一个比较万金油的容器,它是一个可变大小数组,支持随机访问,不过在尾部以外的位置进行增加和删除操作会比较耗时。通常用vector来代替原始的数组来使用,比较方便。

声明与初始化:

首先要包含头文件,vector的头文件名就是< vector >。

声明方式:
#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve;//声明一个存储int型数据的容器ve
    vector<vector<int>> vve;//声明一个二维的vector,C++11标准写法
    vector<vector<string> > vvs;//两个尖角括号之间要留一个空格,旧编译器的写法
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
初始化:

vector的初始化方式很多,书上介绍的如下

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> va;//调用默认构造函数,里面什么也没有
    for(int i=0;i<5;i++)
        va.push_back(i);//放5个元素进去
    vector<int> vb(va);//用va初始化vb,此种用法要求va与vb必须是同一种容器,且类型相同
    vector<int> vc{1,2,3,4};//初始化列表
    vector<int> vc2={1,2,3,4};//同上,C++11新标准
    vector<int> vd(va.begin(),va.end());//用迭代器指定的范围初始化

    list<int> li={2,3,4};
    vector<int> vli(li.begin(),li.end())//使用迭代器可以把不同容器,类型相同的元素用来初始化

    vector<int> ve(10);//包含10初始化值的元素,在ve当中里面有10个0次构造函数是explicit
    vector<int>  vf(10,1);//在vf里面塞进10个1
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

赋值操作:

利用拷贝构造函数和swap函数可以实现赋值操作

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> va,vb,vc;

    va={1,2,3,4};//用C++11的初始化列表来赋值
    for(int i=1;i<=10;i++)//在vb中塞入10个数
        vb.push_back(i);

    va=vb;//把vb拷贝给va

    swap(va,vc);//交换va和vc
    for(auto x:vc)//输出应该是va里面的值
        cout<<x<<" ";
    for(auto x:va)//va里面应该什么也没有
        cout<<x<<" ";

    cout<<endl;

    va.swap(vc);//再把vc和va换回来,用成员函数的形式
    for(auto x:vc)//里面什么也没有
        cout<<x<<" ";
    for(auto x:va)//
        cout<<x<<" ";
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

还可以利用assign函数实现

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> vb,va={1,3,5,7,9};
    vb.assign(va.begin(),va.begin()+3);//使用va的迭代器给vb赋值
    //这里注意,不可以自己给自己用迭代器赋值
    for(int i=0;i<vb.size();i++)
        cout<<vb[i]<<endl;//输出1 3 5

    vb.assign({2,4,6});//里面可以放一个初始化列表
    for(int i=0;i<vb.size();i++)
        cout<<vb[i]<<endl;//输出2 4 6

    list<int> li={2,4,6,8};
    vb.assign(li.begin(),li.end());//可以把其他容器,但是类型相同的迭代器用来赋值
    for(int i=0;i<vb.size();i++)
        cout<<vb[i]<<endl;

    list<string> names={"abc","efg"};
    vector<const char*> oldstyle={"qwe","ert"};
    names.assign(oldstyle.begin(),oldstyle.end());//vector给list初始化,也可以用cbegin
    //把const cahr*赋值给string
    for(auto x:names)
        cout<<x<<endl;//qwe ert

    vector<string> vc;
    vc.assign(3,"abc");//向vc中塞入3个abc
    for(int i=0;i<vc.size();i++)
        cout<<vc[i]<<endl;//abc abc abc
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
vector的比较

vector重载了运算符<,>,==,<=,>=等比较符号。其比较规则与字典序类似,如果两个容器具有相同大小,而且每个元素对应相等,那么这两个vector相同。 
如果两个vector大小不同,但是公有的元素和对应位置全都相同,那么大的容器比小的容器大。 
如果两个容器完全不同,那么,比较第一个不同的元素哪个大那个小作为判断标准。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> v1={1,3,5};
    vector<int> v2={1,4};
    vector<int> v3={1,3,5};
    cout<<(v1<v2)<<endl;//true
    cout<<(v1==v3)<<endl;//true
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

插入元素:

vector当中插入元素主要使用push_back向后面插入一个元素。 
在vector当中没有push_front的用法! 
如果想再任意位置插入一个元素,可以用insert成员函数,同样会涉及到数据移动。 
具体用法:

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve;

    //ve.push_back(e)在ve的尾部添加一个e元素
    ve.push_back(1);//在尾部插入一个1

    //ve.insert(it,n,e)//在it指向元素的前面添加n个元素e
    ve.insert(ve.end(),1,2);//相当于在尾部添加1个2
    ve.insert(ve.begin(),1,0);//在头部添加1个0

    //ve.insert(it,beg,ed)//在it指向元素的前面添加迭代器[beg,ed)范围内的元素
    vector<int> v={-2,-1};
    ve.insert(ve.begin(),v.begin(),v.end());//在ve的前面添加v的元素

    //ve.insert(it,li)在it所指的元素的前面添加一个列表li
    ve.insert(ve.end(),{3,4,5});

    //错误用法
    //ve.insert(ve.begin(),ve.begin(),ve.end())不能用调用对象自己的迭代器当做赋值范围
    for(auto x:ve)
        cout<<x<<endl;
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

这里的push_back操作是对拷贝值进行操作。 
在C++11标准当中,insert函数具有返回值,返回第一个新加入元素的迭代器(不能是插入列表)。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve={1,2,3,4};

    auto it=ve.insert(ve.end(),5);//返回指向最后一个元素的迭代器
    cout<<*it<<endl;//5

    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
emplace操作:

当调用emplace操作的函数时,每次会新构造一个对象,并添加到容器当中。添加的方法要和类当中的构造函数相匹配。 
在vector当中有c.emplace()和c.emplace_back()操作两种。参数当中可以

#include <bits/stdc++.h>
using namespace std;
class A
{
public:
    int a;
    string s;
    A(){a=0,s="";}
    A(int aa,string ss){a=aa,s=ss;}
};
int main()
{
    ios::sync_with_stdio(false);
    vector<A> va;
    va.emplace_back(1,"abc");//添加
    va.emplace_back();//添加一个默认构造函数
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

也可以调用c.emplace(it,arg);其中it为指向一个数据的迭代器,arg为参数。

访问元素:

  • 清空vector可以使用成员函数c.clear()
  • 判断vector是否为空,可以使用成员函数empty(),如果为空返回true,否则返回false
  • vector输出最后一个元素的引用可以用back()成员函数,如果容器为空,则行为未定义
  • vector输出第一个元素的引用可以用front()成员函数,如果容器为空,则行为未定义
  • vector支持用下标访问元素,类似数组一样c[n]其中n是一个无符号整数,如果n大于容器的长度,那么行为未定义
  • vector为了防止越界访问,其中有成员函数c.at(n),返回下标为n的元素的引用。如果下标越界,那么抛出out_of_range的异常

以上返回引用会根据容器的类型来判断,也就是说如果声明了一个const容器,那么返回一个const的引用,否则返回一个普通的引用

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve={1,2,3,4};
    ve.back()=5;//4变成5
    ve.front()=0;//1变成0
    cout<<ve[0]<<" "<<ve[ve.size()-1]<<endl;
    try
    {
        ve.at(6);
    }
    catch(out_of_range &e)
    {
        cerr<<e.what()<<endl;
    }
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

删除元素

vector当中删除元素通常使用pop_back()用来删除最后一个元素,也可以使用erase()成员函数来删除任意位置的元素。由于vector的性质,删除任意位置的元素会降低效率。

  • pop_back()成员函数用来删除vector中的最后一个元素,如果容器为空会出现未定义行为。
  • c.erase(it)成员函数,删除迭代器it所指向的元素,返回一个指向被删除元素之后的迭代器,如果it指向最后一个元素,那么返回以为尾后迭代器(通常是end())。若it就是end(),那么行为未定义。
  • c.erase(beg,ed)删除[beg,ed)范围的元素,同时返回最后一个元素的后面的迭代器,如果ed就是尾后迭代器,那么还返回一个尾后迭代器。
#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve={1,2,3,4};
    ve.erase(ve.begin(),ve.begin()+1);//删除第一个元素,结果是2 3 4

    ve.erase(ve.begin());//删除第一个元素,结果是3 4

    ve.pop_back();//删除最后一个元素,结果变成3
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

注意,删除元素以后会使迭代器失效,所以循环删除元素这样写是错误的。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve={1,2,3,4,5,6,7,8,9};
    for(auto it=ve.begin();it!=ve.end();it++)//错误写法
        if((*it)%2)//删除奇数的元素
            ve.erase(it);

    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

正确的删除方式

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve={1,2,3,4,5,6,7,8,9};
    for(auto it=ve.begin();it!=ve.end();)//没毛病
    {
        if((*it)%2)
            ve.erase(it);
        else
            it++;
    }
    for(auto x:ve)
        cout<<x<<endl;
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

谨记,改变容器的长度的操作会使迭代器实效。

vector的容量与内存管理:

vector是一个可以增长长度的容器,用户可以多自行改变容器长度。C++11标准当中为了节约内存,新增加了一个函数用来回收多余的内存。

  • size()成员函数,返回容器当中元素的个数。
  • resize(n)成员函数,重新定义容器的元素的个数。注意,不是容量。如果n大于当前的size(),那么多出的长度用默认值补充,如果n小于当前的size()那么只留下前n个元素,剩下的删除。
  • resize(n,t)成员函数,原则同上,只不过多出的元素用t补充而已
#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve={1,2,3,4,5,6,7,8,9};
    cout<<ve.size()<<endl;//9
    ve.resize(15);
    cout<<ve.size()<<endl;//15 多出来的用默认值补充

    ve.resize(3);//就留下前3个数,剩下的都删除

    ve.resize(10,-1)//多出的元素用-1补充
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • capacity()成员函数是不重新分配内存的情况下,容器可以保存多少元素
  • reserve(n)成员函数是分配智商能容纳n个元素的内存空间。比如ve当中有三个元素,现在使用ve.reserve(1),那么没有变化。如果使用ve.reserve(10),那么ve.size()还是原来的值,不过ve.capacity()会变成10
#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve;
    for(int i=0;i<100;i++)//放入100个元素
        ve.push_back(i);
    cout<<ve.size()<<endl;//100
    cout<<ve.capacity()<<endl;//128

    ve.reserve(99);//比100小,所以什么也不做

    cout<<ve.size()<<endl;//100
    cout<<ve.capacity()<<endl;//128

    ve.reserve(150);

    cout<<ve.size()<<endl;//100
    cout<<ve.capacity()<<endl;//150
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

最后说一下C++11的新函数,shrink_to_fit()说简单点,就是把多余没用到的空间回收一下,这个函数是向系统提交一个申请,而不是一个强制命令。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<int> ve;
    for(int i=0;i<100;i++)//放入100个元素
        ve.push_back(i);
    cout<<ve.size()<<endl;//100
    cout<<ve.capacity()<<endl;//128

    ve.shrink_to_fit();

    cout<<ve.size()<<endl;//100
    cout<<ve.capacity()<<endl;//100

    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可能会觉得,那每次使用vector后都调用这个函数,不就很省内存了嘛。此言差矣,如果需要再向容器当中塞东西,那么就要重新给容器增加分配的内存,更浪费时间和效率。

多维vector的用法:

用二维向量做例子,所谓多维向量,就是一个向量一面存储的内容是一个向量。通常可以代替二维数组来使用。也可以使用向量的数组来表示。

二维向量的声明:
vector<vector<int>> vve;//在新版本的编译器当中这样写没问题
vector<vector<int> >vvve;//旧版本的编译器会把两个相连的尖括号当成流操作
 
 
  • 1
  • 2
初始化:
#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<vector<int>> vva(10,vector<int>(0));//vve里面放10个vector,每个vector里面初始化一个0元素
    vector<vector<int>> vvb(10,{1,2,3,4});//v里面放10个vector,每个vector用列表初始化
    vector<int> va={0,2,3};
    vector<vector<int>> vvc(3,va);//vvc中里面放入3个va

    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

总之,和初始化一个普通的vector没什么两样,只不过就是初始化的元素变成了vector而已

插入:

这里使用push_back和insert

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<vector<int>> vvb(4,{1,2,3,4});
    for(int i=0;i<3;i++)
    {
        vector<int> vtemp={i};
        vvb.push_back(vtemp);//每次塞入一个用i初始化过的vtemp
    }
    vector<int> vt={666};
    vvb.insert(vvb.begin(),vt);//把vt放在vvb的头部
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
遍历:

一般使用下标遍历、迭代器遍历

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<vector<int>> vvb(4,{1,2,3,4});
    for(int i=0;i!=vvb.size();i++)
    {
        for(int j=0;j!=vvb[i].size();j++)
            cout<<vvb[i][j]<<" ";
        cout<<endl;
    }
    for(auto bit=vvb.begin();bit!=vvb.end();bit++)
    {
        for(auto bbit=bit->begin();bbit!=bit->end();bbit++)//bit类似指向一个vvb[i],里面是向量中的元素
            cout<<*bbit<<" ";
        cout<<endl;
    }
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

当然,也可以用C++11的新遍历方式

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<vector<int>> vvb(4,{1,2,3,4});
    for(auto x:vvb)
    {
        for(auto xx:x)
            cout<<xx<<" ";
        cout<<endl;
    }
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
删除操作:

和一维vector删除元素没什么区别

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    vector<vector<int>> vvb(4,{1,2,3,4});
    vvb.pop_back();//删除最后一个元素
    vvb.erase(vvb.begin());//删除第一个向量元素
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

三维,或者更高维的也是如此。

容器的指针,vector::data:

调用vector::data会返回一个指向第一个元素的指针,由于vector的内存是连续的,所以指针的移动,也就对应vector当中的元素遍历。看代码

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    std::vector<int> myvector (5);

    int* p = myvector.data();
    *p = 10;
    ++p;
    *p = 20;
    p[2] = 100;
    cout << "myvector contains:";
    for(unsigned i=0; i<myvector.size(); ++i)
    cout << ' ' << myvector[i];
    cout << '\n';

  return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

结果是myvector contains: 10 20 0 100 0 
上面的代码来自http://www.cplusplus.com/reference/vector/vector/data/

小结:

vector的成员函数当中还有一个get_allocate,是获取整块内存类似,c语言当中memcpy函数。这里先不介绍,后面补上。 
vector在C++代码当中使用十分广泛,可以用来表示矩阵,表示向量,当做邻接表来使用,十分方便。 
加上与泛型算法和模板的搭配,使用功能也非常强大。 
现在就总结这些,在编程和学习当中如果遇到有关vector的问题,还会在这里继续补充。如果写出来的观点和代码有什么问题,请各位看官不吝指正,我会及时更改。

to be continue~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是手动实现这些头文件里所有的STL容器的代码: ``` // iostream namespace mystd { class ostream { public: ostream& operator<<(const char* str) { // 实现输出字符串的逻辑 return *this; } // 其他重载运算符和成员函数 }; static ostream cout; } // memory namespace mystd { template<typename T> class allocator { public: using value_type = T; // 实现分配和释放内存的函数 }; } // stack namespace mystd { template<typename T, typename Container = mystd::vector<T>> class stack { public: using value_type = T; using container_type = Container; // 实现栈的各种函数 }; } // fstream namespace mystd { class filebuf { public: // 实现文件缓冲区相关函数 }; class ifstream { public: ifstream(const char* filename) { // 实现打开文件的逻辑 } // 实现读取文件的逻辑 }; class ofstream { public: ofstream(const char* filename) { // 实现打开文件的逻辑 } // 实现写入文件的逻辑 }; } // vector namespace mystd { template<typename T, typename Allocator = mystd::allocator<T>> class vector { public: using value_type = T; using allocator_type = Allocator; // 实现vector的各种函数 }; } // cmath namespace mystd { double pow(double base, double exponent) { // 实现求幂函数 } // 其他数学函数的实现 } // iomanip namespace mystd { class setprecision { public: setprecision(int n) { // 实现设置输出精度的逻辑 } // 其他重载运算符和成员函数 }; } // exception namespace mystd { class exception { public: virtual const char* what() const noexcept { return "Unknown exception"; } }; } // climits namespace mystd { constexpr int INT_MAX = 2147483647; // 其他常量的定义 } // array namespace mystd { template<typename T, std::size_t N> class array { public: using value_type = T; // 实现数组的各种函数 }; } // cstdint namespace mystd { using int8_t = signed char; using int16_t = short int; using int32_t = int; using int64_t = long long int; // 其他数据类型的定义 } // string namespace mystd { class string { public: // 实现字符串的各种函数 }; } ``` 以上代码只是简单实现了各个STL容器的基本功能,具体实现方式和函数可能会有所不同,仅供参考。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值