【c++】3.复合类型

第四章 复合类型


数组可以存储多个同类型的值,一种特殊的数组可以存储字符串(一系列字符)。
结构可以存储多个不同类型的值。
而指针则是一种将数据所处位置告诉计算机的变量。

数组

能够存储多个同类型的值。
要创建数组,可使用声明语句,数组声明应指出以下三点:
1.存储在每个元素中的值的类型;
2.数组名;
3.数组中的元素数。
例:下面的声明创建一个名为mouths的数组,该数组有12个元素,每个元素都可以存储一个short类型的值:

short mouths [12];

使用下标或索引对元素进行编号来访问数组元素,c++数组从0开始编号。

//arrayone.cpp --small arrays of intergers
#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= ";
	cout<<yams[0]+yams[1]+yams[2]<<endl;
	cout<<"The package with "<<yams[1]<<"yams costs ";
	cout<<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;
	cout<<"byte.\n";
	cout<<"Size of one element = "<<sizeof yams[0];
	cout<<".bytes\n";
	return 0;
}

使用int yamcosts[3]={20,30,5};给来数组赋值更为简便。
sizeof运算符返回类型或数据对象的长度(单位为字节)。

  • 数组的初始化规则
    只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组:
    int cards[4]={3,4,5,6};
    int hand[4];
    hand[4]={5,6,7,7}; //not allowed
    hand=cards; //not allowed
    初始化数组时,提供的值可以少于数组的元素数目。
    如果只对数组的一部分进行初始化,则编译器将把其他元素设置为0。因此将数组中所有元素设置为0特别简单,只需要写一个0即可:
    long totals[500]={0};
    如果初始化数组时方括号内为空,c++编译器将计算元素个数。
  • c++11数组初始化方法
    可省略等号(=);
    可不在大括号内包含任何东西,这将把所有元素都设置为0;
    列表初始化禁止缩窄转换:
    long plifs[]={25,93,3.0};
    char slifs[]={‘h’,‘i’,11266666,’\0’};
    上述两条语句都不能通过编译,第一句中将浮点数转换为整型是缩窄操作,第二条语句是因为11266666超过了char变量的取值范围(这里假设char变量的长度为8位)

字符串

字符串是存储在内存的连续字节中的一系列字符。意味着可以将字符串存储在char数组中,其中每个字符都位于自己的数组元素中。
以空字符结尾——‘\0’,ASCII码为0;用来标记字符串的结尾。
char cat[8]={’\a’,’\b’,’\0’};

  • 字符串常量或字符串面值
    char bird[11]=“Mr. Cheeps”;
    用引号括起的字符串隐式地包括结尾的空字符。
    应确保数组足够大,能够存储字符串中所有的字符——包括空字符
    在确定存储字符串所需的最短数组时,别忘了将结尾的空字符计算在内。
    字符串常量(使用双引号)不能与字符常量(使用单引号)互换,‘S’只是83的另一种写法,下面的语句把83赋给shirt:
    char shirt=‘S’;
    但“S”不是字符常量,它表示的是两个字符(字符S和\0)组成的字符串。更糟糕的是,“S“实际上表示的是字符串所在的内存地址。

  • 拼接字符串常量
    有时字符串很长,无法放到一行中。c++允许拼接字符串字面值,即将两个用引号括起的字符串合并为一个。事实上,任何两个由空白(空格、制表符、换行符)分割的字符串常量都将自动拼接成一个。

  • 在数组中使用字符串

    有两种方法:将数组初始化为字符串常量、将键盘或文件输入读入到数组中

    strlen()函数返回的是存储在数组中的字符串的长度,而不是数组本身的长度。它只计算可见的字符,而不把空字符计算在内。

    sizeof()指出整个数组的长度

  • 字符串输入

cin使用空白(空格、制表符、换行符)来确定字符串的结束位置,这意味着cin在获取字符数组时只读取一个单词。读取该单词时,cin将该字符串放到数组中,并自动在结尾添加空字符。

  • 每次读取一行字符串输入

    getline()、get()读取一行输入,随后getline()将丢弃换行符,get()将换行符保留在输入序列中

    cin.getline(name,20);//name是数组的名称,20是读取的字符数。

    get()

    get并不再读取并丢弃换行符,而是将其留在输入队列中。假设我们连续两次调用get():

    cin.get(name,Arsize);
    cin.get(dessert,Arsize);
    

    由于在第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符便是换行符。因此get()认为已到达结尾,而没有发现任何可读取的内容。

    使用不带任何参数的cin.get()调用可读取下一个字符(包括换行符),可以采用下面的调用序列:

    cin.get(name,Arsize);
    cin.get();
    cin.get(dessert,Arsize);
    

    另一种使用get()的方式是将两个类成员函数拼接起来,如下所示:

    cin.get(name,Arsize).get();
    

    下面的语句与调用两次cin.getline()函数一样:

    cin.getline(name,Arsize.getline(dessert,Arsize);
    

    当getline()或get()读取空行时:

    当get()读取空行后将设置失效位(failbit)。这意味着接下来的输入被阻断,但可以用下面的命令来恢复输入:

    cin.clear()

    另一个输入的问题是,输入字符串可能比分配的空间长。则getline()和get()将把余下的字符留在输入队列中。

  • 混合输入字符串和数字

    #include <iostream>
    int main(){
    	using namespace std;
    	int year;
    	cin>>year;
    	char address[80];
    	cin.getline(address,80);
    	cout<<"Year="<<year<<endl;
    	cout<<"Address:"<<address<<endl;
    	cout<<"Done!\n";
    	return 0;
    }
    

在这里插入图片描述

上面的程序中,用户没有输入地址的机会,原因在于,当cin读取年份,将回车键生成的换行符留在了输入队列中。后面的cin.getline()看到换行符后,将认为是一个空行,并将一个空字符串赋给address数组。

解决方法是,在读取地址之前先读取并丢弃换行符:

cin>>year;
cin.get();     //or cin.get(ch);

string类简介

string类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。

使用string类,必须在程序中包含头文件string。

string对象可声明为简单变量,而不是数组。

  • c++11字符串初始化

    char first_date[]={“Le Chapon Dodu”};

  • 赋值、拼接和附加

    使用string类时,某些操作比使用数组更简单。例如,不能将一个数组赋给另一个数组。但可以将一个string对象赋给另一个string对象。

    string类简化了字符串合并操作。可以使用+将两个string对象合并起来,还可以使用+=:

    string str3;

    str3=str1+str2;

    str1+=str2;

  • string类的其他操作

    头文件cstring提供了这些函数:

    函数strcpy()将字符串复制到字符数组中,strcat()将字符串附加到字符数组末尾。

    确定字符串中字符数的方法:

    int len1=str1.size();

    int len2=strlen(charr1);

    #include <iostream>
    #include <string>
    #include <cstring>
    int main(){
    	using namespace std;
    	char charr[20];
    	string str;
    
    	cout<<"lengh:"<<strlen(charr)<<endl;
    	cout<<"lengh:"<<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<<"lengh:"<<strlen(charr)<<endl;
    	cout<<"lengh:"<<str.size()<<endl;
    	return 0;
    }
    

在这里插入图片描述
在用户输入之前,该程序指出数组charr中的字符串长度为27,大于数组长度。首先,未初始化的数组的内容是未定义的,其次,函数strlen()从数组的第一个元素开始计算字节数,直到遇到空字符。在这个例子中,在数组末尾的几个字节后才遇到空字符。对于未被初始化的数据,第一个空字符的出现位置是随机的,因此您在运行该程序时,得到的数组长度很可能与此不同。

str中的字符串长度为0;未被初始化的string对象的长度自动设置为0。

getline(cin,str);

它将cin作为参数,指出到哪里去查找输入。

  • 其他形式的字符串字面值

    c++新增了wchar_t,char16_t,char32_t,对于这些类型的字符串字面值,c++分别使用前缀L,u和U来表示。

    c++11新增的另一种类型是原始(raw)字符串。在原始字符串中,字符表示的就是自己。

    cout<<R"(jim “king” tuee uses “\n” instead of endl)"<<’\n’;

结构

同一个结构可以存储多种类型的数据(数组只能储存同类型),因而使用结构可以存储例如:运动员的信息,包括名字、工资、身高、体重、平均得分等。

结构是用户定义的类型,而结构声明定义了这种类型的数据属性。定义了类型后,便可以创建这种类型的变量。

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

定义结构后,便可以创建这种类型的变量了:

inflatable hat;

c++允许在声明结构变量时省略关键字struct。由于hat的类型为inflatable,因此可以用成员运算符(.)来访问各个成员。例如 hat.volume

#include <iostream>
struct inflatable{
	char name[20];
	float volume;
	double price;
};
int main()
{
	using namespace std;
	inflatable guest={
		"gloy",
		1.88,
		29.99
	};
	inflatable pal={
		"bino",
		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;
}

在这里插入图片描述
程序说明:

结构声明的位置很重要,可以将声明放在main()中,也可以放在main()的前面。通常应使用外部声明,所有函数都可以使用这类结构。

变量也可以在函数内部和外部定义,但c++不提倡使用外部变量,提倡使用外部声明。

c++11结构初始化

与数组一样,c++11也支持将列表初始化用于结构,且等号(=)是可选的‘=:

inflatable duck{“BNnijio”,0.12,43.546};

如果大括号里未包含任何东西,各个成员都将被设置为0。

最后,不允许缩窄变换。

结构可以将string类作为成员吗

#include <string>
struct inflatable{
    std::string name;
    float volume;
    double price;
}

答案是肯定的,只要使用的编译器支持以string对象作为成员的结构进行初始化。

其他结构属性

c++使用户定义的类型与内置类型尽可能相似。例如,可以将结构作为参数传递给函数,也可以让函数返回一个结构。另外,还可以使用赋值运算符(=)将结构赋给另一个同类型的结构。

可以同时完成定义结构和创建结构变量的工作,只需把变量名放在结束括号的后面即可:

struct perks{
    int keyz;
    char car[12];
}mr_smith,ms_jones;
//甚至可以初始化这种方式创建的变量
struct perks{
    int keyz;
    char car[12];
}my_glitz={
    7,
    "Packed"
};
//还可以声明没有名称的结构类型,方法是省略名称,同时定义一种结构类型和一个这种类型的变量:
struct {
    int x;
    int y;
}position;//创建一个名为position的结构变量,可以使用运算符来访问它的成员(如position.x),但这种类型没有名称,无法创建这种类型的变量。

结构数组

inflatable 结构包含一个数组。也可以创建元素为结构的数组:

inflatable gifts[100];//100个inflatable结构的数组
//可以与运算符一起使用
cin>>gifts[0].volume;
//初始化结构数组
inflatable guests[2]={
    {"ones",0.3,21.99},
    {"twos",0.5,78.99}
};

结构中的位字段

c++允许指定占用特定位数的结构成员,这使得创建于某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整型或枚举,接下来是冒号,冒号后面是一个数字,它指定了使用的位数。可以使用没有名称的字段来提供间距:

struct torgle{
    unsigned int SN:4;
    unsigned int :4;
    bool goodIn:1;
    bool goodTorgle:1;
};

共用体

共用体是一种数据格式,它能够存储不同的数据类型,但只能存储其中的一种类型。结构可以同时存储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.39;

因此,pail有时可以是int变量,有时又可以是double变量。共同体的长度为其最大成员的长度。

共同体的用途之一是,当数据项使用两种或更多格式(但不会同时使用),可节省空间。例如,假设管理一个小商品目录,其中一些商品的ID为整数,而另一些的ID为字符串,则可以这样做:

struct widgt{
    char brand[20];
    int type;
    union id{
        long id_num;
        char id_char[20];
    }id_val;
};
...
    widgt prize;
...
if (prize.type==1)
    cin>>prize.id_val.id_num;
else:
	cin>>prinze.id_val.id_char;

匿名共用体没有名称,其成员将成为位于相同地址处的变量。显然,每次只有一个成员是当前的成员:

struct widget{
    char brand[20];
    int type;
    union{
        long id_num;
        char id_char[20];
    }id_val;
};
...
    widgt prize;
...
if (prize.type==1)
    cin>>prize.id_num;
else:
	cin>>prinze.id_char;
//由于共用体是匿名的,因此id_num和id_char被视为prize的两个成员,他们的地址相同,所以不需要中间标识符id_val。

枚举

c++的enum工具提供了另一种创建符号常量的方式,这种方式可以代替const。它还允许定义新类型。

enum spectrum{red,orange,yellow,green};

将red、orange、yellow等作为符号常量,它们对应整数值0~3,这些常量叫做枚举量。

默认情况下,将整数值赋给枚举量。第一个枚举量的值为0,第二个枚举量的值为1。可以通过显式地指定整数值来覆盖默认值,可以用枚举名来声明这种类型的变量:

spectrum band;

在不进行强制类型转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量,如下所示:

band=blue;		//vaild
band=2000;		//invaild,spectrum受到限制,只有8个可能的值。
band=orange;	//vaild	
++band;			//invaild
band=orange+red;//invaild

如果band的值为3,++band将其增加到4,而4是无效的。

枚举只定义了赋值运算符,没有算术运算。

枚举量是整型,可被提升为int类型,但int类型不能自动转换为枚举类型:

int color=blue;	//vaild
band=3;			//invaild
color=3+red;	//vaild

如果int值是有效的,则可以通过强制类型转换,将它赋给枚举变量:

band=spectrum(3);

如果试图对一个不适当的值进行强制类型转换,结果是不确定的,这意味着这样做不会出错,但不能依赖得到的结果:

band=spectrum(40003);

枚举常被用来定义相关的符号常量,而不是新类型。例如,可以用枚举来定义switch语句中使用的符号常量。

设置枚举量的值

使用赋值运算符显式地设置枚举量的值:

enum bits{one=1,two=2,four=4,eight=8};

指定的值必须是整数,也可以只显式地定义其中一些枚举量的值:

enum bigstep{first,second=100,third}

first默认为0,后面未被设置的将比前面的枚举量大1。因此,third为101。

可以创建多个值相同的枚举量:

enum{zero,null=0,one,numero_uno=1};

枚举的取值范围

每个枚举都有取值范围,通过强制类型转换,可以将取值范围中的任何整数赋给枚举变量,即使这个值不是枚举值。

enum bits{one=1,two=2,four=4,eight=8};
bits myflag;
maflag=bits(6);		valid

其中6不是枚举值,但它位于枚举定义的取值范围内。

取值范围的定义如下:

首先要找到上限,需要知道枚举值的最大值,找到大于这个最大值的、最小的2的幂,将它减去1,便是上限。例如,上边的bigstep的最大值枚举值为101。在2的幂中,比这个数大的最小值为128,因此取值范围的上限为127.

计算下限:知道枚举量的最小值,如果它不小于0,则取值范围的下限为0,否则采取与上限相同的方式,但加上负号。例如如果最小的枚举量是-6,而比它小的、最大的2的幂是-8,因此下限为-7.

指针和自由存储空间

常规变量的地址:只需对变量应用地址运算符(&),变量home的地址为&home。

指针与c++基本原理

面向对象编程与传统的过程性编程的区别在于:OOP强调的是在运行阶段(而不是编译阶段)进行决策。运行阶段决策就好比度假时,选择参观那些景点取决于天气和当时的心情,而编译阶段决策更像不管在什么条件下,都坚持预先设定的日程安排。

#include <iostream>
int main(){
	using namespace std;
	int updates=6;
	int *p_updates;
	p_updates=&updates;

	cout<<"Values:updates= "<<updates;
	cout<<",*p_updates= "<<*p_updates<<endl;

	cout<<"Addresses:&updates= "<<&updates;
	cout<<",p_updates= "<<p_updates<<endl;

	*p_updates=*p_updates+1;
	cout<<"Now updates= "<<updates<<endl;
	return 0;
}

在这里插入图片描述

int变量updates和p_updates不过是同一枚硬币的两面。变量p_update表示地址,并使用*运算符来获得值。可以将值赋给 *p_update,修改updates的值。

声明和初始化指针

指针声明必须指定指针指向数据的类型,因为不同类型使用的字节数是不同的。

int *p_updates;

p_updates的类型是指向int的指针,或int*。而 *p_updates是int,而不是指针。

*运算符两边的空格是可选的:

int *ptr;	//c语言常用
int* ptr;	//c++使用
int*ptr;		
int* p1,p2;		//声明创建一个指针(P1)和一个int变量p2

指针的危险

在c++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。为数据提供空间是一个独立的步骤,不能忽略这一步:

long * fellow;
*fellow =223323;

上述代码没有将地址赋给fellow,那么223323被放在哪里尼。由于fellow没有被初始化,可能有任何值,fellow指向的地方很可能并不是所要存储的地方。

一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。

指针和数字

要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:

int *pt;
pt=0xB8000000;		//类型不匹配
int *pt;
pt=(int*) 0xB8000000;	//类型匹配

使用new来分配内存

在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。

程序员要告诉new,需要为那种数据类型分配内存,new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针:

int * pn=new int;

new int告诉程序,需要适合存储int的内存,new运算符根据类型来确定需要多少个字节的内存。然后,找到这样的内存,并返回其地址。接下来,把地址赋给pn,pn是被声明为指向int的指针。现在,pn是地址,而*pn是存储在那里的值,将这种方法与将变量的地址赋给指针进行比较:

int higgens;
int * pt=&higgens;

为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下:

typeName * pointer_name=new typeName;

#include <iostream>
int main(){
	using namespace std;
	int nights=1001;
	int *pt=new int;
	*pt=1001;
	cout<<"nights value= ";
	cout<<nights<<":location "<<&nights<<endl;
	cout<<"int ";
	cout<<"value= "<<*pt<<":location= "<<pt<<endl;
	double *pd=new double;
	*pd=10000001.0;

	cout<<"doule ";
	cout<<"value = "<<*pd<<":location= "<<pd<<endl;
	cout<<"location of pointer pd: "<<&pd<<endl;
	cout<<"size of pt= "<<sizeof(pt);
	cout<<":size of *pt= "<<sizeof(*pt)<<endl;
	cout<<"size of pd= "<<sizeof pd;
	cout<<":size of *pd= "<<sizeof(*pd)<<endl;

	return 0;
}

在这里插入图片描述

对于指针,new分配的内存块通常与常规变量声明分配的内存块不同。变量nights和pd的值都存储在被称为栈的内存区域中,而new从被称为堆或自由存储区的内存区域分配内存。

使用delete释放内存

使用delete运算符,使得在使用完内存后,能够将其归还给内存池。

使用delete时,后面要加上指向内存块的指针(这些内存块最初是用new分配的):

int * ps=new int;
...
delete ps;

这将释放ps指向的内存,但不会删除指针ps本身。一定要配对的使用new和delete,否则将发生内存泄漏。

不要尝试释放已经释放的内存块,不能使用delete来释放声明变量所获得的内存:

int *ps =new int;
delete ps;
delete ps;		//not ok 
int jugs=5;
int *pi=&jugs;
delete pi;		//not allowed

使用new来创建动态数组

静态联编:如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否使用数组,数组就在那里,它占用了内存。

动态联编:使用new时,在运行阶段需要数组,则创建它,不需要则不创建。在程序运行时选择数组的长度。

使用new创建动态数组

只有将数组的元素类型和元素数告诉new即可:

int *psome=new int[10];

new运算符返回第一个元素的地址,该地址被赋给指针psome。

对于使用new创建的数组,使用另一种格式的delete释放:

delete [] psome;

方括号告诉数组,应释放整个数组,而不仅仅是指针指向的元素。

由于编译器不能对psome是指向10个整数中的第1个这种情况进行跟踪,因此编写程序时,必须让程序跟踪元素的数目。

使用动态数组

把指针当做数组名使用即可,对于第一个元素,使用psome[0],第2个,使用psome[1],以此类推。

#include <iostream>
int main(){
	using namespace std;
	double * p3=new double[3];
	p3[0]=0.2;
	p3[1]=0.5;
	p3[2]=0.8;
	cout<<"p3[1] is "<<p3[1]<<".\n";
	p3=p3+1;
	cout<<"Now p3[0] is "<<p3[0]<<" and ";
	cout<<"p3[1] is "<<p3[1]<<".\n";
	p3=p3-1;
	delete [] p3;
	return 0;
}

在这里插入图片描述

将p3加1,则p3[0]指的是第2个元素。

指针、数组和指针算术

short tell[10];
cout<<tell<<endl;
cout<<&tell<<endl;

在这里插入图片描述

从数字上来说,两个地址相同,但从概念上来说,&tell[0]是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。因此,表达式tell+1将地址值+2,而表达式&tell+1将地址加10。

指针和字符串

在cout和多数c++表达式中,char数组名、char指针以及用引号括起的字符串常量都被解释为字符串第一个字符的地址

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

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

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

	return 0;
}

在这里插入图片描述
由于bird指针被声明为const,编译器将禁止改变bird指向的位置中的内容:

1.有些编译器将字符串字面值视为只读常量,修改会报错;

2.有些编译器只使用字符串字面值的一个副本来表示程序中所有的该字面值。

也不能将信息读入ps,ps没有初始化,不知道信息被存储在哪里,这甚至可能改写内存中的信息。避免这个问题——使用足够大的char数组来接受输入。

所以,在将字符串读入程序时,应使用已分配的内存地址。该地址可以是数组名,也可以是使用new初始化过的指针。

给cout提供一个指针,它将打印地址。但如果指针的类型是char,则cout将显示指向的字符串*。

如果要显示字符串的地址,则要强制类型转换,如上程序中的 (int*)ps显示为该字符串的地址。

将animal赋给ps并不会复制字符串,而只是复制地址。这样,这两个指针将指向相同的内存单元和字符串。

获得字符串的副本

首先,需要分配内存来储存该字符串——声明一个数组或使用new来完成。

ps=new char[strlen(animal)+1];

字符串”fox“不能填满整个animal数组,因此这样做浪费了空间,上述代码使用strlen()来确定字符串的长度,并将它加1来获得包含空字符的字符串长度。随后用new分配。

接下来,将animal中的字符串复制到新分配的空间中,不能直接赋值,这样只能修改存储在ps中的地址,使用strcpy():

strcpy(ps,animal);

strncpy()函数还接受第三个参数——要复制的最大字符数。

应使用strcpy()或strncpy(),而不是赋值运算符来将字符串赋给数组。

使用new创建动态结构

创建结构

创建一个未命名的inflatable类型,并将其地址赋给一个指针:

inflatable * ps =new inflatable;
访问成员

不能将成员运算符句点用于结构名,因为这种结构没有名称,只知道地址。使用运算符(—>)。例如,如果ps指向一个inflatable结构,则ps—>是被指向的结构的price成员。

另一种访问方式:(*ps).price

#include <iostream>
#include <cstring>	
	struct inflatable{
		char name[20];
		float volume;
		double price;
	};
int main(){
	using namespace std;
	inflatable * ps=new inflatable;
	cin.get(ps->name,20);
	cin>>(*ps).volume;
	cin>>ps->price;
	cout<<"name: "<<(*ps).name<<endl;
	cout<<"price: "<<ps->price<<endl;
	return 0;
}
一个使用new和delete的示例
#include <iostream>
#include <cstring>
using namespace std;
char * getname(void);	
int main(){
	char * name;
	name=getname();
	cout<<name<<" at "<<(int*)name<<"\n";
	delete [] name;

	name=getname();
	cout<<name<<" at "<<(int*)name<<"\n";
	delete [] name;
	return 0;
}
char * getname()
{
	char temp[80];
	cout<<"enter last name: ";
	cin>>temp;
	char*pn =new char[strlen(temp)+1];
	strcpy(pn,temp);

	return pn;
}

在这里插入图片描述

从程序可以看出,c++不保证新释放的内存就是下一个使用new时选择的内存。

这个例子吧new和delete放在不同的函数中,可行但不是一个好的方法。

自动存储、静态存储和动态存储

c++有三种管理数据内存的方式

1.自动存储:

在函数内部定义的常规变量使用自动存储空间,被称为自动变量。它们在所属的函数被调用时自动产生,在该函数结束时消亡。

自动变量是一个局部变量,其作用域为包含它的代码块。

自动变量通常存储在栈中,变量依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量。

2.静态存储:

静态存储是整个程序执行期间都存在的存储方式,使变量成为静态的方式有两种:一种是在函数外面定义它,另一种是在声明变量时使用关键字static:

static double fee=56.50;

3.动态存储:

new和delete提供了一种比自动变量和静态变量更灵活的方法。它们管理一个内存池,在c++中被称为自由存储空间或堆。同上两者变量的内存是分开的。

new和delete能够在一个函数中分配内存,而在另一个函数中释放,数据的生命周期不完全受程序或函数的生存时间控制。

类型组合

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

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

该程序的输出如下:

2003

1999

1998

1999

c++11版本的auto能够推断出ppb的类型:

auto ppb=arp;

数组的替代品

模板类vector

要使用vector对象,必须包含头文件vector;vector包含在名称空间std中,可以使用using编译指令;模板使用不同的语法来指出它储存的数据类型;vector类使用不同的语法来指定元素数。

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

模板类array

array需要包含头文件array,创建对象语法:

array<typeName,n_elem> arr;//创建一个名为arr的array对象,包含n_elem个元素,类型为typeName

比较数组、vector对象和array对象

#include <iostream>
#include <vector>
#include <array>
int main(){
	using namespace std;
	double al[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[0]=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;
}

在这里插入图片描述

从程序可以看出,array对象和数组存储在相同的内存区域(栈),vector存储在另一区域(自由区域或堆);可以将一个array对象赋给另一个array对象;而对于数组,必须逐元素复制数据。

a1[-2]=20.2;

意思即:*(a1-2)=20.2;找到a1指向的地方,向前移两个double元素存储20.2。

也可以使用vector和array的成员函数at():

a2.at[1]=2.3;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值