C++ Primer Plus第6版第4章(1)

第 4 章复合类型

影响最为深远的复合类型是类,它是将学习的 OOP 的堡垒。

C + + 还支持几种更普通的复合类型,它们都来自 C 语言。例如,

  • 数组可以存储多个同类型的值。
  • 一种特殊的数组可以存储字符串(一系列字符)。
  • 结构可以存储多个不同类型的值。
  • 而指针则是一种将数据所处位置告诉计算机的变量。

本章将介绍所有这些复合类型(类除外),还将介绍 new 和 delete 及如何使用它们来管理数据。另外,还将简要地介绍 string 类,它提供了另一种处理字符串的途径。

数组

数组(array)是一种数据格式,能够存储多个同类型的值。

事实上,可以将数组中的每个元素看作是一个简单变量。

数组之所以被称为复合类型,是因为它是使用其他类型来创建的(C 语言使用术语“派生类型”,但由于 C + + 对类关系使用术语“派生”,所以它必须创建一个新术语)。

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

根据 C 语言的经验, 数组名实际是指向数组第一个元素的指针。C++ 的数组和 C 语言的数组类似。

如果只对数组的一部分进行初始化,则编译器将把其他元素设置为 0。因此,将数组中所有的元素都初始化为 0 非常简单— 只要显式地将第一个元素初始化为 0,然后让编译器将其他元素都初始化为 0

#include <iostream>

using  namespace std;

void print_array(int a[],int a_size);

int main() {int i[] = {1,2,3,4};
    int j[4];
    // 初始化数组时,可省略等号(=)int k[4] {1,2};
    // 可不在大括号内包含任何东西,这将把所有元素都设置为零
    int m[5]{};

    int temp;

    print_array(i,sizeof(i)/sizeof(int)); // 1 2 3 4

    print_array(j, sizeof(j) / sizeof(int));; // -858993460 -858993460 -858993460 -858993460

    print_array(k, sizeof(k) / sizeof(int)); // 1 2 0 0

    print_array(m, sizeof(m) / sizeof(int)); // 0 0 0 0 0

    return 0;
}


void print_array(int a[],int a_size) {
    // 打印整数数组
    int temp;

    for (temp = 0; temp < a_size; temp++) cout << a[temp] << ' '; 
    cout << endl;
}

C + + 标准模板库(STL)提供了一种数组替代品— 模板类 vector,而 C + + 11 新增了模板类 array。这些替代品比内置复合类型数组更复杂、更灵活,本章将简要地讨论它们,而第 16 章将更详细地讨论它们。

字符串

字符串是存储在内存的连续字节中的一系列字符。C + + 处理字符串的方式有两种。

  • 第一种来自 C 语言,常被称为 C- 风格字符串(C-style string)。
  • 另一种基于 string 类库的方法。

存储在连续字节中的一系列字符意味着可以将字符串存储在 char 数组中,其中每个字符都位于自己的数组元素中。

C- 风格字符串具有一种特殊的性质:以空字符(null character)结尾,空字符被写作\0,其 ASCII 码为 0,用来标记字符串的结尾。

空字符对 C- 风格字符串而言至关重要。例如,C + + 有很多处理字符串的函数,其中包括 cout 使用的那些函数。它们都逐个地处理字符串中的字符,直到到达空字符为止。

有一种更好的、将字符数组初始化为字符串的方法— 只需使用一个用引号括起的字符串即可,这种字符串被称为字符串常量(string constant)或字符串字面值(string literal),用引号括起的字符串隐式地包括结尾的空字符,因此不用显式地包括它 .

应确保数组足够大,能够存储字符串中所有字符— 包括空字符。使用字符串常量初始化字符数组是这样的一种情况,即让编译器计算元素数目更为安全。让数组比字符串长没有什么害处,只是会浪费一些空间而已。这是因为处理字符串的函数根据空字符的位置,而不是数组长度来进行处理。C + + 对字符串长度没有限制。

使用 标准 C 语言库函数 strlen()来确定字符串的长度。sizeof 运算符指出整个数组的长度:15 字节,但 strlen()函数返回的是存储在数组中的字符串的长度,而不是数组本身的长度。另外,strlen()不把空字符计算在内。

#include <iostream>

using  namespace std;

void print_array_char(char a[],int a_size);

int main() {char c1[10] = {'h','e','l','l','o'};  // 不是字符串 string
    char c2[10] = {'h','e','l','l','o','\0','','Y','o','u'}; // 是字符串
    // 用字符串直接初始化字符串数组
    char c3[] = "Hello world!";


    print_array_char(c1, sizeof(c1) / sizeof(char)); // h e l l o
    print_array_char(c2, sizeof(c2) / sizeof(char)); // h e l l o    Y o u
    print_array_char(c3, sizeof(c3) / sizeof(char)); // H e l l o   w o r l d !
    cout << c1 << endl; // hello
    cout << c2 << endl; // hello
    cout << c3 << endl; // Hello world!

    // 字符串长度不包括字符串结尾字符 , 一般和字符数组长度不相等
    cout << "c1,c2,c3 的长度:" << strlen(c1) << " " << strlen(c2) << " " << strlen(c3) << endl; //c1,c2,c3 的长?5 5 12


    // 字符与字符串
    char c4[] = "s";
    char c5 = 's'; // 单个字符
    cout << sizeof(c4) / sizeof(char) << endl; // 2, 包括 s,\0
    cout << sizeof(c5) / sizeof(char) << endl; // 1

    // 字符串拼接
    cout << "part1" "part2" << endl; //part1part2
    cout << "part1" 
            "part2" << endl;  //part1part2

    // cin 初始化数组
    char name[50];
    cout << "What's your name?\n";
    cin >> name;
    cout << "Hello," << name << ",nice to meet you!\n";
    /*
    What's your name?
    Jerry
    Hello,Jerry,nice to meet you!
    */

    char name2[] = "Hello Kitty";
    name2[7] = '\0';
    cout << name2 << endl; // Hello K
    cout << sizeof(name2) << endl; // 12
    cout << strlen(name2) << endl; // 7
}

void print_array_char(char a[],int a_size) {
    // 打印整数数组
    int temp;

    for (temp = 0; temp < a_size; temp++) cout << a[temp] << ' '; 
    cout << endl;
}

由于不能通过键盘输入空字符,因此 cin 需要用别的方法来确定字符串的结尾位置。cin 使用空白(空格、制表符和换行符)来确定字符串的结束位置,这意味着 cin 在获取字符数组输入时只读取一个单词。读取该单词后,cin 将该字符串放到数组中,并自动在结尾添加空字符。

istream 中的类(如 cin)提供了一些面向行的类成员函数:getline()和 get()。这两个函数都读取一行输入,直到到达换行符。然而,随后 getline()将丢弃换行符,而 get()将换行符保留在输入序列中。

面向行的输入

getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。要调用这种方法,可以使用 cin.getline()。该函数有两个参数。第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数。如果这个参数为 20,则函数最多读取 19 个字符,余下的空间用于存储自动在结尾处添加的空字符。getline()成员函数在读取指定数目的字符或遇到换行符时停止读取。

istream 类有另一个名为 get()的成员函数,该函数有几种变体。其中一种变体的工作方式与 getline()类似,它们接受的参数相同,解释参数的方式也相同,并且都读取到行尾。但 get 并不再读取并丢弃换行符,而是将其留在输入队列中。没有丢弃的换行符将作为下一次读取的输入的开头, 而不是第一次调用的输入的结尾。

get()有另一种变体。使用不带任何参数的 cin.get()调用可读取下一个字符(即使是换行符),因此可以用它来处理换行符,为读取下一行输入做好准备。

如果使用的是 cin.get(name,ArSize),则编译器知道是要将一个字符串放入数组中,因而将使用适当的成员函数。如果使用的是 cin.get(),则编译器知道是要读取一个字符。第 8 章将探索这种特性— 函数重载。

为什么要使用 get(),而不是 getline()呢?首先,老式实现没有 getline()。其次,get()使输入更仔细。例如,假设用 get()将一行读入数组中。如何知道停止读取的原因是由于已经读取了整行,而不是由于数组已填满呢?查看下一个输入字符,如果是换行符,说明已读取了整行;否则,说明该行中还有其他输入。第 17 章将介绍这种技术。

getline()使用起来简单一些,但 get()使得检查错误更简单些。可以用其中的任何一个来读取一行输入;只是应该知道,它们的行为稍有不同。

#include <iostream>
using  namespace std;

int main() {char longName[50];
    cout << "Your long name?" << endl;
    cin.getline(longName, 50);
    cout << "Hello," << longName << ",nice to meet you!\n";
    /*
    Your long name?
    This is my LONG NAME
    Hello,This is my LONG NAME,nice to meet you!
    */

    char longName2[50];
    cout << "Input another long name?" << endl;
    // 第一次用 cin.get(VarName,strLength) 调用的结果是换行符留在了输入队列中
    // 但换行符没有作为变量的一部分, 而会作为下一次输入的变量的一部分
    cin.get(longName2, 50);
    cout << "Hello," << longName2 << ",nice to meet you!\n";

    // 再次调用 cin.getline(), 会将上次输入队列的换行作为输入, 不会给机会输入名字
    char longName3[50];
    cout << "Input another long name? " << endl;
    cin.getline(longName3, 50);
    cout << "Hello," << longName3 << ",nice to meet you!\n";

    /*
    Input another long name?
    This is my LONG NAME2
    Hello,This is my LONG NAME2,nice to meet you!
    Input another long name?
    Hello,,nice to meet you!
    */

    char longName4[50];
    cout << "Input  long name4? " << endl;
    cin.getline(longName4, 50);
    cout << "Hello," << longName4 << ",nice to meet you!\n";

    // 把之上的输入流中的换行符吸收掉
    cin.get();

    char longName5[50];
    cout << "Input  long name5? " << endl;
    cin.getline(longName5, 50);
    cout << "Hello," << longName5 << ",nice to meet you!\n";
    /*
    Input  long name4?
    LN4
    Hello,LN4,nice to meet you!

    Input  long name5?
    LN5
    Hello,LN5,nice to meet you!
    */

    // 串联
    cout << "please input multi lines:\n";

    char longLine1[200];
    char longLine2[200];
    cin.getline(longLine1, 200).getline(longLine2, 200);
    cout << longLine1 << endl;
    cout << longLine2 << endl;
    /*
    please input multi lines:
    Line1dddddddddddddddddddddd
    Line2dddddddddddddddddddddd
    Line1dddddddddddddddddddddd
    Line2dddddddddddddddddddddd
    */
}

小结:

  • getline(Variable Name, Length) 是会清理输入队列末尾的换行符的
  • cin>>VariableName 不会清理换行符
  • get() 可以用于吸收一个字符, 所以可以用来清理输入队列中的换行符
  • 当 get()(不是 getline())读取空行后将设置失效位(failbit)。这意味着接下来的输入将被阻断,但可以用下面的命令来恢复输入:cin.clear() .cin.clear()并不是清理输入队列。
#include <iostream>
using  namespace std;

int main() {char Name[50];
    int age;
    char address[100];
    cout << "Your name?\n";
    cin.getline(Name, 50);
    cout << "Your age?\n";
    cin >> age;   // 数值输入的回车对应的换行符会保留在输入队列中, 需要抛弃掉
    cin.get(); // 吸收掉输入队列中的最后一个换行符, 达到抛弃之前输入的换行符的目的
    cout << "Your address?\n";
    cin.getline(address, 50);

    cout << Name<< " "<< age<< " "<<address<<endl;

    /*
    Your name?
    Jerry
    Your age?
    20
    Your address?
    my address
    Jerry 20 my address
    */
}

string 类简介

ISO/ ANSI C + + 98 标准通过添加 string 类扩展了 C + + 库,因此现在可以 string 类型的变量(使用 C + + 的话说是对象)而不是字符数组来存储字符串。

string 类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。要使用 string 类,必须在程序中包含头文件 string。string 类位于名称空间 std 中。

string 类定义隐藏了字符串的数组性质,让您能够像处理普通变量那样处理字符串。

与使用数组相比,使用 string 对象更方便,也更安全。从理论上说,可以将 char 数组视为一组用于存储一个字符串的 char 存储单元,而 string 类变量是一个表示字符串的实体。

#include <iostream>
#include <string>
using  namespace std;

int main() {
    // 可以使用 C - 风格字符串来初始化 string 对象。string c1 = "Hello World!";
    // 可以使用 cin 来将键盘输入存储到 string 对象中。string c2, c3,c4;
    cout << "Input a string with blank middle:\n";
    getline(cin, c2);
    cin.clear();
    cout << "Input a string with blank middle:\n";
    getline(cin,c3, '\n');// 把停止识别的符号设置为‘\n’(即换行符), 否则遇到空格就停止输入

    // 可以使用 cout 来显示 string 对象。cout << "c2:" << c2 << endl;
    // 可以使用数组表示法来访问存储在 string 对象中的字符。cout << "c2[0]:" << c2[0] << endl;
    cout << "c3:" << c3 << endl;

    //////////////////
    cout << "Input a string with blank middle:\n";
    cin >> c4;
    cout << "c4:" << c4 << endl;

    // 类设计让程序能够自动处理 string 的大小。c4 = "Long Line ddddddddddddddddddddddddddddddd";
    cout << "c4:" << c4 << endl;
}
/*
Input a string with blank middle:
Line One
Input a string with blank middle:
Line Two
c2:Line One
c2[0]:L
c3:Line Two
Input a string with blank middle:
Line Three
c4:Line
c4:Long Line ddddddddddddddddddddddddddddddd
*/

字符串:c 风格和 c ++ 风格

  • 数组名称对应的是指针不是对象, 数组字符串之间不能直接赋值。
  • string 类型的字符串变量对应的是对象不是数组, 可以相互赋值。
  • 处理 string 对象的语法通常比使用 C 字符串函数简单,尤其是执行较为复杂的操作时。
  • 另外,使用字符数组时,总是存在目标数组过小,无法存储指定信息的危险,string 类具有自动调整大小的功能,从而能够避免这种问题发生。C 函数库确实提供了与 strcat()和 strcpy()类似的函数— strncat()和 strncpy(),它们接受指出目标数组最大允许长度的第三个参数,因此更为安全,但使用它们进一步增加了编写程序的复杂度。
  • 函数 strlen()是一个常规函数,它接受一个 C- 风格字符串作为参数,并返回该字符串包含的字符数。函数 size()的功能基本上与此相同,但句法不同 size()是一个类方法。
#include <iostream>
#include <string>
#include <cstring> //strcpy, strcat, string.h 的 c ++ 版本
using  namespace std;

int main()
{char c1[50] = "Hello world!";
    char c2[50];
    string str1 = "Hello world!";
    string str2,str3;

    // c2=c1;//error: invalid array assignment
    str2 = str1;
    str3 = str2 + " There";
    cout << str2<<endl; //Hello world!
    cout<<str3<<endl; //Hello world! There

    strcpy(c2,c1); // 复制字符串数组 c1 到 c2
    cout<<c2<<endl; //Hello world!
    strcat(c2,c1); // 连接字符串 c1 到 c2
    cout<<c2<<endl; //Hello world!Hello world!

    cout<<strlen(c1)<<endl; //12
    // cout<<strlen(str1)<<endl; // error: cannot convert 'std::__cxx11::string' {aka 'std::__cxx11::basic_string<char>'} to 'const char*'

    cout<<str1.size();//12

    return 0;
}

代码片段:

#include <iostream>
#include <string>
#include <cstring> //strcpy, strcat, string.h 的 c ++ 版本
using  namespace std;

int main()
{char c1[50];
    string s1;

    cout << "c1 初始化之前的长度:" << strlen(c1) << endl;
    cout << "s1 初始化之前的长度:" << s1.size() << endl;
    /*
    字符串数组初始化之前的内容是不确定的, 对应的 strlen 的返回值也不同
    c1 初始化之前的长度:4
    s1 初始化之前的长度:0
    */


    cout << " 输入 c1 字符数组:\n";
    // A long sentence, in which the writer delays the core to the middle of the sentence
    cin.getline(c1, 50);  // 需要指定最大长度, 避免超越边界。 最多只能存 49 个字符
    cin.clear();

    cout << " 输入 s1 字符串:\n";
    getline(cin, s1); // cin 的调用方式不同, 也不需要指定最大长度

    cout << "c1:" << c1 << endl;
    cout << "s1:" << s1 << endl;
    cout << "c1 最新的长度:" << strlen(c1) << endl;
    cout << "s1 最新的长度:" << s1.size() << endl;

    /*
    第一组输出: 如果超越了最大边界, 后续的输入会比较奇怪
    输入 c1 字符数组:
    A long sentence, in which the writer delays the core to the middle of the sentence
    输入 s1 字符串:
    c1:A long sentence, in which the writer delays the c
    s1:ore to the middle of the sentence
    c1 最新的长度:49
    s1 最新的长度:33
    */


    /*
    第二组输入: A short sentence
    输入 c1 字符数组:
    A short sentence
    输入 s1 字符串:
    A short sentence
    c1:A short sentence
    s1:A short sentence
    c1 最新的长度:16
    s1 最新的长度:16
    */

    /*
    string 类的输入:
    - getline(cin,VarName): 可以处理空格
    - cin>> VarName : 只能输入一个单词
    */
    cout << "getline(cin,VarName)输入 s1 字符串, 带空格:\n";
    getline(cin, s1);
    cout << "s1:" << s1 << endl;

    cout << "cin>> VarName 输入 s1 字符串, 带空格:\n";
    cin >> s1;
    cout << "s1:" << s1 << endl;
    /*getline(cin,VarName)输入 s1 字符串, 带空格:
    Hello World!
    s1:Hello World!
    cin>> VarName 输入 s1 字符串, 带空格:
    Hello World!
    s1:Hello
    */

    return 0;
}

字符串字面值

  • char
  • wchar_t,char16_t , char32_t。对于这些类型的字符串字面值,C + + 分别使用前缀 L、u 和 U 表示
  • Unicode 字符编码方案 UTF-8。在这种方案中,根据编码的数字值,字符可能存储为 1 ~ 4 个八位组。C + + 使用前缀 u8 来表示这种类型的字符串字面值。
  • 原始(raw)字符串: 在原始字符串中,字符表示的就是自己,例如,序列 \n 不表示换行符,而表示两个常规字符— 斜杠和 n,因此在屏幕上显示时,将显示这两个字符。另一个例子是,可在字符串中使用 ",而无需使用繁琐的\"。当然,既然可在字符串字面量包含 ",就不能再使用它来表示字符串的开头和末尾。因此, 原始字符串将"(和)" 用作定界符,并使用前缀 R 来标识原始字符串
  • 可将前缀 R 与其他字符串前缀结合使用,以标识 wchar_t 等类型的原始字符串。可将 R 放在前面,也可将其放在后面,如 Ru、UR 等。
#include <iostream>
#include <string>
#include <cstring> //strcpy, strcat, string.h 的 c ++ 版本

#include <wchar.h>
#include <locale.h>
using  namespace std;

int main()
{setlocale(LC_ALL, ""); 
    wchar_t w_c[] = L"Hello world!";
    char16_t c16t_c[] = u"Hello world!";
    char32_t c32t_c[] = U"Hello world!";
    char utf8_c[] = u8"Hello world!";

    cout<<w_c<<endl;
    cout<<c16t_c<<endl;
    cout<<c32t_c<<endl;
    cout<<utf8_c<<endl;
    // 原始字符串将 "(和)" 用作定界符,并使用前缀 R 来标识原始字符串
    cout<<R"("Hello \n")"<<endl;
    // 0x61fe00
    // 0x61fde0
    // 0x61fda0
    // Hello world!
    // "Hello \n"


    // 原始字符串语法允许您在表示字符串开头的 " 和(之间添加其他字符,这意味着表示字符串结尾的 " 和) 之间也必须包含这些字符。因此,使用 R" +*(标识原始字符串的开头时,必须使用) +*" 标识原始字符串的结尾。// 貌似只能在 "  和括号之间添加一个字符
    cout<<R"-(Hello)-"<<endl; //Hello

    return 0;
}

结构

结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种类型的数据,这使得能够将有关篮球运动员的信息放在一个结构中,从而将数据的表示合并到一起。如果要跟踪整个球队,则可以使用结构数组。结构也是 C + + OOP 堡垒(类)的基石。学习有关结构的知识将使我们离 C + + 的核心 OOP 更近。

结构是用户定义的类型,而结构声明定义了这种类型的数据属性。定义了类型后,便可以创建这种类型的变量。因此创建结构包括两步。

  • 首先,定义结构描述— 它描述并标记了能够存储在结构中的各种数据类型。
  • 然后按描述创建结构变量(结构数据对象)。

结构声明的位置很重要。有两种选择。
- 可以将声明放在 main()函数中,紧跟在开始括号的后面。
另一种选择是将声明放到 main()的前面。 位于函数外面的声明被称为外部声明。

对于那些包含两个或更多函数的程序来说,差别很大。外部声明可以被其后面的任何函数使用,而内部声明只能被该声明所属的函数使用。通常应使用外部声明,这样所有函数都可以使用这种类型的结构

用已经存在的结构体变量赋值给结构体数组或者其他结构体变量, 会复制值给对方, 并不是把自己的指针给对方。

#include <iostream>
#include <string>
using  namespace std;

struct student {
    int id;
    string fullname;
    int age;
    int high;
}; // 结构体定义是语句, 需要加分号

int main()
{
    // C + + 允许在声明结构变量时省略关键字 struct:struct  student Jerry;
    student Tom {2,"Tom The Cat",2,30}; // 省略了 =

    Jerry={
        1,
        "Jerry The Mouse",
        2,
        5
    }; 

    cout<<Tom.fullname<<",age:"<<Tom.age<<endl;
    // Tom The Cat,age:2


    // 结构体数组
    student friends[3] = {Tom,Jerry};

    for (int i=0;i<sizeof(friends)/sizeof(student);i++){cout<<friends[i].fullname<<",age:"<<friends[i].age<<endl;
    }
    // Tom The Cat,age:2
    // Jerry The Mouse,age:2
    // ,age:0   

    // 用一个结构体给另一个结构体赋值
    student Tom2 = Tom; 
    friends[2]=Tom2;
    Tom.fullname ="Tom Original";
    Tom2.age=3;

    for (int i=0;i<sizeof(friends)/sizeof(student);i++){cout<<friends[i].fullname<<",age:"<<friends[i].age<<endl;
    }
    // Tom The Cat,age:2
    // Jerry The Mouse,age:2
    // Tom The Cat,age:2

    cout<<Tom.fullname<<",age:"<<Tom.age<<endl;
    // Tom Original,age:2
    cout<<Tom2.fullname<<",age:"<<Tom2.age<<endl;
    //Tom The Cat,age:3

    return 0;
}

结构中的位字段

与 C 语言一样,C + + 也允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整型或枚举(稍后将介绍),接下来是冒号,冒号后面是一个数字,它指定了使用的位数。可以使用没有名称的字段来提供间距。每个成员都被称为位字段(bit field)。

位字段通常用在低级编程中。

#include <iostream>
#include <string>
using  namespace std;

int main()
{
    // 位定义的每个变量结尾需要有分号
    struct struct_bits {
        unsigned int id: 4; // 4 bit
        unsigned int : 2; // 两位没有变量名, 用于分隔
        bool good: 1; // 1 位
    };

    struct_bits s1 = {20, true}, s2 = {21, false};
    cout << sizeof(s1) << endl; //8
    cout  <<s1.id<<" "<<s1.good<<endl; // 4 1
    cout  <<s2.id<<" "<<s2.good<<endl; // 5 0

    return 0;
}

共用体

  • 共用体(union)是一种数据格式,它能够存储不同的数据类型,但在某一个时间点只能存储其中的一种类型。
  • 共用体成员名称标识了变量的容量。由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以,共用体的长度为其最大成员的长度。
  • 共用体的用途之一是,当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。
  • 共用体常用于(但并非只能用于)节省内存。当前,系统的内存多达数 GB 甚至数 TB,好像没有必要节省内存,但并非所有的 C + + 程序都是为这样的系统编写的。C + + 还用于嵌入式系统编程,如控制烤箱、MP3 播放器或火星漫步者的处理器。对这些应用程序来说,内存可能非常宝贵。另外,共用体常用于操作系统数据结构或硬件数据结构。
#include <iostream>
#include <string>
using  namespace std;

int main()
{
    union var4all {
        int id_int;
        // string id_string[50];// 不能用字符串对象
        char id_string[50];
    };

    var4all myid;
    myid.id_int = 1;

    cout << "my id:" << myid.id_int << endl;
    cout << "my id:" << myid.id_string << endl;
    // my id:1
    // my id:  // 这里看到的是控制字符 SOH,Start Of Head

    char temp[] = "myStringID";
    int i;
    for(i = 0; i < sizeof(temp) / sizeof(char); i++) {myid.id_string[i] = temp[i];
    }
    myid.id_string[i] = '\0';

    cout << "my id:" << myid.id_int << endl;
    cout << "my id:" << myid.id_string << endl;
    // my id:1951627629
    // my id:myStringID
    // 程序员必须自己确定目前哪个共用体变量在使用

    struct animal {
        string name;
        string type;

        // 匿名共用体, 可以直接调用其中定义的变量
        union {
            int legs_num;  // 腿的数量
            double wing_len; // 翅膀的长度
        };
    };

    animal animals[2];

    animals[0].name = {"Page"};
    animals[0].type = "pig";
    animals[1].name = {"Kowalski"};
    animals[1].type = "Penguin";

    for(int i = 0; i < 2; i++) {if (animals[i].type == "pig") {animals[i].legs_num = 4;
        }
        else {animals[i].wing_len = 10;
        }

        cout << "animal name:" << animals[i].name << ", animals legs num:" << animals[i].legs_num;
        cout << ", animal length of winds:" << animals[i].wing_len << endl;
        // 错误的使用共用体变量的输出
        // animal name:Page, animals legs num:4, animal length of winds:1.97626e-323
        // animal name:Kowalski, animals legs num:0, animal length of winds:10
    }

    for(int i = 0; i < 2; i++) {if (animals[i].type == "pig") {cout << "animal name:" << animals[i].name << ", animals legs num:" << animals[i].legs_num << endl;
        }
        else {cout << "animal name:" << animals[i].name << ", animal length of winds:" << animals[i].wing_len << endl;
        }

    }
    // animal name:Page, animals legs num:4
    // animal name:Kowalski, animal length of winds:10


    return 0;
}

枚举

  • C + + 的 enum 工具提供了另一种创建符号常量的方式,这种方式可以代替 const。它还允许定义新类型,但必须按严格的限制进行。使用 enum 的句法与使用结构相似。
  • 在默认情况下,将整数值赋给枚举量,第一个枚举量的值为 0,第二个枚举量的值为 1,依次类推。可以通过显式地指定整数值来覆盖默认值,实际上可以随意指定整数值
  • 在不进行强制类型转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量
  • 对于枚举,只定义了赋值运算符。具体地说,没有为枚举定义算术运算。 有些实现并没有这种限制,这有可能导致违反类型限制。
  • 枚举的规则相当严格。实际上,枚举更常被用来定义相关的符号常量,而不是新类型。例如,可以用枚举来定义 switch 语句中使用的符号常量
  • 枚举类型有对应的整数, 但最好不要当整数来用
  • 取值范围的定义如下。
    • 首先,要找出上限,需要知道枚举量的最大值。找到大于这个最大值的、最小的 2 的幂,将它减去 1,得到的便是取值范围的上限。例如,前面定义的 bigstep 的最大值枚举值是 101。在 2 的幂中,比这个数大的最小值为 128,因此取值范围的上限为 127。
    • 要计算下限,需要知道枚举量的最小值。
      • 如果它不小于 0,则取值范围的下限为 0;
      • 否则,采用与寻找上限方式相同的方式,但加上负号。例如,如果最小的枚举量为 − 6,而比它小的、最大的 2 的幂是 − 8(加上负号),因此下限为 − 7。
  • 选择用多少空间来存储枚举由编译器决定。对于取值范围较小的枚举,使用一个字节或更少的空间;而对于包含 long 类型值的枚举,则使用 4 个字节。
  • C + + 11 扩展了枚举,增加了作用域内枚举(scoped enumeration),第 10 章的“类作用域”一节将简要地介绍这种枚举。
#include <iostream>
#include <string>
using  namespace std;

int main()
{enum color  {red,green,yellow,blue,violet}; // 定义了 5 种颜色为枚举类型 , 没有 = 

    color myFavoriteColor=blue;
    cout<<"My favorite color:"<<myFavoriteColor<<endl; //3


    // myFavoriteColor=4;  // error: invalid conversion from 'int' to 'main()::color' [-fpermissive]
    // myFavoriteColor++;  // error: invalid conversion from 'int' to 'main()::color' [-fpermissive]

    myFavoriteColor=color(4); // 强制类型转换
    cout<<"My favorite color:"<<myFavoriteColor<<endl; //4

    // 指定编号
    enum nums {one=1,two=2,three=3,four=4,five=5,six=100,seven};
    nums myFavoriteNum =five;
    cout<<"My favorite num:"<<myFavoriteNum<<endl; //5
    myFavoriteNum =six;
    cout<<"My favorite num:"<<myFavoriteNum<<endl; //100
    myFavoriteNum =seven; // 自动加 1
    cout<<"My favorite num:"<<myFavoriteNum<<endl; //101

    // 枚举值的取值范围对应的是一个整数上下限范围, 并不是枚举定义中列出的值
    myFavoriteNum =nums(120); //
    cout<<"My favorite num:"<<myFavoriteNum<<endl; //120, 合法

}
评论(没有评论)