C++ Primer Plus 第四章 复合类型(上篇)————复习总结

第一章 预备知识

第二章 进入C++

第三章 数据处理

如何比较两个C风格字符串是否相等?
如何连接两个C风格字符串?
如何获取C风格字符串的长度?
如何在C++中使用转义序列来表示特殊字符,如双引号、换行符和制表符?
如何在C++中使用原始字符串字面值(raw string literal)来表示包含反斜杠或引号的字符串,而不需要转义?
如何在C++中使用宽字符字面值(wide character literal)来表示非ASCII字符,如汉字或日文?
如何将一个字符串复制到另一个数组中?
什么是缓冲区溢出?
如何比较两个结构是否相等?
如何在结构中嵌套另一个结构?
如何判断共同体当前哪个成员是活动的?
如何指定作用域内枚举的底层类型?

4.1 数组

  • 数组是一种数据格式,能够存储多个同类型的值,每个值都存储在一个独立的数组元素中。

  • 数组声明应指出值的类型、数组名和元素数目,通用格式为typeName arrayName[arraySize],其中arraySize必须是整型常数或const值

  • 数组从0开始编号,使用带索引的方括号表示法来指定数组元素,如yams[0]表示数组yams的第一个元素。

  • 可以在声明数组时初始化数组元素,如int yamcosts[3] = {20, 30, 5};,也可以在声明后给数组元素赋值,如yams[0] = 7;。在这里插入图片描述

  • 可以使用sizeof运算符来获取数组或数组元素的字节数,如sizeof yams表示数组yams的总字节数,sizeof yams[0]表示yams的第一个元素的字节数。

编译器不会检查使用的下标是否有效。例如,如果将一个值赋给不存在的元素months[101],编译器并不会指出错误。但是程序运行后,这种赋值可能引发问题,它可能破坏数据或代码,也可能导致程序异常终止。所以必须确保程序只使用有效的下标值。

4.1.1 程序说明

程序清单中的马铃薯分析程序说明了数组的一些属性,包括声明数组、给数组元素赋值以及初始化数组。

// arrayone.cpp -- small arrays of integers
#include <iostream>
int main()
{
    using namespace std;
    int yams[3]; // creates array with three elements
    yams[0] = 7; // assign value to first element
    yams[1] = 8;
    yams[2] = 6;

    int yamcosts[3] = {20, 30, 5}; // create, initialize array
// NOTE: If your C++ compiler or translator can't initialize
// this array, use static int yamcosts[3] instead of
// int yamcosts[3]

    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;
}

输出:

Total yams = 21
The package with 8 yams costs 30 cents per yam.
The total yam expense is 410 cents.

Size of yams array = 12 bytes.
Size of one element = 4 bytes.
  • 介绍了数组的基本概念和用法,包括如何声明、初始化、访问和操作数组。
  • 这篇内容也展示了一个使用数组的简单示例程序,以及如何使用sizeof运算符来获取数组或数组元素的字节数。
  • 还提到了一些数组的限制和注意事项,如下标必须是有效的,数组大小必须是常量,以及函数中不能使用sizeof获取数组大小

4.1.2 数组的初始化规则

  • 数组的初始化规则是指在定义数组时如何给数组元素赋值的一些约定和限制。
  • 数组的初始化只能在定义数组时进行,不能在之后对整个数组赋值,也不能将一个数组赋给另一个数组。
int cards[4] = {3, 6, 8, 10}; // okay
int hand[4];                  // okay
hand[4] = {5, 6, 7, 9};       // not allowed
hand = cards;                 // not allowed
  • 数组的初始化可以提供一个用花括号括起的值列表,如果值的个数少于数组的元素个数,那么剩下的元素会被自动初始化为0
float hotelTips[5] = {5.0, 2.5};
  • 数组的初始化可以省略方括号中的元素个数,让编译器根据值的个数来推断,但这种做法有一定的风险和局限性
short things[] = {1, 5, 3, 8};
int num_elements = sizeof things / sizeof (short);

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

  • 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
char slifs[4] {'h', 'i', 1122011, '\0'};  // not allowed
char tlifs[4] {'h', 'i', 112, '\0'};      // allowed

第四条语句不能通过编译,因为将浮点数转换为整型是缩窄操作,即使浮点数的小数点后面为零。第五条语句也不能通过编译,因为1122011超出了char变量的取值范围(这里假设char变量的长度为8位)。第六条语句可通过编译,因为虽然112是一个int值,但它在char变量的取值范围内

  • 数组初始化时,可以使用大括号括起的值列表,如果值的个数少于数组的元素个数,那么剩下的元素会被自动初始化为0。
  • 数组初始化时,可以省略方括号中的元素个数,让编译器根据值的个数来推断,但这种做法有一定的风险和局限性。

4.2 字符串

  • 字符串是一种存储在内存的连续字节中的一系列字符,C++有两种处理字符串的方式,一种是C风格字符串,一种是string类库
  • C风格字符串是用char数组来表示的,每个字符占一个数组元素,以空字符(‘\0’)作为字符串的结束标志。
char dog[8] = { 'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'};  // 不是一个字符串
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'};  // 是一个字符串
  • C风格字符串可以用双引号括起的字符串常量来初始化,也可以用单引号括起的字符常量来赋值,但不能将一个字符串赋给另一个字符串。
char bird[11] = "Mr. Cheeps";    // the \0 is understood
char fish[] = "Bubbles";         // let the compiler count
  • C风格字符串可以用sizeof运算符来获取其字节数,也可以用一些处理字符串的函数来操作,如cout和cin。
  • 用引号括起的字符串隐式地包括结尾的空字符,因此不用显式地包括它(参见下图)。另外,各种C++输入工具通过键盘输入,将字符串读入 char数组中时,将自动加上结尾的空字符。
  • 在这里插入图片描述

警告: 
在确定存储字符串所需的最短数组时,别忘了将结尾的空字符计算在内。

注意,字符串常量(使用双引号)不能与字符常量(使用单引号)互换。字符常量(如’S’)是字符串编码的简写表示。在ASCII系统上,'S’只是83的另一种写法,因此,下面的语句将83赋给shirt_size:

char shirt_size = 'S';   // this is fine

但"S"不是字符常量,它表示的是两个字符(字符S和\0)组成的字符串。更糟糕的是,"S"实际上表示的是字符串所在的内存地址。因此下面的语句试图将一个内存地址赋给shirt_size:

char shirt_size = "S";   // illegal type mismatch

由于地址在C++中是一种独立的类型,因此C++编译器不允许这种不合理的做法(本章后面讨论指针后,将回过头来讨论这个问题)。

如何比较两个C风格字符串是否相等?

我们继续往下走。

如何连接两个C风格字符串?

我们继续往下走。

如何获取C风格字符串的长度?

我们继续往下走。

4.2.1 拼接字符串常量

  • C++允许拼接字符串字面值,即将两个用引号括起的字符串合并为一个,不需要使用任何运算符或函数。
  • 拼接字符串字面值时,任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将自动拼接成一个,不会在被连接的字符串之间添加空格。
  • 拼接字符串字面值时,第一个字符串中的空字符(‘\0’)将被第二个字符串的第一个字符取代,因此拼接后的字符串仍然以空字符结尾
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";

问题1:如何在C++中使用转义序列来表示特殊字符,如双引号、换行符和制表符?

我们继续往下走。

问题2:如何在C++中使用原始字符串字面值(raw string literal)来表示包含反斜杠或引号的字符串,而不需要转义?

我们继续往下走。

问题3: 如何在C++中使用宽字符字面值(wide character literal)来表示非ASCII字符,如汉字或日文?

我们继续往下走。

4.2.2 在数组中使用字符串

  • 如何在数组中使用字符串,包括如何将数组初始化为字符串常量,如何将键盘或文件输入读入到数组中,以及如何使用strlen()函数来获取字符串的长度。
  • 字符串是以空字符(‘\0’)作为结束标志的,因此要确保数组足够大,能够存储字符串中所有字符,包括空字符。另外,可以用索引来访问和修改数组中的各个字符。

在这里插入图片描述

  • 建议使用符号常量来指定数组的长度,这样可以方便地修改程序中使用到的数组长度。
const int Size = 15;
char name1[Size];    

问题:如何将一个字符串复制到另一个数组中?

我们继续往下走。

4.2.3 字符串输入

  • in的限制:cin使用空白(空格、制表符和换行符)来确定字符串的结束位置,这意味着cin在获取字符数组输入时只读取一个单词,而不是一整行。cin把Alistair作为第一个字符串,并将它放到name数组中。这把Dreeb留在输入队列中。当cin在输入队列中搜索用户喜欢的甜点时,它发现了Dreeb,因此cin读取Dreeb,并将它放到dessert数组中(参见下图)

在这里插入图片描述

  • 缓冲区溢出:使用cin时,输入字符串可能比目标数组长,导致缓冲区溢出,这是一种常见的安全漏洞。所以,什么是缓冲区溢出?

    我们继续往下走。

  • 高级输入方法:为了解决cin的问题,我们需要使用cin的较高级特性,如getline()函数和string类,它们可以读取一整行字符串,并动态分配内存空间。

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

  • 面向行的输入:cin提供了两种成员函数,getline()和get(),用于读取一整行字符串输入,它们都接受一个字符数组和一个长度参数。
  • getline()和get()的区别:getline()在读取到换行符时,会丢弃它,并在字符串末尾添加空字符(下图所示);get()在读取到换行符时,会保留它在输入队列中,由于第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符便是换行符。因此get()认为已到达行尾,而没有发现任何可读取的内容。使用不带任何参数的cin.get()调用可读取下一个字符(即使是换行符),因此可以用它来处理换行符,为读取下一行输入做好准备。
cin.get(name, ArSize);      // read first line
cin.get();                  // read newline
cin.get(dessert, Arsize);   // read second line

在这里插入图片描述

  • 缓冲区溢出和空行的处理:如果输入字符串比目标数组长,getline()和get()会将多余的字符留在输入队列中,并设置失效位,阻断后续的输入;如果遇到空行,getline()会正常读取,而get()会设置失效位,需要使用cin.clear()来恢复输入。

4.2.5 混合输入字符串和数字

// numstr.cpp -- following number input with line input
#include <iostream>
int main()
{
    using namespace std;
    cout << "What year was your house built?\n";
    int year;
    cin >> year;
    cout << "What is its street address?\n";
    char address[80];
    cin.getline(address, 80);
    cout << "Year built: " << year << endl;
    cout << "Address: " << address << endl;
    cout << "Done!\n";
    return 0;
}

输出:

What year was your house built?
1966
What is its street address?
Year built: 1966
Address:
Done!
  • 混合输入问题:当使用cin读取数字后,再使用cin.getline()读取字符串时,会遇到问题,因为换行符会留在输入队列中,导致cin.getline()读取到一个空字符串
  • 解决方法:在读取字符串之前,先使用cin.get()或者(cin >> year).get()来读取并丢弃换行符,这样就可以正常输入字符串了。
cin >> year;
cin.get(); // or cin.get(ch);
(cin >> year).get(); // or (cin >> year).get(ch);

最后输出:

What year was your house built?
1966
What is its street address?
43821 Unsigned Short Street
Year built: 1966
Address: 43821 Unsigned Short Street
Done!
  • C++ string类:C++提供了一种新的处理字符串的方式,就是string类,它是一种自定义的数据类型,可以方便地操作字符串,而不需要使用指针或者数组。

4.3 string 类简介

  • string类是C++提供的一种新的处理字符串的方式,它可以方便地操作字符串,而不需要使用指针或者数组。
  • 要使用string类,必须在程序中包含头文件string,并且使用名称空间std或者std::string来引用它。
  • string类定义隐藏了字符串的数组性质,让你能够像处理普通变量那样处理字符串
  • string类可以使用赋值运算符和关系运算符来操作字符串,也可以使用下标运算符来访问
    修改字符串中的字符,还可以使用+运算符来连接字符串。
  • string类可以使用cin和cout来输入和输出字符串,而不需要使用特殊的函数。
  • string类可以自动调整字符串的长度(动态内存分配),以适应输入或者操作的结果。成员函数length()或者size()来获取字符串的长度。

示例如下:

// strtype1.cpp -- using the C++ string class
#include <iostream>
#include <string>                  // make string class available
int main()
{
    using namespace std;
    char charr1[20];               // create an empty array
    char charr2[20] = "jaguar";    // create an initialized array
    string str1;                   // create an empty string object
    string str2 = "panther";       // create an initialized string

    cout << "Enter a kind of feline: ";
    cin >> charr1;
    cout << "Enter another kind of feline: ";
    cin >> str1;                  // use cin for input
    cout << "Here are some felines:\n";
    cout << charr1 << " " << charr2 << " "
         << str1 << " " << str2    // use cout for output
         << endl;
    cout << "The third letter in " << charr2 << " is "
         << charr2[2] << endl;
    cout << "The third letter in " << str2 << " is "
         << str2[2] << endl;       // use array notation
    return 0;
}

输出:

Enter a kind of feline: ocelot
Enter another kind of feline: tiger
Here are some felines:
ocelot jaguar tiger panther
The third letter in jaguar is g
The third letter in panther is n

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

  • string类的优势:string类是一种自定义的字符串数据类型,它可以简化字符串的赋值、拼接和附加等操作,而不需要使用数组和指针。
  • 赋值操作:可以使用运算符=将一个string对象赋给另一个string对象,也可以将一个C风格字符串赋给一个string对象,但不能将一个数组赋给另一个数组。
  • 拼接操作:可以使用运算符+将两个string对象或一个string对象和一个C风格字符串合并成一个新的string对象
  • 附加操作:可以使用运算符+=将一个string对象或一个C风格字符串追加到另一个string对象的末尾,从而修改原来的string对象。

4.3.3 string 类的其他操作

  • string类和C风格字符串的比较:string类是一种更方便和安全的字符串数据类型,它可以使用运算符和方法来执行赋值、拼接和附加等操作,而不需要使用cstring库中的函数。
  • string类的赋值操作:可以使用运算符=将一个string对象或一个C风格字符串赋给另一个string对象,例如str1 = str2或str2 = “buzzard”。
  • string类的拼接操作:可以使用运算符+将两个string对象或一个string对象和一个C风格字符串合并成一个新的string对象,例如str3 = str1 + str2;
  • string类的附加操作:可以使用运算符+=将一个string对象或一个C风格字符串附加到另一个string对象的末尾,从而修改原来的string对象,例如str1 += " paste";
  • string类的长度操作:可以使用成员函数size()或length()来获取string对象的长度,它们返回一个无符号整数值,表示string对象中字符的个数,例如int len1 = str1.size();

4.3.4 string 类I/O

  • string类的I/O操作:string类可以使用cin和cout以及运算符>>和<<来进行输入和输出,其句法与处理C风格字符串相同。
  • 读取一行字符串:可以使用cin.getline()函数来将一行输入读取到字符数组中,需要指定数组的最大长度,例如cin.getline(charr, 20)。可以使用getline()函数来将一行输入读取到string对象中,需要将cin作为参数,不需要指定长度,例如getline(cin, str)。
  • string类和istream类的关系:string类是在istream类之后引入的,因此istream类没有处
  • string对象的类方法,而是使用string类的友元函数来实现I/O操作。友元函数是一种可以访问类的私有成员的函数,它不属于任何类

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

  • C++的字符串字面值:C++支持多种类型的字符串字面值,如wchar_t、char16_t、char32_t等,它们分别使用L、u、U等前缀表示。C++11还支持UTF-8编码的字符串字面值,使用u8前缀表示。
  • C++的原始字符串:C++11新增了原始字符串的概念,它使用R前缀和一对定界符表示,如R"(…)“。在原始字符串中,字符表示的就是自己,不会被转义定界符可以自定义,如R”+(…)+"。
  • 输入原始字符串时,按回车键不仅会移到下一行,还将在原始字符串中添加回车字符
  • 可将前缀R与其他字符串前缀结合使用,以标识wchar_t等类型的原始字符串。可将R放在前面,也可将其放在后面,如Ru、UR等。

4.4 结构简介

  • C++的结构:结构是一种用户定义的复合类型,它可以存储多种类型的数据,如char、float、double等。结构可以用来表示一些相关的信息,如篮球运动员的姓名、工资、身高等。
  • C++的结构声明:结构声明定义了结构的名称和成员,使用关键字struct和一对大括号表示,如struct inflatable {…}。结构声明指出了新类型的特征,但不创建任何变量
  • C++的结构变量:结构变量是结构类型的数据对象,它可以按照结构声明中指定的格式存储数据。创建结构变量时,可以省略关键字struct,如inflatable hat;下图所示。

在这里插入图片描述

  • C++的成员运算符:成员运算符(.)用来访问结构变量中的各个成员,如hat.volume表示hat变量中的volume成员。成员运算符可以与其他运算符和函数一起使用,如cout << hat.price;。hat是一个结构,而hat.price是一个double变量。访问类成员函数(如cin.getline())的方式是从访问结构成员变量(如vincent.price)的方式衍生而来的。

4.4.1 在程序中使用结构

示例:

// structur.cpp -- a simple structure
#include <iostream>
struct inflatable // structure declaration
{
    char name[20];
    float volume;
    double price;
};

int main()
{
    using namespace std;
    inflatable guest =
    {
        "Glorious Gloria", // name value
        1.88,              // volume value
        29.99              // price value
    }; // guest is a structure variable of type inflatable
// It's initialized to the indicated values
    inflatable pal =
    {
        "Audacious Arthur",
        3.12,
        32.99
    }; // pal is a second variable of type inflatable
// NOTE: some implementations require using
// static inflatable guest =

    cout << "Expand your guest list with " << guest.name;
    cout << " and " << pal.name << "!\n";
// pal.name is the name member of the pal variable
    cout << "You can have both for $";
    cout << guest.price + pal.price << "!\n";
    return 0;
}

输出:

Expand your guest list with Glorious Gloria and Audacious Arthur!
You can have both for $62.98!
  • 结构声明的位置:结构声明可以放在函数内部或外部,外部声明可以被其后面的任何函数使用,内部声明只能被该声明所属的函数使用。通常应使用外部声明,这样所有函数都可以使用这种类型的结构。

在这里插入图片描述

  • 初始化结构变量:可以使用花括号列表来初始化结构变量,列表中的值按照结构声明中的顺序赋给各个成员,如inflatable guest = {“Glorious Gloria”, 1.88, 29.99};。创建结构变量时,可以省略关键字struct,如inflatable hat;。
inflatable guest =
{
    "Glorious Gloria", // name value
    1.88,              // volume value
    29.99              // price value
};

或者

inflatable duck = {"Daphne", 0.12, 9.98};
  • 访问结构成员:可将每个结构成员看作是相应类型的变量。可以使用成员运算符(.)来访问结构变量中的各个成员,如hat.volume表示hat变量中的volume成员。成员运算符可以与其他运算符和函数一起使用,如cout << hat.price;。如果成员是一个字符数组,可以用下标来访问其中的各个字符,如pal.name[0]是字符A。

4.4.2 C++11结构初始化

  • 与数组一样,C++11也支持将列表初始化用于结构,且等号(=)是可选的:
inflatable duck {"Daphne", 0.12, 9.98}; // can omit the = in C++11
  • 如果大括号内未包含任何东西,各个成员都将被设置为零。例如,下面的声明导致mayor.volume和mayor.price被设置为零,且char数组每个字节都被设置为零:
inflatable mayor {};
  • 最后,不允许缩窄转换

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

可以将成员name指定为string对象而不是字符数组吗?即可以像下面这样声明结构吗?

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

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

一定要让结构定义能够访问名称空间std。为此,可以将编译指令using移到结构定义之前;也可以像前面那样,将name的类型声明为std::string。

4.4.4 其他结构属性

  • C++的结构赋值:C++可以使用赋值运算符(=)将一个结构赋给另一个同类型的结构,这样结构中每个成员都将被设置为另一个结构中相应成员的值,即使成员是数组。这种赋值被称为成员赋值(memberwise assignment)。
// assgn_st.cpp -- assigning structures
#include <iostream>
struct inflatable
{
    char name[20];
    float volume;
    double price;
};
int main()
{
    using namespace std;
    inflatable bouquet =
    {
        "sunflowers",
        0.20,
        12.49
    };
    inflatable choice;
    cout << "bouquet: " << bouquet.name << " for $";
    cout << bouquet.price << endl;

    choice = bouquet; // assign one structure to another
    cout << "choice: " << choice.name << " for $";
    cout << choice.price << endl;
    return 0;
}

输出:

bouquet: sunflowers for $12.49
choice: sunflowers for $12.49
  • 同时定义结构和创建结构变量:C++可以在定义结构的同时创建这种类型的变量方法是在结束括号后面添加变量名,如struct perks {…} mr_smith, ms_jones;。
struct perks
{
    int key_number;
    char car[12];
} mr_smith, ms_jones; // two perks variables

也可以用花括号列表来初始化这些变量,如struct perks {…} mr_glitz = {7, “Packard”};。

struct perks
{
    int key_number;
    char car[12];
} mr_glitz =
{
        7,         // value for mr_glitz.key_number member
        "Packard"  // value for mr_glitz.car member
};
  • 没有名称的结构类型:C++可以声明没有名称的结构类型,方法是省略名称同时定义一种结构类型和一个这种类型的变量,如struct {…} position;。这样只能创建一个这种类型的变量,以后无法再创建这种类型的变量。
struct       // no tag
{
    int x;   // 2 members
    int y;
} position;  // a structure variable

如何比较两个结构是否相等?

我们继续往下走。

如何在结构中嵌套另一个结构?

我们继续往下走。

4.4.5 结构数组

  • 结构数组:结构数组是一种包含多个结构类型的元素的数组,如inflatable gifts[100];。结构数组可以用来存储一些相同类型的结构数据,如充气产品的名称、容量和售价等。
  • 初始化结构数组:可以用花括号列表来初始化结构数组,列表中的每个值又是一个用花括号括起的结构初始化列表,如inflatable guests[2] = {{“Bambi”, 0.5, 21.99}, {“Godzilla”, 2000, 565.99}};。列表中的值按照结构声明和数组顺序赋给各个成员和元素。
  • 访问结构数组:可以用下标和成员运算符来访问结构数组中的各个元素和成员,如guests[0].name表示第一个元素中的name成员。也可以用其他运算符和函数来操作结构数组,如cout << guests[0].price + guests[1].price;。

示例:

// arrstruc.cpp -- an array of structures
#include <iostream>
struct inflatable
{
    char name[20];
    float volume;
    double price;
};
int main()
{
    using namespace std;
    inflatable guests[2] = // 定义一个结构数组
    {
        {"Bambi", 0.5, 21.99}, // 数组中的第一个结构
        {"Godzilla", 2000, 565.99} // 数组中的第二个结构
    };

    cout << "The guests " << guests[0].name << " and " << guests[1].name
         << "\nhave a combined volume of "
         << guests[0].volume + guests[1].volume << " cubic feet.\n";
    return 0;
}

下面是该程序的输出:

The guests Bambi and Godzilla
have a combined volume of 2000.5 cubic feet.

注意:
结构数组本身是一个数组,而不是一个结构,只不过数组的成员时一个个结构对象而已

4.4.6 结构中的位字段

  • 结构中的位字段:位字段是一种指定结构成员占用特定位数的方式,它可以用来创建与硬件设备上的寄存器对应的数据结构。位字段的类型应为整型或枚举,后面跟一个冒号和一个数字,表示使用的位数。可以使用没有名称的字段来提供间距。每个成员都被称为位字段(bit field)。
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
};
  • 定义和使用位字段:可以在结构声明中使用位字段,如struct torgle_register { unsigned int SN : 4; … };。可以像通常那样初始化和访问这些字段,如torgle_register tr = { 14, true, false };和if (tr.goodIn) {…}。
torgle_register tr = { 14, true, false };
...
if (tr.goodIn) // if statement covered in Chapter 6
...
  • 位字段的局限性:位字段通常用在低级编程中,一般来说,可以使用整型和按位运算符来代替这种方式。位字段的行为可能依赖于编译器和硬件平台,因此不具有可移植性。

4.5 共用体

  • 共用体:共用体是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。共用体的句法与结构相似,但含义不同,使用关键字union和一对大括号表示,如:
union one4all
{
    int int_val;
    long long_val;
    double double_val;
};

可以使用one4all变量来存储int、long或double,条件是在不同的时间进行:

one4all pail;
pail.int_val = 15;        // store an int
cout << pail.int_val;
pail.double_val = 1.38;   // store a double, int value is lost
cout << pail.double_val;
  • 定义和使用共用体:可以在共用体声明中指定不同类型的成员,如int、long、double等,但只能在不同的时间使用其中的一个成员,如one4all pail; pail.int_val = 15; pail.double_val = 1.38;。可以使用成员运算符(.)来访问共用体中的各个成员,如cout << pail.int_val;。
  • 共用体的用途:共用体的用途之一是,当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。例如,假设管理一个小商品目录,其中有一些商品的ID为整数,而另一些的ID为字符串。在这种情况下,可以在结构中嵌套一个共用体来存储不同类型的ID。
struct widget
{
char brand[20];
int type;
union id                // format depends on widget type
{
    long id_num;        // type 1 widgets
    char id_char[20];   // other widgets
} id_val;
};
...
widget prize;
...
if (prize.type == 1)             // if-else statement (Chapter 6)
    cin >> prize.id_val.id_num;  // use member name to indicate mode
else
    cin >> prize.id_val.id_char;

那么,如何判断共同体当前哪个成员是活动的?

我们继续往下走。

  • 匿名共用体:匿名共用体没有名称,其成员将成为位于相同地址处的变量。由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员。匿名共用体可以省略中间标识符来访问其成员,如prize.id_num。
struct widget
{
    char brand[20];
    int type;
    union                  // 匿名共同体
{
        long id_num;       // type 1 widgets
        char id_char[20];  // other widgets
    };
};
...
widget prize;
...
if (prize.type == 1)
    cin >> prize.id_num;
else
    cin >> prize.id_char;

共用体常用于(但并非只能用于)节省内存。当前,系统的内存多达数GB甚至数TB,好像没有必要节省内存,但并非所有的C++程序都是为这样的系统编写的。C++还用于嵌入式系统编程,如控制烤箱、MP3播放器或火星漫步者的处理器。对这些应用程序来说,内存可能非常宝贵。另外,共用体常用于操作系统数据结构或硬件数据结构。

4.6 枚举

C++的enum工具,它可以用来创建符号常量新类型

  • 枚举类型:使用enum关键字定义一种新类型,它包含一组符号常量,称为枚举量。例如:
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};

1、让spectrum成为新类型的名称;spectrum被称为枚举(enumeration),就像struct变量被称为结构一样。
2、将red、orange、yellow等作为符号常量,它们对应整数值0~7。这些常量叫作枚举量(enumerator)。

  • 枚举变量:可以用枚举类型的名称来声明变量,这种变量只能存储枚举量中的一个值。
spectrum band; // 一个spectrum类型的变量
band = blue;   // valid, blue is an enumerator
band = 2000;   // invalid, 2000 not an enumerator

因此,spectrum变量受到限制,只有8个可能的值

  • 枚举规则:枚举变量不能进行算术运算,也不能赋值为非枚举量的整数,除非进行强制类型转换。枚举量可以被提升为int类型,但int类型不能自动转换为枚举类型。
  • 枚举用途:枚举更常被用来定义相关的符号常量,而不是新类型。可以省略枚举类型的名称,只使用常量。
enum {red, orange, 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。因此,third的值为101。

  • 最后,可以创建多个值相同的枚举量:
enum {zero, null = 0, one, numero_uno = 1};

其中,zero和null都为0,one和numero_uno都为1。在C++早期的版本中,只能将int值(或提升为int的值)赋给枚举量,但这种限制取消了,因此可以使用long甚至long long类型的值。

4.6.2 枚举的取值范围

  • 枚举的取值范围:枚举变量可以通过强制类型转换,赋值为枚举类型的取值范围内的任何整数值,即使这个值不是枚举量。
enum bits{one = 1, two = 2, four = 4, eight = 8};
bits myflag;

则下面的代码将是合法的:

myflag = bits(6); // valid, because 6 is in bits range

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

  • 取值范围的计算:取值范围的上限是大于最大枚举量的最小2的幂减1,下限是0或者小于最小枚举量的最大2的幂加1。例如,下面的枚举:
enum bigstep{first, second = 100, third};

bigstep的最大值枚举值是101。在2的幂中,比这个数大的最小值为128,因此取值范围的上限为127。
要计算下限,需要知道枚举量的最小值。如果它不小于0,则取值范围的下限为0;
否则,采用与寻找上限方式相同的方式,但加上负号。例如,如果最小的枚举量为−6,而比它小的、最大的2的幂是−8(加上负号),因此下限为−7。

  • 枚举的存储空间:枚举的存储空间由编译器决定,根据取值范围的大小,可能是一个字节或更多。
  • C++11的扩展:C++11增加了作用域内枚举,它可以指定枚举类型的底层类型,并避免枚举量污染命名空间。
    如何指定作用域内枚举的底层类型?

    我们继续往下走。

小思考🤔️

如何比较两个C风格字符串是否相等?

答:

不能直接用==运算符来比较两个C风格字符串,因为这样只会比较它们的地址,而不是内容。可以使用标准库函数strcmp来比较两个C风格字符串,如果它们相等,返回0,如果第一个字符串小于第二个字符串,返回负数,如果第一个字符串大于第二个字符串,返回正数。例如:

char name1[10] = "Alice";
char name2[10] = "Bob";
if (strcmp(name1, name2) == 0)
{
    cout << "The names are the same.\n";
}
else if (strcmp(name1, name2) < 0)
{
    cout << "The name " << name1 << " comes before " << name2 << ".\n";
}
else
{
    cout << "The name " << name1 << " comes after " << name2 << ".\n";
}

返回

如何连接两个C风格字符串?

答:

不能直接用+运算符来连接两个C风格字符串,因为这样只会得到它们的地址之和,而不是内容之和。可以使用标准库函数strcat来连接两个C风格字符串,它会将第二个字符串追加到第一个字符串的末尾,并返回第一个字符串的地址。注意,要保证第一个字符串有足够的空间来容纳第二个字符串。例如:

char first[20] = "Hello";
char second[10] = "World";
strcat(first, second); // first becomes "HelloWorld"
cout << first << endl;

返回

如何获取C风格字符串的长度?

答:

不能直接用sizeof运算符来获取C风格字符串的长度,因为这样只会得到整个数组的字节数,而不是有效字符的个数。可以使用标准库函数strlen来获取C风格字符串的长度,它会返回不包括空字符在内的字符个数。例如:

char name[10] = "Alice";
cout << sizeof name << endl; // prints 10
cout << strlen(name) << endl; // prints 5

返回

如何在C++中使用转义序列来表示特殊字符,如双引号、换行符和制表符?

答:

以在特殊字符前加上反斜杠(\)来表示转义序列。例如:

cout << "She said, \"Hello, world!\"\n"; // print She said, "Hello, world!" and a new line
cout << "This is a\ttab.\n"; // print This is a    tab. and a new line

返回

如何在C++中使用原始字符串字面值(raw string literal)来表示包含反斜杠或引号的字符串,而不需要转义?

答:

可以在双引号前加上R,并在双引号内用一对括号括起字符串来表示原始字符串字面值,如R"(string)"。这样,反斜杠和引号都不需要转义,而且括号内的所有字符都会原样输出。例如:

cout << R"(She said, "Hello, world!\n")"; // print She said, "Hello, world!\n"
cout << R"*(This is a\ttab.\n)*"; // print This is a\ttab.\n

返回

如何在C++中使用宽字符字面值(wide character literal)来表示非ASCII字符,如汉字或日文?

答:

可以在单引号前加上L,并在单引号内用Unicode编码来表示宽字符字面值,如L’\u4e2d’表示汉字“中”。也可以在双引号前加上L,并在双引号内用Unicode编码或本地编码来表示宽字符串字面值,如L"\u4e2d\u6587"或L"中文"表示汉语“中文”。例如:

wchar_t ch = L'\u4e2d'; // assign the Chinese character "中" to a wide char variable
wcout << ch << endl; // print 中
wchar_t str[] = L"\u4e2d\u6587"; // assign the Chinese string "中文" to a wide char array
wcout << str << endl; // print 中文

返回

如何将一个字符串复制到另一个数组中?

答:

不能直接用赋值运算符(=)来将一个字符串复制到另一个数组中,因为这样只会复制它们的地址,而不是内容。可以使用标准库函数strcpy来复制一个字符串到另一个数组中,它会将源字符串的所有字符(包括空字符)复制到目标数组中,并返回目标数组的地址。例如:

char name1[10] = "Alice";
char name2[10];
strcpy(name2, name1); // copy name1 to name2
cout << name2 << endl; // print Alice

返回

什么是缓冲区溢出?

答:

缓冲区溢出是一种程序错误,当输入数据超过了预先分配的内存空间时,会覆盖相邻的内存区域,可能导致程序崩溃或被恶意利用。

返回

如何比较两个结构是否相等?

答:

C++没有提供直接比较两个结构是否相等的运算符,但可以使用逻辑运算符(&&)来比较两个结构中每个成员是否相等,如if (guest.name == pal.name && guest.volume == pal.volume && guest.price == pal.price) {…}。

返回

如何在结构中嵌套另一个结构?

答:

C++可以在结构中嵌套另一个结构,方法是在结构声明中使用另一个已经定义好的结构类型作为成员类型,如struct name {…}; struct person {name fullname; int age;};。这样可以用两个点运算符来访问嵌套结构中的成员,如person1.fullname.first表示person1变量中fullname成员中的first成员。

返回

如何判断共同体当前哪个成员是活动的?

答:

没有直接的方法来判断当前哪个成员是活动的,但可以使用一些辅助变量或标志来记录最后一次对共用体赋值时使用的成员类型,如

struct widget 
{ 
	char brand[20]; 
	int type; 
	union id 
{ 
	long id_num;
	char id_char[20]; 
}; 
};

widget prize;if (prize.type == 1) 
	cin >> prize.id.id_num; 
else 
cin >> prize.id.id_char; // use type to indicate the mode of id。

返回

如何指定作用域内枚举的底层类型?

答:

可以在enum class后面加上冒号和底层类型的名称,例如enum class spectrum : unsigned char {red, orange, yellow, …}。这样,spectrum类型就是一个无符号字符类型。

返回

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值