C++ Primer Plus——第4章 复合类型

复合类型:是基于基本整型和浮点类型创建的。

第4章 复合类型

  • 4.1 数组
    • 4.1.2 数组的初始化规则
    • 4.1.3 C++11数组初始化的方法
  • 4.2 字符串
    • 4.2.1 拼接字符串常量
    • 4.2.2 在数组中使用字符串
    • 4.2.3 字符串输入
    • 4.2.4 每次读取一行字符输入
    • 4.2.5 混合输入字符串和和数字
  • 4.3 string类简介
    • 4.3.1 C++11字符串初始化
    • 4.3.2 赋值、拼接和附加
    • 4.3.3 string类的其他操作
    • 4.3.4 string类I/O
    • 4.3.5 其他形式的字符串字面值
  • 4.4 结构简介
    • 4.4.1 在程序中使用结构
    • 4.4.2 C++11结构初始化
    • 4.4.3 结构可以将string类作为成员吗
    • 4.4.4 其他结构属性
    • 4.4.5 结构数组
    • 4.4.6 结构中的位字段
  • 4.5 共用体
  • 4.6 枚举
    • 4.6.1 设置枚举量的值
    • 4.6.2 枚举的取值范围
  • 4.7 指针和自由存储空间
    • 4.7.1 声明和初始化指针
    • 4.7.2 指针的危险
    • 4.7.3 指针和数字
    • 4.7.4 使用new来分配内存
    • 4.7.5 使用delete释放内存
    • 4.7.6 使用new来创建动态数组
  • 4.8 指针、数组和指针算术
    • 4.8.1 程序说明
    • 4.8.2 指针小结
    • 4.8.3 指针和字符串
    • 4.8.4 使用new创建动态结构
    • 4.8.5 自动存储、静态存储和动态存储
  • 4.9 类型组合
  • 4.10 数组的替代品
    • 4.10.1 模板类vector
    • 4.10.2 模板类array(C++11)
    • 4.10.3 比较数组、vector对象和array对象
  • 4.11 总结

4.1 数组

1、数组是一种数据格式,能够存储多个同类型的值。每个值都存储在一个独立的数组元素中,计算机在内存中依次存储数组的各个元素。
2、数组声明:
(1)存储在每个元素中的值的类型
(2)数组名
(3)数组中的元素数

short months[12];
typeName arrayName[arraySize];
// 表达式arraySize指定元素数目,它必须是整型常数或const值,也可以是常量表达式,即其中所有的值在编译时都是已知的,arraySize不能是变量,变量的值是在程序运行时设置的。

3、可以单独访问数组元素。方法是使用下标或索引来对元素进行编号。C++数组从0开始编号,注意,最后一个元素的索引比数组长度小1。C++使用带索引的方括号表示法来指定数组元素。必须确保程序只使用有效的下标值。
C++ Primer Plus——第4章 复合类型_第1张图片

// 程序清单4.1 arrayone.cpp--small arrays of integers
#include 

using namespace std;

int main(void)
{
    int yams[3];
    yams[0] = 7;
    yams[1] = 8;
    yams[2] = 6;
    // C++允许在声明语句中初始化数组元素,只需提供一个用逗号分隔的值列表(初始化列表),并将它们用花括号括起即可。
    // 如果没有初始化函数中定义的数组,则其元素值将是不确定的,这意味着元素的值为以前驻留在该内存单元中的值
    int yamscosts[3] = {20, 30, 5};

    cout << "Total yams = " << yams[0] + yams[1] + yams[2] << endl;
    cout << "The package with " << yams[1] << " yams costs " << yamscosts[1] << " cents per yam." << endl;
    int total = yams[0] * yamscosts[0] + yams[1] * yamscosts[1] + yams[2] * yamscosts[2];
    cout << "The total yam expense is " << total << " cents." << endl;
    // sizeof运算符返回类型或数据对象的长度(单位为字节)
    // 如果将sizeof运算符用于数组名,得到的将是整个数组中的字节数
    // 如果将sizeof用于数组元素,则得到的将是元素的长度
    cout << "Size of yams array = " << sizeof yams << " bytes." << endl;
    cout << "Size of one element = " << sizeof yams[0] << " bytes." << endl;

    return 0;
}

在这里插入图片描述

4.1.2 数组的初始化规则

// 只有在定义数组时才能使用初始化,此后就不能使用了
int cards[4] = {3, 6, 8, 10};
int hand[4];
hand[4] = {5, 6, 7, 9};  // 不允许
// 不能将一个数组赋给另一个数组
hand = cards; // 不允许
// 可以使用下标分别给数组中的元素赋值
// 初始化数组时,提供的值可以少于数组的元素数目,如果只对数组的一部分进行初始化,则编译器将把其他元素设置为0
// 将数组中所有的元素都初始化为0非常简单——只要显式地将第一个元素初始化为0,然后让编译器将其他元素都初始化为0即可
// 如果初始化数组时方括号([])内为空,C++编译器将计算元素个数
short things[] = {1, 5, 3, 8}; // 编译器使things数组包含4个元素

4.1.3 C++11数组初始化的方法

(1)初始化数组时,可省略等号(=)
(2)可不在大括号内包含任何东西,这将把所有元素都设置为0
(3)列表初始化禁止缩窄转换

long plifs[] = {25, 92, 3.0}; // 不可以,因为将浮点数转换为整型是缩窄操作
char slifs[] = {'h', 'i', 1122011, '\0'}; // 不可以,因为1122011超出了char变量的取值范围
char tlifs[] = {'h', 'i', 112, '\0'}; // 可以

4.2 字符串

1、字符串是存储在内存的连续字节中的一系列字符。C++处理字符串的方式有两种:第一种来自C语言,常被称为C-风格字符串,另一种基于string类库的方法。
2、char数组:可以将字符串存储在char数组中,其中每个字符都位于自己的数组元素中

// C-风格字符串具有一种特殊的性质:以空字符结尾,空字符被写作\0,其ASCII码为0,用来标记字符串的结尾
char dog[8] = {'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'}; // 不是字符串
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; // 是字符串
// C++有很多处理字符串的函数,其中包括cout使用的那些函数。它们都逐个地处理字符串中的字符,直到到达空字符为止
// 如果使用cout显示上面cat这样的字符串,则将显示前7个字符,发现空字符后停止。
// 如果使用cout显示上面的dog数组(它不是字符串),cout将打印出数组中的8个字母,并接着将内存中随后的各个字节解释为要打印的字符,直到遇到空字符为止。
// 可使用字符串常量(字符串字面值)将字符数组初始化为字符串
char bird[11] = "Mr. Cheeps";
char fish[] = "Bubbles";
// 用引号括起的字符串隐式地包括结尾的空字符,因此不用显式地包括它
// 各种C++输入工具通过键盘输入,将字符串读入到char数组中时,将自动加上结尾的空字符
// 应确保数组足够大,能够存储字符串中所有字符——包括空字符

C++ Primer Plus——第4章 复合类型_第2张图片
3、混淆字符串常量和字符常量:字符常量’S’是字符串编码的简写表示。在ASCII系统上,'S’只是83的另一种写法。"S"不是字符常量,它表示的是两个字符(字符S和\0)组成的字符串。"S"实际上表示的是字符串所在的内存地址。

4.2.1 拼接字符串常量

C++允许拼接字符串字面值,即将两个用引号括起的字符串合并为一个。任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将自动拼接成一个。因此,下面所有的输出语句都是等效的。

cout << "I'd give my right arm to be" " a great violinist.\n";
cout << "I'd give my right arm to be a great violinist.\n";
cout << "I'd give my right ar"
"m to be a great violinist.\n"

注意,拼接时不会在被连接的字符串之间添加空格,第二个字符串的第一个字符将紧跟在第一个字符串的最后一个字符(不考虑\0)后面。第一个字符串中的\0字符将被第二个字符串的第一个字符取代。

4.2.2 在数组中使用字符串

要将字符串存储到数组中,最常用的方法有两种——将数组初始化为字符串常量、将键盘或文件输入读入到数组中。

// 程序清单4.2 string.cpp--storing strings in an array
#include 
#include 

using namespace std;

int main(void)
{
    const int Size = 15;
    char name1[Size];
    char name2[Size] = "C++owboy";

    cout << "Howdy! I'm " << name2 << "! What's your name?" << endl;
    cin >> name1;
    // sizeof运算符指出整个数组的长度
    // strlen()函数返回的是存储在数组中的字符串的长度,而不是数组本身的长度
    // strlen()只计算可见的字符,而不把空字符计算在内
    cout << "Well, " << name1 << " , your name has " << strlen(name1) << " letters and is stored" << endl;
    cout << "in an array of " << sizeof(name1) << " bytes." << endl;
    cout << "Your initial is " << name1[0] << "." << endl;
    name2[3] = '\0';
    cout << "Here are the first 3 characters of my name: " << name2 <<endl;

    return 0;
}

C++ Primer Plus——第4章 复合类型_第3张图片
C++ Primer Plus——第4章 复合类型_第4张图片

4.2.3 字符串输入

// 程序清单4.3 instr1.cpp--reading more than one string
#include 

using namespace std;

int main(void)
{
    const int ArSize = 20;
    char name[ArSize];
    char dessert[ArSize];

    // cin使用空白(空格、制表符和换行符)来确定字符串的结束位置
    // 这意味着cin在获取字符数组输入时只读取一个单词。读取该单词后,cin将该字符串放到数组中,并自动在结尾添加空字符
    cout << "Enter your name: " << endl;
    cin >> name;
    cout << "Enter your favourite dessert: " << endl;
    cin >> dessert;
    cout << "I have some delicious " << dessert << " for you, " << name << "." << endl;
    return 0;
}

在这里插入图片描述
C++ Primer Plus——第4章 复合类型_第5张图片

4.2.4 每次读取一行字符输入

istream中的类提供了一些面向行的类成员函数:getline()和get()。这两个函数都读取一行输入,直到达到换行符。然而,随后getline()将丢弃换行符,而get()将换行符保留在输入序列中。
1、面向行的输入:getline()
(1)getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。
(2)cin.getline():该函数有两个参数。第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数。如果这个参数是20,则函数最多读取19个字符,余下的空间用于存储自动在结尾处添加的空字符。getline()成员函数在读取指定数目的字符或遇到换行符时停止读取。

// 程序清单4.4 instr2.cpp--reading more than one word with getline
#include 

using namespace std;

int main(void)
{
    const int ArSize = 20;
    char name[ArSize];
    char dessert[ArSize];

    cout << "Enter your name: " << endl;
    cin.getline(name, ArSize);
    cout << "Enter your favourite dessert: " << endl;
    cin.getline(dessert, ArSize);
    cout << "I have some delicious " << dessert << " for you, " << name << "." << endl;
    return 0;
}

在这里插入图片描述
C++ Primer Plus——第4章 复合类型_第6张图片
2、面向行的输入:get()

// (1)get并不再读取并丢弃换行符,而是将其留在输入队列中
// 由于第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符便是换行符。因此get()认为已到达行尾,而没有发现任何可读取的内容。如果不借助于帮助,get()将不能跨过该换行符
cin.get(name, ArSize);
cin.get(dessert, ArSize);

// (2)使用不带任何参数的cin.get()调用可读取下一个字符(即便是换行符),因此可以用它来处理换行符,为读取下一行输入做好准备
cin.get(name, ArSize);
cin.get();
cin.get(dessert, ArSize);

// (3)使用get()将两个类成员函数拼接起来
// 由于cin.get(name, ArSize)返回一个cin对象,该对象随后将被用来调用get()函数。
cin.get(name, ArSize).get();
cin.getline(name1, ArSize).getline(name2, ArSize);
// 程序清单4.5 instr3.cpp--reading more than one word with get() & get()
#include 

using namespace std;

int main(void)
{
    const int ArSize = 20;
    char name[ArSize];
    char dessert[ArSize];

    cout << "Enter your name: " << endl;
    cin.get(name, ArSize); //cin.get(name, Arsize).get();
    cin.get();
    cout << "Enter your favourite dessert: " << endl;
    cin.get(dessert, ArSize).get();
    cout << "I have some delicious " << dessert << " for you, " << name << "." << endl;
    return 0;
}

在这里插入图片描述
C++允许函数有多个版本,条件是这些版本的参数列表不同——函数重载。
3、getline()与get()的区别:get()使输入更仔细。例:如何知道停止读取的原因是由于已经读取了整行,而不是由于数组已填满呢?查看下一个输入字符,如果是换行符,说明已读取了整行;否则,说明该行中还有其他输入。
4、空行和其他问题
(1)当getline()或get()读取空行时,将发生什么情况?
最初的做法是,下一条输入语句将在前一条getline()或get()结束读取的位置开始读取;但当前的做法是,当get()(不是getline())读取空行后将设置失效位。这意味着接下来的输入将被阻断,但可以用下面的命令来恢复输入:cin.clear()。
(2)输入字符串可能比分配的空间长,如果输入行包含的字符数比指定的多,则getline()和get()将把余下的字符留在输入队列中,而getline()还会设置失效位,并关闭后面的输入。
注:
(1)空行:程序4.3将一直等待用户输入;程序4.3将输入空行显示为空字符\0,并不影响后续输入(输入name为空行后,dessert的输入不受影响);程序4.4将输入空行显示为空字符\0且影响后续输入(输入name为空行后,将没有机会输入dessert)
(2)输入字符串更长:程序4.3将dessert按照输入长度显示而name的结果将无法预期(内存有可能被后续输入覆盖);程序4.4将name按照规定数组长度显示而dessert为空字符;程序4.5将name按照规定数组长度显示但没有机会输入dessert,dessert显示为当前输入缓冲区接下来符合数组长度的字符串。

4.2.5 混合输入字符串和和数字

// 程序清单4.6 numstr.cpp--following number input with line input
#include 

using namespace std;

int main(void)
{
    cout << "What year was your house built?" << endl;
    int year;
    // 当cin读取年份,将回车键生成的换行符留在了输入队列中,后面的cin.getline()看到换行符后,将认为是一个空行,并将一个空字符串赋给address数组
    cin >> year; //(cin >> year).get()
    cin.get(); // cin.get(ch)
    cout << "What is its street address?" << endl;
    char address[80];
    cin.getline(address, 80);
    cout << "Year built: " << year << endl;
    cout << "Address: " << address << endl;
    cout << "Done!" << endl;
    return 0;
}

C++ Primer Plus——第4章 复合类型_第7张图片

4.3 string类简介

可以使用string类型的对象来存储字符串。要使用string类,必须包含头文件string。string类位于名称空间std中,string类定义隐藏了字符串的数组性质,让您能够像处理普通变量那样处理字符串。

// 程序清单4.7 strtype1.cpp--using the C++ string class
#include 
#include 

using namespace std;

int main(void)
{
    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:" << endl;
    cout << charr1 << " " << charr2 << " " << str1 << " " << str2 << endl;

    cout << "The third letter in " << charr2 << " is " << charr2[2] << endl;
    cout << "The third letter in " << str2 << " is " << str2[2] << endl;

    return 0;
}

C++ Primer Plus——第4章 复合类型_第8张图片
1、使用string对象的方式与使用字符数组相同:
(1)可以使用C-风格字符串来初始化string对象
(2)可以使用cin来将键盘输入存储到string对象中
(3)可以使用cout来显示string对象
(4)可以使用数组表示法来访问存储在string对象中的字符
string对象和字符数组之间的主要区别是,可以将string对象声明为简单变量,而不是数组。
2、类设计让程序能够自动处理string的大小。可以将char数组视为一组用于存储一个字符串的char存储单元,而string类变量是一个表示字符串的实体。

string str1; // str1的声明创建一个长度为0的string对象
cin >> str1; // 将输入读取到str1中时,将自动调整str1的长度

4.3.1 C++11字符串初始化

C++11与允许将列表初始化用于C-风格字符串和string对象:

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 赋值、拼接和附加

1、不能将一个数组赋给另一个数组,但可以将一个string对象赋给另一个string对象
2、可以使用运算符+将两个string对象合并起来,还可以使用运算符+=将字符串附加到string对象的末尾

// 程序清单4.8 strtype2.cpp--assigning, adding, and appending
#include 
#include 

using namespace std;

int main(void)
{
    string s1 = "penguin";
    string s2, s3;

    cout << "You can assign one string object to another: s2 = s1." << endl;
    s2 = s1;
    cout << "s1: " << s1 << ", s2: " << s2 << endl;
    cout << "You can assign a C-style string to a string object." << endl;
    cout << "s2 = \"buzzard\"" << endl;
    s2 = "buzzard";
    cout << "s2: " << s2 << endl;
    cout << "You can concatenate strings: s3 = s1 + s2." << endl;
    s3 = s1 + s2;
    cout << "s3: " << s3 << endl;
    cout << "You can append strings." << endl;
    s1 += s2;
    cout << "s1 += s2 yields s1 = " << s1 << endl;
    s2 += " for a day";
    cout << "s2 += \" for a day\" yields s2 = " << s2 << endl;

    return 0;
}

C++ Primer Plus——第4章 复合类型_第9张图片

4.3.3 string类的其他操作

对于C-风格字符串,程序员使用C语言库中的函数来完成这些任务,可以使用函数strcpy()将字符串复制到字符串数组中,使用函数strcat()将字符串附加到字符数组末尾

strcpy(charr1, charr2); // 将charr2的内容复制到charr1中
strcat(charr1, charr2); // 将charr2的内容附加到charr1末尾
// 程序清单4.9 strtype3.cpp--more string class features
#include 
#include 
#include 

using namespace std;

int main(void)
{
    char charr1[20];
    char charr2[20] = "jaguar";
    string str1;
    string str2 = "panther";

    str1 = str2;
    strcpy(charr1, charr2);

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

    int len1 = str1.size();
    int len2 = strlen(charr1);

    cout << "The string " << str1 << " contains " << len1 << " characters." << endl;
    cout << "The string " << charr1 << " contains " << len2 << " characters." << endl;

    return 0;
}

在这里插入图片描述
使用strcpy()和strcat()函数存在目标数组过小,无法存储指定信息的危险,因此提供了strncat()和strncpy()函数,他们接受指出目标数组最大允许长度的第三个参数,因此更为安全。

4.3.4 string类I/O

// 程序清单4.10 strtype4.cpp--line input
#include 
#include 
#include 

using namespace std;

int main(void)
{
    char charr[20];
    string str;

    // 未初始化的数组内容是未定义的,函数strlen()从数组的第一个元素开始计算字节数,直到遇到空字符
    // 对于未被初始化的数据,第一个空字符的出现位置是随机的
    // 用户输入之前,str中的字符串长度为0,这是因为未被初始化的string对象的长度被自动设置为0
    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:" << endl;
    cin.getline(charr, 20);
    cout << "You entered: " << charr << endl;
    cout << "Enter another line of text:" << endl;
    // 这个getline()不是类方法,它将cin作为参数,指出到哪里去查找输入,也没有指出字符串长度的参数,因为string对象将根据字符串的长度自动调整自己的大小
    // istream类中,有处理double、int和其他基本类型的类方法,但没有处理string对象的类方法
    // cin >> str; 之所以可行是因为使用了string类的一个友元函数
    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;
}

C++ Primer Plus——第4章 复合类型_第10张图片

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

1、可以创建wchar_t、char16_t和char32_t的数组和这些类型的字符串字面值,C++分别使用前缀L、u和U表示。

wchar_t title[] = L"Chief Astrogator";
char16_t name[] = u"Felonia Ripova";
char32_t car[] = U"Humber Super Snipe";

2、C++11支持Unicode字符编码方案UTF-8。在这种方案中,根据编码的数字值,字符可能存储为1~4个八位组。C++使用前缀u8来表示这种类型的字符串字面值。
3、C++11新增的另一种类型是原始字符串,在原始字符串中,字符表示的就是自己。原始字符串将"(和")用作定界符,并使用前缀R来标识原始字符串。输入原始字符串时,按回车键不仅会移到下一行,还将在原始字符串中添加回车字符。原始字符串语法允许您在表示字符串开头的"和(之间添加其他字符,这意味着表示字符串结尾的"和)之间也必须包含这些字符。自定义定界符时,在默认定界符之间添加任意数量的基本字符,但空格、左括号、右括号、斜杠和控制字符(如制表符和换行符)除外。

cout << R"(Jim "King" Tutt uses "\n" instead of endl.)";
//显示:Jim "King" Tutt uses "\n" instead of endl.

cout << R"+*("(Who wouldn't?)", she whispered.)+*";
// 显示:"(Who wouldn't?)", she whispered.

4、可将前缀R与其他字符串前缀结合使用,以标识wchar_t等类型的原始字符串。可将R放在前面,也可将其放在后面。

4.4 结构简介

1、结构可以存储多种类型的数据。
2、结构是用户定义的类型,而结构声明定义了这种类型的数据属性。
3、首先,定义结构描述——它描述并标记了能够存储在结构中的各种数据类型。然后按描述创建结构变量。
C++ Primer Plus——第4章 复合类型_第11张图片

4、C++允许在声明结构变量时省略关键字struct
5、使用成员运算符(.)来访问各个成员

4.4.1 在程序中使用结构

// 程序清单4.11 structur.cpp--a simple structure
#include 

using namespace std;

// 位于函数外面的声明被称为外部声明
// 外部声明可以被其后面的任何函数使用,而内部声明只能被该声明所属的函数使用
struct inflatable
{
    char name[20];
    float volume;
    double price;
};

int main(void)
{
    inflatable guest = 
    {
        "Glorious Gloria",
        1.88,
        29.99
    };

    inflatable pal = 
    {
        "Audacious Arthur",
        3.12,
        32.99
    };

    cout << "Expand your guest list with " << guest.name << " and " << pal.name << "!" << endl;
    cout << "You can have both for $" << guest.price + pal.price << "!" << endl;

    return 0;
}

在这里插入图片描述

4.4.2 C++11结构初始化

C++11也支持将列表初始化用于结构
(1)等号(=)是可选的
(2)如果大括号内未包含任何东西,各个成员都将被设置为0
(3)不允许缩窄转换

inflatable duck {"Daphne", 0.12, 9.98};
inflatable mayor {}; //mayor.volume和mayor.price都被设置为0,且mayor.name的每个字节都被设置为0

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

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

4.4.4 其他结构属性

// 程序清单4.12 assgn_st.cpp--assigning structures
#include 

using namespace std;

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

int main(void)
{
    inflatable bouquet = 
    {
        "sunflowers",
        0.20,
        12.49
    };

    inflatable choice;

    cout << "bouquet: " << bouquet.name << " for $" << bouquet.price << endl;

    // 可以使用赋值运算符(=)将结构赋给另一个同类型的结构,这样结构中每个成员都将被设置为另一个结构中相应成员的值,即使成员是数组。这种赋值被称为成员赋值。
    choice = bouquet;
    cout << "choice: " << choice.name << " for $" << choice.price << endl;
    return 0;
}

在这里插入图片描述
1、可以同时完成定义结构和创建结构变量的工作

struct perks
{
	int key_number;
	char car[12];
} mr_smith, ms_jones;

2、可以初始化以这种方式创建的变量

struct perks
{
	int key_number;
	char car[12];
} mr_glitz = 
{
	7,
	"Packard"
};

3、声明没有名称的结构类型,方法是省略名称,同时定义一种结构类型和一个这种类型的变量,由于这种类型没有名称,因此以后无法创建这种类型的变量

struct 
{
	int x;
	int y;
} position;

4、C++结构除了成员变量之外,还可以有成员函数。

4.4.5 结构数组

// 程序清单4.13 arrstruc.cpp--an array of structures
#include 

using namespace std;

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

int main(void)
{
    // 初始化结构数组,可以结合使用初始化数组的规则和初始化结构的规则
    inflatable guests[2] = 
    {
        {"Bambi", 0.5, 21.99},
        {"Godzilla", 2000, 565.99}
    };

    cout << "The guests " << guests[0].name << " and " << guests[1].name << endl;
    cout << "have a combined volume of " << guests[0].volume + guests[1].volume << " cubic feet." << endl;

    return 0;
}

在这里插入图片描述

4.4.6 结构中的位字段

C++允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整型或枚举,接下来是冒号,冒号后面是一个数字,它指定了使用的位数。可以使用没有名称的字段来提供间距。每个成员都被称为位字段。可以像通常那样初始化这些字段,还可以使用标准的结构表示法来访问位字段。位字段通常用在低级编程中。

struct torgle_register
{
	unsigned int SN : 4;
	unsigned int : 4;
	bool goodIn : 1;
	bool goodTorgle : 1;
};
torgle_register tr = {14, true, false};

4.5 共用体

共用体是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。共用体的用途之一是,当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。另外,共用体常用于操作系统数据结构或硬件数据结构。

union one4all
{
	int int_val;
	long long_val;
	double double_val;
};
// 可以使用one4all变量来存储int、long或double,条件是在不同的时间进行
one4all pail;
cout << "sizeof(pail) = " << sizeof(pail) << endl; // 8
pail.int_val = 15;
cout << pail.int_val; // 15; pail.double_val为无意义值
pail.double_val = 1.38; 
cout << pail.double_val; // 1.38; pail.int_val为无意义值
// pail有时可以是int变量,而有时又可以是double变量
// 成员名称标识了变量的容量,由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以,共用体的长度为其最大成员的长度
struct widget
{
	char brand[20];
	int type;
	union id
	{
		long id_num;
		char id_char[20];
	} id_val;
};
widge prize;
if (prize.type == 1) 
	cin >> prize.id_val.id_num;
else 
	cin >> prize.id_val.id_char;

// 匿名共用体没有名称,其成员将成为位于相同地址处的变量,每次只有一个成员是当前的成员
// 由于共用体是匿名的,因此id_num和id_char被视为prize的两个成员,它们的地址相同,所以不需要中间标识符id_val
// 程序员负责确定当前是哪个成员是活动的
struct widget
{
	char brand[20];
	int type;
	union id
	{
		long id_num;
		char id_char[20];
	};
};
widge prize;
if (prize.type == 1) 
	cin >> prize.id_num;
else 
	cin >> prize.id_char;

4.6 枚举

C++的enum工具提供了另一种创建符号常量的方式,这种方式可以代替const。

// 让spectrum成为新类型的名称;spectrum被称为枚举
// 将red、orange、yellow等作为符号常量,它们对应整数值0~7。这些常量叫做枚举量。
enum spectrum {red, orahge, yellow, green, blue, violet, indigo, ultraviolet};

// 在默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,可以通过显式地指定整数值来覆盖默认值
// 在不进行强制类型转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量
spectrum band;
band = blue;
band = 2000; // 不允许,为获得最大限度的可移植性,应将把非enum的值赋给enum变量视为错误

// 对于枚举,只定义了赋值运算,没有为枚举定义算术运算

// 枚举量是整型,可被提升为int类型,但int类型不能自动转换为枚举类型
int color = blue; // 允许
band = 3; // 不允许
color = 3 + red; // 允许
// 由于在算术表达式中,枚举将被转换为整数,因此可以在算术表达式中同时使用枚举和常规整数
// 可将int类型强制转换成枚举类型,如果对一个不适当的值进行强制类型转换,结果则是不确定的
band = spectrum(3);

// 枚举更常用来定义相关的符号常量,而不是新类型
// 如果打算只使用常量,而不创建枚举类型的变量,则可以省略枚举类型的名称
enum {red, orahge, yellow, green, blue, violet, indigo, ultraviolet};

4.6.1 设置枚举量的值

// 可以使用赋值运算符来显式地设置枚举量的值
enum bits {one = 1, two = 2, four = 4, eight = 8};
// 指定的值必须是整数,也可以只显式地定义其中一些枚举量的值
enum bigstep {first, second = 100, third}; // first在默认情况下为0,后面没有被初始化的枚举量的值将比前面的枚举量大1
// 可以创建多个值相同的枚举量
enum {zero, null = 0, one, numero_uno = 1};

4.6.2 枚举的取值范围

每个枚举都有取值范围,通过强制类型转换,可以将取值范围中的任何整数值赋给枚举变量,即使这个值不是枚举值。
上限:找到枚举量的最大值,找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是上限(最大枚举值:101,上界:127)。
下限:找到枚举量的最小值,如果不小于0,则下限为0,否则采用与寻找上限方式相同的方式,加上符号(最小枚举值:-6,下界:-7)。
选择用多少空间来存储枚举由编译器决定。对于取值范围较小的枚举,使用一个字节或更少的空间;而对于包含long类型值的枚举,则使用4个字节。
C++11扩展了枚举,增加了作用域内枚举。

4.7 指针和自由存储空间

指针是一个变量,其存储的是值的地址,而不是值本身。只需对变量应用地址运算符(&),就可以获得它的位置。

// 程序清单4.14 address.cpp--using the & operator to find addresses
#include 

using namespace std;

int main(void)
{
    int donuts = 6;
    double cups = 4.5;
    // 显示地址时,该实现的cout使用十六进制表示法
    cout << "donuts value = " << donuts << " and donuts address = " << &donuts << endl;
    cout << "cups value = " << cups << " and cups address = " << &cups << endl;


    return 0;
}

在这里插入图片描述
OOP强调的是在运行阶段(而不是编译阶段)进行决策。运行阶段指的是程序正在运行时,编译阶段指的是编译器将程序组合起来时。运行阶段决策提供了灵活性,可以根据当时的情况进行调整。
指针用于存储值的地址。因此,指针名表示的是地址。*运算符被称为间接值或解除引用运算符,将其应用于指针,可以得到该地址所存储的值。

// 程序清单4.15 pointer.cpp--our first pointer varialble
#include 

using namespace std;

int main(void)
{
    int updates = 6;
    int *p_updates;
    p_updates = &updates;

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

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

在这里插入图片描述
C++ Primer Plus——第4章 复合类型_第12张图片

4.7.1 声明和初始化指针

指针声明必须指定指针指向的数据的类型。

int *p_updates; 
// *p_updates的类型为int,p_updates指向int类型,p_updates的类型是指向int的指针或int*

// *运算符两边的空格是可选的
int *ptr; // 强调*ptr是一个int类型的值
int* ptr; // 强调的是int*是一种类型——指向int的指针,在C++中,int*是一种复合类型,是指向int的指针
int* p1, p2; // 创建一个指针p1和一个int变量p2,对每个指针变量名,都需要使用一个*

C++ Primer Plus——第4章 复合类型_第13张图片
指针变量不仅仅是指针,而且是指向特定类型的指针。
指向两种长度不同的数据类型的指针变量,这两个变量本身的长度通常是相同的,地址需要2个还是4个字节,取决于计算机系统。

// 程序清单4.16 init_ptr.cpp--initialize a pointer
#include 

using namespace std;

int main(void)
{
    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;
}

在这里插入图片描述

4.7.2 指针的危险

在C++中创建指针,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。

long * fellow;
*fellow = 223323;
// 由于fellow没有被初始化,它可能有任何值,不管值是什么,程序都将它解释为存储223323的地址。fellow指向的地方很有可能并不是所要存储223323的地方
// 一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址

4.7.3 指针和数字

指针与整数是截然不同的类型。整数是可以执行加、减、除等运算的数字,而指针描述的是位置,将两个地址相乘没有任何意义。不能简单地将整数赋给指针,要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型。

int * pt;
pt = (int *) 0xB8000000;

4.7.4 使用new来分配内存

指针用途:
(1)将指针初始化为变量的地址,变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供了一个别名
(2)在运行阶段分配未命名的内存以存储值
在C语言中,可以用库函数malloc()来分配内存;在C++中可以使用new运算符。

// 程序员要告诉new,需要为哪种数据类型分配内存;new找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针
int * pn = new int; 
typeName * pointer_name = new typeName;
// 程序清单4.17 use_new.cpp--using the new operator
#include 

using namespace std;

int main(void)
{
    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;
    // 指向int的指针的长度与指向double的指针相同,他们都是地址,32位操作系统为4,64位操作系统为8
    cout << "size of pt = " << sizeof(pt) << "; size of *pt = " << sizeof(*pt) << endl;
    cout << "size of pd = " << sizeof(pd) << "; size of *pd = " << sizeof(*pd) << endl;
    // 变量nights和pd的值都存储在栈的内存区域中,而new从被称为堆或自由存储区的内存区域分配内存
    return 0;
}

C++ Primer Plus——第4章 复合类型_第14张图片
计算机可能会由于没有足够的内存而无法满足new的请求。在这种情况下,new通常会引发异常,在较老的实现中,new将返回0。在C++中,值为0的指针被称为空指针。C++确保空指针不会指向有效的数据,因此它常被用来表示运算符或函数失败。

4.7.5 使用delete释放内存

delete运算符:使得在使用完内存后,能够将其归还给内存池,归还或释放的内存可供程序的其他部分使用,使用delete时,后面要加上指向内存块的指针(这些内存块最初是用new分配的)

int * ps = new int;
delete ps; // 这将释放ps指向的内存,但不会删除指针ps本身,可以将ps重新指向另一个新分配的内存块

一定要配对地使用new和delete,否则将发生内存泄漏:被分配的内存再也无法使用,如果内存泄漏严重,则程序将由于不断寻找更多内存而终止。
不要尝试释放已经释放的内存块,C++标准指出,这样做的结果将是不确定的,这意味着什么情况都可能发生。
不能使用delete来释放声明变量所获得的内存。
只能用delete来释放使用new分配的内存。然而,对空指针使用delete是安全的。
不要创建两个指向同一个内存块的指针,因为这将增加错误地删除同一个内存块两次的可能性。

4.7.6 使用new来创建动态数组

在编译时给数组分配内存被称为静态联编,意味着数组是在编译时加入到程序中的。但使用new时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度,这被称为动态联编,意味着数组是在程序运行时创建的,这种数组叫做动态数组。
1、使用new创建动态数组

// 创建动态数组:将数组的元素类型和元素数目告诉new
int * psome = new int[10]; // new运算符返回第一个元素的地址
delete [] psome; // 方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素

使用new和delete应遵守的规则:
(1)不要使用delete来释放不是new分配的内存
(2)不要使用delete释放同一个内存块两次
(3)如果使用new[ ]为数组分配内存,则应使用delete[ ]来释放
(4)如果使用new为一个实体分配内存,则应使用delete(没有方括号)来释放
(5)对空指针应用delete是安全的
不能使用sizeof运算符来确定动态分配的数组包含的字节数。
2、使用动态数组
将指针当做数组名使用即可,原因:C和C++内部都使用指针来处理数组。数组和指针基本等价是C和C++的优点之一。

// 程序清单4.18 arraynew.cpp--using the new operator for arrays
#include 

using namespace std;

int main(void)
{
    double *p3 = new double[3];
    p3[0] = 0.2;
    p3[1] = 0.5;
    p3[2] = 0.8;

    cout << "p3[1] is " << p3[1] << "." << endl;
    p3 = p3 + 1; 
    // 指针和数组名的区别:不能修改数组名的值,但指针是变量,因此可以修改它的值,将p3加1后,它将指向下一个元素的地址
    cout << "Now p3[0] is " << p3[0] << " and p3[1] is " << p3[1] << "." << endl;
    p3 = p3 - 1;
    delete [] p3;

    return 0;
}

在这里插入图片描述

4.8 指针、数组和指针算术

指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式。将指针变量加1后,增加的量等于它指向的类型的字节数。

// 程序清单4.19 addpntrs.cpp--pointer addition
#include 

using namespace std;

int main(void)
{
    
    double wages[3] = {10000.0, 20000.0, 30000.0};
    short stacks[3] = {3, 2, 1};

    // C++将数组名解释为数组第1个元素的地址
    double *pw = wages; // wages = &wages[0]
    short *ps = &stacks[0];

    cout << "pw = " << pw << ", *pw = " << *pw << endl;
    pw = pw + 1;
    cout << "add 1 to the pw pointer:" << endl;
    cout << "pw = " << pw << ", *pw = " << *pw << endl;
    cout << "ps = " << ps << ", *ps = " << *ps << endl;
    ps = ps + 1;
    cout << "add 1 to the ps pointer:" << endl;
    cout << "ps = " << ps << ", *ps = " << *ps << endl;
    
    cout << "access two elements with array notation" << endl;
    // stacks[1] = *(stacks+1) 意味着先计算数组第2个元素的地址,然后找到存储在那里的值
    // 如果使用的是指针,而不是数组名,C++也将执行同样的转换
    cout << "stacks[0] = " << stacks[0] << ", stacks[1] = " << stacks[1] << endl;
    cout << "accsee two elements with pointer notation" << endl;

    cout << "*stacks = " << *stacks << ", *(stacks + 1) = " << *(stacks + 1) << endl;
    cout << sizeof(wages) << " = size of wages array" << endl;
    cout << sizeof(pw) << " = size of pw pointer" << endl;
    return 0;
}

C++ Primer Plus——第4章 复合类型_第15张图片

4.8.1 程序说明

C++ Primer Plus——第4章 复合类型_第16张图片
在很多情况下,可以相同的方式使用指针名和数组名。对于它们,可以使用数组方括号表示法,也可以使用解除引用运算符(*)。在多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,而数组名是常量。另一个区别是,对数组应用sizeof运算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度,即使指针指向的是一个数组。

// 数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址
short tell[10];
cout << tell << endl;
cout << &tell << endl;
// 从数字上说,这两个地址相同;
// 但从概念上说,&tell[0](即tell)是一个2字节内存块的地址,而&tell是一个20字节内存块的地址
// 表达式tell+1将地址值加2,而表达式&tell+1将地址加20
// tell是一个short指针(short*),而&tell是一个这样的指针,即指向包含20个元素的short数组(short(*)[10])
short (*pas)[10] = &tell; // pas是一个指针,指向一个数组,每个元素都是short类型
short * pas[10]; // pas是一个数组,每个元素都是short*类型
// 如果要描述变量的类型,可将声明中的变量名删除。

4.8.2 指针小结

1、声明指针

typeName * pointerName

2、给指针赋值
应将内存地址赋给指针。可以对变量名应用&运算符,来获得被命名的内存的地址,new运算符返回未命名的内存的地址。

double * pn;
double * pa;
char * pc;
double bubble = 3.2;
pn = &bubble;
pc = new char;
pa = new double[30];

3、对指针解除引用
对指针解除引用意味着获得指针指向的值。对指针应用解除引用或间接值运算符(*)来解除引用。另一种对指针解除引用的方法是使用数组表示法。不要对未被初始化为适当地址的指针解除引用。

cout << *pn;
*pc = 'S'

4、区分指针和指针所指向的值
5、数组名
在多数情况下,C++将数组名视为数组的第一个元素的地址。一种例外情况是,将sizeof运算符用于数组名用时,此时将返回整个数组的长度(单位为字节)。

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

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

int tacos[10] = {5, 2, 8, 4, 1, 2, 2, 4, 6, 8};
int * pt = tacos;
pt = pt + 1;
int * pe = &tacos[9];
pe = pe - 1;
int diff = pe - pt; // diff = 7

7、数组的动态联编和静态联编

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

8、数组表示法和指针表示法
使用方括号数组表示法等同于对指针解除引用

4.8.3 指针和字符串

char flower[10] = "rose";
cout << flower << "s are red\n";
// cout语句中的flower是包含字符r的char元素的地址
// cout对象认为char的地址是字符串的地址,因此它打印该地址处的字符,然后继续打印后面的字符,直到遇到空字符(\0)为止

在cout和多数C++表达式中,char数组名、char指针以及用引号括起的字符串常量都被解释为字符串第一个字符的地址。
有些编译器将字符串字面值视为只读常量,如果试图修改它们,将导致运行阶段错误。在C++中,字符串字面值都将被视为常量,但并不是所有的编译器都对以前的行为做了这样的修改。有些编译器只使用字符串字面值的一个副本来表示程序中所有的该字面值。

// 程序清单4.20 ptrstr.cpp -- using pointers to strings
#include 
#include 

using namespace std;

int main(void)
{
    char animal[20] = "bear";
    // 将"wren"的地址赋给了bird指针
    // 一般来说,编译器在内存留出一些空间,以存储程序源代码中所有用引号括起的字符串,并将每个被存储的字符串与其地址关联起来
    // 字符串字面值是常量,使用const意味着可以用bird来访问字符串,但不能修改它,编译器将禁止改变bird指向的位置中的内容
    const char *bird = "wren";
    char *ps;

    cout << animal << " and " << bird << endl;
    //cout << ps << endl;

    cout << "Enter a kind of animal: ";
    cin >> animal;
    cout << animal << endl;
    //cin >> ps;
    // 在将字符串读入程序时,应使用已分配的内存地址。该地址可以是数组名,也可以是使用new初始化过的指针;请不要使用字符串常量或未被初始化的指针来接收输入

    ps = animal;
    cout << ps << endl;
    cout << "Before using strcpy():" << endl;
    // 一般来说,如果给cout提供一个指针,它将打印地址
    // 但如果指针的类型为char *,则cout将显示指向的字符串
    // 如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型,如int *
    cout << animal << " at " << (int *) animal << endl;
    cout << ps << " at " << (int *) ps << endl;

    ps = new char[strlen(animal) + 1];
    // strcpy()函数接受2个参数。第一个是目标地址,第二个是要复制的字符串的地址
    strcpy(ps, animal);
    cout << "After using strcpy():" << endl;
    cout << animal << " at " << (int *) animal << endl;
    cout << ps << " at " << (int *) ps << endl;
    delete [] ps;

    return 0;
}

C++ Primer Plus——第4章 复合类型_第17张图片

char food[20] = "carrots";
strcpy(food, "a picnic basket filled with many goodies");
// 函数将字符串中剩余的部分复制到数组后面的内存字节中,这可能会覆盖程序正在使用的其他内存
// 要避免这种问题,请使用strncpy()
// 该函数还接受第3个参数——要复制的最大字符数
// 要注意的是,如果该函数在到达字符串结尾之前,目标内存已经用完,则它不会添加空字符。因此,应该这样使用该函数:
strncpy(food, "a picnic basket filled with many goodies", 19);
food[19] = '\0';
// 这样最多将19个字符复制到数组中,然后将最后一个元素设置成空字符。
// 如果该字符串少于19个字符,则strncpy()将在赋值完该字符串之后加上空字符,以标记该字符串的结尾。

4.8.4 使用new创建动态结构

通过使用new,可以创建动态结构。

inflatable * ps = new inflatable;

C++ Primer Plus——第4章 复合类型_第18张图片
如果结构标识符是结构名,则使用句点运算符;如果标识符是指向结构的指针,则使用箭头运算符。

// 程序清单4.21 newstrct.cpp--using new with a structure
#include 

using namespace std;

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

int main(void)
{
    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 << endl;
    cout << "Price: $" << ps->price << endl;
    delete ps;

    return 0;
}

C++ Primer Plus——第4章 复合类型_第19张图片

// 程序清单4.22 delete.cpp--using the delete operator
#include 
#include 

using namespace std;

// getname()分配内存,而main()释放内存,可将new和delete放在不同的函数中
char * getname();

int main(void)
{
    char *name;
    name = getname(); // 指向getname()函数中分配的内存块
    cout << name << " at " << (int *)name << endl;
    delete [] name;

    name = getname();
    cout << name << " at " << (int *)name << endl;
    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;
}

在这里插入图片描述

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

C++有3中管理数据内存的方式:自动存储、静态存储和动态存储。
1、自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量,这意味着他们在所属的函数被调用时自动产生,在该函数结束时消亡。自动变量是一个局部变量,其作用域为包含它的代码块。自动变量通常存储在栈中。这意味着执行代码块时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出。
2、静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它;另一种是在声明变量时使用关键字static。
3、动态存储
new和delete管理了一个内存池,这在C++中被称为自由存储空间或堆。该内存池同用于静态变量和自动变量的内存是分开的。new和delete让你能够在一个函数中分配内存,而在另一个函数中释放它。数据的生命周期不完全受程序或函数的生存时间控制。
4、栈、堆和内存泄漏
如果没有调用delete,则即使包含指针的内存由于作用域规则和对象生命周期的原因而被释放,在自由存储空间上动态分配的变量或结构也将继续存在。实际上,将会无法访问自由存储空间中的结构,因为指向这些内存的指针无效,这将导致内存泄漏。被泄露的内存将在程序的整个生命周期内都不可使用;这些内存被分配出去,但无法收回。极端情况是,内存泄漏可能会非常严重,以致于应用程序可用的内存被耗尽,出现内存耗尽错误,导致程序崩溃。

4.9 类型组合

// 程序清单4.23 mixtype.cpp--some type combination
#include 

using namespace std;

struct antarctica_years_end
{
    int year;
};

int main(void)
{
    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;
    cout << trio->year << endl;
    // arp是一个数组,数组中的每一个元素为结构体指针,且指向常量
    const antarctica_years_end *arp[3] = {&s01, &s02, &s03};
    cout << arp[1]->year << endl;
    // 指向结构体指针的指针
    const antarctica_years_end **ppa = arp;
    auto ppb = arp;
    cout << (*ppa)->year << endl;
    cout << (*(ppb + 1))->year << endl;

    return 0;
}

在这里插入图片描述
C++ Primer Plus——第4章 复合类型_第20张图片

4.10 数组的替代品

4.10.1 模板类vector

模板类vector也是一种动态数组。
(1)要使用vector对象,必须包含头文件vector
(2)vector包含在名称空间std中
(3)模板使用不同的语法来指定它存储的数据类型
(4)vector类使用不同的语法来指定元素数

// 创建一个名为vt的vector对象,它可存储n_elem个类型为typeName的元素
vector<typeName> vt(n_elem);
// 其中参数n_elem可以是整型常量,也可以是整型变量

vector类的功能比数组强大,但付出的代价是效率稍低。

4.10.2 模板类array(C++11)

(1)模板类array位于名称空间std中
(2)与数组一样,array对象的长度是固定的,使用栈,而不是自由存储区,因此其效率与数组相同,但更方便,更安全
(3)要创建array对象,需要包含头文件array

// 创建一个名为arr的array对象,它可存储n_elem个类型为typeName的元素
array<typeName, n_elem> arr;
// 其中参数n_elem不可以是整型变量

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

// 程序清单4.24 choices.cpp--array variations
// 无论是数组、vector对象还是array对象,都可以使用标准数组表示法来访问各个元素
// array对象和数组存储在栈中,vector对象存储在堆中
// 可以将一个array对象赋给另一个array对象;而对于数组,必须逐元素复制数据
#include 
#include 
#include 

using namespace std;

int main(void)
{
    // 无论是数组、vector对象还是array对象,都可使用标准数组表示法来访问各个元素
    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;
    // 可以将一个array对象赋给另一个array对象;而对于数组,必须逐元素复制数据
    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; // *(a1-2) = 20.2,将信息存储到数组的外面,但不会报错
    // 更好的方法是使用成员函数at()
    // a2.at(1) = 2.3;
    // 使用at(),将在运行期间捕获非法索引,而程序默认将中断
    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;
}

C++ Primer Plus——第4章 复合类型_第21张图片

4.11 总结

你可能感兴趣的:(C++,Primer,Plus,c++)