第四章 复合类型
4.1 数组
数组(array)是一种数据格式,能够存储多个同类型的值。
数据声明应指出以下三点:
- 存储在每个元素中的值的类型;
- 数组名;
- 数组中的元素数。
声明数组的通用格式如下:
typeName arrayName[arraySize];
作为复合类型的数组
使用其他类型来创建。
float loans[20];
loans的类型不是“数组”,而是“float数组”。
有效下标值的重要性
编译器不会检查使用的下标是否有效。程序运行后会引发问题。
4.1.1 程序说明
4.1.2 数组的初始化规则
int cards[4] = {3, 6, 8, 10};
4.1.3 C++11数组初始化方法
可省略等号(=)
double earnings[4] {1.2e4, 1.6e4, 1.1e4, 1.7e4}; // okay with C++11
大括号可不含任何元素,所有元素置为零
unsigned int counts[10] = {}; // all elements set to 0
float balances[100] {}; // all elements set to 0
禁止缩窄操作
long plifs[] = {25, 92, 3.0}; // not allowed
4.2 字符串
是存储在内存的连续字节中的一系列字符。
char boss[8] = "Bozo";
空值字符自动加到结尾;其余的元素设置为\0
4.2.1 拼接字符串常量
任何两个空白(空格、制表符和换行符)分隔的字符串常量都将自动拼成一个。
4.2.2 在数组中使用字符串
要将字符串存储到数组中,最常用的方法有两种----将数组初始化为字符串常量、将键盘或文件输入读入到数组中。
4.2.4 每次读取一行字符串输入
1.面向行的输入:getline()
读取整行,它使用通过回车键输入的换行符来确定输入结尾。调用cin.getline()。
第一个参数时用来存储输入行的数组的名称,第二个参数是要读取的字符数。
2.面向行的输入:get()
不丢弃换行符,两个类成员函数拼接(合并)。
3.空行和其他问题
恢复输入
cin.clear();
4.4 结构简介
结构是用户定义的类型,而结构声明定义了这种类型的数据属性。
struct inflatable // structure declaration
{
char name[20];
float volume;
double price;
};
关键字struct,inflatable类型名称。
4.4.5 结构数组
inflatable guests[2] = // initializing an array of structs
{
{"Bambi", 0.5, 21.99}, // first structure in array
{"Godzilla", 2000, 565.99} // next structure in array
};
4.4.6 结构中的位字段
指定了使用的位数。
struct torgle_register
{
unsigned int SN : 4; // 4 bits for SN value
unsigned int : 4; // 4 bits unused
bool goodIn : 1; // valid input (1 bit)
bool goodTorgle : 1; // successful torgling
};
4.5 共用体
共用体(union)是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。
结构体可以同时存储int、long和double,共用体只能存储int、long或double。
union one4all
{
int int_val;
long long_val;
double double_val;
};
共用体常用于节省内存。
4.6 枚举
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
- 让spectrum称为新类型的名称;spectrum被称为枚举(enumeration),就像struct变量被称为结构一样。
- 将red、orange、yellow等作为符号常量,它们对整数值0~7。这些常量叫作枚举量(enumerator)。
4.6.1 设置枚举量的值
enum bits{one = 1, two = 2, four = 4, eight = 8};
// third为101,比前面枚举量大1
enum bigstep{first, second = 100, third};
// zero和null都为0,one和umero_uno都为1
enum {zero, null = 0, one, numero_uno = 1};
4.6.2 枚举的取值范围
找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是取值范围的上限。
4.7 指针和自由存储空间
指针名表示的是地址。*运算符被称为间接值或解除引用运算符,将其应用于指针,可以得到该地址处存储的值。
4.7.4 使用new来分配内存
// new int告诉程序,需要适合存储int的内存。
int * pn = new int;
4.7.5 使用delete释放内存
int * ps = new int; // allocate memory with new
. . . // use the memory
delete ps; // free memory with delete when done
4.7.6 使用new来创建动态数组
在编译时给数组分配内存被称为静态联编,意味着数组是在编译时加入到程序中的。但使用new时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编,意味着数组是在程序运行时创建。这种数组叫作动态数组。使用静态联编时,必须在编写程序时制定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。
1.使用new创建动态数组
// new运算符返回第一个元素的地址。
int * psome = new int [10]; // get a block of 10 ints
使用new和delete时,应遵守以下规则:
- 不要使用delete来释放不是new分配的内存。
- 不要使用delete释放同一个内存块两次。
- 如果使用new[]为数组分配内存,则应使用delete[]来释放。
- 如果使用new为一个实体分配内存,则应使用delete(没有方括号)来释放。
- 对空指针应用delete是安全的。
4.8.2 指针小结
// 声明指针
typeName * pointerName;
// 给指针赋值
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 1st element of array of 30 double to pa
// 对指针解除引用
cout << *pn; // print the value of bubble
*pc = 'S'; // place 'S' into the memory location whose address is pc
// 区分指针和指针所指向的值,如果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
// 数组名
int tacos[10]; // now tacos is the same as &tacos[0]
// 指针算术
int tacos[10] = {5,2,8,4,1,2,2,4,6,8};
int * pt = tacos; // suppose pf and tacos are the address 3000
pt = pt + 1; // now pt is 3004 if a 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]
// 数组的动态联编和静态联编
int tacos[10]; // static binding, size fixed at compile time
int size;
cin >> size;
int * pz = new int [size]; // dynamic binding, size set at run time
...
delete [] pz; // free memory when finished
// 数组表示法和指针表示法
int * pt = new int [10]; // pt points to block of 10 ints
*pt = 5; // set element number 0 to 5
pt[0] = 6; // reset element number 0 to 6
pt[9] = 44; // set tenth element (element number 9) to 44
int coats[10];
*(coats + 4) = 12; // set coats[4] to 12
4.8.4 使用new创建动态结构
4.8.5 自动存储、静态存储和动态存储
自动存储:在函数内部定义的常规变量使用自动存储空间,被称为自动变量,在所属的函数被调用时自动产生,在该函数结束时消亡。
静态存储:是整个程序执行期间都存在的存储方式。
动态存储:new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。
栈、堆和内存泄露
如果使用new运算符在自由存储空间(或堆)上创建变量后,没有调用delete,将发生什么情况呢?如果没有调用delete,则即使包含指针的内存由于作用域规则和对象生命周期的原因而被释放,在自由存储空间上动态分配的变量或结构也将继续存在。实际上,将会无法访问自由存储空间中的结构,因为指向这些内存的指针无效。这将导致内存泄露。被泄露的内存将在程序的整个生命周期内都不可使用;这些内存被分配出去,但无法收回。极端情况是,内存泄露可能会非常严重,以致于应用程序可用的内存被耗尽,出现内存耗尽错误,导致程序崩溃。另外,这种泄露还会给一些操作系统或在相同的内存空间中运行的应用程序带来负面影响,导致他们崩溃。
4.10.1 模板类vector
类似于string类,也是一种动态数组。
vector<typeName> vt(n_elem);
4.10.2 模板类array(C++11)
vector类的功能比数组强大,但付出的代价是效率稍低。
array对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便,更安全。
array<typeName, n_elem> arr;