C++核心编程Day09

1.STL容器

1.1 set容器
	1.1.1  set容器基本概念
		Set的特性是:所有元素都会根据元素的键值自动被排序。Set的元素不像map那样可以
	同时拥有实值和键值,set的元素即是键值又是实值。Set不允许两个元素有相同的键值。
		set拥有和list某些相同的性质,当对容器中的元素进行插入操作或者删除操作的时候,
	操作之前所有的迭代器,在操作完成之后依然有效,被删除的那个元素的迭代器必然是一
	个例外。
	
	1.1.2  multiset容器基本概念
		multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。set和multiset
	的底层实现是红黑树,红黑树为平衡二叉树的一种。
		当一个二叉搜索树的左子树和右子树不平衡的时候,搜索最大值所花费的时间要比
	搜索最小值所花费的时间要多,由于我们的输入或者经过我们插入或者删除操作,二
	叉树失去平衡,造成搜索效率降低。
		所以我们有了一个平衡二叉树的概念,所谓的平衡不是指的完全平衡。

	1.1.3 set容器常用API
		1.1.3.1 set构造函数
		set<T> st;//set默认构造函数:
		mulitset<T> mst; //multiset默认构造函数: 
		set(const set &st);//拷贝构造函数
		
		1.1.3.2 set赋值操作
		set&operator=(const set &st);//重载等号操作符
		swap(st);//交换两个集合容器
		
		1.1.3.3 set大小操作
		size();//返回容器中元素的数目
		empty();//判断容器是否为空
		
		1.1.3.4 set插入和删除操作
		insert(elem);//在容器中插入元素。
		clear();//清除所有元素
		erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
		erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
		erase(elem);//删除容器中值为elem的元素。
		
		1.1.3.5 set查找操作
		find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
		1.count(key);//查找键key的元素个数
		2.lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
		3.upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
		equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<set>
#include<algorithm>
using namespace std;

void test()
{
	set<int>::iterator it;
	it++;
	it--;
	//it + 2;//error 双向访问迭代器
}

void printSet(set<int>& s)
{
	for (set<int>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

//构造操作
//赋值操作
//大小操作
//插入和删除操作
void test01()
{
	set<int> s;
	s.insert(5);
	s.insert(8);
	s.insert(2);
	s.insert(4);
	s.insert(3);
	s.insert(1);
	//set容器不允许有两个相同的元素
	//底下为原理
	pair<set<int>::iterator,bool>ret = s.insert(1);
	if (ret.second == true)
		cout << "插入成功!" << endl;
	else
		cout << "插入失败!" << endl;

	s.erase(2);
	s.erase(s.begin());
	printSet(s);
}
//改变set容器的排序规则

struct MyFunc
{
	bool operator()(int a1, int a2) const
	{
		return a1 > a2;
	}
};

void printSet1(set<int, MyFunc>& s)
{
	for (set<int, MyFunc>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

void test02()
{
	set<int, MyFunc> s;
	s.insert(5);
	s.insert(8);
	s.insert(2);
	s.insert(4);
	s.insert(3);
	s.insert(1);
	s.insert(1);

	printSet1(s);

}

//通过算法排序
void mySort(int a)
{
	cout<<a<<" ";
}
void test03()
{
	multiset<int> s;
	s.insert(5);
	s.insert(8);
	s.insert(2);
	s.insert(4);
	s.insert(3);
	s.insert(1);
	s.insert(1);
	//不能通过算法排序来排关联式容器
	//sort(s.begin(), s.end());
	
	for_each(s.begin(), s.end(), mySort);
}

//查找操作
void test04()
{
	set<int> s;
	s.insert(5);
	s.insert(8);
	s.insert(2);
	s.insert(4);
	s.insert(3);

	set<int>::iterator it = s.find(4);
	if (it == s.end())
		cout << "查找失败!" << endl;
	else
		cout << "查找成功!" <<*it<< endl;

	//查找大于等于1的最小的数,如果有要查找的数字,则返回该数字,否则返回该数字的距离最近的下一位数字
	it = s.lower_bound(1);
	//查找大于1的最小的数,如果有要查找的数字,则返回该数字,否则返回该数字的距离最近的下一位数字
	//it = s.upper_bound(1);
	if (it == s.end())
		cout << "查找失败!" << endl;
	else
		cout << "查找成功!" << *it << endl;

	//返回大于等于被查数的两个最小数,如果有该数那么返回该数和大于该数的最小数,如果没有则返回两个大于该数的相等的最小数
	pair<set<int>::iterator, set<int>::iterator>ret = s.equal_range(1);

	cout << *(ret).first << endl;
	cout << *(ret).second << endl;

	multiset<int> s1;
	s1.insert(5);
	s1.insert(8);
	s1.insert(2);
	s1.insert(5);
	s1.insert(4);
	s1.insert(3);
	cout << s1.count(3) << endl;
}

//存储对象
class Student
{
public:
	Student(string name, int age)
	{
		this->age = age;
		this->name = name;
	}

	string name;
	int age;
};

struct Myfunc1
{
	bool operator()(const Student& a1,const Student& a2) const
	{
		return a1.age > a2.age;
	}
};

void test05()
{
	//set容器储存对象的时候,需要人为的告诉他规则
	set<Student, Myfunc1> s;
	s.insert(Student("aaa", 18));
	s.insert(Student("bbb", 19));
	s.insert(Student("ccc", 17));
	s.insert(Student("ddd", 21));
	s.insert(Student("eee", 20));

	for (set<Student, Myfunc1>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << "姓名:" << (*it).name << " 年龄:" << (*it).age << endl;
	}
}

int main()
{
	//test01();
	//test02();
	//test03();
	//test04();
	test05();

	system("pause");
	return 0;
}
1.2 对组
	对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分
别用pair的两个公有属性first和second访问。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<vector>
using namespace std;

int main()
{
	pair<string, int> mpr("aa", 10);

	cout << mpr.first << " " << mpr.second << endl;

	//通常使用对组来接收键值对(Key-Value)类型的数据


	system("pause");
	return 0;
}
1.3 map容器
1.3.1 map/multimap容器基本概念
	Map的特性是,所有元素都会根据元素的键值自动排序。Map所有的元素都是pair,同时
拥有实值和键值,pair的第一元素被视为键值,第二元素被视为实值,map不允许两个元
素有相同的键值。
	Map和list拥有相同的某些性质,当对它的容器元素进行新增操作或者删除操作时,操
作之前的所有迭代器,在操作完成之后依然有效,当然被删除的那个元素的迭代器必然
是个例外。
	Multimap和map的操作类似,唯一区别multimap键值可重复。
	Map和multimap都是以红黑树为底层实现机制。
	
1.3.2 map容器常用API
	1.3.2.1 map构造函数
	map<T1, T2> mapTT;//map默认构造函数: 
	map(const map &mp);//拷贝构造函数
	
	1.3.2.2 map赋值操作
	map&operator=(const map &mp);//重载等号操作符
	swap(mp);//交换两个集合容器
	
	1.3.2.3 map大小操作
	size();//返回容器中元素的数目
	empty();//判断容器是否为空
	
	1.3.2.4 map插入数据元素操作
	map.insert(...); //往容器插入元素,返回pair<iterator,bool>
	map<int, string> mapStu;
	// 第一种 通过pair的方式插入对象
	mapStu.insert(pair<int, string>(3, "小张"));
	// 第二种 通过pair的方式插入对象
	mapStu.inset(make_pair(-1, "校长"));
	// 第三种 通过value_type的方式插入对象
	mapStu.insert(map<int, string>::value_type(1, "小李"));
	// 第四种 通过数组的方式插入值
	mapStu[3] = "小刘";
	mapStu[5] = "小王";
	
	1.3.2.5 map删除操作
	clear();//删除所有元素
	erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
	erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
	erase(keyElem);//删除容器中key为keyElem的对组。
	
	1.3.2.6 map查找操作
	find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回map.end();
	count(keyElem);//返回容器中key为keyElem的对组个数。对map来说,要么是0,要么是1。对multimap来说,值可能大于1。
	lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
	upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。	
	equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<map>
#include<algorithm>
using namespace std;

void test()
{
	map<int, string>::iterator it;
	it++;
	it--;
	//it + 2;//并不是随机访问迭代器,是双向迭代器
}
//构造操作
//大小操作
//插入操作
void myPrint(map<int, string>& t)
{
	for (map<int, string>::iterator it = t.begin(); it != t.end(); it++)
	{
		cout << "Key:" << (*it).first << " Value:" << (*it).second << endl;;
	} 
}

void test01()
{
	//4种插入方法
	map<int, string> m;
	//1.
	m.insert(pair<int, string>(3, "aaa"));
	//2.
	m.insert(make_pair(6, "bbb"));
	//3.
	m.insert(map<int, string>::value_type(2, "ccc"));
	//4.
	m[4] = "ddd";//4是键值,ddd是实值
	myPrint(m);

	cout << "Size:" << m.size() << endl;
	cout << m[100] << endl;
	cout << "Size:" << m.size() << endl;

}

//改变规则排序
struct Myfunc1
{
	bool operator()(int v1, int v2) const
	{
		return v1 > v2;
	}
};

void myPrint1(map<int, string, Myfunc1>& t)
{
	for (map<int, string, Myfunc1>::iterator it = t.begin(); it != t.end(); it++)
	{
		cout << "Key:" << (*it).first << " Value:" << (*it).second << endl;;
	}
}

void test02()
{
	//4种插入方法
	map<int, string,Myfunc1> m;
	//1.
	m.insert(pair<int, string>(3, "aaa"));
	//2.
	m.insert(make_pair(6, "bbb"));
	//3.
	m.insert(map<int, string>::value_type(2, "ccc"));
	//4.
	m[4] = "ddd";//4是键值,ddd是实值
	myPrint1(m);
}

//查找操作
void test03()
{
	map<int, string> m;
	m[1] = "aaa";
	m[2] = "bbb";
	m[3] = "ccc";
	m[4] = "ddd";
	m[5] = "eee";

	map<int, string>::iterator it = m.find(5);
	if (it == m.end())
		cout << "查找失败!" << endl;
	else
		cout << "查找成功!" << (*it).second <<endl;

	//查找大于等于0的最小的数,如果有要查找的数字,则返回该数字,否则返回该数字的距离最近的下一位数字
	it = m.lower_bound(0);
	if (it == m.end())
		cout << "查找失败!" << endl;
	else
		cout << "查找成功!" << (*it).second << endl;

	//查找大于6的最小的数,如果有要查找的数字,则返回该数字,否则返回该数字的距离最近的下一位数字
	it = m.upper_bound(6);
	if (it == m.end())
		cout << "查找失败!" << endl;
	else
		cout << "查找成功!" << (*it).second << endl;

	//查找成功返回当前元素以及下一个元素的迭代器,未查找到则报错
	cout << "--------------------" << endl;
	pair<map<int,string>::iterator,map<int,string>::iterator>ret = m.equal_range(5);
	if (ret.first == m.end())
		cout << "查找失败!" << endl;
	else
		cout << "查找成功!" << " "<<
		"第一个对组元素中的Key:"<<ret.first->first << "第一个对组元素中的Value:" << ret.first->second << endl;

	if (ret.second != m.end())
		cout << "第二个对组元素中的Key:" << ret.second->first << "第二个对组元素中的Value:" << ret.second->second << endl;
	else
		cout << "查找失败!" << endl;
}

int main()
{
	//test01();
	//test02();
	test03();

	system("pause");
	return 0;
}
	1.3.2.7 multimap案例
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<algorithm>
#include<ctime>
using namespace std;
#define SALE_DEPATMENT 1//销售
#define DEVELOP_DEPATMENT 2//研发
#define FINACIAL_DEPATMENT 3//财务

class Student
{
public:
	Student(){}
	Student(string name,int age,int salary)
	{
		this->name = name; 
		this->age = age;
		this->salary = salary;
	}
public:
	string name;
	int age;
	int salary;
};

//创建员工
void createStudent(vector<Student>& v)
{
	string randName = "ABCDE";

	for (int i = 0; i < 5; i++)
	{
		Student s;
		s.age = rand() % 20 + 18;
		s.salary = rand() % 1000 + 6000;
		s.name = "员工";
		s.name += randName[i];

		v.push_back(s);
	}
}
//员工分组
void studentWork(vector<Student>& v, multimap<int, Student>& m)
{
	int len = v.size();

	for (vector<Student>::iterator it = v.begin(); it != v.end(); it++)
	{
		int temp = rand() % 3 + 1;
		pair<int, Student> tempStu(temp, *it);
		m.insert(tempStu);
	}

	v.erase(v.begin(), v.end());
}
//打印信息
void printStu(multimap<int, Student> &m)
{
	for (multimap<int, Student>::iterator it = m.begin(); it != m.end(); it++)
	{
		switch(it->first)
		{
		case SALE_DEPATMENT:
			cout << "部门:SALE_DEPATMENT(销售)" << endl;
			break;
		case DEVELOP_DEPATMENT:
			cout << "部门:DEVELOP_DEPATMENT(研发)" << endl;
			break;
		case FINACIAL_DEPATMENT:
			cout << "部门:FINACIAL_DEPATMENT(财务)" << endl;
			break;
		default:
			break;
		}
		cout <<"\t员工姓名:" << it->second.name << " 员工年龄:" << it->second.age << " 员工工资:" << it->second.salary << endl;
	}
}

void test()
{
	srand((unsigned int)time(NULL));
	//保存未分组的员工信息
	vector<Student> v;
	//保存分组后的员工信息
	multimap<int, Student> m;

	//创建员工
	createStudent(v);
	//员工分组
	studentWork(v,m);
	//打印信息
	printStu(m);

}

int main()
{
	test();
	system("pause");
	return 0;
}
1.4 STL中的深浅拷贝
	STL容器所提供的都是值,也就是说当我们给容器中插入元素的时候,容器内部实施
了拷贝动作,将我们要插入的元素再另行拷贝一份放入到容器中,而不是将原数据元素
直接放进容器中,也就是说我们提供的元素必须能够被拷贝。因此:
	1.拷贝构造要能被调用
	2.注意浅拷贝问题

2.函数对象

2.1 基本概念
	重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是
行为类似函数的对象,也叫仿函数(functor),其实就是重载“()”操作符,使得类对象可以
像函数那样调用。
注意:
	1.函数对象(仿函数)是一个类,不是一个函数。
	2.函数对象(仿函数)重载了”() ”操作符使得它可以像函数一样调用。
	3.假定某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,
我们就将这个类称为“一元仿函数”(unary functor);相反,如果重载的operator()
要求获取两个参数,就将这个类称为“二元仿函数”(binary functor)

	谓词:谓词是指普通函数或重载的operator()返回值是bool类型的函数对象(仿函数)。
如果operator接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓
词,谓词可作为一个判断式。	

2.2 函数对象和普通函数的区别
	1.函数对象可以有自己的状态,即普通函数是全局的权限是公共的,但是函数对象可
以是私有的有自己的权限
	2.普通函数没有类型,函数对象有自己的类型
	3.函数对象的执行效率高于普通函数(成员函数自动申请为内联函数)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<vector>
#include<functional>
#include<algorithm>
using namespace std;

bool Myfuc(int a1,int a2)
{
	return a1 > a2;
}

void test01()
{
	vector<int> v;
	v.push_back(5);
	v.push_back(3);
	v.push_back(1);
	v.push_back(2);
	v.push_back(4);
	//sort(v.begin(), v.end(), Myfuc);
	// greater<int>()函数对象,可以作为算法的一种策略
	sort(v.begin(), v.end(), greater<int>());

	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}
//函数对象和普通函数的区别
//1.函数对象可以有自己的状态,即普通函数是全局的权限是公共的,但是函数对象可以是私有的有自己的权限
void func()
{

}
//2.普通函数没有类型,函数对象有自己的类型
struct MyFunc
{
public:
	MyFunc()
	{
		n = 0;
	}
	//函数对象
	void operator()()
	{
		cout << "hhello" << endl;
	}
public:
	int n;

};
//3.函数对象的执行效率高于普通函数(成员函数自动申请为内联函数)

//谓词:普通函数或者是重载的operator()函数返回值是bool类型的函数对象(仿函数),接收参数为一个的时候是一元谓词,两个参数的话是二元谓词
//作用:作为一个判断式


//内建函数对象,需要头文件functional
void test02()
{
	plus<int> myPlus;
	cout << myPlus(10, 20) << endl;
}

int main()
{
	//test01();
	test02();

	system("pause");
	return 0;
}
2.3 函数对象适配
1.进行空间适配,将二元谓词转换为一元谓词
	1.1.1(bind1st和bind2nd)1.进行空间适配第一步,继承binary_function<参数1,参数2,返回值类型>
	1.1.2 第二部加上const称为常函数
	1.1.3 实现函数体
	1.1.4.用bind2nd来绑定函数对象
注:bind1st和bind2nd的区别:bind1st绑定的是第一个参数,bind2nd绑定的是第二个参数

	2.1.1 not1和not2:not1针对一元函数对象,not2针对二元函数对象
	2.1.2 第一步,继承
	2.1.3 3.适配NOT1(针对一元的)(NOT2 针对二元的)
	
2.普通函数进行适配
	2.1 第一步转换为二元参数
	2.2 把普通函数变为函数对象,使用ptr_fun()把普通函数转化为函数对象

3.成员函数适配
	3.1 mem_fun:如果存储的是对象指针,需要使用mem_fun
	3.2 mem_fun_ref:如果存储的是对象,需要使用mem_fun_ref
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<vector>
#include<functional>
#include<algorithm>
using namespace std;

//进行空间适配,将二元谓词转换为一元谓词
//(bind1st和bind2nd)1.进行空间适配第一步,继承binary_function<参数1,参数2,返回值类型>
struct Myfunc:public binary_function<int ,int, void>
{
	void operator()(int v1, int v2) const//2.第二部加上const称为常函数
	{
		cout << "v1 = " << v1 << endl;
		cout << "v2 = " << v2 << endl;

		cout << v1 + v2 << endl;//3.实现函数体
	}
};

void test01()
{
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);

	//for_each(v.begin(), v.end(), [](int val) {cout << val << " "; });
	//4.用bind2nd来绑定函数对象
	for_each(v.begin(), v.end(), bind2nd(Myfunc(),100));
}

//bind1st和bind2nd的区别
//bind1st绑定的是第一个参数,bind2nd绑定的是第二个参数


//(not1和not2):not1针对一元函数对象,not2针对二元函数对象
//1.第一步,继承
struct MyNotFunc:public unary_function<int,bool>
{
public:
	bool operator()(int v) const //2.变为常函数
	{
		return v >= 20;
	}
};

void test02()
{
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);
//	vector<int>::iterator it  = find_if(v.begin(), v.end(), MyNotFunc());
	//3.适配NOT1(针对一元的)
	vector<int>::iterator it = find_if(v.begin(), v.end(), not1(MyNotFunc()));

	if (it == v.end())
		cout << "查找失败!" << endl;
	else
		cout << "查找成功!" << *it << endl;
	//NOT2 针对二元的
	vector<int> v1;
	v1.push_back(20);
	v1.push_back(30);
	v1.push_back(10);
	v1.push_back(50);
	v1.push_back(40);
	//使用适配之前的输出
	sort(v1.begin(), v1.end(), less<int>());
	for_each(v1.begin(), v1.end(), [](int val) {cout << val << endl; });
	//使用适配之后的输出
	sort(v1.begin(), v1.end(),not2(less<int>()));
	for_each(v1.begin(), v1.end(), [](int val) {cout << val << endl; });

}

//普通函数进行适配
//第一步转换为二元参数
void printMyfun(int val,int val2)
{
	cout << val + val2 << endl;
}

void test03()
{
	vector<int> v1;
	v1.push_back(20);
	v1.push_back(30);
	v1.push_back(10);
	v1.push_back(50);
	v1.push_back(40);
	//第二部把普通函数变为函数对象,使用ptr_fun()把普通函数转化为函数对象
	for_each(v1.begin(), v1.end(), bind2nd(ptr_fun(printMyfun),100));
}

//成员函数适配
//1.mem_fun:如果存储的是对象指针,需要使用mem_fun
//2.mem_fun_ref:如果存储的是对象,需要使用mem_fun_ref
class Maker
{
public:
	Maker(string name, int age)
	{
		this->age = age;
		this->name = name;
	}
	void myPrint()
	{
		cout << "Name:" << this->name << " Age:" << this->age << endl;
	}

	int age;
	string name;
};

//void myPrint(Maker &m)
//{
//	cout << "Name:" << m.name << " Age:" << m.age << endl;
//}
void test04()
{
	vector<Maker> v;
	v.push_back(Maker("aa", 10));
	v.push_back(Maker("bb", 20));
	v.push_back(Maker("cc", 30));
	//当容器储存的是对象,那么用mem_fun_ref适配他的成员函数
	for_each(v.begin(), v.end(), mem_fun_ref(&Maker::myPrint));

	vector<Maker*> v1;
	v1.push_back(new Maker("aa", 10));
	v1.push_back(new Maker("bb", 20));
	v1.push_back(new Maker("cc", 30));
	//当容器储存的是对象指针,那么用mem_fun适配他的成员函数
	for_each(v1.begin(), v1.end(), mem_fun(&Maker::myPrint));

}

int main()
{
	//test01();
	//test02();
	//test03();
	test04();


	system("pause");
	return 0;
}
2.4 空间配置器
	如果申请的内存大小超过128,那么空间配置器就自动调用一级空间配置器。反之调
用二级空间配置器。
	一级空间配置器,STL源码中的一级空间配置器命名为class __malloc_alloc_template ,
就是对malloc,free,realloc等系统分配函数的一层封装。二级空间配置器,由一个内
存池和自由链表配合实现的。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值