第四章——复合类型

本章内容包括:
*创建和使用数组
*创建和使用C-风格字符串
*创建和使用string类字符串
*使用方法getline()和get()读取字符串
*混合输入字符串和数字
*创建和使用结构
*创建和使用共用体
*创建和使用枚举
*创建和使用指针
*使用new和delete管理动态内存
*创建动态数组
*创建动态结构
*自动存储、静态存储和动态存储
*vector和array类介绍

4.1 数组
数组是一种数据格式,能够存储多个同类型的值。例如,数组可以存储60个int类型的值,12个short类型的值。300个float类型的值等。
要创建数组,可使用声明语句。数组声明应指出以下三点:
*存储在每个元素中的值的类型
*数组名
*数组中的元素数
在C++中,可以通过修改简单变量的声明,添加中括号(其中包含元素数目)来完成数组声明。例如;我们用下面的声明创建一个名为months的数组,该数组有12个元素,每个元素都可以存储一个short类型的值:

short months[12]; 		//creates array of 12 short

声明数组的通用格式:
类型名 数组名[元素数];

复合类型数组:使用其他类型来创建的。不能仅仅将某种东西声明为数组,它必须是特定类型的数组。没有通用的数组类型,但存在很多特定的数组类型,如char数组或long数组。例如:

float loans[20];		//loans的类型不是数组,而是float数组,这里强调了loans数组是使用float类型创建的。

程序清单

#include <iostream>
int main()
{
	using namespace std;
	int yams[3];
	yams[0]=7;
	yams[1]=8;
	yams[2]=6;

	int yamcosts[3]={20,30,5};
	  
	cout<<"Total yams = "<<yams[0] + yams[1] + yams[2]<<endl;
	cout<<"The package with "<<yams[1]<<" yams costs "<< yamcosts[1]<<" cents per yam.\n";

	int total=yams[0]*yamcosts[0]+yams[1]*yamcosts[1];
	total = total+yams[2]*yamcosts[2];

	cout<<"The total yam expense is "<<total<<" cents.\n";
	cout<<"\nSize of yams array= "<<sizeof yams<<" bytes.\n";
	cout<<"Size of one element = "<<sizeof yams[0]<<" bytes.\n";
	return 0;
}

运行结果
4.2 字符串
字符串是存储在内存的连续字节中的一系列字符。C++处理字符串的方式有两种。
第一种:来自C语言,常被称为C-风格字符串(C-style string)
第二种:基于string类库的方法
存储在连续字节中的一系列字符意味着可以将字符串存储在char数组中,其中每个字符都位于自己的数组元素中。字符串提供了一种存储文本信息的便捷方式,如提供给用户的消息(“请告诉我您的瑞士银行账号”)或者来自用户的响应(“您肯定在开玩笑”),C-风格字符串具有一种特殊的性质:以空字符(null character)结尾,空字符被写作\0,其ASCII码为0,用来标记字符串的结尾。例如,请看下面两个声明:

char dog[8] = {'b','e','a','u','x',' ','i','i'};		//not a string
char cat[8] = {'f','a','t','e','s','s','a','\0'};		//a string

虽然两个数组都是char数组,但是只有第二个数组时字符串。这体现了空字符对C-风格字符串至关重要。
上面的数组示例中,我们使用了大量单引号‘’来将数组初始化为字符串。还有一种更为简单的方式将字符数组初始化为字符串——字符串常量或字符串字面值,如下所示:

char bird[11] = "Mr.Cheeps";
char fish[]="Bubbles";

程序清单:在数组中使用字符串

#include <iostream>
#include <cstring>    	//头文件cstring提供strlen()函数来确定字符串的长度
int main()
{
	using namespace std;
	const int Size = 15;
	char name1[Size];			//empty array
	char name2[Size]="C++owboy";   //initialized array

	cout<<"Howdy!I'm "<<name2;
	cout<<"! What's your name?\n";
	cin>>name1;
	cout<<"Well,"<<name1<<",your name has "<<strlen(name1)<<" letters and is stored\n";
	cout<<"in an array of "<<sizeof(name1)<<" bytes.\n";
	cout<<"Your initial is "<<name1[0]<<".\n";
	name2[3]='\0';
	cout<<"Here are the first 3 characters of my name:";
	cout<<name2<<endl;
	renturn 0;
}

运行结果
注:strlen()函数返回的是存储在数组中的字符串的长度,且不把空字符计算在内,而不是数组本身的长度。
sizeof运算符指出的是整个数组的长度。

程序清单:字符串输入

#include <iostream>
int main()
{
	using namespace std;
	const int ArSize =20;
	char name[ArSize];
	char dessert[ArSize];

	cout<<"Enter your name:\n";
	cin>>name;
	cout<<"Enter your favorite dessert:\n";
	cin>>dessert;
	cout<<"I have some delicious "<<dessert<<" for you,"<<name<<".\n";
	return 0;
}

运行结果
运行结果
可以看到,第二种输出在我们没有输入第二条信息的情况就把信息打印出来了。
这涉及到cin是如何确定用户已完成字符串的输入的。cin使用空白(空格、制表符和换行符)来确定字符串的结束位置。这里cin把ylhrcyx作为第一个字符串,并将其放在name数组中,把cake留在输入队列中,并将它顺延放在dessert数组中。
那么存在一种情况:输入的字符串可能比目标数组长,这样去使用cin不能防止将包含30个字符的字符串放到20个字符的数组中的情况发生。

程序清单:每次读取一行字符串的输入
1、面向行的输入:getline()

cin.getline(name,20);
getline()函数读取整行,它使用通过回车键输入的换行符来确定输入行尾。要调用这种方法,可以使用cin.getline()。该函数有两个参数,第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数。如果这个参数为20,则函数最多读取19个字符,余下的空间用于存储自动在结尾处添加的空字符。getline()成员函数在读取指定数目的字符或遇到换行符时停止读取。
#include <iostream>
int main()
{
	using namespace std;
	const int ArSize=20;
	char name[ArSize];
	char dessert[ArSize];

	cout<<"Enter your name:\n";
	cin.getline(name,ArSize);		//reads through newline
	cout<<"Enetr your favorite dessert:\n";
	cin.getline(dessert,ArSize);
	cout<<"I have some delicious "<<dessert<<" for you,"<<name<<".\n";
	return 0;
}

运行结果
2、面向行的输入:get()
istream类有另一个名为get()的成员函数,该函数有几种变体。其中一种变体的工作方式与getline()类似,它们接受的参数相同,解释参数的方式也相同,并且都读取到行尾。但get并不再读取和丢弃换行符,而是将其留在输入队列中。假设我们连续两次调用get():

cin.get(name,ArSize);
cin.get(dessert,ArSize);		//a problem
由于第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符便是换行符。因此get()认为已到达行尾,而没有发现任何可读取的内容。
get()有另一种变体,使用不带任何参数的cin.get()调用可读取下一个字符,因此可以用它来处理换行符,为读取下一行输入做准备。
cin.get(name,ArSize);			//read first line
cin.get();						//read newline
cin.get(dessert,ArSize); 		//read second line
cin.get(name,ArSize).get();		//concatenate member functions
cin.getline(name1,ArSize).getline(name2,ArSize);

程序清单:拼接方式

#include <iostream>
int main()
{
	using namespace std;
	const int ArSize=20;
	char name[ArSize];
	char dessert[ArSize];

	cout<<"Enter your name:\n";
	cin.get(name,ArSize).get();		//read string,newline
	cout<<"Enter your favorite dessert:\n";
	cin.get(dessert,ArSize).get();
	cout<<"I have some delicious "<<dessert;
	cout<<" for you,"<<name<<".\n";
	return 0;
}

运行结果
4.3 string类简介
C++库扩展了string类,因此可以用string类型的变量而不是字符数组来存储字符串。string类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。
要使用string类,必须在程序中包含头文件string。string类位于名称空间std中,因此我们必须再提供一条using编译指令,或者使用std::string来引用它。string类定义隐藏了字符串的数组性质,使我们能像处理普通变量一样处理字符串。
程序清单:string对象与字符数组之间的一些相同点和不同点

#include <iostream>
#include <string>
int main()
{
	using namespace std;
	char charr1[20];
	char charr2[20]="jaguar";
	string str1;
	string str2="panther";

	cout<<"Enter a kind of feline:";
	cin>>charr1;
	cout<<"Enter another kind of feline:";
	cin>>str1;
	cout<<"Here are some felines:\n";
	cout<<charr1<<" "<<charr2<<" "<<str1<<" "<<str2<<endl;
	cout<<"The third letter in"<<charr2<<" is "<<charr2[2]<<endl;
	cout<<"The third letter in "<<str2<<"is"<<str[2]<<endl;
	return 0;
}

运行结果
从示例可知:
1、可以使用C-风格字符串来初始化string对象
2、可以使用cin来将键盘输入存储到string对象中
3、可以使用cout来显示string对象
4、可以使用数组表示法来访问存储在string对象中的字符

4.3.1 C++11字符串初始化:

char first_date[]={"Le Chapon Dodu"};
char second_date[]={"The Elegant Plate"};
string third_date={The Bread Bowl"};
string fourth_date{"Hank's Fine Eats"};

4.3.2 赋值、拼接和附加
使用string类时,某些操作比使用数组时更简单。例如,不能将一个数组赋给另一个数组,但可以将一个string对象赋给另一个string对象:

char charr1[20];
char charr2[20]="Jaguar";
string str1;
string str2="pantner";
charr1=charr2;		//invalid
str1=str2; 			//valid

程序清单:将C-风格字符串或string对象相加,或将它们附加到string对象的末尾。

#include <iostream>
#include <string>
int main()
{
	using namespace std;
	string s1="penguin";
	string s2,s3;
	
	cout<<"You can assign one string object to another:s2=s1\n";
	s2=s1;
	cout<<"s1: "<<s1<<",s2: "<<s2<<endl;
	cout<<"You can assign a C-style string to a string object.\n";
	cout<<"s2= \"buzzard"\\n";
	s2="buzzard";
	cout<<"s2:"<<s2<<endl;
	cout<<"You can concatenate strings:s3=s1+s2\n";
	s3=s1+s2;
	cout<<"s3: "<<s3<<endl;
	cout<<"You can append strings.\n";
	s1+=s2;
	cout<<"s1+=s2 yields s1="<<s1<<endl;
	s2+="for a day";
	cout<<"s2+=\"for a day"\yields s2="<<s2<<endl;

	return 0;
}

运行结果
4.3.3 string类的其他操作
在C++新增string类之前,程序员也需要完成诸如给字符串赋值等工作。对于C-风格字符串,程序员使用C语言库中的函数来完成这些任务。头文件cstring提供了这些函数。例如:可以使用strcpy()函数将字符串复制到字符串数组中,使用函数strcat()将字符串附加到字符数组末尾:

strcpy(charr1,charr2);   	//copy charr2 to charr1
strcat(charr1,charr2);		//append contents of charr2 to charr1

程序清单:可用于对string对象的技术和用于字符数组的技术进行了比较。

#include <iostream>
#include <string>        	//make string class available
#include <cstring>			//C-style string library
int main()
{
	using namespace std;
	char charr1[20];
	char charr2[20]="jaguar";
	string str1;
	string str2="panther";

	str1=str2;
	strcpy_s(charr1,charr2);

	str1 +="paste";
	strcat_s(charr1,"juice");

	int len1=str1.size();
	int len2=strlen(charr1);
	
	cout<<"The string "<< str1<<" containes "<<len1<<" characters.\n";
	cout<<"The string "<<charr1<<" containes "<<len2<<" characters.\n";
	
	return 0;
}

运行结果
程序清单:string类 I/O

#include <iostream>
#include <string>
#include <cstring>
int main()
{
	using namespace std;
	char charr[20];
	string str;
	
	cout<<"Length of string in charr before input:"<<strlen(charr)<<endl;
	cout<<"Length of string in str before input:"<<str.size()<<endl;
	cout<<"Enter a line of text:\n";
	cin.getline(charr,20);
	cout<<"You entered: "<<charr<<endl;
	cout<<"Enter another line of text:\n";
	getline(cin,str);
	cout<<"You entered: "<<str<<endl;
	cout<<"Length of string in charr after input:"<<strlen(charr)<<endl;
	cout<<"Length of string in str after input: "<<str.size()<<endl;

	return 0;
}

运行结果
注:在用户输入之前,该程序指出数组charr中的字符串长度是51,这比该数组的长度还要大。首先,为初始化的数组的内容是未定义的:其次,函数strlen()从数组的第一个元素开始计算字节数,直到遇到空字符。另外,用户输入之前,str的字符串长度是0,这是因为未被初始化的string对象的长度被自动设置为0。

cin.getline(charr,20);
函数getline()是istream类的一个类方法,第一个参数是目标数组,第二个参数是数组长度,getline()使用它来避免超越数组的边界。
getline(cin,str);
这里的getline()不是类方法。它将cin作为参数,指出到哪里去寻找输入。另外,也没有指出字符串长度的参数,因为string对象将根据字符串的长度自动调整自己的大小。

4.4 结构简介
假设要存储有关篮球运动员的信息,则可能需要存储他的姓名、工资、身高、体重、平均得分、命中率、助攻次数等。我们希望有一个数据格式可以将这些信息存储在一个单元中。数组不能完成这项任务,虽然数组可以存储10个float,但同一个数组不能在一些元素中存储int,在另一些元素中存储float。
C++中的结构可以满足要求。结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种数据类型,这使得能够将有关篮球运动员的信息放在一个结构中,从而将数据的表示合并到一起。如果要跟踪整个球队,则可以使用结构数组。结构也是C++OOP堡垒的基石。
结构是用户定义的类型,而结构声明定义了这种类型的数据属性。定义了类型后,便可以创建这种类型的变量。因此创建结构包括两步。首先,定义结构描述——它描述并标记了能够存储在结构中的各种数据类型。然后按描述创建结构变量。
例如:假设Bloataire公司要创建一种类型来描述其生产线上充气产品的成员。具体地说,这种类型应存储产品名称、容量和售价。下面的结构描述能够满足这些要求:

struct inflatable
{
	char name[20];
	float volume;
	double price;
};

关键字struct表明,这些代码定义的是一个结构的布局。标识符inflatable是这种数据格式的名称,因此新类型的名称为inflatable。这样,便可以像创建char或int类型的变量那样创建inflatable类型的变量了。接下来的大括号中包含的结构存储的数据类型的列表,其中每个列表项都是一条声明语句。这个例子使用了一个适用于存储字符串的char数组、一个float和一个double。列表中的每一项都被称为结构成员,因此inflatable结构有3个成员。总之,结构定义指出了新类型的特征。

程序清单:在程序中使用结构:

#include <iostream>
struct inflatable
{
	char name[20];
	float volume;
	double price;
};
int main()
{
	using namespace std;
	inflatable guest =
	{
		"Glorious Gloria",
		1.88,
		29.99
	};
	inflatable pal=
	{
		"Audacious Arthur",
		3.12,
		32.99
	};
	cout<<"Expand your guest list with "<<guest.name;
	cout<<" and "<<pal.name<<"!\n";
	cout<<"You can have both for $";
	cout<<guest.price+pal.price<<".\n";
	return 0;
}

运行结果
程序说明:结构声明的位置很重要。对于struct而言,有两种选择。可以将声明放在main()函数中,紧跟在开始括号之后。另一种选择是将声明放到main()的前面,位于函数外面的声明被称为外部声明。对于这个程序来说,两种选择之间并没有实际区别。但是对于哪些包含两个或更多函数的程序来说,差别很大。外部声明可以被后面的任何函数使用,而内部声明只能被该声明所属的函数使用。通常应使用外部声明,这样所有的函数都可以使用这种类型的结构。
变量也可以在函数的内部和外部定义,外部变量由所有函数共享,C++不提倡外部变量,但提倡使用外部结构声明。另外,在外部声明符号常量通常更合理。

程序清单:其他结构属性

#include <iostream>
struct inflatable
{
	char name[20];
	float volume;
	double price;
};
int main()
{
	using namespace std;
	inflatable bouquet=
	{
	"sunflowers",
	0.20,
	12.49
	};
	inflatable choice;
	cout<<"bouquet: "<<bouquet.name<<" for $";
	cout<<bouquet.price<<endl;

	choice=bouquet;
	cout<<"choice: "<<choice.name<<" for $";
	cout<<choice.price<<endl;
	return 0;
}

运行结果
程序说明:从此程序可以看出,成员赋值时有效的,因为choice结构的成员值与bouquet结构中存储的值相同。可以同时完成定义结构和创建结构变量的工作。为此,只需将变量名放在结束括号的后面即可。

程序清单:结构数组
inflatable结构包含一个数组name。也可以创建元素为结构的数组,方法和创建基本类型数组完全相同。例如,要创建一个包含100个inflatable结构的数组,可以这样做:
inflatable gifts[100]; //array of 100 inflatable structures
这样,gifts将是一个inflatable数组,其中每个元素都是inflatable对象,可以与成员运算符一起使用。
初始化结构数组:结合使用初始化数组的规则。

inflatable guests[2]=
{
	{"Bambi",0.5,21.99},
	{"Godzilla",2000,565.99}
};

程序清单:使用结构数组的简单示例

#include <iostream>
struct inflatable
{
	charr name[20];
	float volume;
	double price;
};
int main()
{
	using namespace std;
	inflatable guests[2]=
	{
		{"Bambi",0.5,21.99},
		{"Godzilla",2000,565.99}
	};
	cout<<"The guests "<<guests[0].name<<" and "<<guests[1].name<<"\nhave a combined volume of"
		<<guests[0].volume+guests[1].volume<<" cubic feet.\n";
	return 0;
}		

运行结果
4.5 共用体
共用体(union)是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中一种类型。也就是说,结构可以同时存储int、long、double,共用体只能存储int、long或double。共用体的句法与结构相似,但含义不同。例如,请看下面的声明:

union one4all
{
	int int_val;
	long long_val;
	double double_val;
};

可以使用one4all变量来存储int、long、double,条件是在不同时间进行。

one4all pail;
pail.int_val=15;
cout<<pail.int_val;
pail.double_val=1.38;
cout<<pail.double_val;

因此,pail有时可以是int变量,有时又可以是double变量。成员名称标识了变量的容量。由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以,共用体的长度为其最大成员的长度。共用体常用于节省内存。

4.6 枚举
C++的enum工具提供了另一种创建符号常量的方式,这种工具可以代替const。它还允许定义新类型,但必须按严格的限制执行。使用enum的句法和使用结构类似。示例如下:

enum spectrum{red,orange,yellow,green,blue,violet,indigo,ultraviolet};

1、定义spectrum变量为枚举类型
2、将red、orange、yellow等作为符号变量,他们对应整数值0~7。这些常量叫做枚举量。

4.7 指针和自由存储空间
我们提到过计算机程序在存储数据时必须跟踪的3种基本属性。
1、信息存储在何处
2、存储的值为多少
3、存储的信息是什么类型
我们曾经通过定义一个简单变量,用声明语句指出了值的类型和符号名,还让程序为值分配内存,并在颞部跟踪该内存单元。
现在,我们看另一种方式:以指针为基础,指针是一个变量,其存储的是值的地址,而不是值本身。如何找到常规变量的地址呐。只需对变量应用地址运算符&,就可以获得它的位置:例如,如果home是一个变量,则&home是它的地址。

程序清单:地址运算符&的用法

#include <iostream>
int main()
{
	using namespace std;
	int donuts=6;
	double cups=4.5;

	cout<<"donuts value = "<<donuts<<" and donuts address = "<<&donuts<<endl;
	cout<<"cups value = "<<cups<<" and cups address = "<<&cups<<endl;
	return 0;
}

运行结果
使用常规变量时,值是指定的量,而地址为派生量。

指针与C++基本原理
面向对象编程与传统的过程性编程的区别在于,OOP强调的是在运行阶段进行决策。运行阶段指的是程序正在运行时,编译阶段指的是编译器将程序组合起来时。运行阶段决策就好比度假时,选择参观哪些景点取决于天气和当时的心情;而编译阶段决策更像是不管在什么条件下,都坚持预先设定的日程安排。
运行阶段决策提供了灵活性,可以根据当时的情况进行调整。

处理存储数据的新策略刚好相反,将地址视为指定的量,而将值视为派生量。一种特殊类型的变量——指针用于存储值的地址。因此,指针名表示的是地址。运算符被称为间接值或解除引用运算符,将其应用于指针,可以得到该地址处存储的值。例如,假设manly是一个之指针,则manly表示的是一个地址,而manly表示存储在该地址处的值。*manly与常规int变量等效。

程序清单:演示如何声明指针

#include <iostream>
int main()
{
	using namespace std;
	int updates=6;
	int*p_updates;
	p_updates=&updates;
	  
	cout<<"Value:updates = "<<updates<<",P_updates = "<<p_updates<<endl;
	*p_updates = *p_updates+1;
	cout<<"Now updates = "<<updates<<endl;
	return 0;
}

运行结果
程序说明:由此可知,int变量updates和指针变量p_updates不过是同一枚硬币的两面。变量updates表示值,并使用&运算符来获得地址:而变量p_updates表示地址,并使用运算符来获得值。由于p_updates指向updates,因此p_updates和updates完全等价。

程序清单:如何将指针初始化为一个地址

#include <iostream>
int main()
{
	using namespace std;
	int higgens = 5;
	int*pt = &higgens;

	cout<<"Value of higgens = "<<higgens<<";Address of higgens = "<<&higgens<<endl;
	cout<<"Value of *pt = "<<*pt<<";Value of pt = "<<pt<<endl;
	return 0;
}

运行结果
指针的危险:在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。为数据提供空间是一个独立的步骤。
警告:一定要在对指针应用解除引用运算符*之前,将指针初始化为一个确定的、适当的地址。这是关于使用指针的金科玉律。

使用new来分配内存
对指针的工作方式有一定的了解后,我们来看看它如何实现在程序运行时分配内存。前面我们都将指针初始化为变量的地址:变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供了一个别名。指针的真正用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C语言中,可以用库函数malloc()来分配内存,在C++中仍然可以这样做,除此外C++还有更好的方法——new运算符。
在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。这里的关键所在是C++的new运算符。程序员要告诉new,需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。示例如下:
intpn=new int;
new int告诉程序,需要适合存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址。接下来,将地址赋给pn,pn是被声明为指向int的指针。现在,pn是地址,而
pn是存储在那里的值。将这种方法与将变量的地址赋给指针进行比较:
int higgens;
int*pt=&higgens;

程序清单:将new用于两种不同的类型

#include <iostream>
int main()
{
	using namespace std;
	int nights=1001;
	int*pt=new int;
	*pt=1001;

	cout<<"nights value = "<<nights<<";location "<<&nights<<endl;
	cout<<"int value = "<<*pt<<";location = "<<pt<<endl;
	double*pd=new double;
	*pd=10000001.0;

	cout<<"double value = "<<*pd<<";location = "<<pd<<endl;
	cout<<"location of pointer pd:"<<&pd<<endl;
	cout<<"size of pt = "<<sizeof(pt)<<";size of *pt = "<<sizeof(*pt)<<endl;
	cout<<"size of pd = "<<sizeof pd<<";size of *pd = "<<sizeof(*pd)<<endl;
	return 0;
}

运行结果
程序说明:该程序使用new分别为int类型和double类型的数据对象来分配内存。这是在程序运行时进行的。指针pt和pd指向这两个数据对象,如果没有它们,将无法访问这些内存单元。有了这两个指针,就可以像使用变量那样使用pt和pd了。将值赋给pt和pd,从而将这些值赋给新的数据对象。同样,可以通过打印pt和pd来显示这些值,cout知道要读取多少字节以及如何解释它们。

使用delete释放内存
当运算需要内存时,可以使用new来请求,这只是C++内存管理数据包中有魅力的一个方面。另一个方面是delete运算符,它使得在使用完内存后,能够将其归还给内存池,这是通向最有效地使用内存的关键一步。归还或释放free的内存可供程序的其他部分使用。使用delete时,后面要加上指向内存块的指针。

int*ps=new int;		//用new分配内存
delete ps;		//使用delete释放内存

这将释放ps指向的内存,但不会删除指针ps本身。例如,可以将ps重新指向另一个新分配的内存块。一定要配对地使用new和delete,否则将发生内存泄漏,也就是说,被分配的内存再也无法使用了。如果内存泄露严重,则程序将由于不断寻找更多内存而终止。
不要尝试释放已经释放的内存块,C++标准指出,这样做的结果将是不确定的,这意味着什么情况都可能发生。另外,不能使用delete来释放声明变量所获得的内存;

int*ps=new int;		//ok
delete ps;			//ok
delete ps;			//not ok now
int jugs=5;		//ok
int*pi=&jugs;		//ok
delete pi;			//not allowed,memory not allocated by new

警告:只能用delete来释放使用new分配的内存。然而,对空指针使用delete是安全的。
注意:使用delete的关键在于,将它用于new分配的内存,这并不意味着要使用用于new的指针,而是用于new的地址:

int*ps=new int;		//allocate memory
int*pq=ps;  		//set second pointer to same block
delete ps;			//delete with second pointer

一般来说,不要创建两个指向同一内存块的指针,因为这将增加错误地删除同一个内存块两次的可能性。但之后我们会知道,对于返回指针的函数,使用另一个指针确实有道理。

使用new来创建动态数组
如果程序只需要一个值,则可能会声明一个简单变量,因为对于管理一个小型数据对象来说,这样做比使用new和指针更简单,尽管给人留下的印象不那么深刻。通常,对于大型数据(数组、字符串和结构),应使用new,这正是new的用武之地。例如,建设要编写一个程序,它是否需要数组取决于运行时用户提供的信息。如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否使用数组,数组都在那里,它占用了内存。在编译时给数组分配内存被称为静态联编,意味着数组是在编译时加入到程序中的。但使用new时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编,意味着数组是在程序运行时创建的。这种数组叫做动态数组。使用静态联编时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。
1、使用new创建动态数组
在C++中,创建动态数组很容易,只要将数组的元素类型和元素数目告诉new即可。必须在类型名后加上方括号,其中包含元素数目。例如,要创建一个包含10个int元素的数组,如下:

int*psome=new int[10];  	//get a block of 10 ints

new运算符返回第一个元素的地址。在此例中,该地址被赋给指针psome.
当程序使用完new分配的内存块时,应使用delete释放它们。然而,对于使用new创建的数组,应使用另一种格式的delete来释放:

delete[]psome;		//free a dynamic array

方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。如果使用new时不带方括号,则使用delete时,也不带方括号。如果使用new时带方括号,则使用delete时也应带方括号。C++早期版本无法识别方括号表示法。new与delete的格式不匹配导致的后果是不确定的,这意味着程序员不能依赖于某种特定的行为,如下:

int*pt=new int;		
shoet*ps=new short [500];
delete [] pt; 			//effect is undefined,don't do it
delete ps;				//effect is undefined,don't do it

总之,使用new和delete时,应遵守以下规则。
1、不要使用delete来释放不是new分配的内存
2、不要使用delete释放同一个内存块两次
3、如果使用new[]为数组分配内存,则应使用delete[]来释放
4、如果使用new[]为一个实体分配内存,则应使用delete来释放
5、对空指针应用delete是安全的
现在我们回过头来讨论动态数组,psome是指向一个int(数组第一个元素)的指针。您的责任是跟踪内存块中的元素个数。也就是说,由于编译器不能对psome是指向10个整数中的第1个这种情况进行跟踪,因此编写程序时,必须让程序跟踪元素的数目。
实际上,程序确实跟踪了分配的内存量,以便以后使用delete[]运算符时能够正确地释放这些内存。但这些信息不是公用的,例如,不能使用sizeof运算符来确定动态分配的数组包含的字节数。
为数组分配内存的通用格式如下:

type_name*pointer_name = new type_name [num_elements];

使用new运算符可以确保内存块足以储存num_elements个类型为type_name的元素,而pointer_name将指向第一个元素。下面将会看到,可以以使用数组名的方式来使用pointer_name。
2、使用动态数组
创建动态数组后,如何使用它呢?首先,从概念上考虑这个问题,下面的语句创建指针psome,它指向包含10个int值的内存块中的第一个元素:

int*psome=new int[10];		//get a block of 10 ints
可以将它看作是一根指向该元素的手指。假设int占4个字节,则将手指沿正确方向移动4个字节,手指将指向第2个元素。总共有10个元素,这就是手指移动的范围。因此new语句提供了识别内存块中每个元素所需的全部信息。
现在从实际角度考虑这个问题。如何访问其中的元素呢?第一个元素不成问题。由于psome指向数组的第一个元素,因此*psome是第一个元素的值,这样,还有9个元素。现在,我们只需要把指针当作数组名使用即可。也就是对于第一个元素,可以使用psome[0],而不是*psome,对于第2个元素,可以使用paome[1],以此类推。这样,使用指针来访问动态数组就非常简单了。

程序清单:使用new来创建动态数组以及使用数组表示法来访问元素:指出了指针和真正的数组名之间的根本差别。

#include <iostream>
int main()
{
	using namespace std;
	double*p3=new double[3];	//sapce for 3 doubles
	p3[0]=0.2;			//treat p3 like an array name
	p3[1]=0.5;
	p3[2]=0.8;
	cout<<"p3[1] is "<<p3[1]<<".\n";
	p3=p3+1;			//increment the pointer
	cout<<"Now p3[0] is "<<p3[0]<<" and p3[1] is "<<p3[1]<<".\n";
	p3=p3-1;		//point back to beginning
	delete[]p3;
	return 0;
}

在这里插入图片描述
从中可知,指针被当作数组名来使用,p3[0]为第1个元素,以此类推。

p3=p3+1;		//ok for pointers,wrong for array names

不能修改数组名的值。但指针是变量,因此额可以修改它的值。p3+1的效果是表达式p3[0]现在指的是数组的第2个值。因此,将p3+1导致它指向第2个元素而不是第1个。将它减一后,指针将指回原来的值,这样程序就会给delete[]提供正确的地址。

4.8 指针、数组和指针算术
指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式。首先,我们来看一看算术。将整数变量+1后,其值将增加1,;但将指针变量+1后,增加的量等于它指向的类型的字节数。将指向double的指针+1后,如果系统对double使用8个字节存储,则数值将增加8;将指向short的指针加1后,如果系统对short使用2个字节存储,则指针值将增加2.
程序清单:C++将数组名解释为地址

#include <iostream>
int main()
{
	using namespace std;
	double wages[3]={10000.0,20000.0,30000.0};
	short stacks[3]={3,2,1};
//Here are two ways to get the address of an array;
	double*pw=wages;			//name of an array=address
	short*ps=&stacks[0];		//or use address operator
//with array elements
	cout<<"pw = "<<pw<<",*pw = "<<*pw<<endl;
	pw=pw+1;
	cout<<"add 1 to the pw pointer:\n";
	cout<<"pw = "<<pw<<",*pw = "<<*pw<<"\n\n";
	cout<<"ps = "<<ps<<",*ps = "<<*ps<<endl;
	ps=ps+1;
	cout<<"add 1 to the ps pointer:\n";
	cout<<"ps = "<<ps<<",*ps = "<<*ps<<"\n\n";

	cout<<"access two elements with pointer notation\n";
	cout<<"stacks[0] = "<<stacks[0]<<",stack[1] = "<<stacks[1]<<endl;
	cout<<"access two elements with pointer notation\n";
	cout<<"*stacks = "<<*stacks<<",*(stacks+1) = "<<*(stacks+1)<<endl;
	cout<<sizeof(pw)<<" = size of pw pointer\n";
	cout<<sizeof(wages)<<" = size of wages array\n";
	return 0;	
}

在这里插入图片描述
程序说明:在多数情况下,C++将数组名解释为数组的第一个元素的地址。因此,下面的语句将pw声明为指向double类型的指针,然后将它初始化为wages——wages数组中的第一个 元素的地址:
double*pw=wages;
和所有数组一样,wages也存在下面的等式:
wages=&wages[0]=address of first element of array

数组的地址:
当数组取地址时,数组名也不会解释为其地址。数组名被解释为其第一个元素的地址,二队数组名应用地址运算符时,得到的是整个数组的地址:

short rell[10];		//tell an array of 20 bytes
cout<<tell<<endl;		//displays &tell[0]
cout<<&tell<<endl;		//displays address of whole array

从数字上说,这两个地址相同;但从概念上说,&tell[0]是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。因此,表达式tell+1将地址+2,而表达式&tell+2将地址加20.换句话说,tell是一个short指针(*short),而&tell是一个指向包含20个元素的short数组的指针。

指针小结
1、声明指针
要声明指向特定类型的指针,请使用下面的格式:
typeName*pointerName

double*pn;
char*pc;

其中,pn和pc都是指针,而double和char是指向double的指针和指向char的指针。
2、给指针赋值
应将内存地址赋给指针。可以对变量名应用&运算符,来获得被命名的内存的地址,new运算符返回未命名的内存的地址。

double*pn; 		//pn can point to a double value
double*pa;		//so can pa
char*pc;		//pc can point to a char value
double bubble=3.2;
pn=&bubble;		//assign address of bubble to pn
pc=new char;		//assign address of newly allocated char memory to pc
pa=new double[30];		//assign address of lst element of array of 30 double to pa

3、对指针解除引用
对指针解除引用意味着获得指针指向的值。对指针应用解除引用或间接值运算符来解除引用。因此,如果像上面的例子中那样,pn是指向bubble的指针,则pn是指向的值,即3.2.

cout<<*pn;		//print the value of bubble
*pc='s';		//stores the value 5 at that address

另一种对指针解除引用的方法是使用数组表示法,例如,pn[0]与pn是一样的。绝不要对未被初始化为适当地址的指针解除引用。
4、区分指针和指针所指向的值
如果pt是指向int的指针,则
pt不是指向int的指针,而是完全等同于一个int类型的变量。pt才是指针。

int*pt=new int;		//assigns an address to the pointer pt
*pt=5;		//stores the value 5 at that address

5、数组名
在多数情况下,C++将数组名视为数组的第一个元素的地址。

int tacos[10];   	//now tacos is the same as &tacos[0]

一种例外情况是,将sizeof运算符用于数组名用时,此时将返回整个数组的长度。
6、指针算术
C++允许将指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数。还可以将一个指针减去另一个指针,获得两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个数组时,这种运算才有意义;这将得到两个元素的间隔。

int tacos[10]={5,2,8,4,1,2,2,4,6,8};
int*pt=tacos;		//suppose pt and tacos are the address 3000
pt=pt+1;		//now pt is 3004 if an int is 4 bytes
int *pe=&tacos[9];	//pe is 3036 if an int is 4 bytes
pe=pe-1;		//now pe is 3032,the address of tacos[8]
int diff=pe-pt;	  //diff is 7,the separation between tacos[8] and tacos[1]

7、数组的动态联编和静态联编
使用数组声明来创建数组时,将采用静态联编,即数组的长度在编译时设置:
int tacos[10]; //static binding,size fixed at compile time
使用new[]运算符创建数组时,将采用动态联编,即将在运行时为数组提供分配空间,其长度也将在运行时设置。使用完这种数组后,应使用delete[]释放其占用的内存:
int size;
cin>>size;
int*pz=new int[size];

delete [] pz;
8、数组表示法和指针表示法
使用方括号数组表示法等同于对指针解除引用:
tacos[0] means *tacos means the value at address tacos
tacos[3] means *(tacos+3) means the value at address tacos+3
数组名和指针变量都是如此,因此对于指针和数组名,既可以使用指针表示法,也可以使用数组表示法。

程序清单:演示如何使用不同形式的字符串

#include <iostream>
#include <string>
#include <cstring>     	//declare strlen_s(),strcpy_s()
int main()
{
	using namespace std;
	char animal[20] = "bear";
	const char* bird = "wren";
	char* ps;

	cout << animal << " and " << bird << endl;
	cout << "Enter a kind of animal:";
	cin >> animal;

	ps = animal;
	cout << ps << "!\n";
	cout << "Before using strcpy_s():\n";
	cout << animal << " at " << (int*)animal << endl;
	cout << ps << " at " << (int*)ps << endl;

	ps = new char[strlen(animal) + 1];
	strcpy_s(ps,animal);
	cout << "After using strcpy():\n";
	cout << animal << " at " << (int*)animal << endl;
	cout << ps << " at " << (int*)ps << endl;
	delete[] ps;
	return 0;
}

在这里插入图片描述

程序清单:使用new创建一个未命名的结构

#include <iostream>
struct inflatable
{
	char name[20];
	float volume;
	double price;
};
int mian()
{
	using namespace std;
	inflatable*ps=new inflatable;
	cout<<"Enter name of inflatable item:";
	cin.get(ps->name,20);
	cout<<"Enter volume in cubic feet:";
	cin>>(*ps).volume;
	cout<<"Enter price:$";
	cin>>ps->price;
	cout<<"Name:"<<(*ps).name<<endl;
	cout<<"Volume : "<<ps->volume<<" cubic feet\n";
	cout<<"Price:$"<<ps->price<<endl;
	delete ps;
	return 0;
}

在这里插入图片描述
4.9 类型组合
本章介绍了数组、结构和指针。可以各种方式组合它们。
从结构开始:

struct antarctica_year_end
{
	int year;
	/*some really interesting data,etc.*/
};

可以创建这种类型的变量:
antarctica_year_end s01,s02,s03;
然后使用成员运算符访问其成员:
s01.year=1998;
可创建指向这种结构的指针:
antarctica_year_end*pa=&s02;
将该指针设置为有效地址后,就可使用间接成员运算符来访问成员:
pa->year=1999;
可创建结构数组:
antactica_year_end trio[3];

程序清单:

#include <iostream>
struct antarctica_years_end
{
	int year;
	/*some really interesting data,etc.*/
};

int main()
{
	antarctica_years_end s01,s02,s03;
	s01.year=1998;
	antarctica_years_end*pa=&s02;
	pa->year=1999;
	antarctica_years_end trio[3];
	trio[0].year=2003;
	std::cout<<trio->year<<std::endl;
	const antarctica_years_end*arp[3]={&s01,&s02,&s03};
	std::cout<<arp[1]->year<<std::endl;
	const antarctica_years_end**ppa=arp;
	auto ppb=arp;
	std::cout<<(*ppa)->year<<std::endl;
	std::cout<<(*(ppb+1))->year<<std::endl;
	return 0;
}

在这里插入图片描述
4.10 数组的替代品
模板类vector和array是数组的替代品。
模板类vector:
模板类vector类类似于string类,也是一种动态数组。您可以在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入新数据,这种工作是自动完成的。
首先,要使用vector对象,必须包含头文件vector。其次,vector包含在名称空间std中,因此可以使用using编译指令、using声明或std::vector。第三,模板使用不同的语法来指出它存储的数据类型。第四,vector类使用不同的语法来指定元素数。

#include <vector>

using namespace std;
vector<int>vi;
int n;
cin>>n;
vector<double>vd(n);

其中,vi是一个vector对象。由于vector对象在插入或添加值时自动调整长度,因此可以将vi的初始长度设置为零。但要调整长度,需要使用vector包中的各种方法。
一般而言,下面的声明创建一个名为vt的vector对象,它可存储n_elem个类型为typeName的元素:
vectorvt(n_elem);
其中参数n_elem可以是整型变量,可以是整型常量。

模板类array:
vector类的功能比数组强大,但付出的代价是效率稍低。如果需要的是长度固定的数组,使用数组是更佳的选择,但代价是不那么方便和安全。与数组一样,array对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便,更安全。要创建array对象,需要包含头文件array。array对象的创建语法与vector稍有不同:

#include <array>

using namespace std;
array<int,5>ai;
array<double,4>ad={1.2,2.1,3.43,4.3};

推而广之,下面的声明创建一个名为arr的array对象,它包含n_elem个类型为typename的元素:
array<typeName,n_elem>arr;
与创建vector对象不同的是,n_elem不能是变量。

程序清单:了解数组、vector对象和array对象的相似和不同之处。

#include <iostream>
#include <vector>
#include <array>
int main()
{
	using namespace std;
	double a1[4]={1.2,2.4,3.6,4.8};
	vector<double>a2[4];
	a2[0]=1.0/3.0;
	a2[1]=1.0/5.0;
	a2[2]=1.0/7.0;
	a2[3]=1.0/9.0;

	array<double,4>a3={3.14,2.72,1.62,1.41};
	array<double,4>a4;
	a4=a3;
	cout<<"a1[2]: "<<a1[2]<<" at "<<&a1[2]<<endl;
	cout<<"a2[2]: "<<a2[2]<<" at "<<&a2[2]<<endl;
	cout<<"a3[2]: "<<a3[2]<<" at "<<&a3[2]<<endl;
	cout<<"a4[2]: "<<a4[2]<<" at "<<&a4[2]<<endl;

	a1[-2]=20.2;
	cout<<"a1[-2]: "<<a1[-2]<<" at "<<&a1[-2]<<endl;
	cout<<"a3[2]: "<<a3[2]<<" at "<<&a3[2]<<endl;
	cout<<"a4[2]: "<<a4[2]<<" at "<<&a4[2]<<endl;
	return 0;
}

在这里插入图片描述

4.11 总结
数组、结构和指针是C++的3种符合类型。数组可以在一个数据对象那个中存储多个同种类型的值。通过索引或下标,可以访问数组中的各个元素。
结构可以将多个不同类型的值存储在同一个数据对象中,可以使用成员关系运算符(.)来访问其中的成员。使用结构的第一步是创建结构模板,它定义结构存储了哪些成员。模板的名称将成为新类型的标识符,然后就可以声明这种类型的结构变量。
共用体可以存储一个值,但是这个值可以是不同的类型,成员名指出了使用的模式。
指针是被设计用来存储地址的变量。我们说,指针指向它存储的地址。指针声明指出了指针指向的对象的类型。对指针应用解除引用运算符,将得到指针指向的位置中的值。
字符串是以空字符为结尾的一系列字符。字符串可用引号括起的字符串常量表示,其中隐式包含了结尾的空字符。可以将字符串存储在char数组中,可以用被初始化为指向字符串的char指针表示字符串。函数strlen()返回字符串的长度,其中不包括空字符。函数strcpy()将字符串从一个位置复制到另一个位置。在使用这些函数时,应当包含头文件cstring或string.h。
头文件string支持的C++string类提供了另一种对用户更友好的字符串处理方法。具体的说,string对象将根据要存储的字符串自动调整其大小,用户可以使用赋值运算符来复制字符串。
new运算符允许在程序运行时为数据对象请求内存。该运算符返回获得内存的地址,可以将这个地址赋给一个指针,程序将只能使用该指针来访问这块内存。如果数据对象时简单变量,则可以使用解除引用运算符来获得其值;如果数据对象时数组,则可以象使用数组名那样使用指针来访问元素;如果数据对象是结构,则可以用指针解除引用运算符->来访问其成员。
指针和数组紧密相关。如果ar是数组名,则表达式ar[i]被解释为
(ar+1),其中数组名被解释为数组的第一个元素的地址。这样,数组名的作用和指针相同。反过来,可以使用数组表示法,通过指针名来访问new分配的数组中的元素。
运算符new和delete允许显式控制何时给数据对象分配内存,何时将内存归坏给内存池。自动变量是在函数中声明的变量,而静态变量是在函数外部或者使用关键字static声明的变量,这两种变量都不太灵活。自动变量在程序执行到其所属的代码块时产生,在离开该代码块时终止。静态变量在整个程序周期内都存在。

4.12 复习题
1、如何声明下述数据?
a.actor是由30个char组成的数组
答:char actors[30];
b.betsie是由100个short组成的数组
答:short betsie[100];
c.chuck是由13个float组成的数组
答:float chuck[13];
d.dipsea是由64个long double组成的数组
答:long double dipsea[64];

2、使用模板类array而不是数组来完成问题一:
答: a.array<char,30>actors;
b.array<short,100>betsie;
c.array<float,13>chuck;
d.array<long double,64>dipsea;

3、声明一个包含5个元素的int数组,并将它初始化为前5个正奇数。
答:int oddly[5]={1,3,5,7,9};
4、编写一条语句,将问题3中数组第一个元素和最后一个元素的和赋给变量even。
答:int even=oddly[0]+oddly[4];
5、编写一条语句,显示float数组ideas中的第二个元素的值;
答:cout<<ideas[1]<<endl;
6、声明一个char的数组,并将其初始化为字符串“cheeseburger”。
答:char lunch[]=“cheeseburger”;
7、声明一个string对象,并将其初始化为字符串“WaldorfSalad”。
答:string lunch[]=“WaldorfSalad”;
8、设计一个描述鱼的结构声明。结构中应包含品种、重量(整数,单位为盎司)和长度(英寸,包括小数)。
struct fish{ char kind[20]; int weight; float length; };
9、声明一个问题8中定义的结构的变量,并对它进行初始化。

fish petes=
{
	"trout",
	12,
	26.25
};

10、用enum定义一个名为Response的类型,它包含Yes、No和Maybe等枚举量,其中Yes的值为1,No为0,Maybe为2。
答:enum Response{No,Yes,Maybe};
11、假设ted是一个double变量,请声明一个指向ted的指针,并使用该指针来显示ted的值。
答:double*pd=&ted;
cout<<pd<<endl;
12、假设treacle是一个包含10个元素的float数组,请声明一个指向treacle的第一个元素的指针,并使用该指针来显示数组的第一个元素和最后一个元素。
答:float
pt=treacle;
cout<<pt[0]<<" "<<pf[9]<<endl;
13、编写一段代码,要求用户输入一个正整数,然后创建一个动态的int数组,其中包含的元素数目等于用户输入的值。首先使用new来完成这项任务,再使用vector对象来完成这项任务。

unsigned int size;
cout<<"Enter a positive integer: ";
cin>>size;
int*p1=new int[size];
vector<int>p2(size);

14、下面的代码是否有效?如果有效,它将打印出什么效果?

cout<<(int*)"Home of the jolly bytes";

答:有效,表达式"Home of the jolly bytes"是一个字符串常量,因此,它将判定为字符串开始的地址。cout对象将char地址解释为打印字符串,但类型转换(int*)将地址转换为int指针,然后作为地址被打印。总之,该语句打印字符串的地址,只要int类型足够宽,能够存储该地址。
15、编写一段代码,给问题8中描述的结构动态分配内存,再读取该结构的成员的值。

struct fish
{
	char kind[20];
	int weight;
	float length;
};

fish*pole=new fish;
cout<<"Enter kind of fish: ";
cin>>pole->kind;

16、程序清单4.6指出了混合输入数字和一行字符串时存储的问题。如果将下面的代码:
cin.getline(address,80);
替换为:
cin>>address;
将对程序的运行带来什么影响?
答:使用cin>>address将使得程序跳过空白,直接找到非空白字符为止。然后它将读取字符,直到再次遇到空白为止。因此,它将跳过数字输入后的换行符,从而避免这种问题。另一方面,它只读取一个单词,而不是整行。
17、声明一个vector对象和一个array对象,它们都包含10个string对象。指出所需的头文件,但不要使用using。使用const来指定要包含的string对象数。

#include <string>
#include <vector>
#include <array>
const int Str_num{10};;
...
std::vector<std::string>vstr(Str_num);
std::array<std::string,Str_num>astr;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值