【C++ Primer Plus】笔记 - 第四章 复合类型

第四章 复合类型

C++ Primer Plus自学笔记, 可能不适用于所有人, 仅供参考

4.1 数组

1. 创建数组

例: 创建一个名为months的数组,该数组有 12 个元素,每个元素存储一个short类型的值:

    short months[12];
  • months的类型不是"数组", 而是"short数组".
  • 强调了数组是使用short类型创建的.

若将一个值赋给不存在的元素months[101], 编译器不会报错, 但程序运行后可能引发一些严重问题.

  • 必须确保程序只使用有效下标值, 避免数组越界.
// arrayone.cpp
#include <iostream>

int main() {
    using namespace std;
    int yams[3];    //创建有3个元素的int型数组
    yams[0] = 7;
    yams[1] = 8;
    yams[2] = 6;

    int yamcosts[3] = {20, 30, 5};  // 创建初始化数组;

    // 使用数组元素
    cout << "Total yams = ";
    cout << yam[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 expenses is " << total << " cents.\n";

    // 数组及元素的 size 大小
    cout << "\nSize of yams array = " << sizeof yams << " bytes.\n";
    cout << "Size of one element = " << sizeof yams[0] << " 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.
  • 数组的初始化:
    int cards[4] = {3, 6, 8, 10};
    int hand[4];

    float hotelTips[5] = {5.0, 2.5};    // 只初始化前两个元素.
    long totals[500] = {0};     		// 将所有元素都初始化为 0
    short things[] = {1, 5, 3, 8};  	// things 数组将包含 4 个元素.

    // C++11 数组初始化方法
    double earning[4] {1.2e4, 1.6e4, 1.1e4, 1.7e4}; // 省略等于号
    unsigned int counts[10] = {};   		// 将所有元素初始化为0.
    float balances[100] {}; 				// 同上
    long plifs[] = {25, 92, 3.0};   		// 不可行: 禁止缩窄转换
    char slifs[4] {'h', 'i', 1122011, '0'}; // 不可行
    char slifs[4] {'h', 'i', 112, '0'}; 	// 可行: 没有发生缩窄转换.
  • 确定数组的大小:
    short things[] = {1, 5, 3, 8}; 
    int num_elements = sizeof(things) / sizeof(short);
    // 即可得到数组元素个数(4).

4.2 字符串

  • C语言风格字符串
      char dog[8] = {'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'};   // 不是字符串: 字符串应以 '\0' 结尾.
      char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'};  // 是字符串.
      char bird[11] = "Mr. Cheeps";   // 隐式的'\0'.
      char fish[] = "Bubbles";    // 让编译器计算数组长度.
    
      // 注意: 结尾填充空字符'\0'
      // 例如:
      char boss[8] = "Bozo";
      // -> {'B', 'o', 'z', 'o', '\0', '\0', '\0', '\0'}
    
  • 'S'是字符常量, 而"S"不是. "S"是字符串,包含了字符'S''\0'.
  • 实际上, "S"表示的是字符串所在的内存地址.
  • 下面的字符串将一个内存地址赋值给char类型:
      char shirt_size = "S";  // 不合法, 会报错.
    
  • 在数组中使用字符串
    • 引用头文件: <cstring>; 使用strlen()函数, 计算字符串长度, 而不是数组本身的长度. (数组本身长度会包含'\0'.)
  • cin:
    • 使用空格, tab, 换行符来确定字符串的结束位置.
      • 因此, cin只能读取一个单词.
  • 面向行的输入: getline():
    • 读取整行, 通过回车键确定输入结尾.
    • 使用cin.getline(数组名称, 读取的字符数).
      • 例如, 假设使用cin.getline(name, 20), 则该行字符不应超过19个, 余下位置存储空字符'\0'.
  • 面向行的输入: get():
    • 不读取并丢弃行尾的换行符, 而是保留在输入队列中.
    • 使用cin.get()的正确示例:
        cin.get(name, ArSize);      // 读取第一行
        cin.get();                  // 读取换行符
        cin.get(dessert, ArSize);   // 读取第二行
      
  • 混合使用cincin.getline()可能会出现问题.
    • 原因是cin将回车键输入的换行符留在了输入队列中, 并被cin.getline()识别.
    • 正确的示例:
        // 1. 单独进行调用
        cin >> year;
        cin.get();    		// 或者 cin.get(ch);
      
        // 2. 将调用拼接起来:
        (cin >> year).get();  // 或者 (cin >> year).get(ch);
      
  • C++常使用指针处理字符串, 而不是数组.

4.3 string

  • 必须在程序中包含头文件string.
  • 位于命名空间std中, 故需要使用std::string引用.
  • 例如:
    #include <iostream>
    #include <string>
    int main() {
      using namespace std;
    
      char charr1[20];            // 创建空数组
      char charr2[20] = "jaguar"; // 创建初始化数组
      string str1;                // 创建空字符串对象
      string str2 = "panther";    // 创建初始化字符串
    
      // ...
    }
    
    • 可以使用C语言风格字符串来初始化string对象.
    • 可以对string对象使用cincout.
    • 可以用数组表示法访问string中的字符. (如: str[2])
  • C++11 字符串初始化
      char first_date[] = {"Le Chapon Dodu"};
      char second_date[] {"The Elegant Place"};
      string third_date = {"The bread Bowl"};
      string fourth_date {"Hank's Fine Eats"};
    
  • 字符串赋值, 拼接, 附加
      char charr1[20];            // 创建空数组
      char charr2[20] = "jaguar"; // 创建初始化数组
      string str1;                // 创建空字符串对象
      string str2 = "panther";    // 创建初始化字符串
    
      // 赋值
      charr1 = charr2;    // 无效, 没有数组分配
      str1 = str2;        // 有效, 可以分配对象
    
      // 合并
      string str3 = str1 + str2;  // str3 为 str1 与 str2 拼接.
      str1 += str2;               // 在 str1 末尾附加 str 2.
    
    • C-风格字符串: 头文件<cstring>
      strcpy(charr1, charr2); // 将 charr2 复制到 charr1
      strcat(charr1, charr2); // 将 charr2 拼接到 charr1 末尾
      
  • 确定字符串中字符数的方法:
      int len1 = str1.size();
      int len2 = strlen(charr1);
    
  • string类输入输出:
      cin.getline(charr, 20); // 需要指定最大长度
      getline(cin, str);      // 不需要指定长度. cin 为参数
    
  • 原始字符串 (C++11): 使用R"()"包括的内容.
      cout << R"(Jim "King" Tutt uses "\n" instead of endl.)" << '\n';
      // 输出原始字符串 -> Jim "King" Tutt uses "\n" instead of endl.
      cout << "Jim \"King\" Tutt uses \"\\n\" instead of endl." << '\n';
      // 需要使用转义字符.
    

4.4 结构体struct

  • 结构体的声明, 初始化, 使用
    #include <iostream>
    
    // 声明结构体
    struct inflatable {
      char name[20];
      float volume;
      double price;
    };
    
    // 指定结构体中的位字段 (博主实践中暂时没遇到过)
    struct torgle_register {
      unsigned int SN : 4;    // SN 的值为 4 位
      unsigned int : 4;       // 4 位未使用
      bool goodIn : 1;        // 布尔值: 有效输入 (1 bit)
      bool goodTorgle : 1;
    } // 初始化示例: torgle_register tr = {14, true, false};
    
    int main() {
      using namespace std;
    
      inflatable guest = {    // guest 是类型 inflatable 的一个结构体变量
          "Glorious Gloria",  // name 的值
          1.88,               // volume 的值
          29.99               // price 的值
      }; 
    
      inflatable pal = {      // pal 是类型 inflatable 的第二个变量
          "Audacious Arthur", // name
          3.12,               // volume
          32.99               // price
      };
      // 如, pal.name[0] == 'A'.
      // 但是 pal[0] 没有意义, 因为 pal 是结构体, 不是数组.
    
      // 也可以把成员变量放在同一行中:
      inflatable duck = {"Daphne", 0.12, 9.98};
    
      inflatable mayor {};
      // mayor.name 每个字节都为 0;
      // mayor.volume = 0;
      // mayor.price = 0.
    
      // 可以将结构体赋值给同类型的结构体:
      inflatable choice;
      choice = guest;
    
      // 使用结构体中的成员变量:
      cout << "Expand your guest list with " << guest.name;
      cout << " and " << pal.name << "!\n";
      cout << "You can have both for $";
      cout << guest.price + pal.price << "!\n";
    
      // 结构体数组
      // 创建包含 100 个 inflatable 结构体的数组:
      inflatable gifts[100];
      cin >> gifts[0].volume;
      cout << gifts[99].price << endl;
    
      // 结构体数组初始化
      inflatable guests[2] = {
          {"Bambi", 0.5, 21.99},      // 数组中的第一个结构体
          {"Godzilla", 2000, 565.99}  // 数组中的第二个结构体
      }
    
      return 0;
    }
    

4.5 共用体(联合体) union

  • struct类似.
  • 能存储不同的数据类型, 但只能同时存储其中的一种类型.
  • 常用于节省内存: 当数据项使用两种或更多种格式, 但不会同时使用时, 可节省空间.
    • 例如, 一些商品的ID为整数,而另一些的ID为字符串:
      struct widget {
          char brand[20];
          int type;
          // 1. 使用联合体:
          union id {
              long id_num;        // type == 1 的商品
              char id_char[20];   // 其他商品
          } id_val;
          // 2. 使用匿名联合体:
          union {
              long id_num;        // type == 1 的商品
              char id_char[20];   // 其他商品
          };
      };
      ...
      widget prize;   // 结构体变量 prize, 类型为 widget
      ...
      // 1. 使用联合体的情况:
          if (prize.type == 1) {
          cin >> prize.id_val.id_num;
      } else {
          cin >> prize.id_val.id_char;
      }
      // 2. 使用匿名联合体的情况:
      // id_num 和 id_char 被视为 prize 的两个成员,
      // 地址相同, 不需要中间标识符 id_val.
      if (prize.type == 1) {
          cin >> prize.id_num;
      } else {
          cin >> prize.id_char;
      }
    

4.6 枚举 enum

  • 等用到的时候再回来看.

4.7 指针和自由存储空间

#include <iostream>
int main() {
    using namespace std;
    int donuts = 6;
    double cups = 4.5;

    cout << "donuts value = " << donuts;                    // 数值
    cout << " and donuts address = " << &donuts << endl;    // 地址, 十六进制 (&, 取地址符)

    cout << "cups value = " << cups;                // 数值
    cout<< " and cups address = " << &cups << endl; // 地址, 十六进制

    return 0;
}
  • C++使用new请求正确数量的内存,以及使用指针来跟踪新分配的内存的位置.
  • &运算符被称为"取地址".
    • 假设home是一个变量, 则&home表示它的地址.
  • *运算符被称为"解引用".
    • 假设manly是一个指针, 则manly表示的是一个地址,*manly表示存储在该地址的值.
    • *manly与常规的int类型等效.
  int updates = 6;
  int* p_updates;
  p_updates = &updates;   // 指针 p_updates 指向 updates 的地址.

  cout << "Value: updates = " << updates;             // 输出: 6
  cout << ", *p_updates = " << *p_updates << endl;    // 输出: 6

  cout << "Address: &updates = " << &updates;     // 输出: updates 的地址
  cout << ", p_updates = " << p_updates << endl;  // 输出: updates 的地址
  • 声明和初始化指针
    • 强调*ptr是一个int类型的值 (C常用) :
      int *ptr;
      
    • 强调int*是一种类型 - 指向int的指针 (C++常用) :
      int* ptr;
      
    • 事实上, 在哪里添加空格对编译器来说没有任何区别.
  • 使用指针容易出现的危险
      long* fellow;
      *fellow = 223323;
    
    • 上述代码没有指定指针fellow指向的地址, 没有被初始化, 它可能有任何值, 程序都会将223323存储在该地址上, 可能会导致隐匿的bug发生.
  • 指针和数字
    • 不可以直接将数字赋值给指针:
      int* pt;
      pt = 0xB8000000;    // 报错; 类型不匹配.
      
    • 要将数字作为地址值来使用, 应该进行强制类型转换:
      int* pt;
      pt = (int*) 0xB8000000; // 类型匹配.
      
  • 使用new分配内存
    • 相当于C语言中的库函数malloc()分配内存.
      int* pn = new int;
      
    • 需要适合存储int的内存, 找到这样的内存, 并将该内存的地址值赋给pn.
    • pn指向一个"数据对象", 指为数据项分配的内存块.
    • 为一个数据对象 (结构, 基本类型) 获得并指定分配内存的通用格式:
      typeName* pointer_name = new typeName;
      
  • 使用delete释放内存
    • 相当于C语言中的free().
      int* ps = new int;
      delete ps;          // 可行
      delete ps;          // 此时不可行
      int jugs = 5;
      int* pi = &jugs;    // 可行
      delete pi;          // 不可行: delete 只能对 new 分配的内存使用.
    
    • 一般来说, 不要创建两个指向同一个内存块的指针, 可能会导致对同一个内存块delete两次.
  • 使用new来创建动态数组
      int* psome = new int[10];   // 包含 10 个 int 元素的数组
      delete[] psome;
    
      int* pt = new int;
      short* ps = new short[500];
      delete[] pt;    // 未定义行为, 不要使用!
      delete ps;      // 未定义行为, 不要使用!
    
    • 不要使用delete释放不是由new分配的内存.
    • 不要使用delete释放同一个内存块两次.
    • 如果使用new[]为数组分配内存, 则应使用delete[]来释放.
    • 如果使用new为一个实体分配内存, 则应使用delete来释放.
    • 可以对空指针使用delete.
    • 为数组分配内存的通用格式:
      type_name* pointer_name = new type_name[num];
      
  • 使用动态数组
    • 相当于数组的使用.
      double* p3 = new double[3];
      p3[0] = 0.2;
      p3[1] = 0.5;
      p3[2] = 0.8;
      cout << "p3[1] is " << p3[1] << ".\n";  // 输出 -> p3[1] is 0.5.
      
      p3++;   // 指针前进一位
      cout << "Now p3[0] is " << p3[0] << " and ";
      cout << "p3[1] is " << p3[1] << ".\n";  // 输出 -> Now p3[0] is 0.5 and p3[1] is 0.8.
      
      p3--;           // 指针回退
      delete[] p3;    // 释放内存
      
      注意其中delete[]的使用.
  • 49
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值