数组
数组(array)是一种数据格式,能够存储多个同类型的值。例如30个int类型的值,12个float类型的值。每个值都存储在一个独立的数组元素中,计算机在内存中依次存储数组的各个元素。
创建数组时应指出以下三点:
- 存储在每个元素中的值的类型
- 数组名
- 数组中的元素数
在C++中,可以通过修改简单变量的声明,添加中括号(其中包含元素数目)来完成数组声明。如
int months[12];
上面一条语句的意思是,创建一个名为months的数组,该数组有12个元素,每个元素都可以存储一个int类型的值
声明数组的通用格式是:
typeName arrayName[arraySize];
arraySize指定元素数目,它必须是整型常数(10、23)或const值,也可以是常量表达式,即其值在编译时都是已知的,不能是变量
数组可以单独访问数组元素
方法是使用下标或索引来对元素进行编号。C++数组从0开始编号,用带索引的方括号来表示指定元素,例如months[0]表示months数组的第一个元素,months[11]表示数组的最后一个元素
数组的初始化
只有在定义数组的时候才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组
int hards[4] = {3,4,5,6}; //ok
int hands[4]; //ok
hands[4] = {5,6,7,8}; //error
hands = hards; //error
初始化数组时,提供的值可以少于数组的元素数目,如果只对数组的一部分进行初始化,则编译器把其他元素设置为0。
这里需要注意的是,C++11允许使用大括号的初始化方法(列表初始化)来进行数组的初始化。但是列表初始化禁止缩窄转换
long lifts[]={24,56,3.0};
上面这条语句不能通过编译,因为将浮点数转换为整型是缩窄操作。
字符串
字符串是存储在内存的连续字节中的一系列字符。C++处理字符串的方式有两种。第一种来自C语言,被称为C-风格字符串,另外一种是基于string类库的方法
存储在连续字节中的一系列字符意味着可以将字符串存储在char数组中,其中每个字符都位于自己的数组元素中。
C-风格字符串具有一种特殊的性质:以空字符结尾,空字符被写作\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数组,但只有第二个数组是字符串
上述初始化方法需要使用大量单引号,还需要加上空字符,很繁杂。有一种将字符数组初始化为字符串的方法——只使用一个引号括起来的字符串即可,这种字符串被称为字符串常量或字符串字面值
char brid[11]="Mr. Cheeps";
用引号括起来的字符串隐式地包括结尾的空字符。
应确保数组足够大,能够存储字符串中所有字符——包括空字符
拼接字符串常量
C++允许拼接字符串字面值,即将两个用引号括起来的字符串合并为一个。(任何两个由空白、制表符、换行符)分隔的字符串常量都将自动拼接成一个
strlen()只计算可见的字符。sizeof运算符指出整个数组的长度;
在数组中使用字符串
要将字符串存储在数组中,最常用的方法有两种——将数组转化为字符串常量、将键盘或文件输入读入到数组中。
字符串输入
cin使用空白(空格、制表符和换行符)来确定字符串的结束位置,这意味着cin在获取字符数组输入时只读入一个单词。
每次读取一行字符串输入
每次读取一个单词通常不是最好的选择,例如要求程序完整保存输入的New York,而不是只保存了New,需要采取另一种字符串读取方式
- 面向行的输入:getline()
getline()函数读取整行,它通过使用回车键输入的换行符来确定结尾。要调用这种方法,可以使用
cin.getline(name,number);
该函数有两个参数,第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数
(如果这个参数为20,则它最多能读取19个字符,剩下的一个空间用于自动存储结尾的空字符)
getline()成员函数在读取指定数目的字符或遇到换行符时停止读取
getline()函数每次读取一行,通过换行符来确定行尾,但不保存换行符,相反它在存储字符串时,用空字符串来替换换行符
- 面向行的输入:get()
istream类有另一个名为get()的成员函数,它的一种使用方式与getline()类似,接受的参数相同,解释参数的方式也相同,并且都读取到行尾。但get不再丢弃换行符,而是将其留在输入队列中。
cin.get(name,number);
另外还有一种使用方法是,使用不带任何参数的cin.get()调用可读取下一个字符(即使是换行符),因此可以用它来处理换行符,为读取下一行输入做好准备。
cin.get()
string类
可以使用string类型的变量而不是字符数组来存储字符串
#include<iostream>
#include<string>
using namespace std;
int main()
{
char ch[10] = "jaguar";
string str = "pather";
cout << "ch: " << ch << endl;
cout << "str: " << str << endl;
}
string对象和字符数组之间的主要区别是,可以将string对象声明为简单变量,而不是数组,而且类设计让程序能够自动处理string的大小,这使得与使用数组相比,使用string更方便
string对象的一些操作
- 前面讲过不能将一个数组赋值给另一个数组,但可以将一个string对象赋给另一个string对象
- string还可以使用+运算符将两个string对象合并起来,还可以使用+=运算符将字符串附加到string对象的末尾
- 对C风格字符串,头文件cstring提供函数strcpy()将字符串复制到字符数组中,使用strcat()将字符串附加到字符数组末尾;
strcpy(charr1,charr2); //copy charr2 to charr1
strcat(charr1,charr2); //append contents of charr2 to charr1
- string类I/O。可以使用cin和运算符>>来将输入存储到string对象中,使用cout和运算符<<来显示string对象,其句法与处理C风格字符串相同;但是在每次读取一行时两者使用的句法不同;
对于C风格字符串使用方法是
cin.getline(name,number);
这种句点表示法表明,函数getline()是istream类的一个类方法(第一个参数是目标数组,第二个参数是数组长度)
而对于string对象。使用方法是
getline(cin,name);
这里没有使用句点表示法,表明这个getline()不是类方法,它将cin作为参数,指出到哪里去查找输入。
结构简介
数组虽然可以存储多个元素,但是要求所有元素的类型必须相同。C++中的结构是一种比数组更灵活的数据格式,同一个结构中可以存储多种类型的数据。结构是用户定义的类型,而结构声明定义了这种类型的数据属性。
关键字struct表明,这些代码定义的是一个结构的布局,标识符inflatable是这种数据格式的名称。这样便可以像创建char或int类型的变量那样创建inflatable类型的变量了。接下来的大括号中包含的是结构存储的数据类型的列表,其中每一个列表项都是一条声明语句。总之结构定义指出了新类型的特征。定义结构后,便可以使用这种类型的变量了。
#include<iostream>
#include<string>
using namespace std;
struct inflatable
{
char name[20];
float volume;
double price;
};
int main()
{
inflatable guest = { "Glorious",1.88,15.8 };
inflatable pal = { "Audacious",3.12,23.9 };
cout << "guest name: " << guest.name << " guest volume " << guest.volume;
}
结构声名的位置很重要,一种可以放在main()函数中,紧跟在开始括号的后面,另一种选择是将声明放在main()的前面 ,位于函数外面的称为外部声明。外部声明可以被其后面的任何函数使用,而内部声明只能被该声明所属的函数使用。
共用体
共用体是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。也就是说,结构体可以同时存储int、long、和double,共用体只能存储int、long/或double。共用体的句法与结构体相似,但含义不同,例如
union one4all
{
int int_val;
double double_val;
long long_val;
};
可以使用one4all变量来存储int、long/或double,条件是在不同的时间进行:
one4all pail;
pail.int_val=15;
cout<<pail.int_val;
pail.double_val=1.78;
cout<<pail.double_val;
pail有时可以是int变量,有时可以是double变量。
成员名称标识了变量的容量,由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以共用体的长度为其最大成员的长度。共用体的用途之一是当数据项使用两种或更多中格式(不会同时使用)时,可节省空间
枚举
enum的句法与使用结构相似,例如
enum spectrum{red,orange,yellow,green,blue,violet};
这条语句完成两项工作
- 让spectrum成为新类型的名称;spectrum被称为枚举
- 将red、orange等作为符号常量,它们对应整数值0~5。这些常量叫做枚举量
默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个枚举量的值为1,依次类推。
对于枚举,只定义了赋值运算符,没有为其定义算术运算
也可以使用赋值运算符来显式地设置枚举量的值
enum bits{one=1,two=2,four=4,eight=8};
指定的值必须是整数,当然也可只显式地定义其中一些枚举量的值
指针和自由存储空间
前面谈到过计算机程序在存储数据时必须跟踪的3种基本属性(信息存储在何处、存储的值为多少、存储的信息是什么类型)
前面通过定义一个简单变量。声明语句指出了值的类型和符号名,还让程序为值分配内存,并在内部跟踪该内存单元。
下面用另外一种方法——指针。指针是一个变量,其存储的是值的地址,而不是值本身。对变量应用地址运算符(&)就可以获得它的位置。
声明和初始化指针
指针声明必须指定指向的数据的类型
int* ptr;
这表明,*ptr的类型为int。由于*运算符被用于指针,因此ptr变量本身必须是指针。可以这样说ptr指向int类型、ptr的类型是指向int的指针。ptr是指针(地址),*ptr是int而不是指针。
计算机需要跟踪指针指向的值的类型,例如char的地址与double的地址看上去没什么两样,但char和double使用的字节数是不同的,它们存储值时使用的内部格式也不同。
指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值,在这种情况下只能通过指针来访问内存。
#include<iostream>
using namespace std;
int main()
{
int nights = 1001;
int* pt = new int; //allocate space for an int
*pt = 1001; //store a value there
cout << "nights value = " << nights << " ,location " << &nights << endl;
cout << "int ";
cout << "value = " << *pt << " ,location " << pt << endl;
return 0;
}
程序使用new为int类型的数据对象分配内存。这是在程序运行时进行的,指针pt指向这个数据对象,如果没有pt,将无法访问这个数据单元。有了这个指针,就可以像使用变量那样使用*pt了,将值赋给*pt,从而将这些值赋给新的数据对象。
当需要内存时,可以使用new来请求,另外在使用完内存后,使用delete运算符能够将内存还给内存池,归还的内存可供程序的其他部分使用。
int *ps = new int; //ok
delete ps; //ok
delete ps; //not ok
不要尝试释放已经释放的内存块,这样做的结果是不确定的。
【只能用delete来释放使用new分配的内存,对空指针使用delete是安全的】
在使用new和delete时,应遵守以下规则
- 不要使用delete释放不是new分配的内存
- 不要使用delete释放同一个内存块两次
- 如果使用new[ ]为数组分配内存,则应使用delete[ ]来释放
- 如果使用new为一个实体分配内存,则应使用delete(没有方括号)来释放
- 对空指针应用delete是安全的
指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式。将整型变量加一后其值将增加一,但将指针变量加一后,增加的量等于它指向的类型的字节数。(将指向double的指针加1后,如果系统对double使用8个字节存储,则数值将增加8);另外C++将数组名解释为地址