第四章 复合类型
本章内容包括:
l 创建和使用数值。
l 创建和使用C-风格字符串。
l 创建和使用string类字符串
l 使用方法getline()和get()读取字符串。
l 混合输入字符串和数字。
l 创建和使用结构。
l 创建和使用共用体。
l 创建和使用枚举。
创建和使用指针。
l 使用new和delete管理动态内存。
l 创建动态数组。
l 创建动态结构。
l 自动存储、静态存储和动态存储。
l vector和array类简介
假设您开发了一个名叫User-Hostile的计算机游戏,玩家需要用智慧来应对一个神秘、险恶的计算机界面。现在,必须编写一个程序来跟踪5年来游戏每月的销售量,或者希望盘点一下与黑客累积的较量回合。您很快发现,需要一些比C++的简单基本类型更复杂的东西,才能满足这些数据的要求,C++也提供了这样的东西——复合类型。这种类型是基于基本整型和浮点类型创建的。影响最为深远的复合类型是类,它是将学习的OOP的堡垒。然而,C++还支持几种更普通的复合类型,它们都来自于C语言。例如,数组可以存储多个同类型的值。一种特殊的数组可以存储字符串(一系列字符)。结构可以存储多个不同类型的值。而指针则是一种将数据所处的位置告诉计算机的变量。本章将介绍所有这些复合类型(类除外),还将介绍new和delete及如何使用它们来管理数据。另外,还将简单地介绍string类,它提供了另一种处理字符串的途径。
4.1 数组
数组是一种数据格式,能够存储多个同类型的值。例如,数组可以存储60个int类型的值表示游戏5年来的销售量)、12个short值(这些值表示每个月的天数)或365个float值(这些值指出一年中每天在食物方面的开销)。每个值都存储一个独立的数组元素中,计算机在内存中依次存储数组的各个运算。
要创建数组,可使用声明语句。数组声明指出以下三点:
l 存储在每个元素中的值的类型;
l 数组名
l 数组中的元素数
在C++中,可以通过修改简单变量的声明,添加中括号(其中包含元素数目)来完成数组声明。例如,下面的声明创建一个名为months的数组,该数组有12个元素,每个元素都可以存储一个short类型的值:
short months[12];
事实上,可以将数组中的每个元素看作是一个简单变量。
声明数组的通用格式如下:
typeName arrayName[arraySize];
表达式arraySize指定元素数目,它必须是整型常数(如10)或const值,也可以是常量表达式(如8*sizeof(int)),即其中所有的值在编译时都是已知的。具体地说,arraySize不能是变量,变量的值是在程序运行时设置的。然而,本章稍后将介绍如何使用new运算符来避开这种限制。
作为复合类型的数组
数组之所以被称为复合类型,是因为它是使用其他类型来创建的(C语言使用术语“派生类型”,但由于C++对类关系使用术语“派生”,所以它必须创建一个新术语)。不能仅仅将某种东西声明为数组,它必须是特定类型的数组。没有通用的数组类型,但存在很多特定的数组类型,如char数组或long数组。例如,请看下面的声明:
float loans[20];
loans的类型不是“数组”,但是“float数组”。这强调了loans数组是使用float类型创建的。
数组的很多用途都是基于这样一个事实;可以单独访问数组元素。方法是使用下标或索引来对元素进行编号。C++数组从0开始编号(这没有商量的余地,必须从0开始。Pascal和BASIC用户必须调整习惯)。C++使用带索引的方括号表示来指定数组元素。例如,months[0]是months数组的第一个元素,months[11]是最后一个元素。注意,最后一个元素的索引比数组长度小1.因此,数组声明能够使用一个声明创建大量的变量,然后便可以用索引来标识和访问各个元素。
有效下标值的重要性
编译器不会检查使用的下标是否有效。例如,如果将一个赋值给不存在大的元素month[101],编译器并不会指出错误。但是程序运行后,这种赋值可能引发问题,它可能破坏数据或代码,也可能导致程序异常终止。所以必须确保程序只使用有效的下标值。
程序4.1中的马铃薯分析程序说明了数组的一些属性,包括声明数组、给数组元素赋值以及初始化数组。
//arrayone.cpp——small arrays of integers #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<<" bytes.\n"; cout<<"Size of one element = "<<sizeof yams[0]; cout<<" bytes.\n"; return 0; }
4.1.1 程序说明
该程序首先创建一个名为yams的包含3个元素的数组。由于yams有3个元素,它们的编号为0~2分别给这三个元素赋值。Yam的每个元素都是int,都有int类型的权力和特权。因此arrayone.cpp能够将值赋给元素、将元素相加和相乘,并显示它们。
程序给yam的元素赋值时,绕了一个大弯。C++允许在声明语句中初始化数组元素,程序4.1使用这种捷径来给yamcosts数组赋值:
int yamcosts[3] = {20,30,5};
只需提供一个用逗号分隔的值列表(初始化列表),并将它们用花括号括起即可。列表中的空格是可选的。如果没有初始化函数中定义的数组,则其元素值将是不确定的,这意味着元素的值为以前驻留在该内存单元中的值。
接下来,程序使用数组值进行一些计算。程序的这部分由于包含了下标和括号,所以看上去有些混乱。第5章将介绍for循环,它可以提供一种功能强大的方法来处理数组,因而不用显式地书写每个索引。同时,我们仍然见此使用小型数组。
您可能还记得,sizeof运算符返回类型或数据对象的长度(单位为字节)。注意,如果将sizeof运算符用于数组名,得到的将是整个数组中的字节数。但如果就爱那个sizeof用于数组元素,则得到的将是元素的长度(单位为字节),这表明yams[1]只是一个int变量。
4.1.2 数组的初始化规则
C++有几条关于初始化数组的规则,它们限制了初始化的时刻,决定了数组的元素数目与初始化器中值的数目不相同时将发生的情况。我们来看看这些规则。
只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组:
int cards[4] = {3 ,6 , 8 , 10 };
int hand[4];
hand[4] = {5 ,6 ,7 ,9}; //不允许
hand = cards; //不允许
然而,可以使用下标分别给数组中的元素赋值。
初始化数组时,提供大的值可以少于数组元素数目。例如,下面的语句值初始化hotelTips的前两个元素:
float hotelTips[5] = {5.0 , 2.5};
如果只对数组的一部分进行初始化,则编译器将把其他元素设置为0。因此,将数组中所有的元素都初始化为0非常简单——只要显式地将第一个元素初始化为0,然后让编译器将其他元素都初始化为0即可“
long totals[500] = {0};
如果初始化为{1}而不是{0},则第一个元素被设置为1,其他元素都被设置为0。
如果初始化数组时方括号([])为空,C++编译器将计算元素个数。例如,对于下面的声明:
short things[] = { 1 , 5 , 3 , 8};
编译器将使things数组包含4个元素。
让编译器去做
通常,让编译器中计算元素个数是种很糟的做法,因为其计数可能与您想象的不一样。例如,您可能不小心在列表中遗漏了一个值。然而,这种方法对于将字符数组初始化为一个字符串来说比较安全,很快您将明白这一点。如果主要关心的问题是程序,而不是自己是否知道数组的大小,则可以这样做:
short thing[ ] = { 1 , 5 , 3 , 8};
int num_elements = sizeof things / sizeof (short);
这样做是有用还是偷懒取决于具体情况。
4.1.3 C++11数组初始化方法
第3章说过,C++11将使用大括号的初始化(列表初始化)作为一种通用初始化方式,可用于所有类型。数组以后就可使用列表初始化,但C++11中的列表初始化新增了一些功能。
首先,初始化数组时,可省略等号(=):
double earnings [ 4 ] = { 1.2e4 , 1.6e4 , 1.1e4 , 1.7e4};
其次,可不在大括号内包含任何东西,这将把所有元素都设置为零:
unsigned int counts [10] = {} ;
float balances [100] {};
第三,列表初始化禁止缩窄变换,这在第3章介绍过:
long plifs[] = { 25 , 92 , 3.0};
char slifs[4] {‘h’ , ‘i’ , 1122011 , ‘\0’};
char tlifs[4] {‘h’ , ‘i’ , 112 ,’’\0};
在上述代码中,第一条语句不能通过编译,因为将浮点数转换为整型是缩窄操作,即使浮点数的小数点后面为零。第二条语句也不能通过编译,因为1122011超出了char变量的取值范围(这里假设char变量的长度为8位)。第三条语句可通过编译,因为虽然112是一个int值,但它在char变量的取值范围内。
C++标准模板库(STL)提供了一种数组替代品——模块类vector,而C++11新增了模板类array。这些替代品比内置复合类型数组更复杂、更灵活,本章将简要地讨论它们,而第16章将更详细地讨论它们。