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, 合法

}
© Licensed under CC BY-NC-SA 4.0

在b进位制中,以数n起头的数出现的机率为logb(n + 1) − logb(n) —— 本福特定律

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!