C++ day35 string类


类库的最大用武之地就是代码重用。所以任何语言都有自己配套的各种标准库或者第三方库,就是为了把基本的轮子用高质量高效率的代码实现好封装起来,让用户专注于自己的业务开发,不用去实现基础轮子,把精力集中于方案,算法,实验上。

string类

string类的内容非常多。。。

很多很多应用都是需要处理字符串的,一定要建立起对字符串重要性的认识。C语言在头文件string.h中提供了很多字符串函数。C++的头文件string中提供了string类,string类给了很多方法,包括很多构造函数,把字符串赋给变量的函数,合并字符串的函数,比较字符串的函数,访问字符串的每一个字符的重载运算符函数,在字符串中查找某个字符,查找某个子串等算法封装函数。

要想使用一个类,最关键的就是知道他的公有接口,他的公有方法。

string类的真面目:模板类basic_string的类型参数为char的具体化

其实string类并不是简单的一个独立的类,它其实是模板类basic_string的一个类型参数为char的具体化版本,然后用了typedef取名叫做string而已。

但是平时所有的使用,string类的所有方法包括构造函数,你都是看不出这一点的,因为这一点是被隐藏的。同时被隐藏的还有和内存管理有关的一些参数。

typedef basic_string<char> string;

强大的泛型编程,,,我的内心是震惊的

string类的9个构造函数:创建string类对象

constructors

NBTS:null-terminated string,即传统C字符串,以空字符结尾的字符串

size_type是一个依赖于实现的整型,在头文件string中定义

string::npos是字符串的最大长度,一般是unsigned int的最大值。

默认构造函数,复制构造函数,移动构造函数(move constructor)。。。
在这里插入图片描述

示例:7个旧的构造函数,重载=,重载+=,重载<<,重载[]

//str1.cpp
#include <iostream>
#include <string>

int main()
{
	using std::string;
	using std::cout;
	using std::endl;

	string one("Lottery Winner!");//constructor #1
	cout << one << endl;//重载<<运算符用于显示字符串对象

	string two(20, '$');//constructor #2
	cout << two << endl;

	string three(one);//constructor #3,复制构造函数
	cout << three << endl;

	one += "oops!";//重载+=运算符
	cout << one << endl;

	two = "sorry, that was ";//重载运算符=
	three[0] = 'p';//重载了下标运算符,以数组表示法来访问string对象的各个字符
	string four;//不要写为string four()!!!;//constructor #4,默认构造函数,创建一个空串,后面再赋值
	four = two + three;//重载+,字符串拼接
	cout << four << endl;

	char alls[] = "All is well that ends well";
	string five(alls, 20);//constructor #5,只用前20个字符来初始化string对象
	cout << five << "!\n";

	string six(alls + 6, alls + 10);//constructor #6,迭代器,数组名alls是指针,alls+6是char *类型
	//所以char *作为模板的类型参数,替换Iter
	cout << six << endl;

	string six1(&five[6], &five[10]);//constructor #6,迭代器,five[6]是一个char,所以&five[6]是指针
	cout << six1 << "```\n";

	string seven(four, 5, 10);//constructor #7,从第7个位置(第8个字符)开始,复制10个字符
	cout << seven << endl;

	return 0;
}

Lottery Winner!
$$$$$$$$$$$$$$$$$$$$
Lottery Winner!
Lottery Winner!oops!
sorry, that was pottery Winner!
All is well that end!
 wel
 wel```
, that was

可以看到,+,+=,=运算符都是多次重载的,有多个重载版本。主要是因为有C风格字符串和单个字符的存在,所以需要重载多个版本。

one += two;//重载+=,以加一个string对象
one += '!';//重载+=,以加一个字符
two = "OK, OK";//重载=,把一个c风格字符串赋值给string对象,two.operator=("OK, OK")
two = one;//重载=, 把string对象赋值给string对象,two.operator=(one)
two = '?';//重载=,把单个字符赋值给string对象,two.operator=('?')

注意,第5个构造函数的设计不太安全,因为当n大于字符串长度时,还会复制内存中挨着的部分,于是就会出错:

string five(alls, 30);//上面代码的n改为30

复制后了后面的内容,该内容可能不是字符,所以打印出来就是乱码。解码失败。

All is well that ends well 权?

第6个构造函数有一个模板参数,

template<class Iter> string(Iter begin, Iter end);//begin和end是迭代器,就像指针一样,指向内存中的位置。
//构造函数使用begin和end之间的值来初始化string对象。
//[begin, end),包括begin,不包括end

迭代器是广泛用于STL的广义化指针
迭代器是广泛用于STL的广义化指针
迭代器是广泛用于STL的广义化指针
迭代器是广泛用于STL的广义化指针
迭代器是广泛用于STL的广义化指针

C++11新增的两个构造函数

  • 移动构造函数

类似于复制构造函数,会创建一个新的string对象作为传入string对象的副本。但是区别是:它不会把传入的string对象视为const, 即会改变传入对象。

有时候,使用移动构造函数可以优化代码,编译器会专门用它来优化代码性能。这属于移动语义和右值引用的范畴内的内容,后面会说。

  • string (initializer_list<char> il)

这种构造函数用了模板initializer_list, 主要是为了把列表初始化用在string类中。

即:

string pianoMan = {'L', 'i', 's', 'z', 't'};
string comp_lang = {'L', 'i', 's', 'p'};

显然,这种用法并不常用,也没人会主动去用,但是C++11把他添加进来,主要是为了让列表初始化普遍化,而且有利于C风格字符串的使用。

string类的输入方式

回顾C风格字符串的3种输入方式(cin,get, getline)

//方式1
char info[100];
cin >> info;//读取一个词语,相当于cin.operator>>(info),重载了>>运算符

//方式2
cin.getline(info, 100);//读取一行,并删除换行符
cin.getline(info, 100, ':');//有一个可选参数,用于指定用那哪个字符来确定输入的边界
//读取到:就停止,但是:会从输入队列中被删除

//方式3
cin.get(info, 100);//读取一行,不删除换行符

string对象的2种输入方式(cin, getline)

//方式1
string stuff;
cin >> stuff;//读取一个词语,相当于operator>>(cin, stuff)

//方式2
getline(cin, stuff);//读取一行,并删除换行符
//但是!第二个参数是string类对象的getline方法(getline方法有多个重载版本),会自动调整目标string对象的大小,使之刚好能存储输入的字符,即对动态分配内存
getline(stuff, ':');//有一个可选参数,用于指定用那哪个字符来确定输入的边界
//读取到:就停止,但是:会从输入队列中被删除

getline(stuff, ':');叫做指定分界符的getline方法。

C字符串和string对象在cin和getline()中的区别

主要区别:

  • string版本的都会自动调整大小,很安全,不会出现内存问题。

string类对象的字符长度也不是完全没有任何限制!!!其实string对象的最大允许长度是string::npos这个常量指定的,他一般是最大的uint值

但是很显然,一般情况,是不可能需要那么长的长度的,所以这个限制就像是没有限制一样的。但是如果你要把一个很大很大的文件读取为一个string对象,那么就可能会达到这个限制,那就会有影响了。

还有一个隐形限制程序可以使用的内存量。如果程序总共可以使用的内存量小于uint的最大值,那当然就存不了那么多字符了。

string版本的getline函数,发生这三种情况时会停止读取:

  • 到达文件尾。即从文件读取输入,而不是从标准输入(键盘)读取时,遇到文件结尾符号EOF(end of file),输入流的eofbit(一个标记位,用了一个寄存器,占1个bit)会被设置为1;而一旦这个标记位被设置为1,方法==fail()和eof()==都会返回true。
  • 遇到分界字符\n。这时候,会把分界字符\n从输入流中删除,但不存储。
    读取的字符数目达到了最大允许数目,即string::npos的值。则输入流的failbit标记位会被设置为1,从而使得fail()方法返回true.

而cin会把后面的空白符和剩余字符留在输入队列中,并不会扔掉他们哦,程序结束刷新缓冲区才会扔掉他们。(后面有例子)

输入流对象cin,有一个统计系统,专门用来跟踪输入流的错误状态

  • 如果检测到文件结尾,就会把eofbit设置为1,;
  • 如果检测到输入错误,就会把failbit设置为1;
  • 如果出现硬盘故障等无法识别的故障,就会把badbit设置为1;
  • 如果什么问题都没有,一切顺利,就会把goodbit寄存器设置为1.

回忆起做华为软挑比赛的时候,我就是把整个输入文件读取为一个string字符串对象,再进行的后续操作。。。。不过当时300万条转账数据好像也没有达到这个限制。算一下,每条数据算50个字符,则有1.5亿的字符,1.5亿小于uint最大值(2147483648*2-1,40多亿呢),哈哈,当时不懂这个,不过幸好也没有超限。

  • 读取C风格字符串的getline函数是istream类的方法,要用cin对象去调用;但是读取string对象的getline函数是一个独立的方法,cin对象只是函数的一个参数。
示例:可以看到string字符串在内存上是多么安全(都要归功于动态内存分配)

char c[10];
cin >> c;
cout << c << endl;

输入多于10个字符,就出问题了,虽然这里都显示正确了,但是覆盖了别人的内存,应该是引发了某种内存相关的异常,所以程序立刻异常终止。

fdafadsfaffd
fdafadsfaffd


Process returned -1073741819 (0xC0000005)   execution time : 6.872 s
Press any key to continue.

char c[10];
cin.getline(c, 10);
cout << c << endl;

输入虽然多于10,但是只读取了前10个字符,即getline函数做了截断处理,truncation,所以程序没有崩溃,因为只存了前10个字符,内存没有覆盖,也没有泄漏,程序正常结束。

可见对于C字符串,getline函数还是比cin要安全一些。

dafadsfadsdsff
dafadsfad
dafadsfad

Process returned 0 (0x0)   execution time : 6.338 s
Press any key to continue.

string对象的cin输入,很安全,会自己分配内存,没有出现任何内存问题

string s;
cin >> s;
cout << s << endl;
dsfadsfdasfadsfasdas
dsfadsfdasfadsfasdas

Process returned 0 (0x0)   execution time : 2.021 s
Press any key to continue.

string s;
cin >> s;
cout << s << endl;

cin输入到string对象的时候,遇到空白符就会停止,而getline不会。

cin会把后面的空白符和剩余字符留在输入队列中,并不会扔掉他们哦,程序结束刷新缓冲区才会扔掉他们。

fedfa feafe
fedfa

string s;
getline(cin, s);
cout << s << endl;

string版本的getline,不会截断也不会引发内存错误异常,而是自己在函数里动态的分配了够用的内存,保证了字符串输入准确无误。

fdsafadsdasfdasfdasf
fdsafadsdasfdasfdasf

Process returned 0 (0x0)   execution time : 2.254 s
Press any key to continue.

通过这几个示例,可以看到,C字符串用cin,一旦超出C字符串数组长度,会出大事,会引发异常,程序会异常终止;

C字符串用getline, 内存问题没了,可是信息也没了,原来想输入的信息被直接截断,是一种不太完美的安全行为,总觉得不够好,这看似是解决了,可是只是把一种问题转变为另一种问题而已,并没有在根本上解决问题。

string对象用cin搭配>>,getline,都是妥妥的完美解决方案,不会有内存问题,也不会有信息截断的问题。很安全,当然这是因为string类在背后默默做了很多事情,比如在后台默默动态分配足够的内存,以保证程序正常运行。

但是cin输入到string对象的时候,遇到空白符就会停止,而getline不会。所以如果需要输入一个词语,就可以用cin。

而且string对象的输入,根本不需要指定字符数目,一直觉得C指定字符数目这个操作只适合嵌入式或者其他硬件相关的编程,控制性极强,以灵活性为代价,不太适合大多数更普遍的更复杂的字符串操作。

示例:string对象的输入,指定分界符的getline方法

看来我是真的没懂分界符的含义。。。在下面花式乱写文件中的单词格式,,,

//strfile.cpp -- 从文件中读取信息到字符串对象
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>

int main()
{
	using namespace std;
	ifstream fin;
	fin.open("tobuy.txt");
	if (fin.is_open() == false)
	{
		cerr << "Can't open file. Bye!\n";
		exit(EXIT_FAILURE);
	}
	string item;
	int ct = 0;
	getline(fin, item, ':');
	while (fin)
	{
		++ct;
		cout << ct << ": " <<item << endl;
		getline(fin, item, ':');
	}
	cout << "Done!\n";
	fin.close();
	return 0;
}

第一次,完全忘记了自己在用冒号作为分界符。。导致getline函数一次读取完了
在这里插入图片描述

1: apple
pear
grape
book
pen
keyboard
water
jeans
Done!

这一次,记起来冒号了,但是还是用了换行符,所以除了第一个单词外,每个单词第一个字符都是换行符。

一旦给getline方法指定了非换行符的分界符,那么换行符就成了常规字符,并不会被区别对待哦。

在这里插入图片描述

如果不给getline指定分界符getline(fin, item);,那么换行符就是默认的分界符号
在这里插入图片描述
在这里插入图片描述

1: apple
2:
pear
3:
grape
4:
book
5:
pen
6:
keyboard
7:
water
8:
jeans
Done!

在这里插入图片描述

1: apple
2: pear
3: grape
4: book
5: pen
6: keyboard
7: water
8: jeans
Done!

注意如果文件不在工程目录下(不需要在bin目录下哦),那就用双斜杠表示路径:

因为windows系统中,C风格字符串下,斜杠表示转义符号,两个斜杠才表示斜杠

fin.open("E:\\HelloAlgorithm\\CPP\\tobuy.txt");

如果只用单个斜杠fin.open("E:\HelloAlgorithm\CPP\tobuy.txt");,那么得到的结果是

Can't open file. Bye!

如果非要只用一个斜杠,那就反过来,这样也是可以的:

fin.open("E:/HelloAlgorithm/CPP/tobuy.txt");

字符串的其他操作

目前已经学了:

  • 构造函数可以创建string类的对象;
  • cout可以显示string对象;
  • cin和getline可以把数据读取到string对象中;
  • 复制构造函数和重载赋值运算符=可以给string对象赋值;
  • 重载+可以拼接俩string对象,或者拼接string对象和C字符串(不可以拼接俩C字符串!)

字符串的比较:重载了全部6个关系运算符,且每一个用3种方式重载

C语言使用strcmp函数比较字符串

string类重载了>, < , >=, <=, ==, !=六个关系运算符,并且每一个都有三种重载方式:

  • 比较俩string对象
  • 比较string对象和C风格字符串
  • 比较C风格字符串和string对象
    在这里插入图片描述

获取字符串的长度:size(), length()

这俩函数都返回字符串的长度,即字符串的字符数目,只不过length()函数出生的更早,string类最早就是用它,但是后来STL的容器大小都用size()函数来求,所以为了统一,为了提供STL兼容,string类就做了一个size()方法,但是二者完全没差,一样的功能。

示例:capacity()和size()区别;reserve()方法

我们知道string类可以自动调整string对象的大小,即你给他加一个字符或者加一个子串拼接起来,他也可以把已有的字符串加大,而不会发生溢出啥的内存问题。但是它具体是怎么实现的呢??

当然不是直接new一块新内存,然后把新添加的部分存在那里,再用指针把一个字符串的不同存储部分链接起来。我之前一直私以为是这样,实际并不是这样的。

也不是直接new一块新的刚好是拼接后字符串的长度的内存,然后把原来的内存位置销毁,整个地搬到新位置。

因为这两种方式需要频繁分配内存,效率很低。

而是分配一个比实际字符串大得多(比如两倍甚至更多)的内存,以提供足够大的空间,避免要不断分配新内存,降低效率

  • capacity()返回当前分配给字符串对象的内存块的大小,以字节为单位,一般都比实际需要的多
  • reserve()允许程序员自己请求分配多大内存块,参数是自己预定的内存块的最小长度
  • size()返回当前实际使用的内存块大小,即字符数目
int main()
{
	using std::cout;
	using std::cin;
	using std::tolower;
	using std::endl;
	string empty;
	string small = "bit";
	string large = "Elepante are a girl's best friend";
	cout << "Sizes:\n";
	cout << "\tempty: " << empty.size() << endl;
	cout << "\tsmall: " << small.size() << endl;
	cout << "\tlarge: " << large.size() << endl;
	cout << "Capacities:\n";
	cout << "\tempty: " << empty.capacity() << endl;
	cout << "\tsmall: " << small.capacity() << endl;
	cout << "\tlarge: " << large.capacity() << endl;
	empty.reserve(50);
	cout << "Capacity after empty.reserve(50): "
		 << empty.capacity() << endl;
	cout << "Bye!\n";
	return 0;
}

我的编译器的实现采用的方案是:最少容量为15字符,即只要小于15字符,我都直接给分配15字符。其它编译器的实现不一定也是15哈

Sizes:
        empty: 0
        small: 3
        large: 33
Capacities:
        empty: 15
        small: 15
        large: 33
Capacity after empty.reserve(50): 50
Bye!

字符串搜索:搜索或查找子串或字符

find()函数

string类提供了一个find方法的4个重载版本
在这里插入图片描述在这里插入图片描述

示例 猜字符游戏:find()函数,重载关系运算符
//hangman.cpp
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <cctype>
using std::string;
const int NUM = 26;
const string wordlist[NUM] =
{"apiary", "beetle", "cereal", "danger",
"ensign", "florid", "garage", "health", "insult",
"jackal", "keeper", "loaner", "manage", "nonce", "onset",
"plaid", "quilt", "remote", "stolid", "train", "useful", "valid", "whence",
"xenon", "yearn", "zippy"};

int main()
{
	using std::cout;
	using std::cin;
	using std::tolower;
	using std::endl;
	std::srand(std::time(0));
	char play;
	cout << "Will you play a word game? <y/n>";
	cin >> play;
	play = tolower(play);
	while (play == 'y')
	{
		string target = wordlist[std::rand() % NUM];
		int length = target.length();
		string attempt(length, '-');
		string badchars;
		int guesses = 6;
		cout << "Guess my word. It has "
			 << length << " letters, and you guess\n"
			 << "one letter at a time. You get " << guesses
			 << " wrong guesses.\n";
		cout << "Your word: " << attempt << endl;
		while (guesses > 0 && attempt != target)
		{
			char letter;
			cout << "Guess a letter: ";
			cin >> letter;
			//如果在猜错的badchars对象或者猜对的attempt中找到刚输入的字符,则提示已经猜过了
			if (badchars.find(letter) != string::npos
				|| attempt.find(letter) != string::npos){
				cout << "You already guessed that. Try again!\n";
				continue;
			}
			int loc = target.find(letter);
			if (loc == string::npos){
				cout << "Oh, bad guess!\n";
				--guesses;
				badchars += letter;//重载+运算符
			}
			else{
				cout << "Good guess!\n";
				attempt[loc] = letter;
				loc = target.find(letter, loc + 1);
				while (loc != string::npos){
					attempt[loc] = letter;//放在答案字符串的对应位置
					//一个字符可以在一个字符串出现多次,所以还要继续找
					loc = target.find(letter, loc + 1);
				}
			}
			cout << "Your word:" << attempt << endl;
			if(attempt != target){
				if (badchars.length()>0)
					cout << "Bad choices: " << badchars << endl;
				cout << guesses << " bad guesses left\n";

			}
		}
		if (guesses > 0)
			cout << "That's right!\n";
		else
			cout << "Sorry, the word is " << target << ".\n";
		cout << "Will you play another? <y/n>";
		cin >> play;
		play = tolower(play);
	}
	cout << "Bye!\n";
	return 0;
}
Will you play a word game? <y/n>y
Guess my word. It has 6 letters, and you guess
one letter at a time. You get 6 wrong guesses.
Your word: ------
Guess a letter: a
Good guess!
Your word:--a---
6 bad guesses left
Guess a letter: l
Good guess!
Your word:l-a---
6 bad guesses left
Guess a letter: o
Good guess!
Your word:loa---
6 bad guesses left
Guess a letter: n
Good guess!
Your word:loan--
6 bad guesses left
Guess a letter: r
Good guess!
Your word:loan-r
6 bad guesses left
Guess a letter: e
Good guess!
Your word:loaner
That's right!
Will you play another? <y/n>y
Guess my word. It has 6 letters, and you guess
one letter at a time. You get 6 wrong guesses.
Your word: ------
Guess a letter: f
Good guess!
Your word:---f--
6 bad guesses left
Guess a letter: g
Oh, bad guess!
Your word:---f--
Bad choices: g
5 bad guesses left
Guess a letter: h
Oh, bad guess!
Your word:---f--
Bad choices: gh
4 bad guesses left
Guess a letter: v
Oh, bad guess!
Your word:---f--
Bad choices: ghv
3 bad guesses left
Guess a letter: h
You already guessed that. Try again!
Guess a letter: p
Oh, bad guess!
Your word:---f--
Bad choices: ghvp
2 bad guesses left
Guess a letter: o
Oh, bad guess!
Your word:---f--
Bad choices: ghvpo
1 bad guesses left
Guess a letter: w
Oh, bad guess!
Your word:---f--
Bad choices: ghvpow
0 bad guesses left
Sorry, the word is useful.
Will you play another? <y/n>y
Guess my word. It has 6 letters, and you guess
one letter at a time. You get 6 wrong guesses.
Your word: ------
Guess a letter: c
Oh, bad guess!
Your word:------
Bad choices: c
5 bad guesses left
Guess a letter: h
Oh, bad guess!
Your word:------
Bad choices: ch
4 bad guesses left
Guess a letter: q
Oh, bad guess!
Your word:------
Bad choices: chq
3 bad guesses left
Guess a letter: w
Oh, bad guess!
Your word:------
Bad choices: chqw
2 bad guesses left
Guess a letter: e
Good guess!
Your word:-ee--e
Bad choices: chqw
2 bad guesses left
Guess a letter: r
Oh, bad guess!
Your word:-ee--e
Bad choices: chqwr
1 bad guesses left
Guess a letter: y
Oh, bad guess!
Your word:-ee--e
Bad choices: chqwry
0 bad guesses left
Sorry, the word is beetle.
Will you play another? <y/n>

npos是string类的静态变量,静态数据成员。一般是int或者long的最大值,比如 2 3 2 2^32 232,不用减1,因为它不从0开始,他又不是索引。

其他

这5个方法的重载特征标和find方法一样

  • rfind():查找子串最后一次出现的位置

  • find_first_of():查找参数中任何一个字符首次出现的位置

  • find_last_of():查找参数中任何一个字符最后出现的位置

  • find_first_not_of():查找第一个不包含在参数中的字符

  • find_last_not_of():查找最后一个不包含在参数中的字符

string对象转C风格字符串:c_str()方法

有的时候真有这种需求,比如open()方法,它只接受C字符串作为参数,不接受string对象,但是如果你的文件名存在string对象里,那么怎么办呢?

可以用c_str()方法,它返回一个指向c字符串的指针,这个C字符串的内容和string对象的内容一样,但注意,并不是真的把string对象转为了指针哈,只是相当于创建一个C风格字符串,然后把string对象的内容复制过去而已。

string filename;
cout << "Enter file name: ";
cin >> filename;
ofstream fout;
fout.open(filename.c_str());

string类的兄弟姐妹:wstring,u16string, u32string

文首一上来就说了string类的本质,它是基于char数据类型的对于模板类basic_string的一个具体化版本,string是用typedef定义的名字:

typedef basic_string<char> string;

其实,string类还有三个兄弟姐妹,都是对basic_string类的具体化,只不过是基于char类型的兄弟姐妹们(wchar_t,char16_t,char32_t)来的,他们也都用typedef起了简短的名字:

typedef basic_string<wchar_t> wstring;
typedef basic_string<char16_t> u16string;//C++11新增
typedef basic_string<char32_t> u32string;//C++11新增

basic_string类的声明头部:

template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> >
basic_string{···};

其中traits类描述了字符类型,char,wchar_t,char16_t,char32_t都是traits的默认值。

Allocator类管理内存分配,使用new和delete,每一个字符类型都有预定义的allocator模板具体化。

如果你愿意并且能力足够,也可以开发自己的字符类,然后用它去将basic_dtring模板类具体化。

STL标准模板库:一组用于处理各种容器对象的模板

STL演示的是泛型编程。

initializer_list:C++11新增模板,把初始化列表语法用于STL对象

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值